#pragma once

#include "volumeshop.h"

#include "Environment.h"
#include "Volume.h"
#include "Renderer.h"
#include "Vector.h"
#include "Quaternion.h"
#include "Matrix.h"
#include "Color.h"

class RendererArrow : public Renderer
{
public:

	RendererArrow(Environment & envEnvironment) : m_envEnvironment(envEnvironment), m_uWidth(32), m_uHeight(32)
	{
	};

	virtual ~RendererArrow()
	{
	};

	Environment & GetEnvironment()
	{
		return m_envEnvironment;
	};

	virtual void idle()
	{
	};

	virtual void underlay()
	{
	};

	virtual void display()
	{
		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);

		displayArrow();

		glMatrixMode(GL_MODELVIEW);
		glPopMatrix();

		glMatrixMode(GL_PROJECTION);
		glPopMatrix();

		glPopAttrib();
	};

	virtual void overlay()
	{
	};

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

	virtual void mousePress(const MouseEvent & mouEvent)
	{
	};

	virtual void mouseRelease(const MouseEvent & mouEvent)
	{	
	};

	virtual void mouseMove(const MouseEvent & mouEvent)
	{
	};

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

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

protected:

	const Vector GetArrowPoint(const float fT, const Vector vvPoints[4]) const
	{
		const Vector vecResult = vvPoints[0] * (1.0f-fT)*(1.0f-fT)*(1.0f-fT) + vvPoints[1] * 3.0f*fT*(1.0f-fT)*(1.0f-fT) + vvPoints[2] * 3.0f*fT*fT*(1.0f-fT) + vvPoints[3] * fT*fT*fT;
		return vecResult;
	};

	const Vector GetArrowNormal(const float fT, const Vector vvPoints[4]) const
	{
		const Vector vecResult = (vvPoints[1]-vvPoints[0]) * (1.0f-fT)*(1.0f-fT) + (vvPoints[2]-vvPoints[1]) * 2.0f*(1.0f-fT)*fT + (vvPoints[3]-vvPoints[2]) * fT*fT;
		return Vector(vecResult.GetY(),-vecResult.GetX(),0.0f).GetNormalized();
	};

	const Color GetArrowColor(const float fT, const Color vcColors[4]) const
	{
		const float fR = vcColors[0].GetNormalizedRed() * (1.0f-fT)*(1.0f-fT)*(1.0f-fT) + vcColors[1].GetNormalizedRed() * 3.0f*fT*(1.0f-fT)*(1.0f-fT) + vcColors[2].GetNormalizedRed() * 3.0f*fT*fT*(1.0f-fT) + vcColors[3].GetNormalizedRed() * fT*fT*fT;
		const float fG = vcColors[0].GetNormalizedGreen() * (1.0f-fT)*(1.0f-fT)*(1.0f-fT) + vcColors[1].GetNormalizedGreen() * 3.0f*fT*(1.0f-fT)*(1.0f-fT) + vcColors[2].GetNormalizedGreen() * 3.0f*fT*fT*(1.0f-fT) + vcColors[3].GetNormalizedGreen() * fT*fT*fT;
		const float fB = vcColors[0].GetNormalizedBlue() * (1.0f-fT)*(1.0f-fT)*(1.0f-fT) + vcColors[1].GetNormalizedBlue() * 3.0f*fT*(1.0f-fT)*(1.0f-fT) + vcColors[2].GetNormalizedBlue() * 3.0f*fT*fT*(1.0f-fT) + vcColors[3].GetNormalizedBlue() * fT*fT*fT;
		const float fA = vcColors[0].GetNormalizedAlpha() * (1.0f-fT)*(1.0f-fT)*(1.0f-fT) + vcColors[1].GetNormalizedAlpha() * 3.0f*fT*(1.0f-fT)*(1.0f-fT) + vcColors[2].GetNormalizedAlpha() * 3.0f*fT*fT*(1.0f-fT) + vcColors[3].GetNormalizedAlpha() * fT*fT*fT;
		return Color(fR,fG,fB,fA);
	};

	virtual void displayArrow()
	{
		glPushAttrib(GL_ALL_ATTRIB_BITS);

		glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadIdentity();
		//gluOrtho2D(0.0f,float(m_uWidth),0.0f,float(m_uHeight));
		glOrtho(0.0f,float(m_uWidth),0.0f,float(m_uHeight),-1.0f,1.0f);

		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		glLoadIdentity();

		Volume<DataVoxel>::Octree::Iterator iterDataVolume(GetEnvironment().GetDataVolume().GetOctree());
		const Matrix matDataTransformation = (GetEnvironment().GetProjectionTransformation() * (GetEnvironment().GetViewingTransformation()*GetEnvironment().GetDataTransformation()));
		const Box boxDataBounds = (*iterDataVolume).GetBounds().GetTranslated((*iterDataVolume).GetPosition());

		Volume<SelectionVoxel>::Octree::Iterator iterSelectionVolume(GetEnvironment().GetSelectionVolume().GetOctree());
		const Matrix matSelectionTransformation = (GetEnvironment().GetProjectionTransformation() * (GetEnvironment().GetViewingTransformation()*GetEnvironment().GetSelectionTransformation()));
		const Box boxSelectionBounds = (*iterSelectionVolume).GetBounds().GetTranslated((*iterSelectionVolume).GetPosition());

		const Vector vecT(1.0f,1.0f,0.0f);
		const Vector vecS(0.5f*float(m_uWidth),0.5f*float(m_uHeight),-1.0f);

		//const Vector vecOriginalCenter = (matDataTransformation * Vector(82.0f,121.0f,227.0f) + vecT)*vecS;
		//const Vector vecTransformedCenter = (matSelectionTransformation * Vector(82.0f,121.0f,227.0f) + vecT)*vecS;

		const Vector vecOriginalCenter = (matDataTransformation * ((*iterSelectionVolume).GetBounds().GetTranslated((*iterSelectionVolume).GetPosition()).GetCenter()) + vecT)*vecS;
		const Vector vecTransformedCenter = (matSelectionTransformation * ((*iterSelectionVolume).GetBounds().GetTranslated((*iterSelectionVolume).GetPosition()).GetCenter()) + vecT)*vecS;

		const Vector vecOriginalSelectionMinimum = (boxSelectionBounds.GetMinimum(matDataTransformation) + vecT)*vecS;
		const Vector vecOriginalSelectionMaximum = (boxSelectionBounds.GetMaximum(matDataTransformation) + vecT)*vecS;

		const Vector vecTransformedSelectionMinimum = (boxSelectionBounds.GetMinimum(matSelectionTransformation) + vecT)*vecS;
		const Vector vecTransformedSelectionMaximum = (boxSelectionBounds.GetMaximum(matSelectionTransformation) + vecT)*vecS;

		glDepthFunc(GL_ALWAYS);

		glBlendFuncSeparate(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA,GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
		glEnable(GL_BLEND);


		if ((vecTransformedCenter-vecOriginalCenter).GetMagnitude() > 4.0f)
		{
			glColor4f(1.0f,0.0f,0.0f,1.0f);

			Vector vecStart = vecOriginalCenter;
			Vector vecEnd = vecTransformedCenter;

			const Vector vecDistance = (vecEnd-vecStart);			
			float fDistance = sqrtf(vecDistance.GetX()*vecDistance.GetX() + vecDistance.GetY()*vecDistance.GetY());

			const Vector vecOffset1 = Vector(vecTransformedCenter.GetY()-vecOriginalCenter.GetY(),vecOriginalCenter.GetX()-vecTransformedCenter.GetX(),0.0f);
			Vector vecMid1 = vecStart;
			vecMid1[0] += 768.0f*((vecDistance.GetZ())*vecOffset1.GetX()) / fDistance;
			vecMid1[1] += 768.0f*((vecDistance.GetZ())*vecOffset1.GetY()) / fDistance;
			vecMid1[2] = vecEnd.GetZ();

			const Vector vecOffset2 = Vector(vecOriginalCenter.GetX()-vecTransformedCenter.GetX(),(vecOriginalCenter.GetY()-vecTransformedCenter.GetY()),0.0f);
			Vector vecMid2 = vecMid1;
			vecMid2[0] += 768.0f*((vecDistance.GetZ())*vecOffset2.GetX()) / fDistance;
			vecMid2[1] += 768.0f*((vecDistance.GetZ())*vecOffset2.GetY()) / fDistance;
			vecMid2[2] = vecEnd.GetZ();

			const float fX0 = vecMid1.GetX() - vecStart.GetX();
			const float fY0 = vecMid1.GetY() - vecStart.GetY();

			const float fX1 = vecEnd.GetX() - vecStart.GetX();
			const float fY1 = vecEnd.GetY() - vecStart.GetY();

			const float fProduct = fX0*fY1 - fX1*fY0;

			if (fProduct > std::numeric_limits<float>::epsilon())
			{
				Vector vecTemporary = vecMid1;
				vecMid1 = vecMid2;
				vecMid2 = vecTemporary;
			}
			else if (fProduct == 0.0f)
			{
				if ((fX0*fX0 + fY0*fY0) > (fX1*fX1 + fY1*fY1))
				{
					Vector vecTemporary = vecMid1;
					vecMid1 = vecMid2;
					vecMid2 = vecTemporary;
				}
			}

			const float fThickness = 32.0f;

			Vector vecHeadDirection = Vector(vecEnd.GetX()-vecMid2.GetX(),vecEnd.GetY()-vecMid2.GetY(),0.0f).GetNormalized() * fThickness*1.75;
			Vector vecHeadNormal = Vector(vecEnd.GetY()-vecMid2.GetY(),vecMid2.GetX()-vecEnd.GetX(),0.0f).GetNormalized()  * fThickness*1.75*0.75;

			const Vector vecDirection(vecEnd-vecMid2);
			const float fRadiusX = 0.45*/*sqrtf(2.0f)*/std::max(fabs(vecTransformedCenter.GetX()-vecTransformedSelectionMinimum.GetX()),fabs(vecTransformedCenter.GetX()-vecTransformedSelectionMaximum.GetX()));
			const float fRadiusY = 0.45*/*sqrtf(2.0f)*/std::max(fabs(vecTransformedCenter.GetY()-vecTransformedSelectionMinimum.GetY()),fabs(vecTransformedCenter.GetY()-vecTransformedSelectionMaximum.GetY()));
			const float fU = - 1.0f / sqrtf((vecDirection.GetX()*vecDirection.GetX()) / (fRadiusX*fRadiusX) + (vecDirection.GetY()*vecDirection.GetY()) / (fRadiusY*fRadiusY));
			vecEnd = 	vecTransformedCenter+vecDirection*fU;

			const Vector vvArrowPoints[4] = {
				Vector(vecStart.GetX(),vecStart.GetY(), vecStart.GetZ()),
					Vector(vecMid1.GetX(),vecMid1.GetY(), vecStart.GetZ()),
					Vector(vecMid2.GetX(),vecMid2.GetY(), vecEnd.GetZ()),
					Vector(vecEnd.GetX()-vecHeadDirection.GetX(),vecEnd.GetY()-vecHeadDirection.GetY(), vecEnd.GetZ()),
			};

			const Color vcArrowColors[4] = {
				Color(1.0f,1.0f,1.0f,1.0f),
					Color(1.0f,1.0f,1.0f,1.0f),
					Color(1.0f,1.0f,1.0f,1.0f),
					Color(1.0f,1.0f,0.0f,1.0f),
			};

			const Vector vvArrowHead[3] = {
				Vector(vecEnd.GetX()-vecHeadNormal.GetX()-vecHeadDirection.GetX(),vecEnd.GetY()-vecHeadNormal.GetY()-vecHeadDirection.GetY(),vecEnd.GetZ()),
					Vector(vecEnd.GetX()+vecHeadNormal.GetX()-vecHeadDirection.GetX(),vecEnd.GetY()+vecHeadNormal.GetY()-vecHeadDirection.GetY(),vecEnd.GetZ()),
					Vector(vecEnd.GetX(),vecEnd.GetY(),vecEnd.GetZ())
			};

			//glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
			//glEnable(GL_LINE_SMOOTH);

			glBlendFuncSeparate(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA,GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
			glEnable(GL_BLEND);

			glEnable(GL_POLYGON_OFFSET_LINE);
			glPolygonOffset(1.0, 1.0);

			glDisable(GL_POLYGON_OFFSET_LINE);
			glColor4f(1.0f,1.0f,1.0f,1.0f);


			glBegin(GL_QUAD_STRIP);

			for (unsigned int i = 0; i <= 64; i++) 
			{
				const float fT = float(i) / 64.0f;

				const Color colColor = GetArrowColor(fT,vcArrowColors);
				const Vector vecPoint = GetArrowPoint(fT,vvArrowPoints);
				const Vector vecNormal = GetArrowNormal(fT,vvArrowPoints);

				glColor4ubv(colColor.Get());
				glVertex3fv((vecPoint-vecNormal*fThickness*0.5f*fT).Get());
				glVertex3fv((vecPoint+vecNormal*fThickness*0.5f*fT).Get());
			}

			glEnd();

			glBegin(GL_POLYGON);
			glVertex3fv(vvArrowHead[0].Get());
			glVertex3fv(vvArrowHead[1].Get());
			glVertex3fv(vvArrowHead[2].Get());
			glEnd();

			glColor4f(0.0f,0.0f,0.0f,1.0f);
			glLineWidth(3.0f);

			glBegin(GL_LINE_STRIP);

			for (unsigned int i = 0; i <= 64; i++) 
			{
				const float fT = float(i) / 64.0f;

				const Vector vecPoint = GetArrowPoint(fT,vvArrowPoints);
				const Vector vecNormal = GetArrowNormal(fT,vvArrowPoints);
				glVertex3fv((vecPoint-vecNormal*fThickness*0.5f*fT).Get());
			}

			glEnd();


			glBegin(GL_LINE_STRIP);

			for (unsigned int i = 0; i <= 64; i++) 
			{
				const float fT = float(i) / 64.0f;

				const Vector vecPoint = GetArrowPoint(fT,vvArrowPoints);
				const Vector vecNormal = GetArrowNormal(fT,vvArrowPoints);
				glVertex3fv((vecPoint+vecNormal*fThickness*0.5f*fT).Get());
			}

			glEnd();

			glBegin(GL_LINE_LOOP);
			glVertex3fv(vvArrowHead[0].Get());
			glVertex3fv(vvArrowHead[1].Get());
			glVertex3fv(vvArrowHead[2].Get());
			glEnd();

		}
		glMatrixMode(GL_PROJECTION);
		glPopMatrix();
		glMatrixMode(GL_MODELVIEW);
		glPopMatrix();

		glPopAttrib();
	};

private:
	
	Environment & m_envEnvironment;
	unsigned int m_uWidth;
	unsigned int m_uHeight;
};