/*
* Copyright (c) 2000-2010 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: 
*
*/


#include <assert.h>
#include <stdio.h>
#include <string.h>


#include "unistd.h"

#if defined( __MSVCDOTNET__) || defined(__TOOLS2__)
#include <iostream>
#include <fstream>
using std::cout;
using std::endl;
using std::cerr;
#else //!__MSVCDOTNET__
#ifndef __LINUX__
#include <io.h>
#endif //!__LINUX__
#endif //__MSVCDOTNET__

#include "RESOURCE.H"
#include "DATATYPE.H"
#include "MEM.H"     
#include "RCBINSTR.H"
#include "NUMVAL.H"  
#include "ERRORHAN.H"
#include "FILEACC.H" 
#include "VERSION.H" 
#include "CTABLE.H"  
#include "main.h"
#include "TOKENS.H"
#include "localise.h"
#include "qualifar.h"
#include "messages.h"

extern NameIdMap* pResourceNameIds;
extern long CurrentId;

void WriteHeaderFile(FILE* aRSG, IndexTable& aIndex)
	{
	IndexTableIterator next(aIndex);
	IndexTableItem * p;
	while( ( p = next() ) != NULL)
		{
		ResourceHeader& r=p->Resource();
		if (r.iLabel.Length()>0 && !r.iLocal)
			{
			r.iLabel.Upper();
			if (r.iFormatAsHex)
				fprintf(aRSG, "#define %-41s 0x%x\n", r.iLabel.GetAssertedNonEmptyBuffer(), r.iResourceId);
			else
				fprintf(aRSG, "#define %-41s %d\n", r.iLabel.GetAssertedNonEmptyBuffer(), r.iResourceId);
			}
		}
	}

void WriteBitArrayOfResourcesContainingCompressedUnicode(RCBinaryStream& aRSC, const IndexTable& aIndex)
	{
	IndexTableIterator next(aIndex);
	unsigned char bitBuffer = 0;
	int numberOfBitsInBuffer = 0;
	for (;;)
		{
		IndexTableItem* const p = next();
		if (p == NULL)
			{
			if (numberOfBitsInBuffer > 0)
				{
				aRSC.Write(&bitBuffer, 1);
				}
			break;
			}
		if (p->Resource().ContainsCompressedUnicode())
			{
			bitBuffer |= (1 << numberOfBitsInBuffer);
			}
		++numberOfBitsInBuffer;
		if (numberOfBitsInBuffer == 8)
			{
			aRSC.Write(&bitBuffer, 1);
			bitBuffer = 0;
			numberOfBitsInBuffer = 0;
			}
		}
	}

void WriteBinaryResourceData(RCBinaryStream& aRSC, IndexTable& aIndex, int& aSizeOfLargestResourceWhenUncompressed, const char* aDumpDirectory)
	{
	IndexTableIterator next(aIndex);
	IndexTableItem * p;
	int resourceIndex=1;
	while( ( p = next() ) != NULL)
		{
		char* dumpFile=NULL;
		if (aDumpDirectory!=NULL)
			{
			dumpFile=new char[strlen(aDumpDirectory)+20];
			strcpy(dumpFile, aDumpDirectory);
			char resourceIndexAsString[20];
			sprintf(resourceIndexAsString, "%d", resourceIndex);
			strcat(dumpFile, resourceIndexAsString);
			}
		p->SetOffset(aRSC.GetPosition()); // record start of this resource in the index
		p->Resource().StreamOut(aRSC, aSizeOfLargestResourceWhenUncompressed, dumpFile); // write out binary form of resource
		delete [] dumpFile;
		++resourceIndex;
		}
	}

void WriteResourceFile(RCBinaryStream& aRSC, IndexTable& aIndex, bool aThirdUidIsOffset, const char* aDumpDirectory)
	{
	char flags=0;
	if (aThirdUidIsOffset)
		{
		flags|=0x01;
		}
	aRSC << flags; // these flags are to be used only by a dictionary-compressing program rather than to be used directly by Bafl when reading non-dictionary-compressed resource files (as output by Rcomp)
	const int positionToOverWriteFrom=aRSC.GetPosition();
	NumericValue twoByteSizeOfLargestResourceWhenUncompressed(L_WORD);
	aRSC << twoByteSizeOfLargestResourceWhenUncompressed;
	WriteBitArrayOfResourcesContainingCompressedUnicode(aRSC, aIndex); // simply makes space for the bit-array without writing anything sensible in it (as we don't yet know which resources will contain compressed Unicode)
	int sizeOfLargestResourceWhenUncompressed=0;
	WriteBinaryResourceData(aRSC, aIndex, sizeOfLargestResourceWhenUncompressed, aDumpDirectory);
	aIndex.SetIndexOffset(aRSC.GetPosition());
	aRSC << aIndex;
	aRSC.SetPosition(positionToOverWriteFrom);
	twoByteSizeOfLargestResourceWhenUncompressed=sizeOfLargestResourceWhenUncompressed;
	aRSC << twoByteSizeOfLargestResourceWhenUncompressed;
	WriteBitArrayOfResourcesContainingCompressedUnicode(aRSC, aIndex); // overwrites the bit array with correct data

	if(verbose) 
		{
		MOFF; cout << aIndex; cout << endl; MON;
		}
	}

void CheckLabels() // checks whether the labels that are used in the input have been declared
	{
	QualifiedStringArrayIterator nextLabel(pG->UsedIdentifiers);
	QualifiedString * pLabel;
	while ((pLabel = nextLabel() ) != NULL)
		{
		bool found = false; // gets set to true if the label in question is declared
		StringArrayIterator nextDeclared(pG->AllIdentifiers);
		String * pDeclared;
		while ( ( (pDeclared = nextDeclared() ) != NULL) && ( ! found ))
			{
			StringLess stringCompare;
			if( !stringCompare(*pDeclared,(*pLabel).GetEntry()) && !stringCompare((*pLabel).GetEntry(),*pDeclared) )
				{ // this comparison returns true if the label is the same as the declared label
				found = true;
				}
			}
		if( ! found ) // if label hasn't been declared emit warning
			{
			Message * message = pG->Messages.GetEntry(LT_045);
			String fileName = (*pLabel).GetFileName();
			int lineNumber = (*pLabel).GetLineNumber();
			if(message->GetActivated())
				{
				String comment = message->GetMessageOutput();
				comment += (*pLabel).GetEntry();
				ErrorHandler::OutputWholeLine(fileName, lineNumber, comment);
				}
			}
		}
	}


/*  Tokenize expects a string in the following format:
 *	  \d{3}(,\d{3})*
 *  i.e. comma-separated three digit numbers. 
 *  The string should contain no whitespace.
 */
void Tokenize(String aString)
	{
	int length = aString.Length();

	for(int end=3; end<=length; end+=4)
		{
		String messageNumber = aString.ExtractSubString(end-3,end-1);
		if(messageNumber.IsDecNatural())
			{
			Message * message = pG->Messages.GetTextEntry(messageNumber);
			if(message != NULL)
				{
				message->SetActivationStatus(false);
				}
			}
		}
	}
				

void OutputHelp()
	{
	cerr << "Resource compiler version " << version << " (Build " << build << ") (C) 1997-2010 Nokia Corporation." << endl;
	cerr << "Usage: rcomp [-vpul] [-force] [-oRSCFile] [-{uid2,uid3}] [-hHeaderFile] [-sSourceFile] [-iBaseInputFileName]" << endl;
	cerr << "\tv" << "\tverbose" << endl;
	cerr << "\tp" << "\tParser debugging" << endl;
	cerr << "\tl" << "\tCheck localisation comments" << endl;
	cerr << "\tforce" << "\tEmit localisation warnings even if no localisation tags are present" << endl;
	cerr << "\tadd-defaults" << "\tAmend input rss/rpp file to add missing default localisation options" << endl;
	cerr << endl;
	cerr << "\tu" << "\tGenerate Unicode resource binary" << endl;
	cerr << endl;
	cerr << "If no source file is specified, the source will be read from standard input." << endl;
	cerr << "(-i is used to specify the file given to the preprocessor this " << endl;
	cerr << "    name is used in generating debug output.)" << endl;
	}



GlobalData *pG;
GlobalLocalisationData *pGL;
String InputBaseName;

int main(int argc, char * argv[])
	{
	cout << "\n"; 
	int vParam=0;
	bool lParam = false; // used as flag to specify whether checking of localisation comment tags should be performed
	bool lForce = false; // used as flag to force localisation output even if there are no localisation comments
	bool lAddDefaults = false; // used as flag to add missing default localisation data to input rss/rpp file, this is not the default behaviour
	logmemorysetting = 0;
	unsigned short	pParam = 0;
	String			DataOutputFileName;
	String			HeaderOutputFileName;
	String			MessageSuppressionList;
	String			BasePath;
	String			SourceFileName;
	FILE *			pSourceFile;
	char*			uidsParameter=NULL;
	char*			dumpDirectory=NULL;
	fpos_t			filePositionIndicator;
	int				i;
	
	char *fullcommand = argv[0];
	std::string s(fullcommand);

	if(argc<=1)
		{
		OutputHelp();
		exit(-1);
		}
	else
		{
		// Look through arguments for ones beginning with '-?'.
		for(i = 1; i < argc; i++)
			{
			if(* argv[i] == '-')
				{
				char * settings = argv[i] + 1;
			
				if(strchr(settings, '?') )
					{
					OutputHelp();
					exit(-1);
					}
				}
			}

		for(i = 1; i < argc; i++)
			{
			if(* argv[i] == '-')
				{
				char * settings = argv[i] + 1;
			
				if(* settings == 'o' || * settings == 'O')
					{
					DataOutputFileName = (settings + 1);
					continue;
					}

				if(* settings == 'm' || * settings == 'M')
					{
					MessageSuppressionList = (settings + 1);
					continue;
					}
					
				if(* settings == 'h' || * settings == 'H')
					{
					HeaderOutputFileName = (settings + 1);
					continue;
					}

				if(* settings == 'i' || * settings == 'I')
					{
					InputBaseName = (settings + 1);
					String DriveAndDirectory = FileAccess::GetDriveAndDirectory(InputBaseName);
					BasePath = FileAccess::FullPath(DriveAndDirectory);
					continue;
					}

				if(* settings == 's' || * settings == 'S')
					{
					SourceFileName = (settings + 1);
					continue;
					}

				if(* settings == '{')
					{
					uidsParameter = settings + 1;
					char* temp = strchr(uidsParameter, ',');
					if ((temp == NULL) || (temp == uidsParameter) || (strchr(temp + 1, ',') != NULL)) // check that there is *one* comma in this parameter (no more and no less), and that it is not the first thing immediately after the '{'
						{
						OutputHelp();
						exit(-1);
						}
					*temp = ' ';
					temp = strchr(uidsParameter, '}');
					if ((temp == NULL) || (temp[1] != '\0'))
						{
						OutputHelp();
						exit(-1);
						}
					*temp = ' ';
					continue;
					}

				if(* settings == ':')
					{
					dumpDirectory=settings+1;
					continue;
					}

				if(strchr(settings, 'u') || strchr(settings, 'U') )
					{
					SourceCharacterSet = String::CP1252;
					TargetCharacterSet = String::Unicode;
					}

				if(strchr(settings, 'v') || strchr(settings, 'V') )
					vParam = 1;
				if(strchr(settings, 'p') || strchr(settings, 'P') )
					pParam = 1;
				if(strchr(settings, 'l') || strchr(settings, 'L') )
					lParam = true;
				if(strchr(settings, 'f') || strchr(settings, 'F') )
					lForce = true;
				if(strchr(settings, 'a') || strchr(settings, 'A') )
					lAddDefaults = true;
				}
			}
		}
	if(SourceFileName.Length() == 0)
		{
		pSourceFile = NULL ; //stdin;
		}
	else  
	{
		if((pSourceFile = fopen(SourceFileName.GetAssertedNonEmptyBuffer(), "r") ) == NULL)
		{
			cerr << "Failed to open " << SourceFileName << endl;
			exit(-2);
		}
		//Searchig for BOM signature which if found will be ignored
		unsigned char buffer[3];
		fread( buffer, sizeof( char ), 3, pSourceFile);	
		if((buffer[0]!=239) && (buffer[1]!=187) && (buffer[2]!=191))
		{
			// BOM not found. Set the file-position indicator to 0
			filePositionIndicator = fpos_t();
			if(fsetpos(pSourceFile, &filePositionIndicator) !=0)
			{
				perror("fsetpos error");
				exit(-2);
			} 
		}		
	}
		
	verbose = vParam;
	pG = new GlobalData;
	if (pG==NULL)
		exit(-4);
		
	if(NULL == pSourceFile){ // Cache the standard input		
		pG->StdInBufLength = 0; 
		unsigned long allocSize = 0x100000 ; // 1M bytes
		pG->StdInfBufPos = 0 ;
		pG->StdInBuffer = (char*)malloc(allocSize);
		if(NULL == pG->StdInBuffer){
			delete pG ;
			exit(-4);
		}
		int result = 1;
		char* buffer = pG->StdInBuffer;
		FILE *file = stdin ;
		const int KIOBytes = 0x20000 ;
		while(1) {		 
			result = fread(buffer, 1, KIOBytes, file);  
			if(result == 0){
				*buffer = 0;
				break ;
			}
			buffer += result ;
			pG->StdInBufLength += result ;
			if((pG->StdInBufLength + KIOBytes) > allocSize) {
				allocSize += KIOBytes ;
				pG->StdInBuffer = (char*)realloc(pG->StdInBuffer,allocSize);
				if(NULL == pG->StdInBuffer){
					delete pG ;
					exit(-4);
				}
				buffer = pG->StdInBuffer + pG->StdInBufLength;
			}
			
		}
		const unsigned char BOM[] = {0xef , 0xbb, 0xbf, 0x0 };
		if(0 == memcmp(pG->StdInBuffer,BOM,3))
			pG->StdInfBufPos = 3 ; 
	}
	else {
		pG->StdInBuffer = 0 ;
		pG->StdInBufLength = 0; 
	}
	
	Tokenize(MessageSuppressionList);

	pGL = new GlobalLocalisationData;
	if(pG==NULL)
		exit(-4);

	pG->WarningMultiExplained = false;
	pG->FileLineHandler.SetPath(BasePath);
	
	#ifdef __TOOLS2__
	pG->FileLineHandler.SetBase(SourceFileName,0);
	#endif
	
	int ret=ParseSourceFile(pSourceFile, pParam);
	if(pSourceFile != NULL)
		fclose(pSourceFile);
	if(pG->StdInBuffer != NULL)
		free(pG->StdInBuffer);
	
	pGL->StoreFinalComment(); // final comment not stored during running of lex and yacc
	if(lParam && (pGL->LocalisationCommentsExist() || lForce))
		{
		pGL->AnalyseLocalisationData();
		pGL->PrintLocalisationWarnings();
		if(lAddDefaults)
			{
			// only add deafult localisation values to rpp/rss file if the option has been set on the command line
			if(verbose)
				{
				cout << "* Reparsing source file and adding any missing default localisation comments" << endl;
				}
			pGL->OutputLocalisedFile(SourceFileName);
			}
		}
	if (ret != 0) 
		{
		cerr << "RCOMP failed with code " << ret << endl;
		exit(ret);
		}
	// A successful parse, now generate the output files

	CheckLabels(); // check that all labels are declared and emit suitable warnings if not

	if(DataOutputFileName.Length() != 0)
		{


#ifdef __LINUX__

		std::string totalpath(s.substr( 0, s.rfind("/")+1 ));
		const char* uidTool = "uidcrc";

#else

		std::string totalpath(s.substr( 0, s.rfind("\\")+1 ));
		const char* uidTool = "uidcrc.exe";
    
		// in case unix style of seperator is used in windows.
		if(!totalpath.length()){
			totalpath = s.substr( 0, s.rfind("/")+1 );
		}
		
#endif
		
		std::string uidpath(uidTool);
		
		// try to call the uidcrc tool with the full path to where RCOMP resides in
		// if does not exists, search uidcrc in PATH 
		if(totalpath.length()){
		
			std::string trypath(totalpath);
			trypath += uidpath;
			
			std::fstream _file;
			_file.open(trypath.c_str(), std::ios::in);
			if(!_file){
				// try to search from PATH
				totalpath = uidpath;
			}
			else{
				_file.close();
				// invoke from the path RCOMP resides in
				totalpath += uidpath;
			}
		}
		else{
			// search from PATH
			totalpath = uidpath;
		}
		
		// Find and replace all occurences of \ with /
		std::string searchString( "\\" ); 
		std::string replaceString( "/" );
 		std::string::size_type pos = 0;
	    	while ( (pos = totalpath.find("\\", pos)) != std::string::npos ) {
        		totalpath.replace( pos, searchString.size(), replaceString );
		        pos++;
		    }
			
		const char *uidcrcTool = totalpath.c_str();

		bool thirdUidIsOffset=true;
		
		char uidcrcUIDs[3][100];
		strcpy (uidcrcUIDs[0], "0x101f4a6b");

		if (uidsParameter)
			{
			// Command line argument takes precedence 
			
			strcpy (uidcrcUIDs[1], strtok (uidsParameter, " "));
			strcpy (uidcrcUIDs[2], strtok (NULL, " "));
 
			char* const temp = strchr(uidcrcUIDs[2], '*');		
			if (temp == NULL)
				{
				thirdUidIsOffset=false;
				}
			}
		else
			{
			// otherwise use values supplied in source

			extern unsigned long Uid2;
			extern unsigned long Uid3;
			sprintf(uidcrcUIDs[1], "0x%08lx", Uid2);
			if (Uid3 != 0)
				{
				sprintf(uidcrcUIDs[2], "0x%08lx", Uid3);
				thirdUidIsOffset=false;
				}
			}


		if (thirdUidIsOffset)
			{
			const unsigned int idOfAnyResource = CurrentId; // *must* be unsigned so that when we right-shift it, the top bit doesn't get propagated if its set (i.e. "negative")
			sprintf(uidcrcUIDs[2], "0x%08x", idOfAnyResource >> 12); // use the 20 bits derived from the resource file's NAME as the 3rd UID
			}

		if (verbose)
			{
			MOFF; cout << uidcrcTool << " " << uidcrcUIDs[0] << " " << uidcrcUIDs[1] << " " << uidcrcUIDs[2] << " " << DataOutputFileName.GetAssertedNonEmptyBuffer(); cout << endl; MON;
			}

		int namelen = strlen(uidcrcTool) + strlen(uidcrcUIDs[0]) * 3 + DataOutputFileName.Length() + 10; 
		char *uidcrc_params = new char[namelen];
		const int ret = snprintf(uidcrc_params,
					 namelen,
					 "%s %s %s %s %s",
					 uidcrcTool,
					 uidcrcUIDs[0],
					 uidcrcUIDs[1],
					 uidcrcUIDs[2],
					 DataOutputFileName.GetBuffer());
		if(ret <= 0) {
			cerr << "Failed to write UIDs to " << DataOutputFileName << endl;
			exit(ret);
		}
		const int error = system(uidcrc_params);

		if(error != 0)
			{
			cerr << "Failed to write UIDs to " << DataOutputFileName << endl;
			exit(error);
			}
		delete[] uidcrc_params;

		RCBinaryStream RSCStream;
		RSCStream.OpenForAppend(DataOutputFileName);
		if(! RSCStream.IsOpen())
			{
			cerr << "Failed to open " << DataOutputFileName << endl;
			exit(-2);
			}
		WriteResourceFile(RSCStream, pG->Index, thirdUidIsOffset, dumpDirectory);
		}
	
	if(HeaderOutputFileName.Length() != 0)
		{
		FILE* RSG;
		RSG = fopen(HeaderOutputFileName.GetAssertedNonEmptyBuffer(), "w");
		if(RSG==NULL)
			{
			cerr << "Failed to open " << HeaderOutputFileName << endl;
			exit(-2);
			}
		WriteHeaderFile(RSG, pG->Index);
		fclose(RSG);
		}

	delete pG;
	delete pGL;

	return 0;
	}




