// line_completer.cpp
// 
// Copyright (c) 2006 - 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 <fshell/ltkutils.h>
#include "lexer.h"
#include "command_factory.h"
#include "line_completer.h"

_LIT(KTab, "\t");
_LIT(KWild, "*");
_LIT(KDoubleQuote, "\"");
_LIT(KSpace, " ");
_LIT(KFileNameSlash, "\\");
_LIT(KPipe, "|");
_LIT(KDoublePipe, "||");
_LIT(KAmpersand, "&");
_LIT(KDoubleAmpersand, "&&");
_LIT(KAmpersandPipe, "&|");
_LIT(KSemicolon, ";");
_LIT(KDollar, "$");
_LIT(KDashDash, "--");
_LIT(KDash, "-");

CLineCompleter* CLineCompleter::NewL(RFs& aFs, CCommandFactory& aCommandFactory, IoUtils::CEnvironment& aEnv)
	{
	CLineCompleter* self = new(ELeave) CLineCompleter(aFs, aCommandFactory, aEnv);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop(self);
	return self;
	}

CLineCompleter::~CLineCompleter()
	{
	delete iLexer;
	delete iLastUsedCif;
	}

void CLineCompleter::LcCompleteLineL(TConsoleLine& aLine, const TChar& aEscapeChar)
	{
	iLexer->Set(aLine.ContentsToCursor(), aEscapeChar);
	TToken token(iLexer->NextToken());
	TToken firstToken = token;
	TToken prevToken(TToken::EString, KNullDesC, aLine.ContentsToCursor().Length());
	TInt lastTokenIdx = 0;
	while (iLexer->More())
		{
		// More than one token - skip to last.
		prevToken = token;
		token = iLexer->NextToken();
		lastTokenIdx++;
		}

	switch (prevToken.Type())
		{
		case TToken::EPipe:
		case TToken::EAmpersand:
		case TToken::ESemicolon:
		case TToken::EDoublePipe:
		case TToken::EDoubleAmpersand:
		case TToken::EAmpersandPipe:
			CompleteCommandNameL(aLine, token, aEscapeChar);
			break;
		default:
			if (token.Position() + token.String().Length() < aLine.ContentsToCursor().Length())
				{
				// Looks like there's whitespace after this token, so consider it to be completing the empty token.
				token = TToken(TToken::EString, KNullDesC, aLine.ContentsToCursor().Length());
				}
			else if (lastTokenIdx == 0)
				{
				CompleteCommandNameL(aLine, token, aEscapeChar);
				break;
				}

			if (token.String().Left(1) == KDollar)
				{
				CompleteEnvVarL(aLine, token, aEscapeChar);
				}
			else if (token.String() == KDash || token.String().Left(2) == KDashDash)
				{
				// Check the CIF is loaded and it's the right one
				if (iLastUsedCif && iLastUsedCif->Name().CompareF(firstToken.String()) != 0)
					{
					delete iLastUsedCif;
					iLastUsedCif = NULL;
					}
				TInt err = KErrNone;
				if (iLastUsedCif == NULL)
					{
					// We need to supress StaticLeaveIfErrs from CCommandInfoFile::NewL causing a PrintError if there's no CIF for this command,
					// hence the use of TRAP_QUIETLY.
					TRAP_QUIETLY(err, iLastUsedCif = IoUtils::CCommandInfoFile::NewL(iFs, iEnv, firstToken.String()));
					}
				if (err == KErrNone)
					{
					CompleteOptionL(aLine, token, aEscapeChar);
					}
				}
			else
				{
				CompleteFileNameL(aLine, token, aEscapeChar);
				}
			break;
		}
	}

CLineCompleter::CLineCompleter(RFs& aFs, CCommandFactory& aCommandFactory, IoUtils::CEnvironment& aEnv)
	: iFs(aFs), iCommandFactory(aCommandFactory), iEnv(aEnv)
	{
	}

void CLineCompleter::ConstructL()
	{
	iLexer = CLexer::NewL();
	iLexer->DefineTokenTypeL(TToken::EPipe, KPipe);
	iLexer->DefineTokenTypeL(TToken::EAmpersand, KAmpersand);
	iLexer->DefineTokenTypeL(TToken::ESemicolon, KSemicolon);
	iLexer->DefineTokenTypeL(TToken::EDoublePipe, KDoublePipe);
	iLexer->DefineTokenTypeL(TToken::EDoubleAmpersand, KDoubleAmpersand);
	iLexer->DefineTokenTypeL(TToken::EAmpersandPipe, KAmpersandPipe);
	}

HBufC* SimplifyStringLC(const TDesC& aString, const TChar& aEscapeChar)
	{
	HBufC* buf = HBufC::NewLC(aString.Length());
	TPtr bufPtr(buf->Des());
	TLex lex(aString);
	while (!lex.Eos())
		{
		TChar c = lex.Get();
		if (c == aEscapeChar)
			{
			if (!lex.Eos())
				{
				bufPtr.Append(lex.Get());
				}
			}
		else if (c == '\'')
			{
			while (!lex.Eos())
				{
				c = lex.Get();
				if (c == '\'')
					{
					break;
					}
				else
					{
					bufPtr.Append(c);
					}
				}
			}
		else if (c == '"')
			{
			while (!lex.Eos())
				{
				c = lex.Get();
				if (c == aEscapeChar)
					{
					bufPtr.Append(lex.Get());
					}
				else if (c == '"')
					{
					break;
					}
				else
					{
					bufPtr.Append(c);
					}
				}
			}
		else
			{
			bufPtr.Append(c);
			}
		}
	return buf;
	}

void CLineCompleter::CompleteCommandNameL(TConsoleLine& aLine, const TToken& aToken, const TChar& aEscapeChar) const
	{
	RArray<TPtrC> commands;
	CleanupClosePushL(commands);
	iCommandFactory.ListCommandsL(commands);
	HBufC* commandBuf = SimplifyStringLC(aToken.String(), aEscapeChar);

	// Remove commands that don't match aLine.
	TInt i;
	for (i = (commands.Count() - 1); i >= 0; --i)
		{
		if (commands[i].Find(*commandBuf) != 0)
			{
			commands.Remove(i);
			}
		}

	if (commands.Count() > 0)
		{
		CompleteL(aLine, aToken, commands, NULL, NULL);
		}
	else
		{
		CompleteFileNameL(aLine, aToken, aEscapeChar);
		}
	CleanupStack::PopAndDestroy(2, &commands);
	}

void RemovePartialElement(HBufC*& aBuf)
	{
	TPtr ptr(aBuf->Des());
	while (ptr.Length() > 0)
		{
		if (ptr[ptr.Length() - 1] == KPathDelimiter)
			{
			break;
			}
		else
			{
			ptr.Delete(ptr.Length() - 1, 1);
			}
		}
	}

void CLineCompleter::CompleteFileNameL(TConsoleLine& aLine, const TToken& aToken, const TChar& aEscapeChar) const
	{
	HBufC* fileNameBuf = SimplifyStringLC(aToken.String(), aEscapeChar);
	IoUtils::TFileName2 absFileName(*fileNameBuf);
	RemovePartialElement(fileNameBuf);
	absFileName.Append(KWild);
	absFileName.MakeAbsoluteL(iEnv.Pwd());
	CDir* files;
	User::LeaveIfError(iFs.GetDir(absFileName, KEntryAttNormal | KEntryAttDir, ESortByName, files));
	CleanupStack::PushL(files);
	RArray<TPtrC> fileNames;
	CleanupClosePushL(fileNames);
	const TInt numFiles = files->Count();
	if (numFiles == 1)
		{
		absFileName = absFileName.DriveAndPath();
		absFileName.AppendComponentL((*files)[0].iName);
		TEntry entry;
		User::LeaveIfError(iFs.Entry(absFileName, entry));
		User::LeaveIfError(fileNames.Append(absFileName.NameAndExt()));
		CompleteL(aLine, aToken, fileNames, fileNameBuf, entry.IsDir() ? &KFileNameSlash : NULL);
		}
	else if (numFiles > 1)
		{
		for (TInt i = 0; i < numFiles; ++i)
			{
			User::LeaveIfError(fileNames.Append(TPtrC((*files)[i].iName)));
			}
		CompleteL(aLine, aToken, fileNames, fileNameBuf, NULL);
		}
	CleanupStack::PopAndDestroy(3, fileNameBuf);
	}

TBool NeedsQuoting(const TDesC& aDes)
	{
	return ((aDes.Locate(TChar(' ')) >= 0) || aDes.Locate(TChar('\'')) >= 0);
	}

void CLineCompleter::CompleteL(TConsoleLine& aLine, const TToken& aToken, const RArray<TPtrC> aPossibilities, const TDesC* aPrefix, const TDesC* aSuffix, TBool aPrefixIsPartOfToken) const
	{
	const TInt numPossibilities = aPossibilities.Count();

	if (numPossibilities > 1)
		{
		// Fill out possibilities buffer.
		IoUtils::CTextBuffer* possibilities = IoUtils::CTextBuffer::NewLC(0x100);
		for (TInt i = 0; i < numPossibilities; ++i)
			{
			if (aPrefixIsPartOfToken) possibilities->AppendL(*aPrefix);
			possibilities->AppendL(aPossibilities[i]);
			if (i != (numPossibilities - 1))
				{
				possibilities->AppendL(KTab);
				}
			}

		aLine.PrintCompletionPossibilitiesL(possibilities->Descriptor());
		CleanupStack::PopAndDestroy(possibilities);
		}

	if (numPossibilities > 0)
		{
		IoUtils::CTextBuffer* completion = IoUtils::CTextBuffer::NewLC(0x100);
		TPtrC commonChars(NULL, 0);
		if (numPossibilities > 1)
			{
			// Find common leading characters of the possibilities.
			TInt commonCharPos = -1;
			TBool finished(EFalse);
			do
				{
				++commonCharPos;
				TChar character(0);
				for (TInt i = 0; i < numPossibilities; ++i)
					{
					if (commonCharPos >= aPossibilities[i].Length())
						{
						finished = ETrue;
						break;
						}
					else if (i == 0)
						{
						character = aPossibilities[0][commonCharPos];
						character.Fold();
						}
					else
						{
						TChar c(aPossibilities[i][commonCharPos]);
						c.Fold();
						if (c != character)
							{
							finished = ETrue;
							break;
							}
						}
					}
				}
				while (!finished);

			commonChars.Set(aPossibilities[0].Mid(0, commonCharPos));
			}
		else
			{
			commonChars.Set(aPossibilities[0]);
			}

		TBool quote(EFalse);
		if ((aPrefix && NeedsQuoting(*aPrefix)) || NeedsQuoting(commonChars) || (aSuffix && NeedsQuoting(*aSuffix)))
			{
			quote = ETrue;
			}
		if (quote)
			{
			completion->AppendL(KDoubleQuote);
			}
		if (aPrefix)
			{
			completion->AppendL(*aPrefix);
			}
		completion->AppendL(commonChars);
		if ((numPossibilities == 1) && quote)
			{
			completion->AppendL(KDoubleQuote);
			}
		if (numPossibilities == 1)
			{
			if (aSuffix)
				{
				completion->AppendL(*aSuffix);
				}
			else
				{
				completion->AppendL(KSpace);
				}
			}
		if (completion->Descriptor().Length() > 0)
			{
			aLine.Replace(aToken.Position(), completion->Descriptor());
			}
		CleanupStack::PopAndDestroy(completion);
		}
	}

void CLineCompleter::CompleteEnvVarL(TConsoleLine& aLine, const TToken& aToken, const TChar& /*aEscapeChar*/) const
	{
	RArray<TPtrC> matchingVars;
	CleanupClosePushL(matchingVars);
	TPtrC envVar = aToken.String().Mid(1); // Lose the $

	RPointerArray<HBufC> keys;
	LtkUtils::CleanupResetAndDestroyPushL(keys);
	iEnv.GetKeysL(keys);
	
	const TInt count = keys.Count();
	for (TInt i = 0; i < count; i++)
		{
		const TDesC& key = *keys[i];
		if (key.Left(envVar.Length()) == envVar)
			{
			matchingVars.AppendL(TPtrC(key));
			}
		}
	CompleteL(aLine, aToken, matchingVars, &KDollar, NULL, ETrue);
	CleanupStack::PopAndDestroy(2, &matchingVars); // keys, matchingVars
	}

void CLineCompleter::CompleteOptionL(TConsoleLine& aLine, const TToken& aToken, const TChar& /*aEscapeChar*/) const
	{
	RArray<TPtrC> matchingOptions;
	CleanupClosePushL(matchingOptions);
	TPtrC optFrag = aToken.String();
	if (optFrag.Length() >= 2) optFrag.Set(optFrag.Mid(2)); // Lose the --
	else if (optFrag.Length() == 1) optFrag.Set(TPtrC()); // single dash gets dropped completely

	const IoUtils::RCommandOptionList& options = iLastUsedCif->Options();
	const TInt count = options.Count();
	for (TInt i = 0; i < count; i++)
		{
		const TDesC& optName = options[i].Name();;
		if (optName.Left(optFrag.Length()) == optFrag)
			{
			matchingOptions.AppendL(TPtrC(optName));
			}
		}
	CompleteL(aLine, aToken, matchingOptions, &KDashDash, NULL, ETrue);
	CleanupStack::PopAndDestroy(&matchingOptions);
	}
	

