//extends the parser to include perl
//Eglin

#ifndef EMBPARSER_CPP
#define EMBPARSER_CPP

#ifdef EMBPERL

#include "masterentity.h"
#include "../common/debug.h"
#include "embparser.h"

#include <algorithm>

#ifdef WIN32
//borrow this from Wes... I like it
extern char* itoa(int integer);
#endif

PerlembParser::PerlembParser(void) : Parser()
{
	try { perl = new Embperl; map_funs(); }
	catch(const char * msg) { perl = NULL; LogFile->write(EQEMuLog::Status, "Error initializing perlembed: %s", msg); throw msg;}

	//let's load the default script
	try { LoadScript(0, NULL); }
	catch(const char * err)
	{
		LogFile->write(EQEMuLog::Status, "Error loading default script: %s", err);
	}
}

PerlembParser::~PerlembParser()
{
	if(perl)
		delete perl;
}

void PerlembParser::ExportVar(const char * pkgprefix, const char * varname, const char * value) const
{
	if(!perl)
		return;
	try
	{
		//todo: consider replacing ' w/ ", so that values can be expanded on the perl side
// MYRA - fixed eval in ExportVar per Eglin
		perl->eval(std::string("$").append(pkgprefix).append("::").append(varname).append("=q(").append(value).append(");").c_str());
//end Myra

	}
	catch(const char * err)
	{ //todo: consider rethrowing 
		LogFile->write(EQEMuLog::Status, "Error exporting var: %s", err);
	}
}

void PerlembParser::Event(int event, int32 npcid, const char * data, Mob* npcmob, Mob* mob)
{
	if(!perl)
		return;

	string packagename = GetPkgPrefix(npcid);

	if(!isloaded(packagename.c_str()))
	{
		LoadScript(npcid, zone->GetShortName());
	}

	packagename = GetPkgPrefix(npcid);

	if (event == EVENT_TIMER)
	{
		ExportVar(packagename.c_str(), "timer", data);
	}
	int8 fac = 0;
	if (mob && mob->IsClient()) {
		ExportVar(packagename.c_str(), "uguildid", itoa(mob->CastToClient()->GuildDBID()));
// MYRA - corrected spelling for var $uguildrank for event_timer (was $uguildrang)
		ExportVar(packagename.c_str(), "uguildrank", itoa(mob->CastToClient()->GuildRank()));
//end Myra
// MYRA - added vars $status & $cumflag per Eglin
		ExportVar(packagename.c_str(), "status", itoa(mob->CastToClient()->Admin())); 
		ExportVar(packagename.c_str(), "cumflag", itoa(mob->CastToClient()->flag[50])); 
//end Myra
	}

	if (mob && npcmob && mob->IsClient() && npcmob->IsNPC()) {
		Client* client = mob->CastToClient();
		NPC* npc = npcmob->CastToNPC();
		
		// Need to figure out why one of these casts would fail..
		if (client && npc) {
			fac = client->GetFactionLevel(client->GetID(), npcmob->GetID(), client->GetRace(), client->GetClass(), DEITY_AGNOSTIC, npc->GetPrimaryFaction(), npcmob);
		}
		else if (!client) {
			//avoid cerr, since the zone servers may eventually not be running on the same machine/interface
//			cerr << "WARNING: cast failure on mob->CastToClient()" << endl;
			LogFile->write(EQEMuLog::Status, "WARNING: cast failure on mob->CastToClient()");
		}
		else if (!npc) {
//			cerr << "WARNING: cast failure on npcmob->CastToNPC()" << endl;
			LogFile->write(EQEMuLog::Status, "WARNING: cast failure on npcmob->CastToNPC()");
		}
	}
	if (mob) {
		ExportVar(packagename.c_str(), "name", mob->GetName());
		ExportVar(packagename.c_str(), "race", GetRaceName(mob->GetRace()));
		ExportVar(packagename.c_str(), "class", GetEQClassName(mob->GetClass()));
		ExportVar(packagename.c_str(), "ulevel", itoa(mob->GetLevel()));
		ExportVar(packagename.c_str(), "userid", itoa(mob->GetID()));
	}
	if (npcmob)
	{
		ExportVar(packagename.c_str(), "mname", npcmob->GetName());
// MYRA - added vars $mobid & $mlevel per Eglin
		ExportVar(packagename.c_str(), "mobid", itoa(npcmob->GetID())); 
		ExportVar(packagename.c_str(), "mlevel", itoa(npcmob->GetLevel())); 
//end Myra
	}

	if (fac) {
		ExportVar(packagename.c_str(), "faction", itoa(fac));
	}

	if (zone) {
		ExportVar(packagename.c_str(), "zoneln", zone->GetLongName());
		ExportVar(packagename.c_str(), "zonesn", zone->GetShortName());
	}

	switch (event) {
		case EVENT_SAY: {
			ExportVar(packagename.c_str(), "data", itoa(npcid));
			ExportVar(packagename.c_str(), "text", data);
			SendCommands(packagename.c_str(), "EVENT_SAY", npcid, npcmob, mob);
			break;
		}
		case EVENT_TIMER: {
			SendCommands(packagename.c_str(), "EVENT_TIMER", npcid, npcmob, mob);
			break;
		}
		case EVENT_DEATH: {
			SendCommands(packagename.c_str(), "EVENT_DEATH", npcid, npcmob, mob);
			break;
		}
		case EVENT_ITEM: {
			ExportVar(packagename.c_str(), "item1", GetVar("item1", npcid).c_str());
			ExportVar(packagename.c_str(), "item2", GetVar("item2", npcid).c_str()); 
			ExportVar(packagename.c_str(), "item3", GetVar("item3", npcid).c_str()); 
			ExportVar(packagename.c_str(), "item4", GetVar("item4", npcid).c_str()); 
			ExportVar(packagename.c_str(), "copper", GetVar("copper", npcid).c_str()); 
			ExportVar(packagename.c_str(), "silver", GetVar("silver", npcid).c_str()); 
			ExportVar(packagename.c_str(), "gold", GetVar("gold", npcid).c_str()); 
			ExportVar(packagename.c_str(), "platinum", GetVar("platinum", npcid).c_str());  
			string hashname = packagename + std::string("::itemcount"); 
			perl->eval(std::string("%").append(hashname).append(" = ();").c_str()); 
			perl->eval(std::string("++$").append(hashname).append("{$").append(packagename).append("::item1};").c_str()); 
			perl->eval(std::string("++$").append(hashname).append("{$").append(packagename).append("::item2};").c_str()); 
			perl->eval(std::string("++$").append(hashname).append("{$").append(packagename).append("::item3};").c_str()); 
			perl->eval(std::string("++$").append(hashname).append("{$").append(packagename).append("::item4};").c_str()); 
			SendCommands(packagename.c_str(), "EVENT_ITEM", npcid, npcmob, mob);
			break;
		}
		case EVENT_SPAWN: {
			SendCommands(packagename.c_str(), "EVENT_SPAWN", npcid, npcmob, mob);
			break;
		}
		case EVENT_ATTACK: {
			SendCommands(packagename.c_str(), "EVENT_ATTACK", npcid, npcmob, mob);
			break;
		}
		case EVENT_SLAY: {
			SendCommands(packagename.c_str(), "EVENT_SLAY", npcid, npcmob, mob);
			break;
		}
		case EVENT_WAYPOINT: {
			std::string temp = "wp";
				temp += itoa(npcid);
			ExportVar(packagename.c_str(), temp.c_str(), data);
			SendCommands(packagename.c_str(), "EVENT_WAYPOINT", npcid, npcmob, mob);
			break;
		}
		default: {
			// should we do anything here?
			break;
		}
	}
}

void PerlembParser::LoadScript(int npcid, const char * zone) const
{
	if(!perl)
		return;
	string filename= "quests/", packagename = GetPkgPrefix(npcid);
	//each package name is of the form qstxxxx where xxxx = npcid (since numbers alone are not valid package names)
	if(!npcid || !zone)
	{
		filename += DEFAULT_QUEST_PREFIX;
		filename += ".pl";
	}
	else
	{
		filename += zone;
		filename += "/";
		filename += itoa(npcid);
		filename += ".pl";
	}

//  todo: decide whether or not to delete the package to allow for script refreshes w/o restarting the server
//  remember to guard against deleting the default package, on a similar note... consider deleting packages upon zone change
//	try { perl->eval(std::string("delete_package(\"").append(packagename).append("\");").c_str()); }
//	catch(...) {/*perl balked at us trynig to delete a non-existant package... no big deal.*/}

	try { perl->eval_file(packagename.c_str(), filename.c_str()); }
	catch(const char * err)
	{
		//try to reduce some of the console spam... 
		//todo: tweak this to be more accurate at deciding what to filter (we don't want to gag legit errors)
		if(!strstr(err,"No such file or directory"))
			LogFile->write(EQEMuLog::Status, "WARNING: error compiling quest file %s: %s (reverting to default questfile)", filename.c_str(), err);
	}
	//todo: change this to just read eval_file's %cache - duh!
	if(!isloaded(packagename.c_str()))
	{
		//the npc has no qst file, attach the defaults
		std::string setdefcmd = "$";
			setdefcmd += packagename;
			setdefcmd += "::isdefault = 1;";
		perl->eval(setdefcmd.c_str());
		setdefcmd = "$";
			setdefcmd += packagename;
			setdefcmd += "::isloaded = 1;";
		perl->eval(setdefcmd.c_str());
	}
}

//utility - return something of the form "qst1234"... 
//will return "qst[DEFAULT_QUEST_PREFIX]" if the npc in question has no script of its own or failed to compile and defaultOK is set to true
std::string PerlembParser::GetPkgPrefix(int32 npcid, bool defaultOK) const
{
	std::string prefix = "qst";
	std::string temp = prefix + (std::string)(itoa(npcid));
	if(!npcid || (defaultOK && isdefault(temp.c_str())))
	{
		prefix += DEFAULT_QUEST_PREFIX;
		return prefix;
	}
	return temp;
}

void PerlembParser::SendCommands(const char * pkgprefix, const char *event, int32 npcid, Mob* other, Mob* mob)
{
	if(!perl)
		return;
	try
	{
		std::string cmd = "@quest::cmd_queue = (); package " + (std::string)(pkgprefix) + (std::string)(";");
		perl->eval(cmd.c_str());
		perl->dosub(std::string(pkgprefix).append("::").append(event).c_str());
	}
	catch(const char * err)
	{
		//try to reduce some of the console spam... 
		//todo: tweak this to be more accurate at deciding what to filter (we don't want to gag legit errors)
		if(!strstr(err,"Undefined subroutine"))
			LogFile->write(EQEMuLog::Status, "Script error: %s::%s - %s", pkgprefix, event, err);
		return;
	}

	int numcoms = perl->geti("quest::qsize()");
	for(int c = 0; c < numcoms; ++c)
	{
		char var[1024] = {0};
		sprintf(var,"$quest::cmd_queue[%d]{func}",c);
		std::string cmd = perl->getstr(var);
		sprintf(var,"$quest::cmd_queue[%d]{args}",c);
		std::string args = perl->getstr(var);
		size_t num_args = std::count(args.begin(), args.end(), ',') + 1;
		ExCommands(cmd, args, num_args, npcid, other, mob);
	}
}

void PerlembParser::map_funs(void) const
{
	//map each "exported" function to a variable list that we can access from c
	//todo:
	//	break 1|settimer 2|stoptimer 1|dbspawnadd 2|flagcheck 1|write 2|
	//	settarget 2|follow 1|sfollow 1|save 1|setallskill 1
	//update/ensure that the api matches that of the native script engine
	perl->eval(
"{"
"package quest;"
"@cmd_queue = ();"
"sub qsize{return scalar(@cmd_queue)};"
"sub say{push(@cmd_queue,{func=>'say',args=>join(',',@_)});}"
"sub emote{push(@cmd_queue,{func=>'emote',args=>join(',',@_)});}"
"sub shout{push(@cmd_queue,{func=>'shout',args=>join(',',@_)});}"
"sub spawn{push(@cmd_queue,{func=>'spawn',args=>join(',',@_)});}"
"sub echo{push(@cmd_queue,{func=>'echo',args=>join(',',@_)});}"
"sub summonitem{push(@cmd_queue,{func=>'summonitem',args=>join(',',@_)});}"
"sub castspell{push(@cmd_queue,{func=>'castspell',args=>join(',',@_)});}"
"sub depop{push(@cmd_queue,{func=>'depop'});}"
"sub cumflag{push(@cmd_queue,{func=>'cumflag'});}"
"sub flagnpc{push(@cmd_queue,{func=>'flagnpc',args=>join(',',@_)});}"
"sub flagclient{push(@cmd_queue,{func=>'flagclient',args=>join(',',@_)});}"
"sub exp{push(@cmd_queue,{func=>'exp',args=>join(',',@_)});}"
"sub level{push(@cmd_queue,{func=>'level',args=>join(',',@_)});}"
"sub safemove{push(@cmd_queue,{func=>'safemove'});}"
"sub rain{push(@cmd_queue,{func=>'rain',args=>join(',',@_)});}"
"sub snow{push(@cmd_queue,{func=>'snow',args=>join(',',@_)});}"
"sub givecash{push(@cmd_queue,{func=>'givecash',args=>join(',',@_)});}"
"sub pvp{push(@cmd_queue,{func=>'pvp',args=>join(',',@_)});}"
"sub doanim{push(@cmd_queue,{func=>'doanim',args=>join(',',@_)});}"
"sub addskill{push(@cmd_queue,{func=>'addskill',args=>join(',',@_)});}"
"sub me{push(@cmd_queue,{func=>'me',args=>join(',',@_)});}"
// MYRA - added missing commands + itemlink to perl
"sub faction{push(@cmd_queue,{func=>'faction',args=>join(',',@_)});}" 
"sub setguild{push(@cmd_queue,{func=>'setguild',args=>join(',',@_)});}"
"sub rebind{push(@cmd_queue,{func=>'rebind',args=>join(',',@_)});}" 
"sub flagcheck{push(@cmd_queue,{func=>'flagcheck',args=>join(',',@_)});}"
"sub write{push(@cmd_queue,{func=>'write',args=>join(',',@_)});}" 
"sub settime{push(@cmd_queue,{func=>'settime',args=>join(',',@_)});}"
"sub setsky{push(@cmd_queue,{func=>'setsky',args=>join(',',@_)});}" 
"sub settimer{push(@cmd_queue,{func=>'settimer',args=>join(',',@_)});}"
"sub stoptimer{push(@cmd_queue,{func=>'stoptimer',args=>join(',',@_)});}"
"sub settarget{push(@cmd_queue,{func=>'settarget',args=>join(',',@_)});}"
"sub follow{push(@cmd_queue,{func=>'follow',args=>join(',',@_)});}" 
"sub sfollow{push(@cmd_queue,{func=>'sfollow',args=>join(',',@_)});}"
"sub movepc{push(@cmd_queue,{func=>'movepc',args=>join(',',@_)});}"
"sub gmmove{push(@cmd_queue,{func=>'gmmove',args=>join(',',@_)});}"
"sub movegrp{push(@cmd_queue,{func=>'movegrp',args=>join(',',@_)});}"
"sub setallskill{push(@cmd_queue,{func=>'setallskill',args=>join(',',@_)});}"
"sub attack{push(@cmd_queue,{func=>'attack',args=>join(',',@_)});}"
"sub save{push(@cmd_queue,{func=>'save',args=>join(',',@_)});}"
"sub linkitem{push(@cmd_queue,{func=>'linkitem',args=>join(',',@_)});}"
//end Myra
"package main;"
"}"
);//eval
}

#endif //EMBPERL

#endif //EMBPARSER_CPP
