//===========================================================================//
// File:	fcurve.hpp                                                          //
// Project: Adept           Brick: ???                                       //
// Contents: ???                                                             //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
// 00/00/00  XXX  Initial coding.                                            //
//---------------------------------------------------------------------------//
// Copyright (C) 1995-1998, Virtual World Entertainment, Inc.                //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//

#pragma once

#include "gosFX.hpp"

namespace gosFX {

	#if 0
		#define CURVE_LOGIC(string) LOGIC("gosFX::Curve::" string)
	#else
		#define CURVE_LOGIC(string)
	#endif

	class Curve
	{
	public:
		enum CurveType
		{
			e_ConstantType,
			e_LinearType,
			e_SplineType,
			e_ComplexType,
			e_ComplexLinearType,
			e_ComplexComplexType,
			e_ComplexSplineType,
			e_ConstantComplexType,
			e_ConstantLinearType,
			e_ConstantSplineType,
			e_SplineLinearType,
			e_SplineSplineType
		};

		const CurveType
			m_type;

		Curve(CurveType type):
			m_type(type)
				{}

		Stuff::Scalar 
			ExpensiveCompute(Stuff::Scalar tme,int curvenum=0);

		void 
			ExpensiveComputeRange(
				Stuff::Scalar *low,
				Stuff::Scalar *hi,
				int curvenum
				);

		void 
			ExpensiveComputeRange(
				Stuff::Scalar *low,
				Stuff::Scalar *hi
				);

		void
			Save(Stuff::MemoryStream *stream);
		void
			Load(
				Stuff::MemoryStream *stream,
				int gfx_version
			);

		Stuff::Scalar 
			Mid(int curvenum=0);

		bool
			IsComplex();
		
		void 
			SetSeedFlagIfComplex(bool vflag);

		int 
			GetSeedFlagIfComplex();

		void 
			TranslateTo(Stuff::Scalar pos,int curvenum=0);

		void 
			TranslateBy(Stuff::Scalar delta,int curvenum=0);

		void 
			LocalScale(Stuff::Scalar sfactor,int curvenum=0);

		void 
			AxisScale(Stuff::Scalar sfactor,Stuff::Scalar axis=0.0f,int curvenum=0);

		Curve * 
			GetSubCurve(int curvenum);
		
		void
			TestInstance() const
				{}
	};

	//######################################################################
	//####################    ConstantCurve    #############################
	//######################################################################

	class ConstantCurve:
		public Curve
	{
	public:
		typedef Curve BaseClass;

		Stuff::Scalar
			m_value;

		ConstantCurve(CurveType type=e_ConstantType):
			Curve(type)
				{}

		ConstantCurve&
			operator=(const ConstantCurve& curve)
				{m_value = curve.m_value; return *this;}

		void
			Save(Stuff::MemoryStream *stream);
		void
			Load(
				Stuff::MemoryStream *stream,
				int gfx_version
			);

		//------------------------------------------------------
		// for Set...Key(), true=math good, false=math unstable
		//------------------------------------------------------
		bool
			SetCurve(Stuff::Scalar v)
				{Check_Object(this); m_value = v; return true;}

		Stuff::Scalar
			ComputeValue(Stuff::Scalar, Stuff::Scalar)
				{Check_Object(this); return m_value;}
		Stuff::Scalar
			ComputeSlope(Stuff::Scalar)
				{Check_Object(this); return 0.0f;}

		void
			ComputeRange(
				Stuff::Scalar *low,
				Stuff::Scalar *hi
			)
				{
					Check_Object(this); Check_Pointer(low); Check_Pointer(hi);
					*hi = *low = m_value;
				}

		void
			SetMinimum(Stuff::Scalar min)
				{Check_Object(this); Min_Clamp(m_value, min);}
	};

	//######################################################################
	//#####################    LinearCurve    ##############################
	//######################################################################

	class LinearCurve:
		public ConstantCurve
	{
	public:
		typedef ConstantCurve BaseClass;

		Stuff::Scalar
			m_slope;

		LinearCurve(CurveType type=e_LinearType):
			ConstantCurve(type)
				{}

		LinearCurve&
			operator=(const LinearCurve& curve)
				{m_slope = curve.m_slope; m_value = curve.m_value; return *this;}

		void
			Save(Stuff::MemoryStream *stream);
		void
			Load(
				Stuff::MemoryStream *stream,
				int gfx_version
			);

		//------------------------------------------------------
		// for Set...Key(), true=math good, false=math unstable
		//------------------------------------------------------
		bool
			SetCurve(Stuff::Scalar v);
		bool
			SetCurve(
				Stuff::Scalar v0,
				Stuff::Scalar v1
			);

		Stuff::Scalar
			ComputeValue(Stuff::Scalar t, Stuff::Scalar)
				{
					Check_Object(this); Verify(t>=0.0f && t<=1.0f);
					return m_slope*t + m_value;
				}
		Stuff::Scalar
			ComputeSlope(Stuff::Scalar)
				{Check_Object(this); return m_slope;}

		void
			ComputeRange(
				Stuff::Scalar *low,
				Stuff::Scalar *hi
			);

		void
			SetMinimum(Stuff::Scalar min);
	};

	//######################################################################
	//######################    SplineCurve    ##############################
	//######################################################################

	class SplineCurve:
		public LinearCurve
	{
	public:
		typedef LinearCurve BaseClass;

		Stuff::Scalar
			m_a,
			m_b;

		SplineCurve():
			LinearCurve(e_SplineType)
				{}

		SplineCurve&
			operator=(const SplineCurve& curve);

		void
			Save(Stuff::MemoryStream *stream);
		void
			Load(
				Stuff::MemoryStream *stream,
				int gfx_version
			);

		//------------------------------------------------------
		// for Set...Key(), true=math good, false=math unstable
		//------------------------------------------------------
		bool
			SetCurve(Stuff::Scalar v);
		bool
			SetCurve(
				Stuff::Scalar v0,
				Stuff::Scalar v1
			);
		bool
			SetCurve(
				Stuff::Scalar v0,
				Stuff::Scalar s0,
				Stuff::Scalar v1,
				Stuff::Scalar s1
			);

		Stuff::Scalar
			ComputeValue(Stuff::Scalar t, Stuff::Scalar)
				{
					Check_Object(this); Verify(t>=0.0f && t<=1.0f);
					return ((m_a*t + m_b)*t + m_slope)*t + m_value;
				}
		Stuff::Scalar
			ComputeSlope(Stuff::Scalar t)
				{Check_Object(this); return (3.0f*m_a*t + 2.0f*m_b)*t + m_slope;}

		void
			ComputeRange(
				Stuff::Scalar *low,
				Stuff::Scalar *hi
			);

		void
			SetMinimum(Stuff::Scalar min);
	};

	//######################################################################
	//######################    CurveKey    ###############################
	//######################################################################

	class CurveKey
	{
	public:
		Stuff::Scalar
			m_time,
			m_slope,
			m_value;

		//------------------------------------------------------
		// for Set...Key(), true=math good, false=math unstable
		//------------------------------------------------------
		bool
			SetConstantKey(
				Stuff::Scalar key_time,
				Stuff::Scalar v
			);
		bool
			SetLinearKey(
				Stuff::Scalar key_time,
				Stuff::Scalar v0,
				Stuff::Scalar v1,
				Stuff::Scalar dt
			);

		Stuff::Scalar
			ComputeValue(Stuff::Scalar t)
				{Check_Object(this); return m_slope*t + m_value;}
		Stuff::Scalar
			ComputeSlope(Stuff::Scalar)
				{Check_Object(this); return m_slope;}

		void
			ComputeRange(
				Stuff::Scalar *low,
				Stuff::Scalar *hi,
				Stuff::Scalar dt
			);

		void
			SetMinimum(
				Stuff::Scalar minimum,
				Stuff::Scalar dt
			);

		void
			TestInstance() const
				{}
	};

	//######################################################################
	//########################    ComplexCurve    ################################
	//######################################################################

	class ComplexCurve:
		public Curve
	{
	protected:
		Stuff::DynamicArrayOf<CurveKey>
			m_keys;

	public:
		typedef Curve BaseClass;

		ComplexCurve();
		ComplexCurve(const ComplexCurve &fcurve);
		ComplexCurve(
			Stuff::MemoryStream *stream,
			int gfx_version
		);

		ComplexCurve&
			operator=(const ComplexCurve &fcurve);

		void
			Save(Stuff::MemoryStream *stream);
		void
			Load(
				Stuff::MemoryStream *stream,
				int gfx_version
			);

		//-----------------------------------------------------------------
		// Warning:  both index of and pointer to CurveKey's are volitile
		//           client code should store neither between transactions
		//-----------------------------------------------------------------

		CurveKey&
			operator[](int index)
				{Check_Object(this); return m_keys[index];}
		int
			GetKeyCount()
				{Check_Object(this); return m_keys.GetLength();}
		int
			GetKeyIndex(Stuff::Scalar time)
				{
					Check_Object(this);
					int i;
					for (i=0; i<m_keys.GetLength(); ++i)
						if (m_keys[i].m_time > time)
							break;
					return --i;
				}

		int
			InsertKey(Stuff::Scalar time);
		void
			DeleteKey(int index);

		void
			SetCurve(Stuff::Scalar v);
		void
			SetCurve(
				Stuff::Scalar starting_value,
				Stuff::Scalar ending_value
			);

		Stuff::Scalar
			ComputeValue(Stuff::Scalar time, Stuff::Scalar)
				{
					Check_Object(this);
					CURVE_LOGIC("ComplexCurve");
					Verify(time>=0.0f && time<=1.0f);
					CurveKey &key = (*this)[GetKeyIndex(time)];
					return key.ComputeValue(time - key.m_time);
				}
		Stuff::Scalar
			ComputeSlope(Stuff::Scalar time)
				{
					Check_Object(this);
					CurveKey &key = (*this)[GetKeyIndex(time)];
					return key.ComputeSlope(time - key.m_time);
				}

		void
			ComputeRange(
				Stuff::Scalar *low,
				Stuff::Scalar *hi
			);

		void
			SetMinimum(Stuff::Scalar min);
	};

	//######################################################################
	//#####################    SeededCurveOf    ############################
	//######################################################################

	template <class C, class S,Curve::CurveType type> class SeededCurveOf:
		public Curve
	{
	public:
		typedef Curve BaseClass;

		C
			m_ageCurve;
		S
			m_seedCurve;
		bool
			m_seeded;

		SeededCurveOf():
			Curve(type)
				{}

		SeededCurveOf<C,S,type>&
			operator=(const SeededCurveOf<C,S,type>& curve);
		void
			Save(Stuff::MemoryStream *stream);
		void
			Load(
				Stuff::MemoryStream *stream,
				int gfx_version
			);

		Stuff::Scalar
			ComputeValue(
				Stuff::Scalar age,
				Stuff::Scalar seed
			)
				{
					Check_Object(this);
					CURVE_LOGIC("SeededCurve");
					Stuff::Scalar result = m_ageCurve.ComputeValue(age, 0.0f);
					if (m_seeded)
						result *= m_seedCurve.ComputeValue(seed, 0.0f);
					return result;
				}
		void
			ComputeRange(
				Stuff::Scalar *low,
				Stuff::Scalar *hi
			);

		void
			SetMinimum(Stuff::Scalar min)
				{
					min = Stuff::Sqrt(min);
					m_ageCurve.SetMinimum(min);
					m_seedCurve.SetMinimum(min);
				}

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Testing
	//
	public:
		void
			TestInstance() const
				{}
	};

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	template <class C, class S,Curve::CurveType type> SeededCurveOf<C,S,type>&
		SeededCurveOf<C,S,type>::operator=(const SeededCurveOf<C,S,type>& curve)
	{
		m_ageCurve = curve.m_ageCurve;
		m_seedCurve = curve.m_seedCurve;
		m_seeded = curve.m_seeded;
		return *this;
	}

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	template <class C, class S,Curve::CurveType type> void
		SeededCurveOf<C,S,type>::Save(Stuff::MemoryStream *stream)
	{
		m_ageCurve.Save(stream);
		m_seedCurve.Save(stream);
		*stream << m_seeded;
	}

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	template <class C, class S,Curve::CurveType type> void
		SeededCurveOf<C,S,type>::Load(
			Stuff::MemoryStream *stream,
			int gfx_version
		)
	{
		m_ageCurve.Load(stream, gfx_version);
		m_seedCurve.Load(stream, gfx_version);
		*stream >> m_seeded;
	}

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	template <class C, class S,Curve::CurveType type> void
		SeededCurveOf<C,S,type>::ComputeRange(
			Stuff::Scalar *low,
			Stuff::Scalar *hi
		)
	{
		Stuff::Scalar low_age, hi_age;
		m_ageCurve.ComputeRange(&low_age, &hi_age);
		Stuff::Scalar low_seed, hi_seed;
		m_seedCurve.ComputeRange(&low_seed, &hi_seed);
		*low = *hi = low_age*low_seed;
		Stuff::Scalar temp = low_age*hi_seed;
		if (temp<*low)
			*low = temp;
		else if (temp>*hi)
			*hi = temp;
		temp = hi_age*low_seed;
		if (temp<*low)
			*low = temp;
		else if (temp>*hi)
			*hi = temp;
		temp = hi_age*hi_seed;
		if (temp<*low)
			*low = temp;
		else if (temp>*hi)
			*hi = temp;
	}

}
