#pragma once

#include "volumeshop.h"

#include <map>
#include <vector>
#include <bitset>
#include "Color.h"
#include "Compositor.h"
#include "Attribute.h"
#include "Element.h"
#include "Exception.h"

class ColorTransferFunction
{
public:

	ColorTransferFunction(void) : m_bUpdate(true), m_bUpload(true), m_uSize(512), m_opeOperator(Compositor::OPERATOR_OVER)
	{
		glGenTextures(1,&m_uTexture);
		const GLenum glError = glGetError();

		if (glError != GL_NO_ERROR)
		{
			std::cerr << "Error generating texture (" << gluErrorString(glError) << ")." << std::endl << std::endl;
			throw Exception("Error generating texture (%s).",gluErrorString(glError));
		}

		m_vecValues.resize(2);
		m_vecAxes.resize(2);
		m_vecAxesVisible.resize(2);
		m_vecUpdate.assign(2,true);
	};

	~ColorTransferFunction(void)
	{
		if (m_uTexture)
		{
			glDeleteTextures(1,&m_uTexture);
			const GLenum glError = glGetError();

			if (glError != GL_NO_ERROR)
			{
				std::cerr << "Error deleting texture (" << gluErrorString(glError) << ")." << std::endl << std::endl;
				throw Exception("Error deleting texture (%s).",gluErrorString(glError));
			}
		}
	};

	void SetOperator(const Compositor::Operator & opeOperator)
	{
		m_opeOperator = opeOperator;
		m_bUpdate = true;
		m_bUpload = true;
	};

	const Compositor::Operator & GetOperator() const
	{
		return m_opeOperator;
	};

	void add(unsigned int uIndex, const float fPosition, const Color &colColor)
	{		
		std::map<float,Color> & mapValues = m_vecValues[uIndex];
		mapValues[fPosition] = colColor;
		
		m_vecUpdate[uIndex] = true;
		m_bUpdate = true;
		m_bUpload = true;
	};

	void remove(unsigned int uIndex, const float fPosition)
	{
		if (uIndex < m_vecValues.size())
		{
			std::map<float,Color> & mapValues = m_vecValues[uIndex];
			std::map<float,Color>::iterator i = mapValues.find(fPosition);
			
			if (i != mapValues.end())
			{
				mapValues.erase(i);
				m_vecUpdate[uIndex] = true;
				m_bUpdate = true;
				m_bUpload = true;
			}
		}
	};

	const std::map<float,Color> Get(const unsigned int uIndex) const
	{
		return m_vecValues[uIndex];
	};

	const Color & Get(const unsigned int uX, const unsigned int uY)
	{
		update();
		return m_vecTable[uX+uY*m_uSize];
	};

	void clear(const unsigned int uIndex)
	{
		m_vecValues[uIndex].clear();

		m_bUpdate = true;
		m_bUpload = true;
		m_vecUpdate[uIndex] = true;
	};


	void clear()
	{
		m_vecValues.clear();
		m_vecValues.resize(2);

		m_bUpdate = true;
		m_bUpload = true;
		m_vecUpdate.assign(2,true);
	};

	void bind()
	{
		if (m_uTexture)
		{
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D,m_uTexture);

			const GLenum glError = glGetError();

			if (glError != GL_NO_ERROR)
			{
				std::cerr << "Error binding texture (" << gluErrorString(glError) << ")." << std::endl << std::endl;
				throw Exception("Error binding texture (%s).",gluErrorString(glError));
			}

			update();

			if (m_bUpload)
			{
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
				glTexImage2D(GL_TEXTURE_2D,0, GL_RGBA,m_uSize,m_uSize,0,GL_RGBA,GL_UNSIGNED_BYTE,(void *) &(m_vecTable[0]));
				m_bUpload = false;
			}
		}
	};

	void release()
	{
		if (m_uTexture)
		{
			glDisable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D,0);

			const GLenum glError = glGetError();

			if (glError != GL_NO_ERROR)
			{
				std::cerr << "Error releasing texture (" << gluErrorString(glError) << ")." << std::endl << std::endl;
				throw Exception("Error releasing texture (%s).",gluErrorString(glError));
			}
		}
	};

	const bool IsVisble(unsigned int uIndex, const float fValue)
	{
		return IsVisble(uIndex,fValue,fValue);
	};

	const bool IsVisble(unsigned int uIndex, const float fStart, const float fEnd)
	{
		update();

		const unsigned int uStart = unsigned int(fStart * float(m_uSize-1));
		const unsigned int uEnd = unsigned int(fEnd * float(m_uSize-1));
		const bool bMax = m_vecAxesVisible[uIndex][uEnd];
		const bool bMin = (uStart > 0) ? m_vecAxesVisible[uIndex][uStart-1] : false;

		return (bMax || bMin);
	};

	const bool IsVisble(const float fStartHorizontal, const float fEndHorizontal, const float fStartVertical, const float fEndVertical)
	{
		update();

		const unsigned int uStartHorizontal = unsigned int(fStartHorizontal * float(m_uSize-1));
		const unsigned int uEndHorizontal = unsigned int(fEndHorizontal * float(m_uSize-1));
		const unsigned int uStartVertical = unsigned int(fStartVertical * float(m_uSize-1));
		const unsigned int uEndVertical = unsigned int(fEndVertical * float(m_uSize-1));

		const bool bMaxMax = m_vecTableVisible[uEndHorizontal + uEndVertical*m_uSize];
		const bool bMaxMin = (uStartVertical > 0) ? m_vecTableVisible[uEndHorizontal + (uStartVertical-1)*m_uSize] : false;
		const bool bMinMax = (uStartHorizontal > 0) ? m_vecTableVisible[uStartHorizontal-1 + uEndVertical*m_uSize] : false;
		const bool bMinMin = ((uStartHorizontal > 0) && (uStartVertical > 0)) ? m_vecTableVisible[uStartHorizontal-1 + (uStartVertical-1)*m_uSize] : false;

		return (bMaxMax || bMaxMin || bMinMax || bMinMin); 
	};

	operator Element() const
	{
		Element eleElement("colortransferfunction");

		Element & eleHorizontal = eleElement[ Element("horizontal") ];
		const std::map<float,Color> & mapHorizontal = Get(0);

		for (std::map<float,Color>::const_iterator i=mapHorizontal.begin();i!=mapHorizontal.end();i++)
		{
			Element eleNode = Element("node");
			eleNode[Attribute("value") ] = i->first;
			eleNode[Element("color") ] = i->second;
			eleHorizontal.add(eleNode);
		}

		Element & eleVertical = eleElement[ Element("vertical") ];
		const std::map<float,Color> & mapVertical = Get(1);

		for (std::map<float,Color>::const_iterator i=mapVertical.begin();i!=mapVertical.end();i++)
		{
			Element eleNode = Element("node");
			eleNode[Attribute("value") ] = i->first;
			eleNode[Element("color") ] = i->second;
			eleVertical.add(eleNode);
		}

		return eleElement;		
	};

	ColorTransferFunction & operator=(const Element & eleElement)
	{
		clear(0);

		for (Element::ConstantIterator i = eleElement[ Element("horizontal") ][ Element("node") ]; !i.IsAtEnd(); ++i)
		{
			Color colColor;
			colColor = ((const Element &) i[ Element("color") ]);
			add(0,i[ Attribute("value") ], colColor);
		}

		clear(1);

		for (Element::ConstantIterator i = eleElement[ Element("vertical") ][ Element("node") ]; !i.IsAtEnd(); ++i)
		{
			Color colColor;
			colColor = ((const Element &) i[ Element("color") ]);
			add(1,i[ Attribute("value") ], colColor);
		}

		return *this;
	};

protected:

	void update()
	{
		if (m_bUpdate)
		{
			update(0);
			update(1);

			m_vecTable.resize(m_uSize*m_uSize);

			for (unsigned int j=0;j<m_uSize;j++)
			{
				const Color & colB = m_vecAxes[1][j];

				for (unsigned int i=0;i<m_uSize;i++)
				{
					const Color & colA = m_vecAxes[0][i];
					Color colResult = Compositor(m_opeOperator,colA,colB);

					const unsigned int uIndex = i + m_uSize*j;
					m_vecTable[uIndex] = colResult;					
				}
			}

			m_vecTableVisible.resize(m_uSize*m_uSize);
			std::vector<bool> vecLine(m_uSize);

			m_vecTableVisible[0] = (m_vecTable[0].GetAlpha() > 0);

			for (unsigned int i=1;i<m_uSize;i++)
				m_vecTableVisible[i] = m_vecTableVisible[i-1] || (m_vecTable[i].GetAlpha() > 0);

			for  (unsigned int j=1;j<m_uSize;j++)
			{
				vecLine[0] = (m_vecTable[j*m_uSize].GetAlpha() > 0);
				m_vecTableVisible[j*m_uSize] = m_vecTableVisible[(j-1)*m_uSize] || vecLine[0];

				for (unsigned int i=1;i<m_uSize;i++)
				{
					vecLine[i] = vecLine[i-1] || (m_vecTable[i + j*m_uSize].GetAlpha() > 0);
					m_vecTableVisible[i + j*m_uSize] = m_vecTableVisible[i + (j-1)*m_uSize] || vecLine[i];
				}
			}

			// This is not very clean
			for (unsigned int j=0;j<m_uSize;j++)
				m_vecTable[j*m_uSize] = m_vecAxes[1][j];

			for (unsigned int i=0;i<m_uSize;i++)
				m_vecTable[i] = m_vecAxes[0][i];

/*
			for (unsigned int j=0;j<m_uSize;j++)
				for (unsigned int i=0;i<m_uSize;i++)
					if (m_vecTableVisible[i+j*m_uSize])
						m_vecTable[i+j*m_uSize] = Color(255,0,0);
					else
						m_vecTable[i+j*m_uSize] = Color(0,255,0);
*/
			m_vecTable[0] = Color(0,0,0,0);

			m_bUpdate = false;
		}
	};

	void update(unsigned int uIndex)
	{
		if (m_vecUpdate[uIndex])
		{
			std::map<float,Color> & mapValues = m_vecValues[uIndex];
			std::vector<Color> & vecAxis = m_vecAxes[uIndex];
			vecAxis.resize(m_uSize);

			std::map<float,Color>::iterator i;
			std::map<float,Color>::iterator j;

			for (i = mapValues.begin(); i != mapValues.end();)
			{
				j = i;
				j++;

				if (j != mapValues.end())
				{
					const float &fStartCoordinate = (*i).first*float(m_uSize-1);
					const float &fEndCoordinate = (*j).first*float(m_uSize-1);

					const Color &colStartValue = (*i).second;
					const Color &colEndValue = (*j).second;

					const float fD = 1.0f / (fEndCoordinate - fStartCoordinate);
					float fT = 0.0f;

					for (float fCoordinate = fStartCoordinate; fCoordinate < fEndCoordinate; fCoordinate++)
					{
						const Color colValue = colStartValue * (1.0f - fT) + colEndValue * fT;
						vecAxis[unsigned int(fCoordinate)] = colValue;
						fT += fD;
					}

					vecAxis[unsigned int(fEndCoordinate)] = colEndValue;
				}

				i = j;
			}

			std::vector<bool> & vecAxisVisible = m_vecAxesVisible[uIndex];
			vecAxisVisible.resize(m_uSize);

			vecAxisVisible[0] = (vecAxis[0].GetAlpha() > 0);

			for (unsigned int i=1; i < m_uSize; i++)
				vecAxisVisible[i] = vecAxisVisible[i-1] || (vecAxis[i].GetAlpha() > 0);

			m_vecUpdate[uIndex] = false;
		}
	};

private:

	unsigned int m_uTexture;
	unsigned int m_uSize;

	Compositor::Operator m_opeOperator;

	std::vector< std::map<float,Color> > m_vecValues;
	
	std::vector< std::vector<Color> > m_vecAxes;
	std::vector<Color> m_vecTable;

	std::vector< std::vector<bool> > m_vecAxesVisible;
	std::vector<bool> m_vecTableVisible;

	std::vector<bool> m_vecUpdate;
	bool m_bUpdate;
	bool m_bUpload;
};
