// command_info_file.cpp
// 
// Copyright (c) 2009 - 2010 Accenture. All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the "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:
// Accenture - Initial contribution
//

#include <f32file.h>
#include "ioutils.h"
#include "command_base.h"

_LIT(KCifExt, ".cif");
_LIT(KCifPathVar, "CIF_PATH");
_LIT(KCifPath, "\\resource\\cif\\fshell");
_LIT(KEnumSeparator, ",");
_LIT(KEnumQuote, "\"");
_LIT(KNewLine, "\r\n");
_LIT(KPartialCommandStart, "==");
_LIT(KCommandStart, "\r\n==");
_LIT(KCmndName, "name");
_LIT(KCmndShortDescription, "short-description");
_LIT(KCmndLongDescription, "long-description");
_LIT(KCmndSeeAlso, "see-also");
_LIT(KCmndCopyright, "copyright");
_LIT(KCmndSmokeTest, "smoke-test");
_LIT(KCmndArgument, "argument");
_LIT(KCmndOption, "option");
_LIT(KCmndInclude, "include");
_LIT(KCmndSubCommand, "sub-command");
_LIT(KCmndEnumValue, "enum-value");
_LIT(KCmndFlagOptional, "optional");
_LIT(KCmndFlagMultiple, "multiple");
_LIT(KCmndFlagLast, "last");
_LIT(KTypeBool, "bool");
_LIT(KTypeInt, "int");
_LIT(KTypeUint, "uint");
_LIT(KTypeString, "string");
_LIT(KTypeFileName, "filename");
_LIT(KTypeEnum, "enum");
_LIT(KTypeReal, "real");
_LIT(KTypeUint64, "uint64");
_LIT(KTypeInt64, "int64");

using namespace IoUtils;

TInt SkipTo(TLex& aLex, const TDesC& aDes)
	{
	TInt ret = aLex.Remainder().Find(aDes);
	if (ret >= 0)
		{
		aLex.Inc(ret);
		return KErrNone;
		}
	return KErrNotFound;
	}

TInt SkipToEnd(TLex& aLex, const TDesC& aDes)
	{
	TInt ret = aLex.Remainder().Find(aDes);
	if (ret >= 0)
		{
		aLex.Inc(ret + aDes.Length());
		return KErrNone;
		}
	return KErrNotFound;
	}

TInt SkipToNextCommand(TLex& aLex)
	{
	if ((aLex.Offset() == 0) && (aLex.Remainder().Find(KPartialCommandStart) == 0))
		{
		return KErrNone;
		}

	return SkipTo(aLex, KCommandStart);
	}

TPtrC NextCommand(TLex& aLex)
	{
	TPtrC command;
	if (SkipToNextCommand(aLex) == KErrNone)
		{
		SkipToEnd(aLex, KPartialCommandStart);
		aLex.Mark();
		aLex.SkipCharacters();
		command.Set(aLex.MarkedToken());
		}
	else
		{
		aLex.Inc(aLex.Remainder().Length());
		}
	return command;
	}

TPtrC LineRemainder(TLex& aLex)
	{
	aLex.Mark();
	if (SkipTo(aLex, KNewLine) == KErrNone)
		{
		return aLex.MarkedToken();
		}
	else
		{
		return aLex.Remainder();
		}
	}

TPtrC NextWord(TLex& aLex)
	{
	aLex.SkipSpaceAndMark();
	aLex.SkipCharacters();
	TPtrC word(aLex.MarkedToken());
	return word;
	}

TPtrC TextToNextCommand(TLex& aLex)
	{
	aLex.Mark();

	if (SkipToNextCommand(aLex) != KErrNone)
		{
		aLex.Inc(aLex.Remainder().Length());
		}

	TLex lex(aLex.MarkedToken());
	
	// Strip leading white space.
	while (!lex.Eos())
		{
		if (lex.Peek().IsSpace())
			{
			lex.Get();
			}
		else
			{
			break;
			}
		}
	lex.Mark();
	TInt startOffset = lex.Offset();

	// Remove trailing white space.
	lex.Inc(lex.Remainder().Length());
	while (lex.Offset() > startOffset)
		{
		lex.UnGet();
		if (!lex.Peek().IsSpace())
			{
			lex.Get();
			break;
			}
		}

	return lex.MarkedToken();
	}

void AppendL(RBuf& aBuf, const TDesC& aDes, TBool aQuote)
	{
	const TInt requiredSpace = aDes.Length() + ((aBuf.Length() == 0) ? 0 : KEnumSeparator().Length()) + (aQuote ? 2 : 0);
	if ((aBuf.Length() + requiredSpace) > aBuf.MaxLength())
		{
		aBuf.ReAllocL(Max(aBuf.Length() * 2, aBuf.Length() + requiredSpace));
		}
	if (aBuf.Length() > 0)
		{
		aBuf.Append(KEnumSeparator);
		}
	if (aQuote)
		{
		aBuf.Append(KEnumQuote);
		}
	aBuf.Append(aDes);
	if (aQuote)
		{
		aBuf.Append(KEnumQuote);
		}
	}

void GetEnumValuesL(TLex& aLex, RBuf& aValues, RBuf& aDescriptions)
	{
	aValues.CreateL(0x100);
	aDescriptions.CreateL(0x100);

	while (!aLex.Eos())
		{
		TLexMark mark;
		aLex.Mark(mark);
		TPtrC command(NextCommand(aLex));
		if (command == KCmndEnumValue)
			{
			TPtrC value(NextWord(aLex));
			TPtrC description(TextToNextCommand(aLex));
			if (value.Length() == 0)
				{
				User::Leave(KErrArgument);
				}
			AppendL(aValues, value, EFalse);
			if (description.Length() > 0)
				{
				AppendL(aDescriptions, description, ETrue);
				}
			}
		else
			{
			aLex.UnGetToMark(mark);
			break;
			}
		}
	}


EXPORT_C CCommandInfoFile* CCommandInfoFile::NewL(RFs& aFs, const TDesC& aFileName)
	{
	CCommandInfoFile* self = new(ELeave) CCommandInfoFile(aFileName);
	CleanupStack::PushL(self);
	self->ConstructL(aFs);
	CleanupStack::Pop(self);
	return self;
	}

EXPORT_C CCommandInfoFile* CCommandInfoFile::NewL(RFs& aFs, const CEnvironment& aEnvironment, const TDesC& aCommandName)
	{
	TFileName2 fileName;
	TRAPD(err, fileName.Copy(aEnvironment.GetAsDesL(KCifPathVar)));
	if (err) fileName.Copy(KCifPath);

	// Search the drive the command is running from first (rather than what the session path happens to be, which was the previous behaviour)
	if (!fileName.HasDriveLetter()) fileName.Insert(0, _L("?:"));
	fileName[0] = RProcess().FileName()[0];

	fileName.AppendComponentL(aCommandName);
	fileName.SetExtL(KCifExt);
	StaticLeaveIfErr(fileName.FindFile(aFs), _L("Couldn't find \"%S\""), &fileName);

	CCommandInfoFile* cif = CCommandInfoFile::NewL(aFs, fileName);
	if (cif->Name() != aCommandName)
		{
		CleanupStack::PushL(cif);
		StaticLeaveIfErr(KErrArgument, _L("Command named \"%S\" in implementation but \"%S\" in \"%S\""), &aCommandName, &cif->Name(), &fileName);
		}
	return cif;
	}

void CCommandInfoFile::ConstructL(RFs& aFs)
	{
	ReadFileL(aFs, iFileName);
	}

void CCommandInfoFile::ReadFileL(RFs& aFs, const TDesC& aFileName)
	{
	// Read the file into a buffer.
	RFile file;
	StaticLeaveIfErr(file.Open(aFs, aFileName, EFileRead | EFileShareReadersOnly), _L("Couldn't open '%S' for reading"), &aFileName);
	CleanupClosePushL(file);
	TInt fileSize;
	User::LeaveIfError(file.Size(fileSize));
	RBuf8 buf8;
	buf8.CreateL(fileSize);
	buf8.CleanupClosePushL();
	User::LeaveIfError(file.Read(buf8));
	RBuf& buf = NewBuffer();
	buf.CreateL(fileSize);
	buf.Copy(buf8);
	CleanupStack::PopAndDestroy(2, &file);
	TLex lex(buf);
	while (!lex.Eos())
		{
		if (iProcessInclude || !iCurrentChild)
			{
			ReadDetailsL(lex, aFs, aFileName);
			}
		else
			{
			iCurrentChild->ReadDetailsL(lex, aFs, aFileName);
			}
		}
	}

void CCommandInfoFile::ReadDetailsL(TLex& aLex, RFs& aFs, const TDesC& aFileName)
	{
	SkipToNextCommand(aLex); // Ignore everything before the first '==' command.

	while (!aLex.Eos())
		{
		TLexMark mark;
		aLex.Mark(mark);
		TPtrC command(NextCommand(aLex));
		if (command == KNullDesC)
			{
			// Do nothing - we're at the end of the string.
			}
		else if (command == KCmndName)
			{
			aLex.SkipSpaceAndMark();
			aLex.SkipCharacters();
			iName.Set(aLex.MarkedToken());
			}
		else if (command == KCmndShortDescription)
			{
			iShortDescription.Set(TextToNextCommand(aLex));
			}
		else if (command == KCmndLongDescription)
			{
			iLongDescription.Set(TextToNextCommand(aLex));
			}
		else if (command == KCmndSeeAlso)
			{
			iSeeAlso.Set(TextToNextCommand(aLex));
			}
		else if (command == KCmndCopyright)
			{
			iCopyright.Set(TextToNextCommand(aLex));
			}
		else if (command == KCmndSmokeTest)
			{
			// Hmm no easy way to get the line number we're currently on
			iSmokeTestLineNumber = 1;
			TLex lex(aLex);
			lex.Inc(-aLex.Offset()); // Only way to put a TLex back to the beginning!
			TPtrC preceding = lex.Remainder().Left(aLex.Offset());
			const TUint16* ptr = preceding.Ptr();
			const TUint16* end = ptr + preceding.Length();
			while (ptr != end)
				{
				if (*ptr++ == '\n') iSmokeTestLineNumber++;
				}
			// At this point iSmokeTestLineNumber points to the "==smoketest" line - add 2 to skip this line and the blank line below it
			iSmokeTestLineNumber += 2;
			iSmokeTest.Set(TextToNextCommand(aLex));
			}
		else if (command == KCmndArgument)
			{
			ReadArgumentL(aLex, aFileName);
			}
		else if (command == KCmndOption)
			{
			ReadOptionL(aLex, aFileName);
			}
		else if (command == KCmndInclude)
			{
			if (iParent == NULL)
				{
				iProcessInclude = EFalse;
				TLex lineLex(LineRemainder(aLex));
				TPtrC fileName(NextWord(lineLex));
				TFileName2* fullFileName = new(ELeave) TFileName2(aFileName);
				CleanupStack::PushL(fullFileName);
				fullFileName->SetNameAndExtL(fileName);
				ReadFileL(aFs, *fullFileName);
				CleanupStack::PopAndDestroy(fullFileName);
				break;
				}
			else
				{
				// We're a sub-command. Let control return to the root to handle the include.
				aLex.UnGetToMark(mark);
				iParent->ProcessInclude(*this);
				break;
				}
			}
		else if (command == KCmndSubCommand)
			{
			if (iParent == NULL)
				{
				TLex lineLex(LineRemainder(aLex));
				AddSubCommandL(lineLex, aLex, aFs, aFileName);
				}
			else
				{
				// We're a sub-command. Let control return to the root to handle the next sub-command.
				aLex.UnGetToMark(mark);
				iParent->ProcessNewChild();
				break;
				}
			}
		else
			{
			StaticLeaveIfErr(KErrArgument, _L("Unknown command \"%S\" in \"%S\""), &command, &aFileName);
			}
		}
	}

void CCommandInfoFile::ReadArgumentL(TLex& aLex, const TDesC& aFileName)
	{
	TLex lineLex(LineRemainder(aLex));
	TUint flags = 0;
	TBool multiple(EFalse);
	TPtrC argType(NextWord(lineLex));
	TPtrC argName(NextWord(lineLex));
	TPtrC envVar;
	while (!lineLex.Eos())
		{
		TPtrC word(NextWord(lineLex));
		if (word == KCmndFlagOptional)
			{
			flags |= KValueTypeFlagOptional;
			}
		else if (word == KCmndFlagMultiple)
			{
			multiple = ETrue;
			}
		else if (word == KCmndFlagLast)
			{
			flags |= KValueTypeFlagLast;
			}
		else
			{
			envVar.Set(word);
			}
		}

	if (argType.Length() == 0)
		{
		StaticLeaveIfErr(KErrArgument, _L("Argument missing type"));
		}
	else if (argName.Length() == 0)
		{
		StaticLeaveIfErr(KErrArgument, _L("Argument missing name"));
		}
	else
		{
		TPtrC argDescription = TextToNextCommand(aLex);
		if (argType == KTypeEnum)
			{
			RBuf& enumValues = NewBuffer();
			RBuf& enumDescriptions = NewBuffer();
			GetEnumValuesL(aLex, enumValues, enumDescriptions);
			if (multiple)
				{
				RArray<TInt> dummyArray;
				if (enumDescriptions.Length() > 0)
					{
					iArguments.AppendEnumL(dummyArray, argName, argDescription, enumValues, enumDescriptions, flags, envVar);
					}
				else
					{
					iArguments.AppendEnumL(dummyArray, argName, argDescription, enumValues, flags, envVar);
					}
				}
			else
				{
				TInt dummyInt;
				if (enumDescriptions.Length() > 0)
					{
					iArguments.AppendEnumL(dummyInt, argName, argDescription, enumValues, enumDescriptions, flags, envVar);
					}
				else
					{
					iArguments.AppendEnumL(dummyInt, argName, argDescription, enumValues, flags, envVar);
					}
				}
			}
		else if (argType == KTypeInt)
			{
			if (multiple)
				{
				RArray<TInt> dummyArray;
				iArguments.AppendIntL(dummyArray, argName, argDescription, flags, envVar);
				}
			else
				{
				TInt dummyInt;
				iArguments.AppendIntL(dummyInt, argName, argDescription, flags, envVar);
				}
			}
		else if (argType == KTypeUint)
			{
			if (multiple)
				{
				RArray<TUint> dummyArray;
				iArguments.AppendUintL(dummyArray, argName, argDescription, flags, envVar);
				}
			else
				{
				TUint dummyUint;
				iArguments.AppendUintL(dummyUint, argName, argDescription, flags, envVar);
				}
			}
		else if (argType == KTypeString)
			{
			if (multiple)
				{
				RPointerArray<HBufC> dummyArray;
				iArguments.AppendStringL(dummyArray, argName, argDescription, flags, envVar);
				}
			else
				{
				HBufC* dummyHBufC;
				iArguments.AppendStringL(dummyHBufC, argName, argDescription, flags, envVar);
				}
			}
		else if (argType == KTypeFileName)
			{
			if (multiple)
				{
				RArray<TFileName2> dummyArray;
				iArguments.AppendFileNameL(dummyArray, argName, argDescription, flags, envVar);
				}
			else
				{
				TFileName2 dummyFileName;
				iArguments.AppendFileNameL(dummyFileName, argName, argDescription, flags, envVar);
				}
			}
		else if (argType == KTypeReal)
			{
			if (multiple)
				{
				RArray<TReal64> dummyArray;
				iArguments.AppendRealL(dummyArray, argName, argDescription, flags, envVar);
				}
			else
				{
				TReal64 dummyReal;
				iArguments.AppendRealL(dummyReal, argName, argDescription, flags, envVar);
				}
			}
		else if (argType == KTypeUint64)
			{
			if (multiple)
				{
				RArray<TUint64> dummyArray;
				iArguments.AppendUintL(dummyArray, argName, argDescription, flags, envVar);
				}
			else
				{
				TUint64 dummyUint64;
				iArguments.AppendUintL(dummyUint64, argName, argDescription, flags, envVar);
				}
			}
		else if (argType == KTypeInt64)
			{
			if (multiple)
				{
				RArray<TInt64> dummyArray;
				iArguments.AppendIntL(dummyArray, argName, argDescription, flags, envVar);
				}
			else
				{
				TInt64 dummyInt64;
				iArguments.AppendIntL(dummyInt64, argName, argDescription, flags, envVar);
				}
			}
		else
			{
			StaticLeaveIfErr(KErrArgument, _L("Unknown argument type \"%S\" in \"%S\""), &argType, &aFileName);
			}
		}
	}

void CCommandInfoFile::ReadOptionL(TLex& aLex, const TDesC& aFileName)
	{
	TLex lineLex(LineRemainder(aLex));
	TBool multiple(EFalse);
	TPtrC optType(NextWord(lineLex));
	TPtrC optShortName(NextWord(lineLex));
	TPtrC optLongName(NextWord(lineLex));
	TPtrC envVar;
	while (!lineLex.Eos())
		{
		TPtrC word(NextWord(lineLex));
		if (word == KCmndFlagMultiple)
			{
			multiple = ETrue;
			}
		else
			{
			envVar.Set(word);
			}
		}

	if (optType.Length() == 0)
		{
		StaticLeaveIfErr(KErrArgument, _L("Option missing type"));
		}
	else if (optShortName.Length() == 0)
		{
		StaticLeaveIfErr(KErrArgument, _L("Option missing short name"));
		}
	else if (optShortName.Length() != 1)
		{
		StaticLeaveIfErr(KErrArgument, _L("Option short name not a single character"));
		}
	else if (optLongName.Length() == 0)
		{
		StaticLeaveIfErr(KErrArgument, _L("Option missing long name"));
		}
	else
		{
		TPtrC argDescription = TextToNextCommand(aLex);
		if (optType == KTypeEnum)
			{
			RBuf& enumValues = NewBuffer();
			RBuf& enumDescriptions = NewBuffer();
			GetEnumValuesL(aLex, enumValues, enumDescriptions);
			if (multiple)
				{
				RArray<TInt> dummyArray;
				if (enumDescriptions.Length() > 0)
					{
					iOptions.AppendEnumL(dummyArray, optShortName[0], optLongName, argDescription, enumValues, enumDescriptions, 0, envVar);
					}
				else
					{
					iOptions.AppendEnumL(dummyArray, optShortName[0], optLongName, argDescription, enumValues, 0, envVar);
					}
				}
			else
				{
				TInt dummyInt;
				if (enumDescriptions.Length() > 0)
					{
					iOptions.AppendEnumL(dummyInt, optShortName[0], optLongName, argDescription, enumValues, enumDescriptions, 0, envVar);
					}
				else
					{
					iOptions.AppendEnumL(dummyInt, optShortName[0], optLongName, argDescription, enumValues, 0, envVar);
					}
				}
			}
		else if (optType == KTypeBool)
			{
			if (multiple)
				{
				RArray<TBool> dummyArray;
				iOptions.AppendBoolL(dummyArray, optShortName[0], optLongName, argDescription, 0, envVar);
				}
			else
				{
				TBool dummyBool;
				iOptions.AppendBoolL(dummyBool, optShortName[0], optLongName, argDescription, 0, envVar);
				}
			}
		else if (optType == KTypeInt)
			{
			if (multiple)
				{
				RArray<TInt> dummyArray;
				iOptions.AppendIntL(dummyArray, optShortName[0], optLongName, argDescription, 0, envVar);
				}
			else
				{
				TInt dummyInt;
				iOptions.AppendIntL(dummyInt, optShortName[0], optLongName, argDescription, 0, envVar);
				}
			}
		else if (optType == KTypeUint)
			{
			if (multiple)
				{
				RArray<TUint> dummyArray;
				iOptions.AppendUintL(dummyArray, optShortName[0], optLongName, argDescription, 0, envVar);
				}
			else
				{
				TUint dummyUint;
				iOptions.AppendUintL(dummyUint, optShortName[0], optLongName, argDescription, 0, envVar);
				}
			}
		else if (optType == KTypeString)
			{
			if (multiple)
				{
				RPointerArray<HBufC> dummyArray;
				iOptions.AppendStringL(dummyArray, optShortName[0], optLongName, argDescription, 0, envVar);
				}
			else
				{
				HBufC* dummyHBufC;
				iOptions.AppendStringL(dummyHBufC, optShortName[0], optLongName, argDescription, 0, envVar);
				}
			}
		else if (optType == KTypeFileName)
			{
			if (multiple)
				{
				RArray<TFileName2> dummyArray;
				iOptions.AppendFileNameL(dummyArray, optShortName[0], optLongName, argDescription, 0, envVar);
				}
			else
				{
				TFileName2 dummyFileName;
				iOptions.AppendFileNameL(dummyFileName, optShortName[0], optLongName, argDescription, 0, envVar);
				}
			}
		else if (optType == KTypeReal)
			{
			if (multiple)
				{
				RArray<TReal64> dummyArray;
				iOptions.AppendRealL(dummyArray, optShortName[0], optLongName, argDescription, 0, envVar);
				}
			else
				{
				TReal64 dummyReal;
				iOptions.AppendRealL(dummyReal, optShortName[0], optLongName, argDescription, 0, envVar);
				}
			}
		else if (optType == KTypeUint64)
			{
			if (multiple)
				{
				RArray<TUint64> dummyArray;
				iOptions.AppendUintL(dummyArray, optShortName[0], optLongName, argDescription, 0, envVar);
				}
			else
				{
				TUint64 dummyUint64;
				iOptions.AppendUintL(dummyUint64, optShortName[0], optLongName, argDescription, 0, envVar);
				}
			}
		else if (optType == KTypeInt64)
			{
			if (multiple)
				{
				RArray<TInt64> dummyArray;
				iOptions.AppendIntL(dummyArray, optShortName[0], optLongName, argDescription, 0, envVar);
				}
			else
				{
				TInt64 dummyInt64;
				iOptions.AppendIntL(dummyInt64, optShortName[0], optLongName, argDescription, 0, envVar);
				}
			}
		else
			{
			StaticLeaveIfErr(KErrArgument, _L("Unknown option type \"%S\" in \"%S\""), &optType, &aFileName);
			}
		}
	}

void CCommandInfoFile::ProcessNewChild()
	{
	if (iParent)
		{
		iParent->ProcessNewChild();
		}
	else
		{
		ASSERT(!iProcessInclude);
		iCurrentChild = NULL;
		}
	}

void CCommandInfoFile::ProcessInclude(CCommandInfoFile& aChild)
	{
	if (iParent)
		{
		iParent->ProcessInclude(aChild);
		}
	else
		{
		iProcessInclude = ETrue;
		iCurrentChild = &aChild;
		}
	}

void CCommandInfoFile::AddSubCommandL(TLex& aNameLex, TLex& aDataLex, RFs& aFs, const TDesC& aFileName)
	{
	TPtrC subCommandName(NextWord(aNameLex));
	TBool found(EFalse);
	for (TInt i = (iChildren.Count() - 1); i >= 0; --i)
		{
		if (iChildren[i]->Name() == subCommandName)
			{
			iChildren[i]->AddSubCommandL(aNameLex, aDataLex, aFs, aFileName);
			found = ETrue;
			break;
			}
		}
	__ASSERT_ALWAYS(found || (aNameLex.Remainder().Length() == 0), IoUtils::Panic(ECifSubCommandParentNotFound));
	if (!found)
		{
		CCommandInfoFile* subCommand = new(ELeave) CCommandInfoFile(*this);
		CleanupStack::PushL(subCommand);
		subCommand->iName.Set(subCommandName);
		subCommand->ReadDetailsL(aDataLex, aFs, aFileName);
		iChildren.AppendL(subCommand);
		CleanupStack::Pop(subCommand);
		}
	}

RBuf& CCommandInfoFile::NewBuffer()
	{
	RBuf buf;
	iBufs.AppendL(buf);
	return iBufs[iBufs.Count() - 1];
	}

EXPORT_C CCommandInfoFile::~CCommandInfoFile()
	{
	iArguments.Close();
	iOptions.Close();

	const TInt numBufs = iBufs.Count();
	for (TInt i = 0; i < numBufs; ++i)
		{
		iBufs[i].Close();
		}
	iBufs.Close();
	iChildren.ResetAndDestroy();
	}

EXPORT_C const TDesC& CCommandInfoFile::Name() const
	{
	return iName;
	}

EXPORT_C const TDesC& CCommandInfoFile::ShortDescription() const
	{
	return iShortDescription;
	}

EXPORT_C const TDesC& CCommandInfoFile::LongDescription() const
	{
	return iLongDescription;
	}

EXPORT_C const TDesC& CCommandInfoFile::SeeAlso() const
	{
	return iSeeAlso;
	}

EXPORT_C const TDesC& CCommandInfoFile::Copyright() const
	{
	return iCopyright;
	}

EXPORT_C const TDesC& CCommandInfoFile::SmokeTest() const
	{
	return iSmokeTest;
	}

EXPORT_C TInt CCommandInfoFile::GetSmokeTestStartingLineNumber() const
	{
	return iSmokeTestLineNumber;
	}

EXPORT_C const RCommandArgumentList& CCommandInfoFile::Arguments()
	{
	return iArguments;
	}

EXPORT_C const RCommandOptionList& CCommandInfoFile::Options() const
	{
	return iOptions;
	}

template <class T> void CombineL(const T& aSourceList, T& aDestinationList, const TDesC& aListType, const TDesC& aCifName)
	{
	const TInt sourceCount = aSourceList.Count();
	const TInt destinationCount = aDestinationList.Count();

	if (sourceCount < destinationCount) // Note, allow the source to have more than the destination because some things are compile time configurable but CIF files don't support this.
		{
		StaticLeaveIfErr(KErrArgument, _L("%S count mismatch between \"%S\" (%d) and implementation (%d)"), &aListType, &aCifName, sourceCount, destinationCount);
		}

	for (TInt i = 0; i < destinationCount; ++i)
		{
		const TDesC& destinationName = aDestinationList[i].Name();
		TInt sourcePos = aSourceList.Find(destinationName);
		if (sourcePos < 0)
			{
			StaticLeaveIfErr(KErrArgument, _L("Couldn't find %S \"%S\" in \"%S\""), &aListType, &destinationName, &aCifName);
			}

		TUint sourceType = aSourceList[sourcePos].ValueType();
		TUint sourceTypeForComparison = sourceType & ~(KValueTypeFlagOptional|KValueTypeFlagLast); // destType can't know about these flags, so clear them from sourceType so we don't have problems when destType doesn't set them
		TUint destType = aDestinationList[i].ValueType();
		if (sourceTypeForComparison != destType)
			{
			StaticLeaveIfErr(KErrArgument, _L("Type mismatch for %S '%S' between \"%S\" (0x%x) and implementation (0x%x)"), &aListType, &destinationName, &aCifName, sourceType, destType);
			}
		aDestinationList[i].Combine(aSourceList[sourcePos]);
		}
	}

EXPORT_C void CCommandInfoFile::AssignL(RCommandArgumentList& aArguments, RCommandOptionList& aOptions) const
	{
	CombineL(iArguments, aArguments, _L("Argument"), iFileName);
	CombineL(iOptions, aOptions, _L("Option"), iFileName);
	}

EXPORT_C TInt CCommandInfoFile::NumSubCommands() const
	{
	return iChildren.Count();
	}

EXPORT_C const CCommandInfoFile& CCommandInfoFile::SubCommand(TInt aIndex) const
	{
	return *(iChildren[aIndex]);
	}

EXPORT_C const CCommandInfoFile* CCommandInfoFile::SubCommand(const TDesC& aSubCommandName) const
	{
	for (TInt i = (NumSubCommands() - 1); i >= 0; --i)
		{
		const CCommandInfoFile& thisSubCommand = SubCommand(i);
		if (thisSubCommand.Name() == aSubCommandName)
			{
			return &thisSubCommand;
			}
		}
	return NULL;
	}

CCommandInfoFile::CCommandInfoFile(const TDesC& aFileName)
	: iFileName(aFileName)
	{
	}

CCommandInfoFile::CCommandInfoFile(CCommandInfoFile& aParent)
	: iFileName(aParent.iFileName), iParent(&aParent)
	{
	}

EXPORT_C const TDesC& CCommandInfoFile::CifFileName() const
	{
	return iFileName;
	}
