#pragma once

#include "volumeshop.h"

#include "Environment.h"
#include "Interactor.h"
#include "Vector.h"
#include "Timer.h"
#include "Matrix.h"
#include "Gaussian.h"
#include <float.h>

class InteractorSelectionPainter : public Interactor
{
public:

	InteractorSelectionPainter(Environment & envEnvironment) : m_envEnvironment(envEnvironment), m_bTracking(false), m_eMode(NONE)
	{
	};

	virtual ~InteractorSelectionPainter()
	{
	};

	Environment & GetEnvironment()
	{
		return m_envEnvironment;
	};

	virtual void idle()
	{
	};

	virtual void underlay()
	{
	}

	virtual void display()
	{
		const float fElastisity = -1000.0f;
		const float fDifference = m_timTimer;
		m_timTimer.start();

		const float fDecay = expf(fDifference * fElastisity);
/*
		glEnable(GL_SCISSOR_TEST);

		const unsigned int uX = unsigned int((0.5f*(1.0f+m_vecLastPosition.GetX())-0.03125)*float(m_uWidth));
		const unsigned int uY =unsigned int((0.5f*(1.0f+m_vecLastPosition.GetY())-0.03125)*float(m_uHeight));
		const unsigned int uWidth = unsigned int(0.0625f*float(m_uWidth));
		const unsigned int uHeight = unsigned int(0.0625f*float(m_uHeight));

		glScissor(uX,uY,uWidth,uHeight);
*/
	};

	virtual void overlay()
	{
		glPushAttrib(GL_ALL_ATTRIB_BITS);

		glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glMultMatrixf(GetEnvironment().GetProjectionTransformation().Get());

		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		glMultMatrixf(GetEnvironment().GetViewingTransformation().Get());

		glDepthMask(GL_TRUE);
		glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE);
		glEnable(GL_DEPTH_TEST);
		glDepthFunc(GL_LESS);

		glPushMatrix();
		glMultMatrixf(GetEnvironment().GetDataTransformation().Get());
		Volume<DataVoxel>::Octree::Iterator iterDataVolume(GetEnvironment().GetDataVolume().GetOctree());

		glDepthFunc(GL_ALWAYS);

		glEnable(GL_POINT_SMOOTH);
		glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);

		glEnable(GL_BLEND);				
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		
		glPointSize(16.0f);

		if (m_eMode == PAINTING)
		{
			glColor4f(1.0f,0.0f,0.0f,0.25f);
			glBegin(GL_POINTS);
			glVertex3fv(m_vecBrushPosition.Get());
			glEnd();
		}
		else if (m_eMode == ERASING)
		{
			glColor4f(0.0f,1.0f,0.0f,0.25f);
			glBegin(GL_POINTS);
			glVertex3fv(m_vecBrushPosition.Get());
			glEnd();
		}

		glPopMatrix();


		glMatrixMode(GL_MODELVIEW);
		glPopMatrix();

		glMatrixMode(GL_PROJECTION);
		glPopMatrix();

		glPopAttrib();
	};

	virtual void reshape(const unsigned int uWidth, const unsigned int uHeight)
	{
		m_uWidth = uWidth;
		m_uHeight = uHeight;
	};

	virtual void mousePress(const MouseEvent & mouEvent)
	{
		m_bTracking = true;
		m_vecLastPosition = mouEvent.GetPosition();
		m_vecStartPosition = m_vecLastPosition;

		if (mouEvent.GetButton() == MouseEvent::BUTTON_LEFT)
		{
			paint(m_vecStartPosition);
			m_eMode = PAINTING;
		}
		/*
		if (mouEvent.GetButton() == MouseEvent::BUTTON_RIGHT)
		{
			for (unsigned int k=0;k<GetEnvironment().GetSelectionVolume().GetSizeZ()/2;k++)
			{
				for (unsigned int j=0;j<GetEnvironment().GetSelectionVolume().GetSizeY();j++)
					for (unsigned int i=0;i<GetEnvironment().GetSelectionVolume().GetSizeX();i++)
						GetEnvironment().GetSelectionVolume().Set(i,j,k,SelectionVoxel(1.0f));

			}

			GetEnvironment().update();
		}
		*/
		else if (mouEvent.GetButton() == MouseEvent::BUTTON_MIDDLE)
		{
			paint(m_vecStartPosition,0.125f);
			GetEnvironment().update();
		}
		else if (mouEvent.GetButton() == MouseEvent::BUTTON_RIGHT)
		{
			erase(m_vecStartPosition);
			m_eMode = ERASING;
		}

	};

	virtual void mouseRelease(const MouseEvent & mouEvent)
	{
		m_bTracking = false;
		m_eMode = NONE;
		GetEnvironment().SetQuality(Environment::QUALITY_HIGH);
		GetEnvironment().update();
	};


	virtual void mouseMove(const MouseEvent & mouEvent)
	{
		if (m_bTracking)
		{
			if (m_eMode == PAINTING)
				paint(mouEvent.GetPosition());
			else if (m_eMode == ERASING)
				erase(mouEvent.GetPosition());

			m_vecLastPosition = mouEvent.GetPosition();
		}
	};

	virtual void keyboardPress(const KeyboardEvent & keyEvent) 
	{
	};

	virtual void keyboardRelease(const KeyboardEvent & keyEvent)
	{
	};

protected:

	const Quaternion GetRotation(const Vector & vecCurrentPosition, const Vector & vecLastPosition, const Vector & vecStartPosition)
	{
		Vector vecCurrent(vecCurrentPosition - vecStartPosition);
		Vector vecLast(vecLastPosition - vecStartPosition);

		const float fCurrent = sqrtf(vecCurrent.GetX() * vecCurrent.GetX() + vecCurrent.GetY() * vecCurrent.GetY());
		vecCurrent.SetZ(cosf((PI / 2.0f) * ((fCurrent < 1.0f) ? fCurrent : 1.0f)));
		vecCurrent.normalize();

		const float fLast = sqrtf(vecLast.GetX() * vecLast.GetX() + vecLast.GetY() * vecLast.GetY());
		vecLast.SetZ(cosf((PI / 2.0f) * ((fLast < 1.0f) ? fLast : 1.0f)));
		vecLast.normalize();

		const Vector vecAxis = GetEnvironment().GetViewingUserTransformation().GetInverse()*vecLast.GetCross(vecCurrent);
		const float fAngle = 90.0f * (vecCurrent - vecLast).GetMagnitude();

		return Quaternion(RADIANS(fAngle),vecAxis);
	};

	const Vector GetScale(const Vector & vecCurrentPosition, const Vector & vecLastPosition, const Vector & vecStartPosition)
	{
		const float fScale = 0.5f * (vecCurrentPosition - vecLastPosition).GetY();
		return Vector(fScale,fScale,fScale);
	};

	const Vector GetTranslation(const Vector & vecCurrentPosition, const Vector & vecLastPosition, const Vector & vecStartPosition)
	{
		const Vector vecPan((vecCurrentPosition - vecLastPosition).GetX(),(vecCurrentPosition - vecLastPosition).GetY(),0.0f);
		return GetEnvironment().GetViewingUserTransformation().GetInverse()*vecPan;
	};

protected:

	void paint(const Vector & vecPosition, const float fIntensity = 1.0f)
	{
		static const Gaussian gauBrush(21);

		const Vector vecStart(vecPosition.GetX(),vecPosition.GetY(),-1.0f);
		const Vector vecEnd(vecPosition.GetX(),vecPosition.GetY(),1.0f);
		const Matrix matTransformation = (GetEnvironment().GetProjectionTransformation() * (GetEnvironment().GetViewingTransformation() * GetEnvironment().GetDataTransformation())).GetInverse();
		const Matrix matInverseTransformation = (GetEnvironment().GetProjectionTransformation() * (GetEnvironment().GetViewingTransformation()*GetEnvironment().GetDataTransformation()));

		Vector vecRayStart = matTransformation * vecStart;
		Vector vecRayEnd = matTransformation * vecEnd;
		Vector vecRayDirection = (vecRayEnd-vecRayStart).GetNormalized();		

		std::cout << "S: " << vecRayStart << std::endl;
		std::cout << "E: " << vecRayEnd << std::endl;
		std::cout << "D: " << vecRayDirection << std::endl << std::endl;

		for (Volume<DataVoxel>::RayIterator i(GetEnvironment().GetDataVolume(),vecRayStart,vecRayDirection);!i.IsAtEnd();++i)
		{
			if (IsNotSelected(i.GetX(),i.GetY(),i.GetZ()))
			{
				if (IsVisible(i.GetX(),i.GetY(),i.GetZ()))
				{
					m_vecBrushPosition = i.GetPosition();
					const int iHalf = int(gauBrush.GetSize()) / 2 - 1;

					for (int iZ = -iHalf; iZ <= iHalf; iZ++)
					{
						for (int iY = -iHalf; iY <= iHalf; iY++)
						{
							for (int iX = -iHalf; iX <= iHalf; iX++)
							{
								const int iVolumeX = (int(i.GetX())+iX);
								const int iVolumeY = (int(i.GetY())+iY);
								const int iVolumeZ = (int(i.GetZ())+iZ);

								//if (IsVisible(iVolumeX,iVolumeY,iVolumeZ))
								{
									const float fBrushValue = gauBrush.Get(iX,iY,iZ);
									const SelectionVoxel selOldValue = GetEnvironment().GetSelectionVolume().Get(unsigned int(iVolumeX),unsigned int(iVolumeY),unsigned int(iVolumeZ));
									const float fNewValue = std::max(0.0f,std::min(1.0f,selOldValue.Get() + fIntensity*fBrushValue));
									GetEnvironment().GetSelectionVolume().Set(unsigned int(iVolumeX),unsigned int(iVolumeY),unsigned int(iVolumeZ),SelectionVoxel(fNewValue));
								}
							}
						}
					}

					GetEnvironment().SetQuality(Environment::QUALITY_LOW);
					GetEnvironment().update();
/*
					Vector vecCorners[8];

					vecCorners[0] = Vector(float(i.GetX()) - 1.0f - float(gauBrush.GetSize())*0.5f,float(i.GetY()) - 1.0f - float(gauBrush.GetSize())*0.5f,float(i.GetZ()) - 1.0f - float(gauBrush.GetSize())*0.5f);
					vecCorners[1] = Vector(float(i.GetX()) + 1.0f + float(gauBrush.GetSize())*0.5f,float(i.GetY()) - 1.0f - float(gauBrush.GetSize())*0.5f,float(i.GetZ()) - 1.0f - float(gauBrush.GetSize())*0.5f);
					vecCorners[2] = Vector(float(i.GetX()) - 1.0f - float(gauBrush.GetSize())*0.5f,float(i.GetY()) + 1.0f + float(gauBrush.GetSize())*0.5f,float(i.GetZ()) - 1.0f - float(gauBrush.GetSize())*0.5f);
					vecCorners[3] = Vector(float(i.GetX()) + 1.0f + float(gauBrush.GetSize())*0.5f,float(i.GetY()) + 1.0f + float(gauBrush.GetSize())*0.5f,float(i.GetZ()) - 1.0f - float(gauBrush.GetSize())*0.5f);
					vecCorners[4] = Vector(float(i.GetX()) - 1.0f - float(gauBrush.GetSize())*0.5f,float(i.GetY()) - 1.0f - float(gauBrush.GetSize())*0.5f,float(i.GetZ()) + 1.0f + float(gauBrush.GetSize())*0.5f);
					vecCorners[5] = Vector(float(i.GetX()) + 1.0f + float(gauBrush.GetSize())*0.5f,float(i.GetY()) - 1.0f - float(gauBrush.GetSize())*0.5f,float(i.GetZ()) + 1.0f + float(gauBrush.GetSize())*0.5f);
					vecCorners[6] = Vector(float(i.GetX()) - 1.0f - float(gauBrush.GetSize())*0.5f,float(i.GetY()) + 1.0f + float(gauBrush.GetSize())*0.5f,float(i.GetZ()) + 1.0f + float(gauBrush.GetSize())*0.5f);
					vecCorners[7] = Vector(float(i.GetX()) + 1.0f + float(gauBrush.GetSize())*0.5f,float(i.GetY()) + 1.0f + float(gauBrush.GetSize())*0.5f,float(i.GetZ()) + 1.0f + float(gauBrush.GetSize())*0.5f);

					Vector vecMinimum = matInverseTransformation * vecCorners[0];
					Vector vecMaximum = matInverseTransformation * vecCorners[0];

					for (unsigned int j=0;j<8;j++)
					{
						const Vector vecTransformed = matInverseTransformation * vecCorners[j];

						vecMinimum.SetX(std::min(vecMinimum.GetX(), vecTransformed.GetX()));
						vecMinimum.SetY(std::min(vecMinimum.GetY(), vecTransformed.GetY()));
						vecMinimum.SetZ(std::min(vecMinimum.GetZ(), vecTransformed.GetZ()));

						vecMaximum.SetX(std::max(vecMaximum.GetX(), vecTransformed.GetX()));
						vecMaximum.SetY(std::max(vecMaximum.GetY(), vecTransformed.GetY()));
						vecMaximum.SetZ(std::max(vecMaximum.GetZ(), vecTransformed.GetZ()));
					}

					GetEnvironment().update(vecMinimum.GetX(),vecMinimum.GetY(),vecMaximum.GetX(),vecMaximum.GetY());
*/
					break;
				}
			}
		}
	};

	void erase(const Vector & vecPosition)
	{
		static const Gaussian gauBrush(21);

		const Vector vecStart(vecPosition.GetX(),vecPosition.GetY(),0.0f);
		const Vector vecEnd(vecPosition.GetX(),vecPosition.GetY(),1.0f);
		const Matrix matTransformation = (GetEnvironment().GetProjectionTransformation() * (GetEnvironment().GetViewingTransformation()*GetEnvironment().GetDataTransformation())).GetInverse();
		const Matrix matInverseTransformation = (GetEnvironment().GetProjectionTransformation() * (GetEnvironment().GetViewingTransformation()*GetEnvironment().GetDataTransformation()));

		Vector vecRayStart = matTransformation * vecStart;
		Vector vecRayEnd = matTransformation * vecEnd;
		Vector vecRayDirection = (vecRayEnd-vecRayStart).GetNormalized();		

		for (Volume<DataVoxel>::RayIterator i(GetEnvironment().GetDataVolume(),vecRayStart,vecRayDirection);!i.IsAtEnd();++i)
		{
		//	if (GetEnvironment().GetTransferFunction().IsVisble(0,(*i).GetValue()))
			{
				if (GetEnvironment().GetSelectionVolume().Get(i.GetX(),i.GetY(),i.GetZ()) > 0.0)
				{
					m_vecBrushPosition = i.GetPosition();
//					std::cout << i.GetX() << "," << i.GetY() << "," << i.GetZ() << std::endl;
					const int iHalf = int(gauBrush.GetSize()) / 2;

					for (int iZ = -iHalf; iZ < iHalf; iZ++)
					{
						for (int iY = -iHalf; iY < iHalf; iY++)
						{
							for (int iX = -iHalf; iX < iHalf; iX++)
							{
								const int iVolumeX = int(i.GetX())+iX;
								const int iVolumeY = int(i.GetY())+iY;
								const int iVolumeZ = int(i.GetZ())+iZ;

								if (iVolumeX >= 0 && iVolumeX < int(GetEnvironment().GetSelectionVolume().GetSizeX()) && iVolumeY >= 0 && iVolumeY < int(GetEnvironment().GetSelectionVolume().GetSizeY()) && iVolumeZ >= 0 && iVolumeZ < int(GetEnvironment().GetSelectionVolume().GetSizeZ()))
								{
									const float fBrushValue = gauBrush.Get(iX,iY,iZ);
									const SelectionVoxel selOldValue = GetEnvironment().GetSelectionVolume().Get(unsigned int(iVolumeX),unsigned int(iVolumeY),unsigned int(iVolumeZ));
									const SelectionVoxel selNewValue = std::max(0.0f,std::min(1.0f,selOldValue.Get() - fBrushValue));
									GetEnvironment().GetSelectionVolume().Set(unsigned int(iVolumeX),unsigned int(iVolumeY),unsigned int(iVolumeZ),selNewValue);
								}
							}
						}
					}

					GetEnvironment().SetQuality(Environment::QUALITY_LOW);
					GetEnvironment().update();
/*
					Vector vecCorners[8];

					vecCorners[0] = Vector(float(i.GetX()) - 1.0f - float(gauBrush.GetSize())*0.5f,float(i.GetY()) - 1.0f - float(gauBrush.GetSize())*0.5f,float(i.GetZ()) - 1.0f - float(gauBrush.GetSize())*0.5f);
					vecCorners[1] = Vector(float(i.GetX()) + 1.0f + float(gauBrush.GetSize())*0.5f,float(i.GetY()) - 1.0f - float(gauBrush.GetSize())*0.5f,float(i.GetZ()) - 1.0f - float(gauBrush.GetSize())*0.5f);
					vecCorners[2] = Vector(float(i.GetX()) - 1.0f - float(gauBrush.GetSize())*0.5f,float(i.GetY()) + 1.0f + float(gauBrush.GetSize())*0.5f,float(i.GetZ()) - 1.0f - float(gauBrush.GetSize())*0.5f);
					vecCorners[3] = Vector(float(i.GetX()) + 1.0f + float(gauBrush.GetSize())*0.5f,float(i.GetY()) + 1.0f + float(gauBrush.GetSize())*0.5f,float(i.GetZ()) - 1.0f - float(gauBrush.GetSize())*0.5f);
					vecCorners[4] = Vector(float(i.GetX()) - 1.0f - float(gauBrush.GetSize())*0.5f,float(i.GetY()) - 1.0f - float(gauBrush.GetSize())*0.5f,float(i.GetZ()) + 1.0f + float(gauBrush.GetSize())*0.5f);
					vecCorners[5] = Vector(float(i.GetX()) + 1.0f + float(gauBrush.GetSize())*0.5f,float(i.GetY()) - 1.0f - float(gauBrush.GetSize())*0.5f,float(i.GetZ()) + 1.0f + float(gauBrush.GetSize())*0.5f);
					vecCorners[6] = Vector(float(i.GetX()) - 1.0f - float(gauBrush.GetSize())*0.5f,float(i.GetY()) + 1.0f + float(gauBrush.GetSize())*0.5f,float(i.GetZ()) + 1.0f + float(gauBrush.GetSize())*0.5f);
					vecCorners[7] = Vector(float(i.GetX()) + 1.0f + float(gauBrush.GetSize())*0.5f,float(i.GetY()) + 1.0f + float(gauBrush.GetSize())*0.5f,float(i.GetZ()) + 1.0f + float(gauBrush.GetSize())*0.5f);

					Vector vecMinimum = matInverseTransformation * vecCorners[0];
					Vector vecMaximum = matInverseTransformation * vecCorners[0];

					for (unsigned int i=1;i<8;i++)
					{
						const Vector vecTransformed = matInverseTransformation * vecCorners[i];

						vecMinimum.SetX(std::min(vecMinimum.GetX(), vecTransformed.GetX()));
						vecMinimum.SetY(std::min(vecMinimum.GetY(), vecTransformed.GetY()));
						vecMinimum.SetZ(std::min(vecMinimum.GetZ(), vecTransformed.GetZ()));

						vecMaximum.SetX(std::max(vecMaximum.GetX(), vecTransformed.GetX()));
						vecMaximum.SetY(std::max(vecMaximum.GetY(), vecTransformed.GetY()));
						vecMaximum.SetZ(std::max(vecMaximum.GetZ(), vecTransformed.GetZ()));
					}

					GetEnvironment().update(vecMinimum.GetX(),vecMinimum.GetY(),vecMaximum.GetX(),vecMaximum.GetY());					
					break;*/
				}
			}
		}
	};

	const bool IsVisible(const int iVolumeX, const int iVolumeY, const int iVolumeZ)
	{
		const DataVoxel datCenter = GetEnvironment().GetDataVolume().Get(iVolumeX,iVolumeY,iVolumeZ);

		if (GetEnvironment().GetBackgroundSelectionColorTransferFunction().IsVisble(0,datCenter.GetValue()))
			return true;
		
		DataVoxel datMinimum = datCenter;
		DataVoxel datMaximum = datCenter;

		for (int k=-1;k<1;k++)
		{
			for (int j=-1;j<1;j++)
			{
				for (int i=-1;i<1;i++)
				{
					const int iX = iVolumeX+i;
					const int iY = iVolumeY+j;
					const int iZ = iVolumeZ+k;

					if (iX >= 0 && iX < int(GetEnvironment().GetDataVolume().GetSizeX()) && iY >= 0 && iY < int(GetEnvironment().GetDataVolume().GetSizeY()) && iZ >= 0 && iZ < int(GetEnvironment().GetDataVolume().GetSizeZ()))
					{
						const DataVoxel datValue = GetEnvironment().GetDataVolume().Get(iX,iY,iZ);
						
						if (GetEnvironment().GetBackgroundSelectionColorTransferFunction().IsVisble(0,datValue.GetValue()))
							return true;
		
						datMinimum = std::min(datMinimum,datValue);
						datMaximum = std::max(datMaximum,datValue);
					}
				}
			}
		}

		return GetEnvironment().GetBackgroundSelectionColorTransferFunction().IsVisble(0,datMinimum.GetValue(),datMaximum.GetValue());
	};

	const bool IsNotSelected(const int iVolumeX, const int iVolumeY, const int iVolumeZ)
	{
		const SelectionVoxel selCenter = GetEnvironment().GetSelectionVolume().Get(iVolumeX,iVolumeY,iVolumeZ);

		if (selCenter.Get() < 1.0f)
			return true;

		SelectionVoxel selMinimum = selCenter;

		for (int k=-1;k<1;k++)
		{
			for (int j=-1;j<1;j++)
			{
				for (int i=-1;i<1;i++)
				{
					const int iX = iVolumeX+i;
					const int iY = iVolumeY+j;
					const int iZ = iVolumeZ+k;

					if (iX >= 0 && iX < int(GetEnvironment().GetSelectionVolume().GetSizeX()) && iY >= 0 && iY < int(GetEnvironment().GetSelectionVolume().GetSizeY()) && iZ >= 0 && iZ < int(GetEnvironment().GetSelectionVolume().GetSizeZ()))
					{
						const SelectionVoxel selValue = GetEnvironment().GetSelectionVolume().Get(iX,iY,iZ);

						if (selValue.Get() < 1.0f)
							return true;

						selMinimum = std::min(selMinimum,selValue);
					}
				}
			}
		}

		return (selMinimum.Get() < 1.0f);
	};

private:

	Environment & m_envEnvironment;
	
	Timer m_timTimer;
	Vector m_vecStartPosition;
	Vector m_vecLastPosition;
	Vector m_vecBrushPosition;
	bool m_bTracking;
	
	enum { NONE, PAINTING, ERASING} m_eMode;
	
	unsigned int m_uWidth;
	unsigned int m_uHeight;
};