/*
* Copyright (c) 2006-2009 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: 
*
*/


#ifdef _MSC_VER
#pragma warning (disable: 4786)
#endif // _MSC_VER

// System includes
#include <iosfwd>
#include <sstream>
#include <iostream>
#include <fstream>
#include <algorithm>

#ifndef __TOOLS2_LINUX__
#include <windows.h>
#endif

// User includes
#include "commandparser.h"
#include "openssllicense.h"
#include "sisregistry.h"
#include "parameterlist.h"
#include "is_utils.h"
#include "stringutils.h"

// Global/static variables
CCommandParser::WarnMap CCommandParser::KString2Warning;


CCommandParser::CCommandParser():
    iWarnLevel( WARN_WARN )
    {
    }

CCommandParser::~CCommandParser()
    {
    }

void CCommandParser::DisplayError( int aError )
    {
	const wchar_t* msg;
    //
	switch( aError )
	{
	case ECmdLineUnknownOption:
		msg = L"Unknown option specified";
		break;
	case ECmdLineMissingParamFile:
		msg = L"Can't open the parameter file specified";
		break;
	case ECmdLineNoDirArgument:
		msg = L"No directory(s) specified";
		break;
	case ECmdLineNoSisArgument:
		msg = L"No SIS file(s) specified";
		break;
	case ECmdLineNoRomRofsLogsArgument:
		msg = L"No ROM/ROFS[BUILD] log file(s) specified";
		break;
	case ECmdLineNoConfigFileArgument:
		msg = L"No configuration file specified";
		break;
	case ECmdLineNoSisStubPathArgument:
		msg = L"No SIS stub directory specified";
		break;
	case ECmdLineMissingParams:
		msg = L"Missing parameters";
		break;
    case ECmdLineInvalidLanguage:
		msg = L"Invalid language code";
        break;
	case ECmdLineMissingPackageUID:
		msg = L"Package UID not found";
        break;
	default:
		msg = L"Unknown error";
		break;
	}
    //
	LERROR(msg << std::endl);
	DisplayUsage();
    }

void CCommandParser::DisplayOpenSSLCopyright()
	{
	//displays OpenSSL copyright notice.
	for (int index = 0; index < (sizeof(openSSLLicenseString)/sizeof(openSSLLicenseString[0])); ++index)
		{
		std::cout << openSSLLicenseString [index] << std::endl;
		}
	}

void CCommandParser::DisplayUsage()
    {
	DisplayVersion();

	std::cout
		#ifdef SYMBIAN_UNIVERSAL_INSTALL_FRAMEWORK
		<< "Usage: " << CommandName () << " [-z dir] [-c dir] [-e] [-f] [-k [4.0 | 5.0 | 5.1 | 5.2 | 5.3 | 5.4]]\n"
		#else
		<< "Usage: " << CommandName () << " [-z dir] [-c dir] [-e] [-k [4.0 | 5.0 | 5.1 | 5.2 | 5.3 | 5.4]]\n"
		#endif
		<< "\t\t[-s [sisfile | dir] [+drive [+mcard | +mcardnr | +nonremovablepkg | +mcardalone | +mcardalonenr] [+sucert]]] [-s ...]\n"
		<< "\t\t[-p param_file] [-d drive] [-r rofsbuild_log_file1,rofsbuild_log_file2,...]\n" 
		<< "\t\t[-t romstubdir] [-n language_code] [-i config_file] \n"
		<< "\t\t[-x pkgUID[,pkgUID2,...]] [-w [off | error | warn | info]] [-l logfile]\n\n"
		<< "Where:\t-h\t\tDisplays help\n"
		#ifdef SYMBIAN_UNIVERSAL_INSTALL_FRAMEWORK
		<< "\t-a\t\tThe path representing the location of Resource files\n"
		<< "\t  \t\tSpecifying the ROM Drive (-z) is mandatory.\n"
		<< "\t  \t\tLocalize resource file is read from (Rom_drive\\path_specified_in_resource_file)\n"
		#endif //SYMBIAN_UNIVERSAL_INSTALL_FRAMEWORK
		<< "\t-c\t\tThe directory representing the system drive on the device\n"
		<< "\t-d\t\tThe system drive letter [default to 'C']\n"
		<< "\t-e\t\tDisable eclipsing and SID checks using Z drive \n"
		<< "\t  \t\t-z or -r not required when this option used \n"
		#ifdef SYMBIAN_UNIVERSAL_INSTALL_FRAMEWORK
		<< "\t-f\t\tUsed to specify that origin of a component has not been verified during installation.\n"
		<< "\t\t\tBy default the origin is verified. This option should not be provided along with registry version information.\n"		
		#endif
		
		<< "\t-i\t\tConfig file with HAL attribute values for installation \n";
	std::cout
		#ifdef SYMBIAN_UNIVERSAL_INSTALL_FRAMEWORK
		<< "\t-k\t\tSIS Registry version to generate (default is to generate the db related registry entry) \n"
		#else
		<< "\t-k\t\tSIS Registry version to generate (default v" << SisRegistry::KSisRegistryMajorVersion 
		<< "." << SisRegistry::KSisRegistryMinorVersion << ") \n"
		#endif //SYMBIAN_UNIVERSAL_INSTALL_FRAMEWORK
		<< "\t-l\t\tThe file to write diagnostics to (stderr by default)\n"
		<< "\t-n\t\tLanguage code - decimal number as defined in TLanguage enum\n"
		<< "\t-o\t\tOutput licence information\n"
		<< "\t-p\t\tA file to take additional parameters from.\n"
		<< "\t  \t\tCommand line args after the file will override the file contents\n"
		<< "\t-r\t\tList of rom/rofs build log files; should not use with -z option or\n"
		<< "\t	\t\tdefine the Z drive in the config file\n"
		<< "\t-s\t\tThe SIS file(s) to install. Any directory passed in will be searched\n"
		<< "\t\t\tfor SIS files; should not use with -x option\n"

		<< "\t-t\t\tROM Stub sis files directory, must be used with -r option\n"
		<< "\t-v\t\tDisplays the version of the tool\n"
		<< "\t-w\t\tThe level of diagnostics to display- WARN, ERROR, INFO, OFF\n"
		<< "\t  \t\t(warn by default)\n"

		<< "\t-x\t\tRemove the files installed of a package; should not use with -s option\n"
		<< "\t-z\t\tThe directory representing the Z: drive on the device\n"
		<< "\tpkgUID\t\tThe installed package UID either in Hex or Decimal format (e.g. 0x12345678, 305419896)\n"
		<< "\tsisfile\t\tThe SIS file to be installed\n"
		<< "\tdir\t\tThe directory that contains the installing SIS files\n"
		<< "\tdrive\t\tThe drive letter on the device\n"
		<< "\tmcard\t\tOption to generate the stub SIS file for the installing package\n"
		<< "\tmcardnr\t\tOption to generate the non-removable stub SIS file for the installing package\n"
		<< "\tnonremovablepkg\tOption to generate the non-removable SIS file for the installing package\n\n"
		<< "\tmcardalone\tOption to just create a pre-installed package to the media card\n"
		<< "\tmcardalonenr\tOption to generate the non-removable stub SIS file for the installing" << std::endl
		<< "\t\t\tpackage without generating the SISregistry entry\n"
		<< "\tsucert\t\tTo indicate that the SIS file has been signed with a SU certificate\n\n";
    }

void CCommandParser::DisplayVersion()
	{
	std::cout << "\nINTERPRETSIS  " << " Version  3.0.0 ." << std::endl;
	std::cout << "Copyright (c) 2009 Symbian Software Ltd. All rights reserved.\n " << std::endl;
	}
 

CParameterList* CCommandParser::ParseOptions(int argc, const char**argv)
    {
	CParameterList* paramList = new CParameterList();
	bool bAbort = ParseParam(argc, argv, paramList);
	if(bAbort)
		{
		delete paramList;
		paramList = NULL;
		}
	return paramList;
    }
bool CCommandParser::ParseParam(int argc, const char**argv, CParameterList* aParamList)
	{
	if (argc < 2)
	    {
		throw CCommandParser::ECmdLineUnknownOption;
	    }
	
	bool bContinue = true;
	
	while ((--argc > 0) && bContinue)
	    {
		++argv;
		if (**argv != '-')
			{
			continue;
            }

		bool err = false;
		const char* optPtr = *argv;

		switch (toupper(*++optPtr))
		    {
			#ifdef SYMBIAN_UNIVERSAL_INSTALL_FRAMEWORK
			case 'A':
				{
				if (argc <= 1)
					throw CCommandParser::ECmdLineNoDirArgument;
				
				--argc;
				aParamList->SetResourceFilePath(*(++argv));
				break;	
				}
			#endif //SYMBIAN_UNIVERSAL_INSTALL_FRAMEWORK
			case 'C':
				{
				if (argc <= 1)
					throw CCommandParser::ECmdLineNoDirArgument;
				
				--argc;
				aParamList->SetSystemDrive(*(++argv));
				break;
				
				}
			case 'D':
				{	
				--argc;
				wchar_t buf[2048];
				ConvertMultiByteToWideChar(*++argv,-1, buf, 2048);
				aParamList->SetSystemDriveLetter(tolower(buf[0]));
				break;
				}					
			case 'E':
				{
				aParamList->SetFlag(CParameterList::EFlagsDisableZDriveChecksSet);
				break;
				}
			#ifdef SYMBIAN_UNIVERSAL_INSTALL_FRAMEWORK
			case 'F':
				{				
				aParamList->SetOriginVerificationStatus(false);
				break;
				}
			#endif
			case 'H':
			case '?':
				{
				DisplayUsage();
				bContinue = false;
				break;
				}
			case 'I':
				{
				if (argc <= 1)
					throw CCommandParser::ECmdLineNoConfigFileArgument;
	
				--argc;
				aParamList->SetConfigFile(*(++argv));
				break;
				}
			case 'K':
				{
				// Last command
				if (argc == 1)
					{
					aParamList->SetSisRegistryVersion(SisRegistry::KRegistryV40string);
					}
				else
					{
					// Check that the next arg is valid
					std::string versionStr = *(++argv);

					if (**argv == '-')
						{
						aParamList->SetSisRegistryVersion(SisRegistry::KRegistryV40string);
						--argv;
						}
					else
						{
						--argc;
						aParamList->SetSisRegistryVersion(versionStr);
						}
					}
				break;
				}
			case 'L':
				{
				if (argc > 1)
					{
					--argc;
					wchar_t buf[2048];
					ConvertMultiByteToWideChar(*++argv,-1, buf, 2048);
					iLogFile = buf;
					}
				break;
				}
			case 'N':
				{
				if (argc <= 1)
					throw CCommandParser::ECmdLineInvalidLanguage;

				--argc;
				aParamList->SetLanguage(static_cast< CSISLanguage::TLanguage >( String2Language( *(++argv) ) ));
				break;
				}
			case 'O':
				{
				DisplayOpenSSLCopyright();
				bContinue = false;
				break;
				}
			case 'P':
				{
				if (!(err = argc <= 1))
					{
					--argc;
					std::string paramFile = *(++argv);
					ParseParam(argc, paramFile, aParamList);
					}
				break;
				}
			case 'R':
				{
				if (argc <= 1)
					throw CCommandParser::ECmdLineNoRomRofsLogsArgument;

				--argc;
				aParamList->EmptyRomLogFiles();
#ifdef _MSC_VER
				FilePtr funtionPtr = aParamList->AddRomLogFile;
#else
				FilePtr funtionPtr = &(aParamList->AddRomLogFile);
#endif // _MSC_VER	
				String2List(*aParamList, funtionPtr, *(++argv) );
				break;
				}
			case 'S':
				{
				if (argc <= 1)
					throw CCommandParser::ECmdLineNoSisArgument;

				--argc;

				String2SISFileList(*aParamList, argc, ++argv);
				break;
				}
			case 'T':
				{
				if (argc <= 1)
					throw CCommandParser::ECmdLineNoSisStubPathArgument;

				--argc;
				aParamList->SetStubDir(*(++argv));
				break;
				}
			case 'V':
				{
				DisplayVersion();
				bContinue = false;
				break;
				}
			case 'W':
				{
				--argc;
				iWarnLevel = String2Warn(*(++argv));
				break;
				}
			case 'X':
				{
				if (argc <= 1)
					throw CCommandParser::ECmdLineMissingPackageUID;

				--argc;
				aParamList->EmptyPkgUids();
#ifdef _MSC_VER
				FilePtr funtionPtr = (aParamList->AddPkgUid);
#else
				FilePtr funtionPtr = &(aParamList->AddPkgUid);
#endif // _MSC_VER	
				String2List(*aParamList, funtionPtr, *(++argv) );
				break;
				}
			case 'Z':
				{
				if (argc <= 1)
					throw CCommandParser::ECmdLineNoDirArgument;
			
				--argc;
				aParamList->SetZDrive(*(++argv));
				break;
				}
			default:
				{
				LERROR(Utf8ToUcs2(std::string(optPtr)));
				throw CCommandParser::ECmdLineUnknownOption;
				}
			}

		if (err)
			{
			throw CCommandParser::ECmdLineUnknownOption;
			}
		} // Finished parsing all the command line options
	
	return !bContinue; 
    }


void CCommandParser::ParseParam(int argc, const std::string& paramFile, CParameterList* aParamList)
    {
	if (paramFile.size() > 0)
	    {
		std::ifstream params;
		params.open(paramFile.c_str(), std::ios::in);

		if (!params.good())
            {
			throw CCommandParser::ECmdLineMissingParamFile;
            }

		std::vector<std::string> tokens;
		argc = 0;
		while (params.good())
		    {
			std::string token;
			params >> token;
			if(token != "")
				{
				argc++;
				tokens.push_back(token);
				}
		    }

		const char** newArgv = new const char*[tokens.size()+1];
		for (int i = 0 ; i < tokens.size() ; ++i)
            {
			newArgv[i+1] = tokens[i].c_str();
            }

        ParseParam(argc+1, newArgv, aParamList);
		delete [] newArgv;
        params.close();
	    }
    }

void CCommandParser::String2List(CParameterList& aParamList, FilePtr& aFilePtr, const std::string& aOptionsString)
    {
	std::string::const_iterator it = aOptionsString.begin();
	std::string::const_iterator end = aOptionsString.end();
	std::string::const_iterator currentPos = it;
    //
	while (currentPos != end)
	    {
		currentPos = std::find(it, end, ',');
		std::wstring x;
		Utf8ToUcs2(std::string(it,(currentPos-it)), x);
		//aOptionsList.push_back(x);
		(aParamList.*aFilePtr)(x);

		if (currentPos == end)
			{
			return;
			}

		it = currentPos;
		++it;
	    }
    }

void CCommandParser::String2SISFileList(CParameterList& aParamList, int aArgc, const char**aArgv)
    {
	std::string sisFileOption(*aArgv);

	std::string::const_iterator it = sisFileOption.begin();
	std::string::const_iterator end = sisFileOption.end();
	std::string::const_iterator currentPos = it;

	currentPos = std::find(it, end, ',');

	while (currentPos != end)
	    {
		currentPos = std::find(it, end, ',');
		std::wstring x;
		Utf8ToUcs2(std::string(it,(currentPos-it)), x);

		InstallSISFile sisFileName(x, '$', false);
		aParamList.AddSISFile(sisFileName);

		if (currentPos == end)
			{
			return;
			}

		it = currentPos;
		++it;
	    }
	
	// At this stage, aArgv could be something like: file.sis +e +mcard +sucert
	// As the sisfile attributes are predetermined input, therefore we can just
	// parse according to the predetermined inputs.

	std::wstring fileName;
	Utf8ToUcs2(sisFileOption, fileName);

	InstallSISFile sisFileName(fileName, '$', false);
		
	while (--aArgc > 0)
		{ // Process associated SIS file attributes
		++aArgv;

		if (**aArgv == '-')
			{ // No more attribute to process
			aParamList.AddSISFile(sisFileName);

			--aArgv;
			++aArgc;
			return;
			}

		if (**aArgv == '+')
			{
			std::string versionStr;

			// Check that the next arg is valid
			versionStr = *(aArgv);
			
			if (versionStr.size() == 2)
				{
				// Found the associated SIS file drive; e.g. +e
				const char* optPtr = *aArgv;

				char drive = tolower(*++optPtr);

				if (drive < 'a' || drive > 'y')
					throw CCommandParser::ECmdLineInvalidSISFileAttribute;

				// Set target drive
				sisFileName.iTargetDrive = drive;

				continue;
				}

			versionStr = StringUtils::ToUpper( versionStr );

			if (versionStr == "+MCARD")
				{
				sisFileName.iGenerateStub = true;
				}
			else if (versionStr == "+MCARDNR")
				{
				sisFileName.iGenerateStub = true;
				sisFileName.iReadOnly = true;
				}
			else if (versionStr == "+MCARDALONE")
				{
				sisFileName.iGenerateStub = true;
				sisFileName.iNotRegister = true;
				}
			else if (versionStr == "+MCARDALONENR")
				{
				sisFileName.iGenerateStub = true;
				sisFileName.iNotRegister = true;
				sisFileName.iReadOnly = true;
				}
			else if (versionStr == "+SUCERT")
				{
				sisFileName.iSUFlag = true;
				}
			else if (versionStr == "+NONREMOVABLEPKG")
				{
				sisFileName.iNonRemovable = true;
				}
			else
				throw CCommandParser::ECmdLineInvalidSISFileAttribute;
			}
			else
			{
			throw CCommandParser::ECmdLineInvalidSISFileAttribute;
			}
		}
	aParamList.AddSISFile(sisFileName);
	}


WarnLevel CCommandParser::String2Warn(const std::string& level)
    {
	std::string l(level);
	std::transform(l.begin(),l.end(),l.begin(), toupper);
    //
	if (KString2Warning.size() == 0)
	    {
		KString2Warning["OFF"] = WARN_OFF;
		KString2Warning["WARN"] = WARN_WARN;
		KString2Warning["ERROR"] = WARN_ERROR;
		KString2Warning["INFO"] = WARN_INFO;
	    }
    //
	WarnMap::const_iterator result = KString2Warning.find(l);
	if (result == KString2Warning.end())
	    {
		throw CCommandParser::ECmdLineUnknownOption;
	    }
    //
	return result->second;
    }


int CCommandParser::String2Language( const std::string& aLanguage )
    {
	std::istringstream stringStream( aLanguage );
    int language = 1;
    //
    if ( stringStream >> language )
        {
        }
    else
        {
        throw CCommandParser::ECmdLineInvalidLanguage;
        }
    //
    return language;
    }
