//------------------------------------------------------------------------------
// <copyright file="Test1.h" company="LiquidRobotics">
//
// Copyright (c) 2014 Liquid Robotics, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// This software is associated with a release.  Liquid Robotics makes no
// representation that the design, interfaces or component distribution will
// remain unchanged in future releases.
//
// </copyright>
//
// <summary>
// </summary>
//
// <owner></owner>
//------------------------------------------------------------------------------

#ifndef __TEST1_H__
#define __TEST1_H__

#include "AsynchronousProcessExecutor.h"
#include "LqrSMACByteArray.h"
#include "LqrSMACLineMachine.h"
#include "LqrSMACSemaphore.h"
#include "LqrSMACStdOutLogger.h"
#include <string>

class Test1
{
public:

	typedef LiquidRobotics::PPPHostLib::AsynchronousProcessExecutor AsynchronousProcessExecutor;
	typedef LiquidRobotics::SMACFramework::ByteArray ByteArray;
	typedef LiquidRobotics::SMACFramework::Logger Logger;
	typedef LiquidRobotics::SMACFramework::LineMachine LineMachine;
	typedef boost::shared_ptr<Test1> Ptr;
	typedef LiquidRobotics::SMACFramework::Semaphore Semaphore;;
	typedef LiquidRobotics::SMACFramework::StdOutLogger StdOutLogger;
	typedef std::string string;

	Test1()
	{
		logger.reset(new StdOutLogger("Test1", 1, Logger::LT_DEBUG));
	}

	void Kill()
	{
		if (procExec)
		{
			AsynchronousProcessExecutor::Lock lock(*procExec);

			if (procExec->ProcessExecuting())
				procExec->Kill();
		}
	}

	void Run(const string& commandLine)
	{
		using namespace std;

		if (procExec)
			throw runtime_error("Cannot call Run() again until previous process exits");

		while(exitSem.TryWait());

		procExec.reset(new AsynchronousProcessExecutor(logger, SIGTERM, true, SIGKILL, 500, true, 1024));
		lineMachine.reset(new LineMachine);
		lineMachine2.reset(new LineMachine);
		callback.reset(new Callback(*this, logger, lineMachine, lineMachine2, exitSem));
		callbackLineMachineConnection.reset(new ScopedConnection(lineMachine->hereIsLine_Signal.connect(callback->stdOutReceiver)));
		callbackLineMachine2Connection.reset(new ScopedConnection(lineMachine2->hereIsLine_Signal.connect(callback->stdErrReceiver)));
		callbackReportConnection.reset(new ScopedConnection(procExec->ReportSignal.connect(*callback)));
		callbackExitConnection.reset(new ScopedConnection(procExec->ExitSignal.connect(*callback)));
		procExec->RunAsynch(commandLine, true, true);
	}

	void Wait()
	{
		Semaphore::Lock lock(exitSem);
	}

	static void RunSimpleExecutionTest(const string& commandLine)
	{
		Test1::Ptr test1;
		test1.reset(new Test1);
		test1->Run(commandLine);
		test1->Wait();
		test1.reset();
	}

	static void RunTestWithKill(const string& commandLine, unsigned waitMillisecondsBeforeKill)
	{
		Test1::Ptr test1;
		test1.reset(new Test1);
		test1->Run(commandLine);
		usleep(1000*waitMillisecondsBeforeKill);
		test1->Kill();
		test1->Wait();
		test1.reset();
	}

	static void RunTests()
	{
		RunSimpleExecutionTest("ls /bin");
		RunTestWithKill("find / -print", 5000);
	}

private:

	typedef boost::signals2::scoped_connection ScopedConnection;
	typedef boost::shared_ptr<ScopedConnection> ScopedConnectionPtr;

	friend struct Callback;

	struct Callback
	{
		typedef boost::shared_ptr<Callback> Ptr;

		Callback(Test1& _parent, Logger::Ptr _logger, LineMachine::Ptr _lineMachine, LineMachine::Ptr _lineMachine2, Semaphore& _exitSem)
		: parent(_parent)
		, logger(_logger)
		, lineMachine(_lineMachine)
		, lineMachine2(_lineMachine2)
		, stdOutReceiver(_logger, false)
		, stdErrReceiver(_logger, true)
		, exitSem(_exitSem)
		{
		}

		void operator()(AsynchronousProcessExecutor& sender, int resultCode)									// handles process exit callback
		{
			using namespace std;

			if (logger)
			{
				Logger::Lock logLock(*logger);
				logger->Info << "Process exited with resultCode=" << resultCode << endl;
			}

			if (!exitSem.GetCount())
				exitSem.Post();
		}

		void operator()(AsynchronousProcessExecutor& sender, const ByteArray& dataReceived, bool fromStdErr)	// handles reporting callback
		{
			if (!fromStdErr && lineMachine)
				lineMachine->Feed(dataReceived);
			else if (fromStdErr && lineMachine2)
				lineMachine2->Feed(dataReceived);
		}

		struct L2
		{
			L2(Logger::Ptr _logger, bool _fromStdErr)
			: logger(_logger)
			, fromStdErr(_fromStdErr) { }

			void operator()(LineMachine& sender, const string& receivedLine)
			{
				using namespace std;

				if (logger)
				{
					Logger::Lock logLock(*logger);
					logger->Info << "Process output '" << receivedLine << "' from " << (fromStdErr ? "stderr" : "stdout") << endl;
				}
			}

			Logger::Ptr logger;
			const bool fromStdErr;
		};

		Test1& parent;
		Logger::Ptr logger;
		LineMachine::Ptr lineMachine;
		LineMachine::Ptr lineMachine2;
		L2 stdOutReceiver;
		L2 stdErrReceiver;
		Semaphore& exitSem;
	};

	void OnExit()
	{
		callbackExitConnection.reset();
		callbackReportConnection.reset();
		callbackLineMachineConnection.reset();
		callbackLineMachine2Connection.reset();
		callback.reset();
		lineMachine.reset();
		lineMachine2.reset();
		procExec.reset();
	}

	Logger::Ptr logger;
	Semaphore exitSem;
	AsynchronousProcessExecutor::Ptr procExec;
	Callback::Ptr callback;
	LineMachine::Ptr lineMachine;
	LineMachine::Ptr lineMachine2;
	ScopedConnectionPtr callbackLineMachineConnection;
	ScopedConnectionPtr callbackLineMachine2Connection;
	ScopedConnectionPtr callbackReportConnection;
	ScopedConnectionPtr callbackExitConnection;
};

#endif
