//
// Asura_Spline.h
//
// Created by KOR - 18/01/2008
//
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

// This file's header
#include "Core/Asura_Spline.h"

// Asura
#include "Core/Asura_Maths.h"

#ifdef ASURA_TOOLS
#include "Core/Asura_Timers.h"
#include "Rendering/Asura_Colour.h"
#include "Rendering/Asura_Render.h"
#endif

#ifdef ASURA_TOOLS
// Initialise these with a dodgy colour so the colours will be recalculated initially.
Asura_RGBA Asura_Spline<Asura_Vector_3>::s_xBaseColour( 0x00000000 );
Asura_RGBA Asura_Spline<Asura_Vector_3>::s_xCustomColour( 0x00000000 );
u_int Asura_Spline<Asura_Vector_3>::s_auSplineColours[ NUM_COLOURS ];

bool Asura_Spline<Asura_Vector_3>::RenderSection( const bool bHighLOD,
												  const Asura_Vector_3& xP0,
												  const Asura_Vector_3& xD0Tangent,
												  const float fD0,
												  const float fD1,
												  Asura_Vector_3& xP1,
												  Asura_Vector_3& xD1Tangent )
{
	GetPositionFromDistance( fD1, xP1 );
	xP1.y -= 0.01f;

	// Approximate our d1 tangent.
	xD1Tangent = xP1 - xP0;
	const float fDistance = xD1Tangent.Normalise();

	// If the tangent deviates enough and we aren't subdividing too much then sub-divide further, otherwise just render.
	if ( xD0Tangent * xD1Tangent < 0.9961947f && fDistance > ( bHighLOD ? 0.25f : 2.0f ) ) // ~5°
	{
		const float fD0Point5 = fD0 + ( fD1 - fD0 ) * 0.5f;
		Asura_Vector_3 xP0Point5;
		Asura_Vector_3 xD0Point5Tangent;
		RenderSection( bHighLOD, xP0, xD0Tangent, fD0, fD0Point5, xP0Point5, xD0Point5Tangent );
		RenderSection( bHighLOD, xP0Point5, xD0Point5Tangent, fD0Point5, fD1, xP1, xD1Tangent );
		return true;
	}
	else
	{
		const u_int uBin = static_cast< u_int >( fD0 * 2.0f - Asura_Timers::GetLocalUnscaledTime() * 8.0f ) % NUM_COLOURS;
		Asura_Render::RenderLine( xP0, xP1, s_auSplineColours[ uBin ] );
#if 0
		Asura_Vector_3 xScreenSpace = xP0;
		Asura_Camera::ProjectWorldCoordsToScreenSpace(xScreenSpace, true);
		if (xScreenSpace.z > 0.0f) // In front of camera
		{
			Asura_Render::Overlay.TextureIndex = ASURA_TEXTURE_NONE;
			Asura_Render::Overlay.Colour = 0xffffffff;
			Asura_Render::Overlay.SetRectXY(xScreenSpace.x - 2, xScreenSpace.y - 2, 4, 4);
			Asura_Render::Overlay.TranslucencyMode = ASURA_TRANSLUCENCY_OFF;
			Asura_Render::Overlay.Render();
		}
#endif
		return false;
	}
}

void Asura_Spline<Asura_Vector_3>::CalcColours( const Asura_RGBA& xBaseColour, const Asura_RGBA& xCustomColour )
{
	s_xBaseColour   = xBaseColour;
	s_xCustomColour = xCustomColour;

	s_auSplineColours[ 0 ] = xBaseColour.ToUIntARGB( true );
	const float fAngDiff = Asura_Maths::Pi / NUM_COLOURS;
	for ( u_int u = 1; u < NUM_COLOURS; ++u )
	{
		const float fProportion = Asura_Maths::Sine( u * fAngDiff );
		Asura_RGBA xFinalColour;
		xFinalColour.Lerp( xBaseColour, xCustomColour, fProportion );
		s_auSplineColours[ u ] = xFinalColour.ToUIntARGB( true );
	}
}

template <>
void Asura_Spline<Asura_Vector_3>::RenderLines(const Asura_RGBA& xBaseColour, const Asura_RGBA& xCustomColour, const bool bHighLOD /*= true*/)
{
	if ( xBaseColour != s_xBaseColour || xCustomColour != s_xCustomColour )
	{
		CalcColours( xBaseColour, xCustomColour );
	}

	Asura_Vector_3 xP0 = m_pxPositions[ 1 ];
	xP0.y -= 0.01f;
	Asura_Vector_3 xD0Tangent = m_pxPositions[ m_uNumberOfPoints - 2 ] - xP0;
	xD0Tangent.Normalise();

	const float fTotalLength = GetTotalLength();
	float fInc = bHighLOD ? 0.25f : 1.0f;
	Asura_Vector_3 xP1;
	Asura_Vector_3 xD1Tangent;
	for ( float fLength = 0.0f; fLength < fTotalLength;  )
	{
		const float fLengthPlusInc = Asura_Maths::Min( fLength + fInc, fTotalLength );
		if ( RenderSection( bHighLOD, xP0, xD0Tangent, fLength, fLengthPlusInc, xP1, xD1Tangent ) )
		{
			// If we subdivided we reset the increment to the smallest value.
			fInc = bHighLOD ? 0.25f : 1.0f;
		}
		else
		{
			// If we didn't subdivide we can increase the increment.
			const float fIncMax = bHighLOD ? 1.0f : 8.0f;
			fInc = Asura_Maths::Min( fInc * 2.0f, fIncMax );
		}
		xP0	       = xP1;
		xD0Tangent = xD1Tangent;
		fLength    = fLengthPlusInc;
	}
}
#endif

Asura_Oriented_Spline::Asura_Oriented_Spline()
: m_pxOrientations(NULL)
{
}

Asura_Oriented_Spline::~Asura_Oriented_Spline()
{
	delete [] m_pxOrientations;
}

void Asura_Oriented_Spline::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	u_int uVersion;
	xStream >> uVersion;
	Asura_Assert(uVersion <= CHUNK_VERSION, "Unrecognised chunk version");

	PARENT::ReadFromChunkStream(xStream);

	// Should have number of points now.
	const u_int uNumberOfPositionsIgnoringEnds = m_uNumberOfPoints - 2;
	if(!m_pxOrientations)
	{
		Asura_ResourceAllocateArray(m_pxOrientations, uNumberOfPositionsIgnoringEnds);
	}
	Asura_ReadFromStream_Array_Wrapper<Asura_Quat> xOrientationArray(uNumberOfPositionsIgnoringEnds, m_pxOrientations);
	xStream >> xOrientationArray;
}

void Asura_Oriented_Spline::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	xStream << CHUNK_VERSION;

	PARENT::WriteToChunkStream(xStream);

	const u_int uNumberOfPositionsIgnoringEnds = m_uNumberOfPoints - 2;
	Asura_WriteToStream_Array_Wrapper<Asura_Quat> xOrientationArray(uNumberOfPositionsIgnoringEnds, m_pxOrientations);
	xStream << xOrientationArray;
}

const Asura_Quat& Asura_Oriented_Spline::GetPointOrientation(u_int uIndex) const
{
	Asura_Assert(uIndex < m_uNumberOfPoints - 2, "Index out of range");
	return m_pxOrientations[uIndex];
}

float Asura_Oriented_Spline::GetPosAndOriFromDistance(float fDistance, Asura_Vector_3& xPosition, Asura_Quat& xOrientation) const
{
	u_int uSection = 0;
	float fLerp = 0.f;
	const float fActualDistance = GetLerpAndSectionFromDistance(fDistance, fLerp, uSection);
	GetPositionInSection(uSection, fLerp, xPosition);
	xOrientation.Slerp(m_pxOrientations[uSection], m_pxOrientations[uSection + 1], fLerp);
	return fActualDistance;
}

void Asura_Oriented_Spline::CopyFrom(const Asura_Oriented_Spline* pxFrom)
{
	// Copy parent first.
	PARENT::CopyFrom(pxFrom);

	// Now copy orientations.
	Asura_ResourceDeallocateArray(m_pxOrientations);
	const u_int uNumberOfPositionsIgnoringEnds = m_uNumberOfPoints - 2;
	Asura_ResourceAllocateArray(m_pxOrientations, uNumberOfPositionsIgnoringEnds);
	for(u_int u = 0; u < uNumberOfPositionsIgnoringEnds; ++u)
	{
		m_pxOrientations[u] = pxFrom->m_pxOrientations[u];
	}
}

#ifdef ASURA_TOOLS
void Asura_Oriented_Spline::SetPointPositions(u_int uNumberOfPoints, const Asura_Vector_3* pxPoints)
{
	Asura_ResourceDeallocateArray(m_pxOrientations);
	Asura_ResourceAllocateArray(m_pxOrientations, uNumberOfPoints);
	for(u_int u = 0; u < uNumberOfPoints; ++u)
	{
		m_pxOrientations[u].Identity();
	}
	PARENT::SetPointPositions(uNumberOfPoints, pxPoints);
}

void Asura_Oriented_Spline::AddPoint(const u_int uIndexAfter, const Asura_Vector_3& xPosition)
{
	// Add orientation before m_uNumberOfPoints is changed.
	AddArrayElement<Asura_Quat>(m_pxOrientations, m_uNumberOfPoints - 2, Asura_Maths::IdentityQuat, uIndexAfter - 1);
	PARENT::AddPoint(uIndexAfter, xPosition);
}

void Asura_Oriented_Spline::AddPointWithOri(const u_int uIndexAfter, const Asura_Vector_3& xPos, const Asura_Quat& xOri)
{
	// Add orientation before m_uNumberOfPoints is changed.
	AddArrayElement<Asura_Quat>(m_pxOrientations, m_uNumberOfPoints - 2, xOri, uIndexAfter - 1);
	PARENT::AddPoint(uIndexAfter, xPos);
}

void Asura_Oriented_Spline::RemovePoint(const u_int uIndex)
{
	// Remove orientation before m_uNumberOfPoints is changed.
	RemoveArrayElement<Asura_Quat>(m_pxOrientations, m_uNumberOfPoints - 2, uIndex - 1);
	PARENT::RemovePoint(uIndex);
}

void Asura_Oriented_Spline::ReverseSpline()
{
	const u_int uNumPoints = GetNumberOfPoints() - 2;

	Asura_Quat* pxOris = NULL;
	Asura_ResourceAllocateArray( pxOris, uNumPoints );
	u_int uArrayIndex = uNumPoints - 1;
	for ( u_int u = 0; u < uNumPoints; u++ )
	{
		pxOris[u] = GetPointOrientation( uArrayIndex );
		--uArrayIndex;
	}

	PARENT::ReverseSpline();

	for ( u_int u = 0; u < uNumPoints; u++ )
	{
		SetPointOrientation( u, pxOris[u] );
	}
	Asura_ResourceDeallocateArray( pxOris );
}

Asura_Quat& Asura_Oriented_Spline::GetPointOrientation(u_int uIndex)
{
	Asura_Assert(uIndex < m_uNumberOfPoints - 2, "Index out of range");
	return m_pxOrientations[uIndex];
}

void Asura_Oriented_Spline::SetPointOrientation(u_int uIndex, const Asura_Quat& xQuat)
{
	Asura_Assert(uIndex < m_uNumberOfPoints - 2, "Index out of range");
	m_pxOrientations[uIndex] = xQuat;
	OnChanged();
}

void Asura_Oriented_Spline::ApplyChangesForTemplateInstance(Tools_EntityTemplateInstance* pxInstance)
{
	PARENT::ApplyChangesForTemplateInstance( pxInstance );

	const u_int uNumPoints = GetNumberOfPoints();
	for ( u_int u = 0; u < uNumPoints - 2; u++ )
	{
		const Asura_Quat xNewOri = pxInstance->TransformQuat( GetPointOrientation( u ) );
		SetPointOrientation( u, xNewOri );
	}
}
#endif//def ASURA_TOOLS
