//------------------------------------------------------------------------------
// <copyright file="LqrSMACParserFormatter.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 LQRSMACPARSERFORMATTER_H_
#define LQRSMACPARSERFORMATTER_H_

#include <deque>
#include <fstream>
#include <iostream>
#include <list>
#include <map>
#include <set>
#include <string>
#include <typeinfo>
#include <utility>
#include <boost/shared_ptr.hpp>
#include "LqrSMACByteArray.h"
#include "LqrSMACDeclareException.h"
#include "LqrSMACSharedLib.h"

namespace LiquidRobotics {
namespace SMACFramework {
namespace PacketParserFormatter {

typedef ByteArray::byte byte;
typedef unsigned short size_type;
typedef std::istream istream;
typedef std::ostream ostream;
typedef std::string string;
typedef std::type_info type_info;

class IPFDomain;									// a restricted set of names
class IPFFormatter;									// a storable instance that also has internal fields that can be looked up
class IPFItemLookup;								// an instance with internal fields that can be looked up
class IPFLinkage;									// a storable instance with internal fields that can be looked up and with some qualfications
class IPFNamedItem;									// an item with an name
class IPFParserFormatter;							// a set of rules for parsing or formatting binary data using name/value pairs
class IPFStream;									// a storage/retrieval stream

typedef std::pair<string,string> AttributePair;
typedef std::map<string,AttributePair> AttributeMap;
typedef boost::shared_ptr<IPFFormatter> IPFFormatterPtr;
typedef boost::shared_ptr<IPFItemLookup> IPFItemLookupPtr;
typedef boost::shared_ptr<IPFNamedItem> IPFNamedItemPtr;
typedef std::list<IPFNamedItemPtr> IPFNamedItemPtrList;
typedef std::map<string, IPFNamedItemPtr> IPFNamedItemPtrMap;
typedef std::pair<string,string> LinkagePair;
typedef std::set<LinkagePair> LinkageSet;
typedef std::set<string> StringSet;

extern const string AttributeName_endian;			// default is little
extern const string AttributeName_name;				// mandatory non-blank field
extern const string AttributeName_position;			// default is blank, which indicates sequential assignment
extern const string AttributeName_size;				// default is blank, which defaults to 1 (cannot define size_bytes)
extern const string AttributeName_size_bytes;		// default is blank, which defaults to 1 (cannot define size)
extern const string AttributeName_size_max;			// maximum value of size or size_bytes
extern const string AttributeName_size_min;			// minimum value of size or size_bytes
extern const string AttributeName_type;				// mandatory non-blank field
extern const string AttributeName_within;			// default is blank, which indicates not within another field

extern const string Category_domain;				// Domain { list of DomainMember };
extern const string Category_domain_member;			// DomainMember +name;
extern const string Category_enumeration;			// Enumeration +name +type { list of EnumerationValue }; -> type of data
extern const string Category_enumeration_value;		// EnumerationValue +name +value;
extern const string Category_field;					// Field +name +type ...;
extern const string Category_format;				// Format +name { list of fields }; -> type of data
extern const string Category_linkage;				// Linkage +name +type +base +within +selector;

extern const string EndianTag_big;					// if numeric value, sent MSB first
extern const string EndianTag_little;				// if numeric value, sent LSB first (default)

extern const string PositionTag_begin_offset;		// indicates an offset from the beginning of the structure
extern const string PositionTag_end_offset;			// indicates an offset from the end of the structure
extern const string PositionTag_follows;			// field follows the named field within the structure
extern const string PositionTag_preceeds;			// field preceeds the named field within the structure

extern const string SizeTag_linked;					// size of field is determined by the field
extern const string SizeType_remaining;				// the field is dimensioned according to the number of unused bytes that follow

extern const string TypeName_bit;					// bit field (position is starting bit number, size is number of bits, within must be defined)
extern const string TypeName_byte;					// byte field (uint8_t, used to define bit fields or general purpose packets)
extern const string TypeName_char;					// character field (int8_t)
extern const string TypeName_short;					// 16-bit integer
extern const string TypeName_ushort;				// 16-bit unsigned integer
extern const string TypeName_long;					// 32-bit integer
extern const string TypeName_ulong;					// 32-bit unsigned integer
extern const string TypeName_longlong;				// 64-bit integer (if supported)
extern const string TypeName_ulonglong;				// 64-bit unsigned integer (if supported)
extern const string TypeName_float;					// 32-bit floating point
extern const string TypeName_double;				// 64-bit floating point

DECLARE_EXCEPTION(BadAttributeName, std::runtime_error);
DECLARE_EXCEPTION(BadAttributeValue, std::runtime_error);
DECLARE_EXCEPTION(BadCategoryName, std::runtime_error);
DECLARE_EXCEPTION(CircularDependency, std::runtime_error);
DECLARE_EXCEPTION(ConfigFileParserError, std::runtime_error);
DECLARE_EXCEPTION(FailedToOpenLexicalAnalyzer, std::runtime_error);
DECLARE_EXCEPTION(FileNotFound, std::runtime_error);
DECLARE_EXCEPTION(ItemIsNotADomain, std::runtime_error);
DECLARE_EXCEPTION(LexicalAnalysisError, std::runtime_error);
DECLARE_EXCEPTION(NoSuchDomainName, std::runtime_error);
DECLARE_EXCEPTION(NoSuchItemName, std::runtime_error);

class SHARED_LIB_PUBLIC SystemError : public std::runtime_error
{
public:

	SystemError(const string& whatArg, int _errnoSave) throw();

	const int errnoSave;

	string DescribeError() const;
	static string DescribeError(int _errno);
};

DECLARE_EXCEPTION(UnexpectedEndOfStream, std::runtime_error);

class SHARED_LIB_PUBLIC IPFStream
{
public:

	// Tests if the stream is in read mode
	virtual bool IsReadStream() const = 0;

	// Tests if the stream is in write mode
	virtual bool IsWriteStream() const = 0;

	// Returns the length of the stream (valid only for write streams)
	virtual size_type GetStreamSize() const = 0;

	// Reads a byte of data from the stream
	virtual bool Read(byte& data) = 0;

	// Writes a byte of data to the stream
	virtual void Write(byte data) = 0;
};

class SHARED_LIB_PUBLIC IPFNamedItem
{
public:

	virtual ~IPFNamedItem();

	// Property AttributeSet provides read-only access to the full set of attributes
	virtual const AttributeMap& GetAttributeSet() const = 0;

	// Property AttributeValue accesses individual attributes by name
	virtual const string& GetAttributeValue(const string& attributeName) = 0;
	virtual void SetAttributeValue(const string& attributeName, const string& newValue) = 0;

	// Property Dependencies provides read-only access to the list of names on which this item depends
	// (these items must be fully defined in order to complete configuration)
	virtual const StringSet& GetDependencies() const = 0;

	// Property IsWriteable determines if the value is complete enough to write
	virtual bool GetIsWriteable() const = 0;
	virtual void SetIsWriteable(bool newValue) const = 0;

	// Property Name gives the name of the instance
	virtual const string& GetName() const = 0;
	virtual void SetName(const string& newValue) = 0;

	// Property ParentName gives the name of the parent instance (lookups
	// find items by Name or ParentName.Name)
	virtual const string& GetParentName() const = 0;
	virtual void SetParentName(const string& newValue) = 0;

	// Duplicates the target instance
	virtual IPFNamedItemPtr Duplicate() const = 0;

	// Writes the full content of this instance as text
	virtual void FullContentTo(ostream& stm) const = 0;

	// Validates the configuration
	virtual void ValidateConfig() const = 0;
};

class SHARED_LIB_PUBLIC IPFStorableItem : public IPFNamedItem
{
public:

	virtual ~IPFStorableItem();

	// Property Size is the number of elements in this value
	virtual size_type GetSize() const = 0;
	virtual void SetSize(size_type newValue) = 0;

	// Property SizeInBytes is the number of bytes for this data
	virtual size_type GetSizeInBytes() const = 0;
	virtual void SetSizeInBytes(size_type newValue) = 0;

	// Property Value is the field value carried
	template<typename T>
	void GetValue(T& result) const
	{
		GetValueGeneric(&result);
		return result;
	}

	template<typename T>
	void SetValue(const T& newValue)
	{
		SetValueGeneric(&newValue);
	}

	// Property ValueGeneric is the same as Value, but is treated generically as a void
	virtual void GetValueGeneric(void* pResult) const = 0;
	virtual void SetValueGeneric(const void* pNewValue) = 0;

	// Property ValueType returns the type info for the encapsulated data
	virtual type_info GetValueType() const = 0;

	// Reads/writes the value from a IPCStream object
	virtual void From(IPFStream& stm) = 0;
	virtual void To(IPFStream& stm) = 0;

	// Reads/writes the value to a text stream
	virtual void From(istream& stm) = 0;
	virtual void To(ostream& stm) const = 0;

};

class SHARED_LIB_PUBLIC IPFItemLookup
{
public:

	virtual ~IPFItemLookup();


	virtual bool Has(const string& itemName) const = 0;

	virtual IPFNamedItem& Lookup(const string& itemName) = 0;
	virtual const IPFNamedItem& Lookup(const string& itemName) const = 0;
};

class SHARED_LIB_PUBLIC IPFFormatter : public IPFStorableItem, public IPFItemLookup
{
public:

	virtual ~IPFFormatter();
};

class SHARED_LIB_PUBLIC IPFDomain : public IPFNamedItem, public IPFItemLookup
{
public:

	virtual ~IPFDomain();
};

class SHARED_LIB_PUBLIC IPFFormat : public IPFStorableItem, public IPFItemLookup
{
public:

	virtual ~IPFFormat();
};

class SHARED_LIB_PUBLIC IPFLinkage : public IPFFormat
{
public:

	virtual ~IPFLinkage();
};

class SHARED_LIB_PUBLIC IPFParserFormatter : public IPFItemLookup
{
public:

	virtual ~IPFParserFormatter();

	virtual IPFNamedItemPtr CreateNamedItem(const string& itemName, const AttributeMap& attributes) = 0;

	virtual IPFFormatterPtr CreateFormatter(const string& itemName) = 0;

	virtual bool HasDomain(const string& domainName) const = 0;

	virtual IPFDomain& LookupDomain(const string& itemName) = 0;
	virtual const IPFDomain& LookupDomain(const string& itemName) const = 0;

	virtual IPFFormatterPtr Parse(const ByteArray& input, const string& itemName) = 0;
};

namespace Detail
{

class SHARED_LIB_PUBLIC NamedItem : public IPFNamedItem
{
public:

	NamedItem(IPFParserFormatter& _parent, istream& configuration);

	virtual ~NamedItem();

	// --- IPFNamedItem ---
	// Property AttributeSet provides read-only access to the full set of attributes
	virtual const AttributeMap& GetAttributeSet() const;

	// Property AttributeValue accesses individual attributes by name
	virtual const string& GetAttributeValue(const string& attributeName);
	virtual void SetAttributeValue(const string& attributeName, const string& newValue);

	// Property Dependencies provides read-only access to the list of names on which this item depends
	// (these items must be fully defined in order to complete configuration)
	virtual const StringSet& GetDependencies() const;

	// Property IsWriteable determines if the value is complete enough to write
	virtual bool GetIsWriteable() const;
	virtual void SetIsWriteable(bool newValue) const;

	// Property Name gives the name of the instance
	virtual const string& GetName() const;
	virtual void SetName(const string& newValue);

	// Property ParentName gives the name of the parent instance (lookups
	// find items by Name or ParentName.Name)
	virtual const string& GetParentName() const;
	virtual void SetParentName(const string& newValue);

	// Duplicates the target instance
	virtual IPFNamedItemPtr Duplicate() const;

	// Writes the full content of this instance as text
	virtual void FullContentTo(ostream& stm) const;

	// Validates the configuration
	virtual void ValidateConfig() const;
};

class SHARED_LIB_PUBLIC ConfigStream
{
public:

	typedef std::string string;

	enum TokenType
	{
		TT_NONE,
		TT_COMMENT,
		TT_ERROR,
		TT_EQUALS,
		TT_LCURL,
		TT_LINE,
		TT_RCURL,
		TT_SEMICO,
		TT_SYMBOL,
		TT_QUOTED
	};

	static std::string TokenType_ToString(TokenType tt);

	struct Token
	{
		TokenType tokenType;
		string data;
		int lineNum;
	};

	ConfigStream(const string& _pathName);

	virtual ~ConfigStream();

	const string& GetPathName() const;
	void Push(const Token& token);
	bool Read(Token& result);

protected:

	virtual bool HandleReceivedData(const Token& token);
	bool ReadLine(string& strResult, bool upcase, bool unescape = false);

private:

	typedef std::deque<Token> TokenQueue;

	int lineNum;
	const string pathName;
	FILE* fp;
	bool complete;
	TokenQueue tokenQueue;
};

// Configuration item from the setup file
//
// category attribute="value" ... attribute="value" { subconfig; ... subconfig; }
class SHARED_LIB_PUBLIC ConfigItem
{
public:

	typedef boost::shared_ptr<ConfigItem> Ptr;
	typedef std::list<ConfigItem::Ptr> PtrList;
	typedef boost::shared_ptr<PtrList> PtrListPtr;

	ConfigItem();

	virtual ~ConfigItem();

	bool Initialize(ConfigStream& stm, bool nested);

	AttributeMap attributes;
	string category;
	PtrListPtr subConfig;
};

std::ostream& operator<<(std::ostream& stm, const ConfigItem& ci);

class SHARED_LIB_PUBLIC Domain : public IPFDomain
{
public:

	Domain(IPFParserFormatter& _parent, istream& configuration);

	virtual ~Domain();

	// --- IPFItemLookup ---
	virtual bool Has(const string& itemName) const;

	virtual IPFNamedItem& Lookup(const string& itemName);
	virtual const IPFNamedItem& Lookup(const string& itemName) const;

	// --- IPFNamedItem ---
	// Property AttributeSet provides read-only access to the full set of attributes
	virtual const AttributeMap& GetAttributeSet() const;

	// Property AttributeValue accesses individual attributes by name
	virtual const string& GetAttributeValue(const string& attributeName);
	virtual void SetAttributeValue(const string& attributeName, const string& newValue);

	// Property Dependencies provides read-only access to the list of names on which this item depends
	// (these items must be fully defined in order to complete configuration)
	virtual const StringSet& GetDependencies() const;

	// Property IsWriteable determines if the value is complete enough to write
	virtual bool GetIsWriteable() const;
	virtual void SetIsWriteable(bool newValue) const;

	// Property Name gives the name of the instance
	virtual const string& GetName() const;
	virtual void SetName(const string& newValue);

	// Property ParentName gives the name of the parent instance (lookups
	// find items by Name or ParentName.Name)
	virtual const string& GetParentName() const;
	virtual void SetParentName(const string& newValue);

	// Duplicates the target instance
	virtual IPFNamedItemPtr Duplicate() const;

	// Writes the full content of this instance as text
	virtual void FullContentTo(ostream& stm) const;

	// Validates the configuration
	virtual void ValidateConfig() const;

private:

	IPFParserFormatter& parent;						// owner of this domain
	StringSet domainNames;							// names of the top level items in the domain
};

}

class SHARED_LIB_PUBLIC ParserFormatter : public IPFParserFormatter
{
public:

	friend class Domain;

	ParserFormatter();
	virtual ~ParserFormatter();

	void Initialize(const string& configPath);
	void Clear();

	// IPFIParserFormatter
	virtual IPFNamedItemPtr CreateNamedItem(const string& itemName, const AttributeMap& attributes);
	virtual IPFFormatterPtr CreateFormatter(const string& itemName);
	virtual bool HasDomain(const string& domainName) const;
	virtual IPFDomain& LookupDomain(const string& itemName);
	virtual const IPFDomain& LookupDomain(const string& itemName) const;
	virtual IPFFormatterPtr Parse(const ByteArray& input, const string& itemName);

	// IPFItemLookup
	virtual bool Has(const string& itemName) const;

	virtual IPFNamedItem& Lookup(const string& itemName);
	virtual const IPFNamedItem& Lookup(const string& itemName) const;

private:

	IPFNamedItemPtrMap namesToItems;				// includes all types and domains
};

}
}
}

#endif /* LQRSMACPARSERFORMATTER_H_ */
