//------------------------------------------------------------------------------
// <copyright file="NMEAParser.cpp" 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>
//   Implementation of the NMEAParser class, which blocks bytes of received data
//   into NMEA sentences and fires events
// </summary>
//
// <owner>Mike Cookson</owner>
//------------------------------------------------------------------------------

#include "NMEAParser.h"
#include <string>
#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>

using namespace std;
using namespace boost;

namespace LiquidRobotics {
namespace GPSListenerLib {

NMEAParser::NMEAParser(const string::size_type __MaxLength)
: MaxLength(__MaxLength)
, StartSentence((byte) '$')
, ChecksumDelimiter('*')
{
	Reset();
}


NMEAParser::~NMEAParser()
{
}

void NMEAParser::Feed(const ByteArray& bytes)
{
	BOOST_FOREACH(byte dataByte, bytes)
		Feed(dataByte);
}

void NMEAParser::Reset()
{
	inSentence = false;
	gotCR = false;
	sentenceComplete = false;
	sentence.str("");
	xsum = 0;
}

NMEAParser::ScopedConnectionPtr NMEAParser::SubscribeSentenceEvents(T_HereIsParsedSentence_Signal::slot_type slot)
{
	ScopedConnectionPtr result;
	result.reset(new ScopedConnection(hereIsParsedSentence_Signal.connect(slot)));

	return result;
}

NMEAParser::ScopedConnectionPtr NMEAParser::SubscribeErrorEvents(T_HereIsParsingError_Signal::slot_type slot)
{
	ScopedConnectionPtr result;
	result.reset(new ScopedConnection(hereIsParsingError_Signal.connect(slot)));

	return result;
}

inline string::size_type my_find_first_of(const string& inString, const char item, string::size_type startPos = 0)
{
	return inString.find_first_of(item, startPos);
#if 0
	// find_first_of generates a warning in Release mode, so I'm re-coding it

	const string::size_type len = inString.length();
	const char* const pData = /*inString.data()*/ NULL;

	for (string::size_type idx = startPos; idx < len; ++idx)
		if (pData == NULL)
			return idx;

	return inString.npos;
#endif
}

void NMEAParser::Feed(byte dataByte)
{
	if (dataByte == StartSentence)
	{
		if (inSentence)
			hereIsParsingError_Signal(*this, "Received sentence start in the middle of a sentence");

		Reset();
	}

	if (dataByte == StartSentence)
		inSentence = true;

	else if (!inSentence)
		;

	else if (dataByte == (byte) '\r')
	{
#if 0
		if (gotCR)
		{
			hereIsParsingError_Signal(*this, "Received CR when expecting LF");
			Reset();
		}
		else
			gotCR = true;
#endif

		sentenceComplete = inSentence;
	}

	else if (dataByte == (byte) '\n')
	{
#if 0
		if (gotCR)
			sentenceComplete = true;
		else
		{
			hereIsParsingError_Signal(*this, "Received LF with no preceeding CR");
			Reset();
		}
#endif

		sentenceComplete = inSentence;
	}

#if 0
	else if (gotCR)
	{
		hereIsParsingError_Signal(*this, "Received something other than LF after CR");
		Reset();
	}
#endif

	else if ((dataByte < 0x20) || (dataByte > 0x7F) || strchr("\x7E\x7F", (char) dataByte))
	{
		hereIsParsingError_Signal(*this, "Invalid data in sentence");
		Reset();
	}

	else
	{
		xsum ^= dataByte;
		sentence << (char) dataByte;

		if (sentence.tellp() > MaxLength)
		{
			hereIsParsingError_Signal(*this, "Sentence is too long");
			Reset();
		}
	}

	if (sentenceComplete)
	{
		string temp = sentence.str();
		sentence.str("");

		size_t len = temp.length();
		size_t csumDelimiterIdx = my_find_first_of(temp, ChecksumDelimiter);
		size_t csumDelimiterIdx2 = (csumDelimiterIdx == temp.npos) ? 0 : my_find_first_of(temp, ChecksumDelimiter, csumDelimiterIdx+1);
		char* endptr = NULL;
		const char* asPtr = temp.c_str();
		byte computedChecksum;

		if ((temp.length() <= 3)
		 || (csumDelimiterIdx != (len-3))
		 || (csumDelimiterIdx2 != temp.npos))

			hereIsParsingError_Signal(*this, "Checksum delimiter not found or invalid length");

		else if (computedChecksum = (byte) strtoul(&asPtr[csumDelimiterIdx+1], &endptr, 16),
				 endptr && *endptr)

			hereIsParsingError_Signal(*this, "Conversion error in checksum field");

		else
		{
			xsum ^= temp[len-1];
			xsum ^= temp[len-2];
			xsum ^= temp[len-3];

			if (xsum != computedChecksum)
			{
				stringstream msg;
				msg << "Checksum is not matching for " << temp;

				string msgStr = msg.str();

				hereIsParsingError_Signal(*this, msgStr);
			}

			else
			{
				ParsedSentence parsedSentence;
				string sub = temp.substr(0,len-3);

				split(parsedSentence, sub, is_any_of(","));

				if (!parsedSentence.size())
					hereIsParsingError_Signal(*this, "Empty array of tokens");

				else if (parsedSentence[0].length() != 5)
				{
					stringstream msg;
					msg << "The first parameter (" << parsedSentence[0] << ") is not 5 characters in length";

					string msgStr = msg.str();
					hereIsParsingError_Signal(*this, msgStr);
				}

				else
					hereIsParsedSentence_Signal(*this, temp, parsedSentence);
			}
		}

		Reset();
	}
}

struct Context
{
	typedef LiquidRobotics::SMACFramework::ByteArray ByteArray;

	Context(const ByteArray& bytes, const string::size_type maxLength, NMEAParser::BulkParsed& _bp)
	: bp(_bp)
	{
		NMEAParser parser(maxLength);
		NMEAParser::ScopedConnectionPtr nmeaReceiverConnection;
		NMEAParser::ScopedConnectionPtr nmeaReceiverConnection2;

		nmeaReceiverConnection = parser.SubscribeSentenceEvents(boost::bind(&Context::ReceivedSentence, this, _1, _2, _3));
		nmeaReceiverConnection2 = parser.SubscribeErrorEvents(boost::bind(&Context::NoteParsingError, this, _1, _2));

		parser.Feed(bytes);
	}

	void ReceivedSentence(NMEAParser& sender, const string& rawSentence, const NMEAParser::ParsedSentence& parsedSentence)
	{
		NMEAParser::BulkParsedItemPtr item;
		item.reset(new NMEAParser::BulkParsedItem);
		item->rawSentence = rawSentence;
		item->parsedSentence.assign(parsedSentence.begin(), parsedSentence.end());

		bp.itemArray.push_back(item);
	}

	void NoteParsingError(NMEAParser& sender, const string& errorMessage)
	{
		NMEAParser::StringPtr sp;
		sp.reset(new string(errorMessage));

		bp.errorArray.push_back(sp);
	}

	NMEAParser::BulkParsed& bp;
};

NMEAParser::BulkParsed::BulkParsed(const ByteArray& bytes, const string::size_type maxLength)
{
	Context ctx(bytes, maxLength, *this);
}

}
}
