#include <tcl.h>
#include <tk.h>

#include "tobject.h"
#include "ptr.h"

#include "tk/tcl_interp.h"
#include "tk/gui/tk_button.h"
#include "tk/gui/tk_toplevel.h"
#include "tk/gui/tk_menu.h"
#include "tk/gui/tk_entry.h"
#include "tk/gui/tk_place_layout.h"
#include "tk/gui/options/enums/tk_enumoption.h"

#include "tk/gui/image/tk_photo.h"

#include "exceptions/exceptionbase.h"

#include "modulemanager/help.h"
#include "modulemanager/modulemanager.h"

#include "boom/boom.h"

#include "testsuite/testadd.h"
#include "testsuite/testcompare.h"

#include <iostream>

using namespace std;

ModuleType* types[]=
{
	new BoomLoader(),
	new TestAddLoader(),
	new TestCompareLoader(),
	
	0
};

Ptr<ModuleManager> manager;
Ptr<TkGui> gui;

// if set determines module
IModule* activeModule=0;

int module_list(ClientData clientData,Tcl_Interp *interp,int objc, Tcl_Obj *CONST objv[])
{
	cout<<"Available module types"<<endl;
	for(ModuleManager::const_iterator it=manager->begin();it!=manager->end();it++)
	{
		cout<<(*it)->getName()<<endl;
	}

	return TCL_OK;
}

// prikaz pro shellove vytvoreni instance modulu
int module(ClientData clientData,Tcl_Interp *interp,int objc, Tcl_Obj *CONST objv[])
{
	if (objc!=3)
	{
		cout<<"Syntax error !"<<endl;
		cout<<"usage: "<<HelpLoader::getCommandHelp("module").getUsage()<<endl;
		return TCL_ERROR;
	}
	char* moduleTypeName=Tcl_GetString(objv[1]);
	char* instanceName=Tcl_GetString(objv[2]);

	try
	{
		ModuleType* moduleType=manager->getModuleType(moduleTypeName);
		if (moduleType==0) 
		{
			cout<<"Unknown module type."<<endl;
			return TCL_ERROR;
		}
		IModule* newModule=moduleType->newModule();
		moduleType->registerModule(newModule,instanceName);
		newModule->afterInstantiate();
	}
	catch(ExceptionBase* e)
	{
		cout<<e->stackTrace();
		return TCL_ERROR;
	}

	cout<<"Created module instance \""<<instanceName<<"\"."<<endl;

	return TCL_OK;
}

int vars(ClientData clientData,Tcl_Interp *interp,int objc, Tcl_Obj *CONST objv[])
{
	if (!((objc==2)||(objc==1&&activeModule!=0)))
	{
		cout<<"Syntax error !"<<endl;
		cout<<"usage: "<<HelpLoader::getCommandHelp("vars").getUsage()<<endl;
		return TCL_ERROR;
	}
	IModule* module;
	if (objc==2)
	{
		char* instanceName=Tcl_GetString(objv[1]);

		module=manager->getModule(instanceName);
		if (module==0)
		{
			cout<<"No such module."<<endl;
			return TCL_ERROR;
		}
	}
	else
	{
		module=activeModule;
	}
	module->printVariables(cout);
	return TCL_OK;
}

int run(ClientData clientData,Tcl_Interp *interp,int objc, Tcl_Obj *CONST objv[])
{
	if (!((objc==2)||(objc==1&&activeModule!=0)))
	{
		cout<<"Syntax error !"<<endl;
		cout<<"usage :"<<HelpLoader::getCommandHelp("run");
		return TCL_ERROR;
	}
	IModule* module;
	if (objc==2)
	{
		char* instanceName=Tcl_GetString(objv[1]);
		module=manager->getModule(instanceName);
		if (module==0)
		{
			cout<<"No such module."<<endl;
			return TCL_ERROR;
		}
	}
	else
	{
		module=activeModule;
	}

	try
	{
		cout<<"Running module."<<endl;
		module->run();
	}
	catch(ExceptionBase* e)
	{
		cout<<e->stackTrace();
		return TCL_ERROR;
	}

	return TCL_OK;
}


int sets(ClientData clientData,Tcl_Interp *interp,int objc, Tcl_Obj *CONST objv[])
{
	if (!(objc==4||(objc==3&&activeModule!=0)))
	{
		cout<<"Syntax error !"<<endl;
		cout<<"usage :"<<HelpLoader::getCommandHelp("sets");
		return TCL_ERROR;
	}

	char *instanceName,*varName,*varValue;
	IModule *module;
	
	if (objc==4)
	{
		instanceName=Tcl_GetString(objv[1]);
		varName=Tcl_GetString(objv[2]);
		varValue=Tcl_GetString(objv[3]);
	
		module=manager->getModule(instanceName);
		if (module==0)
		{
			cout<<"No such module."<<endl;
			return TCL_ERROR;
		}
	} else
	{
		varName=Tcl_GetString(objv[1]);
		varValue=Tcl_GetString(objv[2]);
		module=activeModule;
	}

	VarInst* var=module->getVariable(varName);
	if (var==0)
	{
		cout<<"No such variable "<<varName<<endl;
		return TCL_ERROR;
	}
	bool result;
	try
	{
		result=var->fromString(varValue);
	}
	catch(ExceptionBase* e)
	{
		cout<<e->stackTrace();
		return TCL_ERROR;
	}
	if (result==false)
	{
		cout<<"Invalid variable value !"<<endl;
		return TCL_ERROR;
	}
	cout<<varValue<<endl;
	return TCL_OK;
}

int on(ClientData clientData,Tcl_Interp *interp,int objc, Tcl_Obj *CONST objv[])
{
	if (objc<3)
	{
		cout<<"Syntax error !"<<endl;
		cout<<"usage :"<<HelpLoader::getCommandHelp("on");
		return TCL_ERROR;
	}

	char* instanceName=Tcl_GetString(objv[1]);
	char* commandName=Tcl_GetString(objv[2]);
	
	IModule* module=manager->getModule(instanceName);
	if (module==0)
	{
		cout<<"No such module."<<endl;
		return TCL_ERROR;
	}

	Command* cmd=module->getModuleType()->getCommand(commandName);
	if (cmd==0)
	{
		cout<<"No such command defined in "<<module->getModuleType()->getName()<<"."<<endl<<endl;
		cout<<"Module type '"<<module->getModuleType()->getName()<<"' specific commands are :"<<endl;
		int i=0;
		Ptr<Command>* cmdList=module->getModuleType()->getCommands();
		while (!cmdList[i].isNull())
		{
			cout<<cmdList[i]->getName()<<endl;
			i++;
		}	
		return TCL_ERROR;
	}

	if (!cmd->isArityValid(objc-3))
	{
		cout<<"wrong number of arguments"<<endl;
		return TCL_ERROR;
	}

	
	string cmdName=Tcl_GetString(objv[2]);
	vector<string> cmdParams;
	for(int i=3;i<objc;i++)
		cmdParams.push_back(Tcl_GetString(objv[i]));

	MesgCommand command(cmdName,cmdParams);
	try
	{
		module->receiveMessage(&command);
	}
	catch(ExceptionBase* e)
	{
		cout<<e->stackTrace();
		return TCL_ERROR;
	}
	return TCL_OK;
}

int helps(ClientData clientData,Tcl_Interp *interp,int objc, Tcl_Obj *CONST objv[])
{
	if (!HelpLoader::isHelpEnabled())
	{
		cout<<"Help system disabled."<<endl;
		return TCL_ERROR;
	}
	if (objc>2)
	{
		cout<<"Syntax error !"<<endl;
		cout<<"usage :"<<HelpLoader::getCommandHelp("helps");
		return TCL_ERROR;
	}
	if (objc==1) HelpLoader::getAllCommands(cout);
	else
	{
		const char* moduleName=Tcl_GetString(objv[1]);
		try
		{
			cout<<HelpLoader::getCommandHelp(moduleName);
		}
		catch(ExceptionBase* e)
		{
			cout<<e->stackTrace();
		}
	}
	return TCL_OK;
}

int default_module(ClientData clientData,Tcl_Interp *interp,int objc, Tcl_Obj *CONST objv[])
{
	if (objc!=2)
	{
		cout<<"usage :"<<HelpLoader::getCommandHelp("default");
		return TCL_ERROR;
	}
	
	char* instanceName=Tcl_GetString(objv[1]);
	IModule* module=manager->getModule(instanceName);
	if (module==0)
	{
		cout<<"No such module."<<endl;
		return TCL_ERROR;
	}
	activeModule=module;
	cout<<"Default module set to \""<<module->getName()<<"\"."<<endl;
	return TCL_OK;
}


int Tcl_AppInit(Tcl_Interp *interp);

int main(int argc, char * argv[])
{
	try
	{
		HelpLoader::loadCommandHelp();
	}
	catch(ExceptionBase* e)
	{
		cout<<e->stackTrace();
		cout<<"Failed to load help. Command helps will be disabled"<<endl;
	}

	try
	{
		Tk_Main(argc, argv, Tcl_AppInit);
	}
	catch(ExceptionBase* e)
	{
		cout<<e->stackTrace();
		return -1;
	}
	return 0;
}

int Tcl_AppInit(Tcl_Interp *interp) 
{
	// inicializuju shell
	if (Tcl_Init(interp) == TCL_ERROR) 
	{
		return TCL_ERROR;
	}
	// inicializuju grafiku
	if (Tk_Init(interp) == TCL_ERROR) 
	{
		return TCL_ERROR;
	}
	
	Tcl_CreateObjCommand(interp,"modlist",module_list,(ClientData)NULL,(Tcl_CmdDeleteProc*)NULL);
	Tcl_CreateObjCommand(interp,"module",module,(ClientData)NULL,(Tcl_CmdDeleteProc*)NULL);
	Tcl_CreateObjCommand(interp,"vars",vars,(ClientData)NULL,(Tcl_CmdDeleteProc*)NULL);
	Tcl_CreateObjCommand(interp,"sets",sets,(ClientData)NULL,(Tcl_CmdDeleteProc*)NULL);
	Tcl_CreateObjCommand(interp,"run",run,(ClientData)NULL,(Tcl_CmdDeleteProc*)NULL);
	Tcl_CreateObjCommand(interp,"on",on,(ClientData)NULL,(Tcl_CmdDeleteProc*)NULL);
	Tcl_CreateObjCommand(interp,"helps",helps,(ClientData)NULL,(Tcl_CmdDeleteProc*)NULL);
	Tcl_CreateObjCommand(interp,"default",default_module,(ClientData)NULL,(Tcl_CmdDeleteProc*)NULL);

	try
	{
		Ptr<TclInterp> minterp=new TclInterp(interp,"log.txt");
		TclInterp::defaultInterp=minterp;

		gui=new TkGui(minterp);
		gui->mainWindow->setWindowState(TkWinStateOption::withdrawn);

		manager=new ModuleManager(types,gui);
		ModuleManager::manager=manager;

		TXmlObject::registerAll();

		gui->load();

		//designer=new Designer(minterp,gui,manager);
		
	}
	catch(ExceptionBase* e)
	{
		cout<<e->stackTrace()<<endl;
	}

	return TCL_OK;
}
