#pragma once

#include "volumeshop.h"

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

class InteractorDataTrackball : public Interactor
{
public:

	InteractorDataTrackball(Environment & envEnvironment) : m_envEnvironment(envEnvironment), m_bTracking(false), m_quaRotation(Vector(0.0f,0.0f,0.0f),0.0f), m_vecScale(0.0f,0.0f,0.0f), m_vecTranslation(0.0f,0.0f,0.0f), m_vecPin(0.0f,0.0f,0.0f), m_eMode(NONE)
	{
	};

	virtual ~InteractorDataTrackball()
	{
	};

	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 = 0.0f;//expf(fDifference * fElastisity);

		Volume<DataVoxel>::Octree::Iterator iterDataVolume(GetEnvironment().GetDataVolume().GetOctree());
		Vector vecCenter = (*iterDataVolume).GetBounds().GetTranslated((*iterDataVolume).GetPosition()).GetCenter();
		vecCenter = GetEnvironment().GetDataTransformation()*vecCenter;

		Matrix matDataTransformation;
		matDataTransformation.translate(vecCenter);
		matDataTransformation.rotate(m_quaRotation);
		matDataTransformation.scale(Vector(1.0f,1.0f,1.0f)+m_vecScale);
		matDataTransformation.translate(-vecCenter);
		matDataTransformation.translate(m_vecTranslation);	
		GetEnvironment().SetDataUserTransformation(matDataTransformation * GetEnvironment().GetDataUserTransformation());	
		//GetEnvironment().SetDataPinPosition(GetEnvironment().GetDataPinPosition() + m_vecPin);

		m_quaRotation *= fDecay;
		m_vecScale *= fDecay;
		m_vecTranslation *= fDecay;
		m_vecPin *= fDecay;
	};

	virtual void overlay()
	{
		glPushAttrib(GL_ALL_ATTRIB_BITS);

		glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadIdentity();

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

		glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE);
		glDepthMask(GL_FALSE);
		glDepthFunc(GL_ALWAYS);

		const Matrix matTransformation = GetEnvironment().GetProjectionTransformation() * (GetEnvironment().GetViewingTransformation() * GetEnvironment().GetDataTransformation());
		Volume<DataVoxel>::Octree::Iterator iterDataVolume(GetEnvironment().GetDataVolume().GetOctree());

		const Vector vecDataCenter = (*iterDataVolume).GetBounds().GetTranslated((*iterDataVolume).GetPosition()).GetCenter();
		const Vector vecDataSurface = vecDataCenter + Vector((*iterDataVolume).GetBounds().GetTranslated((*iterDataVolume).GetPosition()).GetRadius(),0.0f,0.0f);

		const Vector vecTransformedCenter = (GetEnvironment().GetViewingTransformation() * GetEnvironment().GetDataTransformation())*vecDataCenter;
		const Vector vecTransformedSurface = (GetEnvironment().GetViewingTransformation() * GetEnvironment().GetDataTransformation())*vecDataSurface;

		const Vector vecProjectedCenter = GetEnvironment().GetProjectionTransformation() * vecTransformedCenter;
		const Vector vecProjectedSurfaceHorizontal = GetEnvironment().GetProjectionTransformation() * (vecTransformedCenter+Vector((vecTransformedSurface-vecTransformedCenter).GetMagnitude(),0.0f,0.0f));
		const Vector vecProjectedSurfaceVertical = GetEnvironment().GetProjectionTransformation() * (vecTransformedCenter+Vector(0.0f,(vecTransformedSurface-vecTransformedCenter).GetMagnitude(),0.0f));

		const Vector vecCenter(vecProjectedCenter);
		const Vector vecRadius((vecProjectedSurfaceHorizontal-vecProjectedCenter).GetMagnitude(),(vecProjectedSurfaceVertical-vecProjectedCenter).GetMagnitude(),1.0f);

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

		glBegin(GL_QUAD_STRIP);

		for(int i=0;i<=360;i++)
		{
			const float fAngle = float(i)/ 57.29577957795135f;	
			const Vector vecStart(vecRadius.GetX()*sinf(fAngle),vecRadius.GetY()*cosf(fAngle),0.0f);
			const Vector vecEnd(1.05f*vecRadius.GetX()*sinf(fAngle),1.05f*vecRadius.GetY()*cosf(fAngle),0.0f);

			glColor4f(0.125f,0.125f,1.0f,0.0f);
			glVertex3fv((vecCenter+vecStart).Get());

			glColor4f(0.125f,0.125f,1.0f,0.5f);
			glVertex3fv((vecCenter+vecEnd).Get());
		}

		for(int i=0;i<=360;i++)
		{
			const float fAngle = float(i)/ 57.29577957795135f;	
			const Vector vecStart(1.05*vecRadius.GetX()*sinf(fAngle),1.05*vecRadius.GetY()*cosf(fAngle),0.0f);
			const Vector vecEnd(1.15*vecRadius.GetX()*sinf(fAngle),1.15*vecRadius.GetY()*cosf(fAngle),0.0f);

			glColor4f(0.125f,0.125f,1.0f,0.66f);
			glVertex3fv((vecCenter+vecStart).Get());

			glColor4f(0.125f,0.125f,1.0f,0.66f);
			glVertex3fv((vecCenter+vecEnd).Get());
		}

		for(int i=0;i<=360;i++)
		{
			const float fAngle = float(i)/ 57.29577957795135f;	
			const Vector vecStart(1.15*vecRadius.GetX()*sinf(fAngle),1.15*vecRadius.GetY()*cosf(fAngle),0.0f);
			const Vector vecEnd(1.2*vecRadius.GetX()*sinf(fAngle),1.2*vecRadius.GetY()*cosf(fAngle),0.0f);

			glColor4f(0.125f,0.125f,1.0f,0.5f);
			glVertex3fv((vecCenter+vecStart).Get());

			glColor4f(0.125f,0.125f,1.0f,0.0f);
			glVertex3fv((vecCenter+vecEnd).Get());
		}

		glEnd();		

		glMatrixMode(GL_MODELVIEW);
		glPopMatrix();

		glMatrixMode(GL_PROJECTION);
		glPopMatrix();

		glPopAttrib();
	};

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

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

		if (mouEvent.GetButton() == MouseEvent::BUTTON_LEFT)
		{
			m_eMode = ROTATING;
		}
		else if (mouEvent.GetButton() == MouseEvent::BUTTON_RIGHT)
		{
			m_eMode = ZOOMING;
		}
		else if (mouEvent.GetButton() == MouseEvent::BUTTON_MIDDLE)// && !GetEnvironment().IsDataPinEnabled())
		{
			m_eMode = PANNING;
		}
		/*
		else if (mouEvent.GetButton() == MouseEvent::BUTTON_MIDDLE && GetEnvironment().IsDataPinEnabled())
		{
			m_eMode = PINNING;
		}
		*/
	};

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


	virtual void mouseMove(const MouseEvent & mouEvent)
	{
		if (m_bTracking)
		{
			const Vector vecCurrentPosition = mouEvent.GetPosition();

			if (m_eMode == ROTATING)
			{
				m_quaRotation = GetRotation(vecCurrentPosition,m_vecLastPosition,m_vecStartPosition);
			}
			else if (m_eMode == ZOOMING)
			{
				m_vecScale = GetScale(vecCurrentPosition,m_vecLastPosition,m_vecStartPosition);
			}
			else if (m_eMode == PANNING)
			{
				m_vecTranslation = GetTranslation(vecCurrentPosition,m_vecLastPosition,m_vecStartPosition);
			}
			else if (m_eMode == PINNING)
			{
				m_vecPin = GetPin(vecCurrentPosition,m_vecLastPosition,m_vecStartPosition);
			}

			m_vecLastPosition = vecCurrentPosition;

			GetEnvironment().SetQuality(Environment::QUALITY_LOW);
			GetEnvironment().update();
		}
	};

	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)
	{
		const Matrix matTransformation = GetEnvironment().GetProjectionTransformation() * (GetEnvironment().GetViewingTransformation() * GetEnvironment().GetDataTransformation());
		Volume<DataVoxel>::Octree::Iterator iterDataVolume(GetEnvironment().GetDataVolume().GetOctree());

		const Vector vecDataCenter = (*iterDataVolume).GetBounds().GetTranslated((*iterDataVolume).GetPosition()).GetCenter();
		const Vector vecDataSurface = vecDataCenter + Vector((*iterDataVolume).GetBounds().GetTranslated((*iterDataVolume).GetPosition()).GetRadius(),0.0f,0.0f);

		const Vector vecTransformedCenter = (GetEnvironment().GetViewingTransformation() * GetEnvironment().GetDataTransformation())*vecDataCenter;
		const Vector vecTransformedSurface = (GetEnvironment().GetViewingTransformation() * GetEnvironment().GetDataTransformation())*vecDataSurface;

		const Vector vecProjectedCenter = GetEnvironment().GetProjectionTransformation() * vecTransformedCenter;
		const Vector vecProjectedSurfaceHorizontal = GetEnvironment().GetProjectionTransformation() * (vecTransformedCenter+Vector((vecTransformedSurface-vecTransformedCenter).GetMagnitude(),0.0f,0.0f));
		const Vector vecProjectedSurfaceVertical = GetEnvironment().GetProjectionTransformation() * (vecTransformedCenter+Vector(0.0f,(vecTransformedSurface-vecTransformedCenter).GetMagnitude(),0.0f));

		const Vector vecCenter(vecProjectedCenter);
		const Vector vecRadius((vecProjectedSurfaceHorizontal-vecProjectedCenter).GetMagnitude(),(vecProjectedSurfaceVertical-vecProjectedCenter).GetMagnitude(),1.0f);

		if (vecRadius.GetX() > 0.0f && vecRadius.GetY() > 0.0f)
		{
			Vector vecCurrent((vecCurrentPosition-vecCenter) / vecRadius);
			Vector vecLast((vecLastPosition-vecCenter) / vecRadius);

			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();

			return Quaternion(GetEnvironment().GetViewingTransformation().GetRotation().GetInverse()*(vecLast.GetCross(vecCurrent)),vecLast.GetDot(vecCurrent));
		}

		return Quaternion();
	};

	const Vector GetScale(const Vector & vecCurrentPosition, const Vector & vecLastPosition, const Vector & vecStartPosition)
	{
		const float fScale = (fabs(vecCurrentPosition.GetX()-vecLastPosition.GetX()) > fabs(vecCurrentPosition.GetY()-vecLastPosition.GetY())) ? vecCurrentPosition.GetX()-vecLastPosition.GetX() : vecCurrentPosition.GetY()-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);
		
		Matrix matTransform = GetEnvironment().GetViewingTransformation();
		matTransform.SetTranslation(Vector(0.0f,0.0f,0.0f));
		matTransform.invert();

		return matTransform*vecPan;
	};

	const Vector GetPin(const Vector & vecCurrentPosition, const Vector & vecLastPosition, const Vector & vecStartPosition)
	{
		return Vector((vecCurrentPosition - vecLastPosition).GetX(),(vecCurrentPosition - vecLastPosition).GetY(),0.0f);
	};

private:

	Environment & m_envEnvironment;
	
	Timer m_timTimer;
	Vector m_vecStartPosition;
	Vector m_vecLastPosition;

	Quaternion m_quaRotation;
	Vector m_vecScale;
	Vector m_vecTranslation;
	Vector m_vecPin;

	bool m_bTracking;
	enum { NONE, ROTATING, ZOOMING, PANNING, PINNING} m_eMode;
};