//------------------------------------------------------------------------------
// <copyright file="LqrSMACCSVSample.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 CSVSample class, which encapsulates a set of CSV values
// </summary>
//
// <owner>Mike Cookson</owner>
//------------------------------------------------------------------------------

#include "LqrSMACCSVSample.h"
#include <sstream>
#include <stdexcept>
#include <ctype.h>

using namespace std;

namespace LiquidRobotics {
namespace SMACFramework {

inline void SkipWhitespace(istream& stm)
{
	if (stm.good())
	{
		bool white;

		do
		{
			const int ch = stm.peek();
			white = stm.good() && (ch < 0x80) && isspace(ch) && (ch != '\r') && (ch != '\n');
			if (white) stm.get();
		} while(white);
	}
}

inline bool AtComma(istream& stm)
{
	return stm.good() && (stm.peek() == ',');
}

inline bool AtEOL(istream& stm)
{
	bool result;

	const int ch1 = stm.peek();

	if (ch1 == '\r')
	{
		stm.get();
		const int ch2 = stm.peek();
		result = stm.good() && (ch2 == '\n');
		stm.putback(ch1);
	}
	else
		result = (ch1 == '\n');

	return result;
}

inline void EatEOL(istream& stm)
{
	const int ch1 = stm.peek();

	if (ch1 == '\n')
		stm.get();
	else if (ch1 == '\r')
	{
		stm.get();
		const int ch2 = stm.peek();
		if (stm.good() && (ch2 == '\n'))
			stm.get();
	}
}

inline bool AtDoubleQuote(istream& stm)
{
	const int ch1 = stm.peek();
	const bool result = stm.good() && (ch1 == '\"');
	return result;
}

CSVSample::CSVSample(size_type initialSize, size_type reservedSize)
{
	if (reservedSize)
		container.reserve(reservedSize);

	if (initialSize)
		container.resize(initialSize);
}

CSVSample::CSVSample(const CSVSample& rhs)
: container(rhs.container)
{
}

CSVSample::CSVSample(const ContainerType& rhs)
: container(rhs)
{
}

CSVSample::CSVSample(const ItemType& rhs, size_type reservedSize)
{
	if (reservedSize)
		container.reserve(reservedSize);

	container.push_back(rhs);
}

CSVSample::~CSVSample()
{
}

CSVSample& CSVSample::operator=(const CSVSample& rhs)
{
	if (this != &rhs)
		container.assign(rhs.container.begin(), rhs.container.end());

	return *this;
}

CSVSample& CSVSample::operator+=(const ItemType& rhs)
{
	container.push_back(rhs);
	return *this;
}

void CSVSample::Clear()
{
	container.clear();
}

CSVSample::ContainerType& CSVSample::Container()
{
	return container;
}

const CSVSample::ContainerType& CSVSample::Container() const
{
	return container;
}

CSVSample::ItemType& CSVSample::operator[](size_type index)
{
	if (index >= container.size())
	{
		stringstream msg;
		msg << "CSVSample[" << index << "] is out of range: the container only has " << container.size() << " elements";
		throw invalid_argument(msg.str());
	}

	return container[index];
}

const CSVSample::ItemType& CSVSample::operator[](size_type index) const
{
	if (index >= container.size())
	{
		stringstream msg;
		msg << "CSVSample[" << index << "] is out of range: the container only has " << container.size() << " elements";
		throw invalid_argument(msg.str());
	}

	return container[index];
}

bool CSVSample::IsEmpty() const
{
	return container.size() == 0;
}

void CSVSample::Reserve(size_type newReserved)
{
	container.reserve(newReserved);
}

CSVSample::size_type CSVSample::Size() const
{
	return container.size();
}

void CSVSample::FormatCSV(std::ostream& stm) const
{
	bool first = true;

	for (ContainerType::const_iterator ci = container.begin(); ci != container.end(); ++ci)
	{
		if (first)
			first = false;
		else
			stm << ",";

		FormatCSVField(stm, *ci);
	}

	stm << endl;
}

void CSVSample::ParseCSV(std::istream& stm)
{
	container.clear();

	bool looping = true;

	do
	{
		ItemType item;
		bool positiveItem;

		try
		{
			item = ParseCSVField(stm, positiveItem);
		}
		catch(...)
		{
			positiveItem = false;
			looping = false;
		}

		if (looping)
		{
			if (AtComma(stm))
			{
				stm.get();
				container.push_back(item);
			}
			else if (AtEOL(stm))
			{
				if (container.size() || positiveItem)
					container.push_back(item);

				EatEOL(stm);
				looping = false;
			}
			else if (stm.eof())
				looping = false;
		}
	} while(looping);
}

void CSVSample::FromString(const string& str)
{
	stringstream stm(str);
	ParseCSV(stm);
}

string CSVSample::ToString() const
{
	stringstream stm;

	FormatCSV(stm);

	string result = stm.str();
	return result;
}

void CSVSample::FormatCSVField(std::ostream& stm, const ItemType& item)
{
	if (item.find_first_of(",\"\r\n") == string::npos)
		stm << item;
	else
	{
		const char DOUBLEQUOTE = '\"';
		stm.put('\"');

		for (ItemType::const_iterator ci = item.begin(); ci != item.end(); ++ci)
		{
			if (*ci == DOUBLEQUOTE)
			{
				stm.put(DOUBLEQUOTE);
				stm.put(DOUBLEQUOTE);
			}
			else
				stm.put(*ci);
		}

		stm.put(DOUBLEQUOTE);
	}
}

CSVSample::ItemType CSVSample::ParseCSVField(std::istream& stm)
{
	bool positiveItem;
	return ParseCSVField(stm, positiveItem);
}

CSVSample::ItemType CSVSample::ParseCSVField(std::istream& stm, bool& positiveItem)
{
	stringstream content;
	bool looping = true;

	positiveItem = false;

	while(stm.good() && looping)
	{
		if (AtComma(stm) || AtEOL(stm) || stm.eof())
			looping = false;

		else if (AtDoubleQuote(stm))
		{
			if (content.tellp())
				throw invalid_argument("Unexpected double-quote after the start of the CSV field");

			stm.get();
			positiveItem = true;

			while(looping)
			{
				if (AtDoubleQuote(stm))
				{
					stm.get();

					if (AtDoubleQuote(stm))
						content.put('\"');
					else
					{
						SkipWhitespace(stm);

						if (AtComma(stm) || AtEOL(stm))
							looping = false;
						else
							throw invalid_argument("Unexpected data found after double quote");
					}
				}
				else if (stm.good())
					content.put(stm.get());
				else if (stm.eof())
					throw invalid_argument("Unexpected end of file in quoted item");
				else
					throw invalid_argument("Unexpected read failure in quoted item");
			}
		}
		else
		{
			const int ch = stm.get();

			if ((ch >= 0x80) || !isspace(ch))
				positiveItem = true;

			content.put(ch);
		}
	}

	if (!stm.good())
		throw runtime_error("At end of file");

	ItemType item = content.str();
	return item;
}

}
}
