#pragma once

#include "volumeshop.h"

#include "Exception.h"

#include <map>
#include <vector>
#include <bitset>
#include <stdlib.h>
#include <time.h>
#include "Color.h"

class LightingTransferFunction
{
public:

	LightingTransferFunction(void) : m_bUpdate(true), m_uSize(512)
	{
		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));
		}

		clear();

		addAmbient(0.25);
		addDiffuse(0.375);
		addSpecular(0.375,32.0f);
	};

	~LightingTransferFunction(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 clear()
	{
		m_vecTable.assign(m_uSize*m_uSize,Color(0.0f,0.0f,0.0f,1.0f));
		m_bUpdate = true;
	};

	void Set(const unsigned int uX, const unsigned int uY, const Color & colElement)
	{
		m_vecTable[uX+uY*m_uSize] = colElement;
		m_bUpdate = true;
	};

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

	void addAmbient(const float fAmbient)
	{
		for (unsigned int j=0; j < m_uSize; j++)
		{
			for (unsigned int i=0; i < m_uSize; i++)
			{
				m_vecTable[i+j*m_uSize].SetNormalizedRed(fAmbient);
			}
		}
		m_bUpdate = true;
	};

	void addDiffuse(const float fDiffuse)
	{
		for (unsigned int j=0; j < m_uSize; j++)
		{
			for (unsigned int i=0; i < m_uSize; i++)
			{
				const float fX = fabs(2.0f*(float(i) / float(m_uSize-1))-1.0f);
				m_vecTable[i+j*m_uSize].SetNormalizedGreen(fDiffuse * fX);
			}
		}
		m_bUpdate = true;
	};

	void addSpecular(const float fSpecular, const float fExponent)
	{
		for (unsigned int j=0; j < m_uSize; j++)
		{
			for (unsigned int i=0; i < m_uSize; i++)
			{
				const float fY = fabs(2.0f*(float(j) / float(m_uSize-1))-1.0f);

				m_vecTable[i+j*m_uSize].SetNormalizedBlue(fSpecular * powf(fY,fExponent));
			}
		}
		m_bUpdate = true;
	};

	void addSilhouette(const float fThickness = 0.33f,const float fOpacity = 2.0f)
	{
		for (unsigned int j=0;j<m_uSize;j++)
		{
			for (unsigned int i=0;i<m_uSize;i++)
			{
				const float fX = 2.0f*(float(i) / float(m_uSize-1))-1.0f;
				const float fY = 2.0f*(float(j) / float(m_uSize-1))-1.0f;

				//const float fLine = 1.0-4.0*(sqrtf((fA + 2.0f*fB)/3.0f) - ((fA + 2.0f*fB) / 3.0f));
				const float fAngle = (-fX + 2.0f*(fY))/3.0f;
				const float fLine = expf(16.0f*fAngle*fAngle*fAngle*fAngle)-1.0f;//(fA + 2.0f*fB)/3.0f   e^(-(x^2)/(1/48))
				
				if (fLine < 1.0f)
				{
					m_vecTable[i+j*m_uSize] = m_vecTable[i+j*m_uSize]*(fLine);
					m_vecTable[i+j*m_uSize].SetNormalizedAlpha(m_vecTable[i+j*m_uSize].GetNormalizedAlpha()*(std::max(0.0f,fLine*fOpacity)));
				}
			}
		}
		m_bUpdate = true;
	};

	void addMetal(const float fA = 1.0f, const float fB = 0.0f)
	{
		//srand(117);
		//srand(333);
		//srand(124);
		srand(1);
		

		std::vector<float> vecValues;
		vecValues.resize(16);

		std::vector<float> vecLine;
		vecLine.resize(m_uSize);

		for (unsigned int i=0;i<vecValues.size();i++)
		{
			const float fR = cosf(0.5f*PI*float(i) + 0.25f*PI*float(rand()) / float(RAND_MAX));
			const float fS = fabs(fB + fR * fA) + (float(rand()) / float(RAND_MAX)-0.5f)*0.5f;
			vecValues[i] = fS;
		}

		for (unsigned int i=0;i<vecValues.size();i++)
		{
			const float & fPrevious = vecValues[(i-1) % vecValues.size()];
			const float & fNext = vecValues[(i+1) % vecValues.size()];
			float & fCurrent = vecValues[i];
			fCurrent = std::max(0.0f,std::min(1.0f,(1.0f / 7.0f) * (fPrevious * 1.0f + fCurrent * 5.0f + fNext * 1.0f)));
		}

		for (unsigned int i=0;i<m_uSize;i++)
		{
			const float fCurrentPosition = float(vecValues.size()*i) / float(m_uSize);
			const unsigned int uCurrentPosition = unsigned int(fCurrentPosition);
			const unsigned int uNextPosition = (uCurrentPosition + 1) % vecValues.size();

			const float & fCurrent = vecValues[uCurrentPosition];
			const float & fNext = vecValues[uNextPosition];
						
			vecLine[i] = fCurrent + (fNext - fCurrent) * (fCurrentPosition - float(uCurrentPosition));
		}

		for (unsigned int j=0;j<m_uSize;j++)
		{
			for (unsigned int i=0;i<m_uSize;i++)
			{
				const float fX = fabs(2.0f*(float(i) / float(m_uSize-1))-1.0f);
				m_vecTable[i+j*m_uSize].SetNormalizedGreen(vecLine[i]);
			}
		}

		m_bUpdate = true;
	};

	void catoonize()
	{
		for (unsigned int j=0;j<m_uSize;j++)
		{
			for (unsigned int i=0;i<m_uSize;i++)
			{
				Color & colColor = m_vecTable[i+j*m_uSize];

				colColor.SetNormalizedRed(float(int(colColor.GetRed() / 50)) / 4.0f);
				colColor.SetNormalizedGreen(float(int(colColor.GetGreen() / 50)) / 4.0f);
				colColor.SetNormalizedBlue(float(int(colColor.GetBlue() / 60)) / 4.0f);
			}
		}
		m_bUpdate = 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));
			}

			if (m_bUpdate)
			{
				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_bUpdate = 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));

			}
		}
	};

private:

	unsigned int m_uTexture;
	unsigned int m_uSize;

	std::vector< Color > m_vecTable;
	bool m_bUpdate;
};
