#pragma once

#include <memory>
#include <map>

#include "bitstream.h"
#include "uniconv.h"
#include "except.h"

#ifndef _WIN32
#include <alloca.h>
#else
#include <malloc.h>
#endif

using namespace std;

class BitStruct {

public:
	enum FIELD_TYPE
	{
		RAW,
		INTEGER,
		STRING,
		DATE14,
		DATE13,
		DATE16,
	};

private:
	class Field {
	public:
		Field(): 
			m_fieldType(FIELD_TYPE::INTEGER),
			m_offset(0),
			m_size(0)
		{

		}

		Field(FIELD_TYPE fieldType, int offset, int size)
		{
			m_fieldType = fieldType;
			m_offset = offset;
			m_size = size;
		}

	public:
		FIELD_TYPE m_fieldType;
		int m_offset;
		int m_size;
	};

	typedef std::map<string, Field> FieldMap;

public:
	BitStruct(int bitSize = 0) :
		m_bits(bitSize)
	{

	}

	BitStruct(vector<unsigned char>& buf, int bitSize):
		m_bits(buf, bitSize)
	{

	}

	void Create(int bitSize = 0)
	{
		m_bits.Create(bitSize);
	}

	void Attach(vector<unsigned char>& buf, int bitSize)
	{
		m_bits.Attach(buf, bitSize);
	}

	void AddField(const char * fieldName, FIELD_TYPE fieldType, int fieldBitSize, int offset = -1 )
	{
		if (offset == -1)
			offset = currentFieldOffset;

		m_fieldMap.insert(FieldMap::value_type(fieldName, Field(fieldType, offset, fieldBitSize)));

		currentFieldOffset = offset + fieldBitSize;
	}

	bool GetField(const char * fieldName, FIELD_TYPE * fieldType, int * fieldSize, int * fieldOffset) const
	{
		FieldMap::const_iterator iter = m_fieldMap.find(fieldName);
		
		if (iter == m_fieldMap.end())
			return false;

		*fieldType = iter->second.m_fieldType;
		*fieldSize = iter->second.m_size;
		*fieldOffset = iter->second.m_offset;

		return true;
	}

	bool EnumFields(void **enumHandle, const char **fieldName, FIELD_TYPE * fieldType, int * fieldSize, int * offset)
	{
		FieldMap::iterator * pIter;
		static string name;

		if (*enumHandle == NULL)
		{
			pIter = new FieldMap::iterator(m_fieldMap.begin());
			if (!pIter)
				return false;
		}
		else
			pIter = (FieldMap::iterator *)(*enumHandle);

		if (*pIter == m_fieldMap.end())
		{
			delete pIter;
			return false;
		}

		name = (*pIter)->first;
		*fieldName = name.c_str();

		*fieldType = (*pIter)->second.m_fieldType;
		*fieldSize = (*pIter)->second.m_size;
		*offset = (*pIter)->second.m_offset;

		pIter->operator ++();  // *Iter++ does not work. Think why :)

		*enumHandle = pIter;

		return true;
	}

	void Set(const char * fieldName, const void * buf, unsigned len)
	{
		int offset = 0, size = m_bits.GetSize();

		if (fieldName)
		{
			FieldMap::iterator iter;
			iter = m_fieldMap.find(fieldName);
			if (iter == m_fieldMap.end())
				throw new LicensingException(STATUS_FIELD_NOT_FOUND);

			offset = iter->second.m_offset;
			size = iter->second.m_size;
		}

		m_bits.Seek(offset);

		int bitlen = len << 3;

		if (bitlen >= size)
			m_bits.Write(buf, size);
		else
		{
			int bits = size - bitlen;
			unsigned char * zeroes = (unsigned char *)_alloca((bits + 7) >> 3);
			memset(zeroes, 0, (bits + 7) >> 3);
			m_bits.Write(buf, bitlen);
			m_bits.Write(zeroes, bits);
		}
	}

	void Set(const char * fieldName, const char * data)
	{
		Set(fieldName, data, (unsigned)strlen(data));
	}

	void Set(const char * fieldName, int data)
	{
#ifndef LICENSING_BIG_ENDIAN
		Set(fieldName, &data, sizeof(data));
#else
		unsigned char buf[sizeof(int)] = { 0, 0, 0, 0 };

		buf[0] = (unsigned char)(data & 0xFF);
		buf[1] = (unsigned char)((data >> 8) & 0xFF);
		buf[2] = (unsigned char)((data >> 16) & 0xFF);
		buf[3] = (unsigned char)((data >> 24) & 0xFF);

		Set(fieldName, buf, sizeof(data));
#endif
	}

	void Set(const char * fieldName, int year, int month, int day)
	{
		FieldMap::iterator iter;
		iter = m_fieldMap.find(fieldName);
		if (iter == m_fieldMap.end() )
			throw new LicensingException(STATUS_FIELD_NOT_FOUND);

		int date;

		switch (iter->second.m_fieldType)
		{
		case FIELD_TYPE_DATE13:
			date = PackDate13(year, month, day);
			break;

		case FIELD_TYPE_DATE14:
			date = PackDate14(year, month, day);
			break;

		case FIELD_TYPE_DATE16:
			date = PackDate16(year, month, day);
			break;

		default:
			throw new LicensingException(STATUS_INVALID_PARAM, "A data value is being set to a non-date field");
		}

		Set(fieldName, date);
	}

	void Get(const char * dataField, void * buf, int * len)
	{
		if (dataField)
		{
			FieldMap::const_iterator iter;

			iter = m_fieldMap.find(dataField);
			if ( iter == m_fieldMap.end() )
				throw new LicensingException(STATUS_FIELD_NOT_FOUND);

			if ( *len < ( iter->second.m_size + 7 ) >> 3 )
				throw new LicensingException(STATUS_BUFFER_TOO_SMALL);

			m_bits.Seek(iter->second.m_offset);
			m_bits.Read(buf, iter->second.m_size);

			*len = (iter->second.m_size + 7) >> 3;
		} else
		{
			int validationDataLen = (m_bits.GetSize() + 7) >> 3;
			
			if (*len < validationDataLen)
			{
				*len = validationDataLen;
				throw new LicensingException(STATUS_BUFFER_TOO_SMALL);
			}

			memcpy(buf, m_bits.GetBuffer(), validationDataLen);
			*len = validationDataLen;
		}
	}

	int GetInt(const char * dataField)
	{
		FieldMap::const_iterator iter;

		iter = m_fieldMap.find(dataField);
		if ( iter == m_fieldMap.end() )
			throw new LicensingException(STATUS_FIELD_NOT_FOUND);

		unsigned char buf[sizeof(int)] = {0, 0, 0, 0};

		if ( sizeof(buf) < ( iter->second.m_size + 7 ) >> 3 )
			throw new LicensingException(STATUS_BUFFER_TOO_SMALL);

		m_bits.Seek(iter->second.m_offset);
		m_bits.Read(buf, iter->second.m_size);

#ifndef LICENSING_BIG_ENDIAN
		return *(int *)buf;
#else
		return ((int)buf[3] << 24) | ((int)buf[2] << 16) | ((int)buf[1] << 8) | (int)buf[0];
#endif
	}

	void GetDate(const char * dataField, int * year, int * month, int * day)
	{
		FieldMap::const_iterator iter;

		iter = m_fieldMap.find(dataField);
		if ( iter == m_fieldMap.end() )
			throw new LicensingException(STATUS_FIELD_NOT_FOUND);

		unsigned short packedDate = (unsigned short)GetInt(dataField);

		switch (iter->second.m_fieldType)
		{
		case FIELD_TYPE_DATE13:
			UnpackDate13(packedDate, year, month, day);
			break;

		case FIELD_TYPE_DATE14:
			UnpackDate14(packedDate, year, month, day);
			break;

		case FIELD_TYPE_DATE16:
			UnpackDate16(packedDate, year, month, day);
			break;

		default:
			throw new LicensingException(STATUS_INVALID_PARAM, "An attempt was made to query a date from a non-date field");
		}
	}

	void * GetBuffer(int * sizeInBits = NULL)
	{
		return m_bits.GetBuffer(sizeInBits);
	}

	BitStream& GetBitStream()
	{
		return m_bits;
	}

	int GetCurrentFieldOffset()
	{
		return currentFieldOffset;
	}

	void ResetFieldOffset()
	{
		currentFieldOffset = 0;
	}

	int GetSize()
	{
		return m_bits.GetSize();
	}

private:
    unsigned short PackDate13(int year, int month, int day)
    {
        if (year < 2012 || year > 2027)
			throw new LicensingException(STATUS_INVALID_PARAM, "Invalid year for a 13-bit date. Allowed values are 2012-2027");

       return (unsigned short)(((month - 1) << 9) | ((day - 1) << 4) | (year - 2012));
    }

    void UnpackDate13(unsigned short packedDate, int * year, int * month, int * day)
    {
        *year = 2012 + (packedDate & 0x0F);
		*month = 1 + (packedDate >> 9);
		*day = 1 + ((packedDate >> 4) & 0x1F);
    }

    unsigned short PackDate14(int year, int month, int day)
    {
        if (year < 2010 || year > 2041)
			throw new LicensingException(STATUS_INVALID_PARAM, "Invalid year for a 14-bit date. Allowed values are 2010-2041");

       return (unsigned short)(((month - 1) << 10) | ((day - 1) << 5) | (year - 2010));
    }

    void UnpackDate14(unsigned short packedDate, int * year, int * month, int * day)
    {
        *year = 2010 + (packedDate & 0x1F);
		*month = 1 + (packedDate >> 10);
		*day = 1 + ((packedDate >> 5) & 0x1F);
    }

    unsigned short PackDate16(int year, int month, int day)
    {
        if (year < 2000 || year > 2127)
			throw new LicensingException(STATUS_INVALID_PARAM, "Invalid year for a 16-bit date. Allowed values are 2010-2127");

        return (unsigned short)(((year - 2000) << 9) | (month << 5) | day);
    }

    void UnpackDate16(unsigned short packedDate, int * year, int * month, int * day)
    {
        *year = 2000 + (packedDate >> 9);
		*month = (packedDate & 0x01FF) >> 5;
		*day = packedDate & 0x001F;
    }

	FieldMap m_fieldMap;
	BitStream m_bits;
	int currentFieldOffset;
};
