//-------------------------------------------------------------------------------------------------
// <copyright file="LWInvokeCmd.cpp" company="LiquidRobotics">
//  Copyright (c) Liquid Robotics Corporation, confidential and proprietary.  All rights reserved. 
// </copyright>
//
// <summary>
//	Implementation of LWInvokeCmd, a command class that is created from a SendCommand
//	invocation.  Commands are first queued up on a queue and run consecutively on the
//	controller's command thread.  LWInvokeCmd passes a method invocation to the
//	appropriate lua instance and waits for completion.
// </summary>
//
// <owner>Mike Cookson</owner>
//-------------------------------------------------------------------------------------------------

#include "LWInvokeCmd.h"
#include "LWInvokeLuaInternalCmd.h"
#include "LqrSMACBooleanParser.h"
#include "LqrSMACCmdLineParser.h"
#include "LqrSMACCmdOutputFormatterAlias.h"
#include "LqrSMACLogger.h"
#include "nsextend.h"
#include <boost/algorithm/string.hpp>

using namespace std;
using namespace LiquidRobotics::SMACFramework;

namespace LiquidRobotics {
namespace LuaWrapper {

LWInvokeCmd::LWInvokeCmd(TCon& _ctrl, const string& _cmd, const string& _instanceName, const string& _functionName,
		const StringArray& _args, Logger& _logger)
: LWConcreteCmd(_ctrl)
, ctrl(_ctrl)
, logger(_logger)
, sem(0)
, isAsynch(false)
, isComplete(false)
, cmd(_cmd)
, instanceName(_instanceName)
, functionName(_functionName)
, args(_args.begin(), _args.end())
{
}

LWInvokeCmd::~LWInvokeCmd()
{
}

void LWInvokeCmd::Execute()
{
	LWAsynchScriptContextLua* pAsynch = NULL;

	try
	{
		Logger::Locked(logger)->Debug << "LWInvokeCmd::Execute()" << endl;

		SignalStarted();

		TCon::CommandPtr cmdp;
		LuaInstance* pNull = NULL;

		pAsynch = new LWAsynchScriptContextLua(*pNull); // the null reference is not actually used
		cb = pAsynch->Subscribe(boost::bind(&LWInvokeCmd::OnAsynchNotify, this, _1, _2));

		LWInvokeLuaInternalCmd* pCmd = new LWInvokeLuaInternalCmd(ctrl, "Invoke", instanceName, functionName, args, pAsynch);
		cmdp.reset(pCmd);

		CmdOutputFormatter::Ptr childFormatter;
		childFormatter.reset(new CmdOutputFormatterAlias(formatter));
		cmdp->SetOutputFormatter(childFormatter);

		pAsynch = NULL;

		string cmdResponse = ctrl.RunLuaCommand(cmdp);

		//if (formatter)
		//	formatter->AppendOutput(cmdResponse);

		if (!formatter)
			reasonCode = cmdResponse;

		if (isAsynch)
			Semaphore::Lock semLock(sem);			// TODO: add a time limit here?

		SetException(TEx::NoException, "");
	}
	catch(exception& ex)
	{
		if (pAsynch) delete pAsynch;
		const int exNum = TEx::ExceptionToNumber(ex);
		const string exMsg = ex.what();

		SetException(exNum, exMsg);
	}
	catch(...)
	{
		if (pAsynch) delete pAsynch;
		TEx::NotReady exc("");
		const int exNum = TEx::ExceptionToNumber(exc);

		SetException(exNum, "caught unknown exception");
	}
}

// parse the command
LWInvokeCmd::CmdPtr LWInvokeCmd::Parse(const string& arg1, const string& arg2, TCon& ctrl, const CmdLineParser& parser, ParsingContext& context, Logger& logger)
{
	CmdPtr result;
	const int numParams = parser.Count();

	if ((numParams >= 2) && boost::iequals(parser[0], arg1) && boost::iequals(parser[1], arg2))
	{
		stringstream msg;

		switch(numParams)
		{
		case 2:
		{
			msg << "Missing lua instance name parameter in '" << parser.GetOriginalCmdLine() << "'";
			throw invalid_argument(msg.str());
		}

		case 3:
		{
			msg << "Missing target function/procedure name in '" << parser.GetOriginalCmdLine() << "'";
			throw invalid_argument(msg.str());
		}

		default:
		{
			StringArray args;

			if (numParams > 4)
			{
				args.resize(numParams-4);

				for (int idx = 4; idx < numParams; ++idx)
					args[idx-4] = parser[idx];
			}

			result.reset(new LWInvokeCmd(ctrl, parser.GetOriginalCmdLine(), parser[2], parser[3], args, logger));
		}

		}
	}

	return result;
}

static string EscapeString(const string& input)
{
	stringstream msg;

	for (string::const_iterator ci = input.begin(); ci != input.end(); ++ci)
	{
		const char c = *ci;

		switch(c)
		{
		case '\'':	msg << "\\'";		break;
		case '\"':	msg << "\\\"";		break;
		case '\b':	msg << "\\b";		break;
		case '\n':	msg << "\\n";		break;
		case '\r':	msg << "\\r";		break;
		case '\t':	msg << "\\t";		break;

		default:

			if (isprint(c))
				msg << c;
			else
				msg << "\\x" << ultos(c, 16, 2);
		}
	}

	return msg.str();
}

void LWInvokeCmd::OnAsynchNotify(LWAsynchScriptContextLua& sender, const LWAsynchScriptContextLua::Event& evt)
{
	switch(evt.eventType)
	{
	case LWAsynchScriptContextLua::Event::AppendOutput:

		Logger::Locked(logger)->Debug << "OnAsynchNotify::AppendOutput '" << EscapeString(evt.s) << "'" << endl;
		if (!isComplete && formatter)
			formatter->AppendOutput(evt.s);

		break;

	case LWAsynchScriptContextLua::Event::ReportComplete:

		Logger::Locked(logger)->Debug << "OnAsynchNotify::ReportComplete" << endl;
		if (!isComplete)
		{
			isComplete = true;
			sem.Post();
		}

		break;

	case LWAsynchScriptContextLua::Event::SetAsynch:

		Logger::Locked(logger)->Debug << "OnAsynchNotify::SetAsynch" << endl;
		isAsynch = true;
		break;

	case LWAsynchScriptContextLua::Event::SetSuccessFlag:

		Logger::Locked(logger)->Debug << "OnAsynchNotify::SetStatusFlag " << BooleanToString(evt.b) << endl;
		SetSuccessFlag(evt.b);
		break;
	}
}

}
}
