//=============================================================================
//
//   File : kvi_kvs_parser_command.cpp
//   Creation date : Thu 03 Nov 2003 13.23 CEST by Szymon Stefanek
//
//   This file is part of the KVirc irc client distribution
//   Copyright (C) 2003 Szymon Stefanek (pragma at kvirc dot net)
//
//   This program is FREE software. You can redistribute it and/or
//   modify it under the terms of the GNU General Public License
//   as published by the Free Software Foundation; either version 2
//   of the License, or (at your opinion) any later version.
//
//   This program is distributed in the HOPE that it will be USEFUL,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//   See the GNU General Public License for more details.
//
//   You should have received a copy of the GNU General Public License
//   along with this program. If not, write to the Free Software Foundation,
//   Inc. ,59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
//=============================================================================

#define __KVIRC__


#include "kvi_kvs_parser.h"

#include "kvi_kvs_treenode.h"

#include "kvi_kvs_report.h"
#include "kvi_kvs_kernel.h"

#include "kvi_kvs_parser_macros.h"

#include "kvi_locale.h"

#include "kvi_kvs_script.h"

#include "kvi_cmdformatter.h"


KviKvsTreeNodeCommand * KviKvsParser::parseCommand()
{
	KVSP_ASSERT(KVSP_curCharIsLetter || (KVSP_curCharUnicode == '_'));


	const QChar * pIdentifier = KVSP_curCharPointer;

	while(KVSP_curCharIsLetterOrNumber || (KVSP_curCharUnicode == '_'))KVSP_skipChar;

	int iIdentifierLen = KVSP_curCharPointer - pIdentifier;

	const QChar * pSecondPart = 0;
	int iSecondPartLen = 0;
	
	bool bHasNamespaceSoMustBeAlias = false;

	if(KVSP_curCharUnicode == '.')
	{
		// a module command
		KVSP_skipChar;

		pSecondPart = KVSP_curCharPointer;

		if(!KVSP_curCharIsLetter)
		{
			warning(KVSP_curCharPointer - 1,__tr2qs("Stray dot ('.') character or invalid following module command name"));
			error(KVSP_curCharPointer,__tr2qs("Syntax error: malformed module command identifier"));
			return 0;
		}

		KVSP_skipChar;
		while(KVSP_curCharIsLetterOrNumber || (KVSP_curCharUnicode == '_'))KVSP_skipChar;

		iSecondPartLen = KVSP_curCharPointer - pSecondPart;
	} else while(KVSP_curCharUnicode == ':')
	{
		// an alias with namespace(s) ?

		// here we allow the syntax of the form
		// <namespace>::{<namespace>::}<alias_name>

		bHasNamespaceSoMustBeAlias = true;

		KVSP_skipChar;
		if(KVSP_curCharUnicode == ':')
		{
			KVSP_skipChar;

			if(!KVSP_curCharIsLetter)
			{
				warning(KVSP_curCharPointer - 1,__tr2qs("Stray '::' sequence or invalid following alias name"));
				error(KVSP_curCharPointer,__tr2qs("Syntax error: malformed alias identifier"));
				return 0;
			}

			KVSP_skipChar;
			while(KVSP_curCharIsLetterOrNumber || (KVSP_curCharUnicode == '_'))KVSP_skipChar;
		} else {
			warning(KVSP_curCharPointer - 1,__tr2qs("Stray ':' character: did you mean '...<namespace>::<alias_name>' ?"));
			error(KVSP_curCharPointer,__tr2qs("Syntax error: malformed (alias?) command identifier"));
			return 0;
		}

		iIdentifierLen = KVSP_curCharPointer - pIdentifier;
	}

	QString szIdentifier(pIdentifier,iIdentifierLen);

	skipSpaces();

	KviKvsTreeNodeSwitchList * sw = 0;
	KviKvsTreeNodeData * pRebindData;

	if(KVSP_curCharUnicode == '-')
	{
		// extract the switch
		sw = parseCommandSwitchList();
		if(!sw)
		{
			if(error())
				return 0;
			// else it might be a negative number or something that does not seem
			// to be a switch anyway
			pRebindData = 0;
		} else {
			pRebindData = sw->getStandardRebindingSwitch();
		}
	} else {
		pRebindData = 0;
	}

	KviKvsTreeNodeCommand * cmd;

	if(!bHasNamespaceSoMustBeAlias)
	{
		// perl.begin has a *really* half special parsing routine
		if(iIdentifierLen == 4)
		{
			if(pIdentifier->lower().unicode() == 'p')
			{
				if(KviQString::equalCI(szIdentifier,"perl"))
				{
					if(pSecondPart)
					{
						QString szSecondPart(pSecondPart,iSecondPartLen);
						if(KviQString::equalCI(szSecondPart,"begin"))
						{
							// yep, that's perl.begin
							cmd = parseSpecialCommandPerlBegin();
							if(!cmd)
							{
								// might be an error , but might be not...
								// it is an error only if error() returns true
								// but since the caller will take care of it
								// we just return 0
								if(sw)delete sw;
								if(pRebindData)delete pRebindData;
								return 0;
							}
							cmd->setLocation(pIdentifier);
							if(sw)
							{
								cmd->setSwitchList(sw);
								// cmd becomes child of the rebinding switch
								if(pRebindData)return new KviKvsTreeNodeRebindingSwitch(pRebindData->location(),pRebindData,cmd);
							}
							return cmd;
						}
					}
				}
			}
		}

		if(!pSecondPart) 
		{
			// is this a special command ?

			// Here theoretically we could also lookup special commands composed of two parts but we actually don't need it.
			
			// Looking up only the first part if there is a second part, instead,
			// is dangerous since it may generate infinite loops (help.open vs help)

			KviKvsSpecialCommandParsingRoutine * ccpr = KviKvsKernel::instance()->findSpecialCommandParsingRoutine(szIdentifier);
		
			if(ccpr)
			{
				cmd = (this->*(ccpr->proc))();
				if(!cmd)
				{
					// might be an error , but might be not...
					// it is an error only if error() returns true
					// but since the caller will take care of it
					// we just return 0
					if(sw)delete sw;
					if(pRebindData)delete pRebindData;
					return 0;
				}
				cmd->setLocation(pIdentifier);
				if(sw)
				{
					cmd->setSwitchList(sw);
					// cmd becomes child of the rebinding switch
					if(pRebindData)return new KviKvsTreeNodeRebindingSwitch(pRebindData->location(),pRebindData,cmd);
				}
				return cmd;
			}
		}

		// is it a callback command ?
		if(KVSP_curCharUnicode == '(')
		{	
			// core callback command
			// module callback command
			KviKvsTreeNodeDataList * dl = parseCommaSeparatedParameterList();
			if(!dl)
			{
				if(sw)delete sw;
				if(pRebindData)delete pRebindData;
				return 0;
			}
			if(!skipSpacesAndNewlines())
			{
				if(sw)delete sw;
				if(pRebindData)delete pRebindData;
				delete dl;
				return 0;
			}
	
			const QChar * pClbkBegin = KVSP_curCharPointer;
	
			KviKvsTreeNodeInstruction * ins = parseInstruction();
			if(!ins)
			{
				if(error())
				{
					if(sw)delete sw;
					if(pRebindData)delete pRebindData;
					return 0;
				}
				// actually we need empty callbacks (for alias() at least)
				// the single command implementations should take care of checking it
				/*else {
				
					warning(pIdentifier,__tr2qs("Callback command called with an empty callback instruction"));
					error(KVSP_curCharPointer,__tr2qs("Callback commands must have a callback instruction"));
					if(sw)delete sw;
					delete dl;
					return 0;
					
				}*/
			} else {
				delete ins; // in fact we don't need it, it will be reparsed the first time it is called
				// Q: Couldn't we actually use the already parsed tree ?
				// A: No: the tree must be reparsed in a new parser context
				//    since we're keeping track of global and local variables...
				//    The locals of this context are NOT the same as the locals
				//    of the other context.
			}
	
			QString szCallbackName = szIdentifier;
			szCallbackName += " callback";
	
			QString szBlock(pClbkBegin,KVSP_curCharPointer - pClbkBegin);
			KviCommandFormatter::bufferFromBlock(szBlock);
	
			KviKvsScript * clbk = new KviKvsScript(szCallbackName,szBlock);
	
			if(pSecondPart)
			{
				cmd = new KviKvsTreeNodeModuleCallbackCommand(pIdentifier,szIdentifier,QString(pSecondPart,iSecondPartLen),dl,clbk);
			} else {
				KviKvsCoreCallbackCommandExecRoutine * r = KviKvsKernel::instance()->findCoreCallbackCommandExecRoutine(szIdentifier);
				if(r)
				{
					cmd = new KviKvsTreeNodeCoreCallbackCommand(pIdentifier,szIdentifier,dl,r,clbk);
				} else {
					error(KVSP_curCharPointer,__tr2qs("Unknown callback command \"%Q\""),&szIdentifier);
					if(sw)delete sw;
					if(pRebindData)delete pRebindData;
					delete dl;
					delete clbk;
					return 0;
				}
			}
	
			if(sw)
			{
				cmd->setSwitchList(sw);
				// cmd becomes child of the rebinding switch
				if(pRebindData)return new KviKvsTreeNodeRebindingSwitch(pRebindData->location(),pRebindData,cmd);
			}
			
			return cmd;
		}
	}

	// must be core simple command, module simple command or alias
	KviKvsTreeNodeDataList * pl = parseCommandParameterList();
	if(!pl)
	{
		if(sw)delete sw;
		if(pRebindData)delete pRebindData;
		return 0; // this MUST be an error
	}

	if(bHasNamespaceSoMustBeAlias)
	{
		// alias for sure, bind at runtime
		cmd = new KviKvsTreeNodeAliasSimpleCommand(pIdentifier,szIdentifier,pl);
	} else {
		if(pSecondPart)
		{
			cmd = new KviKvsTreeNodeModuleSimpleCommand(pIdentifier,szIdentifier,QString(pSecondPart,iSecondPartLen),pl);
		} else {
			KviKvsCoreSimpleCommandExecRoutine * r = KviKvsKernel::instance()->findCoreSimpleCommandExecRoutine(szIdentifier);
			if(r)
			{
				cmd = new KviKvsTreeNodeCoreSimpleCommand(pIdentifier,szIdentifier,pl,r);
			} else {
				// must be an alias in root namespace, bind at runtime
				cmd = new KviKvsTreeNodeAliasSimpleCommand(pIdentifier,szIdentifier,pl);
			}
		}
	}

	if(sw)
	{
		cmd->setSwitchList(sw);
		// cmd becomes child of the rebinding switch
		if(pRebindData)return new KviKvsTreeNodeRebindingSwitch(pRebindData->location(),pRebindData,cmd);
	}

	return cmd;
}


