//-------------------------------------------------------------------------------------
// <copyright file="SensorControllerMultipleLinesCmd.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>
//	 Declarations for SensorControllerMultipleLinesCmd template class, a template-
//	 based implementation of a command object that itself executes sub-commands.  The
//	 template parameters are:
//
//		- TControllerBase, the controller interface that is made available to all commands,
//		  usually abstract; this will be a base class for SensorController
//
//		- TCmdBase, the abstract base class that defines command operations
//
//		- TCmdID, the type if command ID that is used to track commands
//
//		- TExceptions, a class-based namespaces for exceptions that can be thrown
// </summary>
//
// <owner>Mike Cookson</owner>
//-------------------------------------------------------------------------------------

#ifndef __EventLib_SensorControllerMultipleLinesCmd_h_
#define __EventLib_SensorControllerMultipleLinesCmd_h_

#include "LqrSMACCmdOutputFormatter.h"
#include "LqrSMACLogger.h"
#include "LqrSMACSharedLib.h"
#include "SensorConcreteCommand.h"
#include <fstream>
#include <iostream>
#include <list>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
#include <boost/filesystem.hpp>

namespace LiquidRobotics {
namespace EventLib  {

template<class TControllerBase, class TCmdBase, class TCmdID, class TExceptions>
class SHARED_LIB_PUBLIC SensorControllerMultipleLinesCmd : public SensorConcreteCommand<TControllerBase,TCmdBase,TCmdID,TExceptions>
{
public:

	typedef SensorConcreteCommand<TControllerBase,TCmdBase,TCmdID,TExceptions> Base;
	typedef LiquidRobotics::SMACFramework::CmdOutputFormatter CmdOutputFormatter;
	typedef SensorControllerMultipleLinesCmd<TControllerBase,TCmdBase,TCmdID,TExceptions> SensorControllerMultipleLinesCmdT;
	typedef typename Base::CmdBasePtr CmdPtr;
	typedef std::string string;
	typedef std::pair<string,CmdPtr> CmdItem;
	typedef std::list<CmdItem> CommandList;
	typedef boost::shared_ptr<CommandList> CommandListPtr;
	typedef LiquidRobotics::SMACFramework::Logger Logger;
	typedef typename TControllerBase::ParsingContext ParsingContext;
	typedef std::stringstream stringstream;
	typedef std::vector<string> StringArray;

	SensorControllerMultipleLinesCmd(TControllerBase& _ctrl, CommandListPtr _cmdList, Logger& _logger)
	: Base(ctrl)
	, ctrl(_ctrl)
	, cmdList(_cmdList)
	, logger(_logger)
	{
	}

	virtual void Execute()
	{
		using namespace std;

		stringstream concatData;

		try
		{
			Base::SignalStarted();

			for (typename CommandList::const_iterator ci = cmdList->begin(); ci != cmdList->end(); ++ci)
			{
				if (Base::formatter)
				{
					CmdOutputFormatter::Ptr fmt = Base::formatter->NewChild();
					fmt->SetCommandName(ci->first);
					ci->second->SetOutputFormatter(fmt);
				}

				ci->second->Execute();
				ci->second->SignalComplete();
				ci->second->GetResult();
			}

			Base::SetException(TExceptions::NoException, "");
		}
		catch(exception& ex)
		{
			const int exNum = TExceptions::ExceptionToNumber(ex);

			stringstream msg;
			msg << "caught exception " << typeid(ex).name() << " '" << ex.what() << "'";

			Base::SetException(exNum, msg.str());
		}
		catch(...)
		{
			typename TExceptions::NotReady exc("");
			const int exNum = TExceptions::ExceptionToNumber(exc);

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

	static CmdPtr _Parse(TControllerBase& ctrl, const StringArray& cmdLines, ParsingContext& context, Logger& logger)
	{
		using namespace std;
		using namespace boost;
		using namespace LiquidRobotics::SMACFramework;

		CmdPtr result;
		CommandListPtr listPtr;
		listPtr.reset(new CommandList);

		for (StringArray::const_iterator ci = cmdLines.begin(); ci != cmdLines.end(); ++ci)
		{
			string trimmed = *ci;
			trim(trimmed);

			if (trimmed.length())
			{
				typename TControllerBase::ParsingContext context2(context);
				context2.SetCmd(trimmed);
				CmdPtr cmdp = ctrl.ParseCommand(context2);
				listPtr->push_back(pair<string,CmdPtr>(trimmed,cmdp));
			}
		}

		if (!listPtr->empty())
			result.reset(new SensorControllerMultipleLinesCmdT(ctrl, listPtr, logger));

		return result;
	}

	static CmdPtr Parse(TControllerBase& ctrl, const string& fileName, ParsingContext& context, Logger& logger, const size_t maxLength = 10000)
	{
		using namespace std;

		StringArray cmdLines;

		{
			filebuf fb;

			if (!fb.open(fileName.c_str(), ios::in))
			{
				stringstream msg;
				msg << "Failed to open command file \"" << fileName << "\"";
				throw invalid_argument(msg.str());
			}

			istream istm(&fb);
			string cmdLine;
			size_t totalSize = 0;

			while(getline(istm, cmdLine), istm.good())
			{
				cmdLines.push_back(cmdLine);
				totalSize += cmdLine.length();

				if (totalSize >= maxLength)
				{
					stringstream msg;
					msg << "Command file \"" << fileName << "\" exceeds maximum length of " << maxLength;
					throw invalid_argument(msg.str());
				}
			}
		}

		CmdPtr result = _Parse(ctrl, cmdLines, context, logger);

		if (!result)
		{
			stringstream msg;
			msg << "No commands resulted from reading command file \"" << fileName << "\"";
			throw invalid_argument(msg.str());
		}

		return result;
	}

	static CmdPtr Parse(TControllerBase& ctrl, const StringArray& cmdLines, ParsingContext& context, Logger& logger)
	{
		using namespace std;

		CmdPtr result = _Parse(ctrl, cmdLines, context, logger);

		if (!result)
			throw invalid_argument("Command line list did not result in any parsed commands");

		return result;
	}

private:

	TControllerBase& ctrl;
	CommandListPtr cmdList;
	Logger& logger;
};

}
}

#endif
