//////////////////////////////////////////////////////////////////////
//
//  CryEngine Source code
//	
//	File:BSplineOpen
//  Declaration of
//     interface IBSpline3Packed
//     template <> struct TBSplineVec3dPackedBase
//     template <> struct TBSplineVec3dPackedDesc
//     template <> struct TBSplineVec3dPacked
//
//	History:
//	06/21/2002 :Created by Sergiy Migdalskiy <sergiy@crytek.de>
//
//////////////////////////////////////////////////////////////////////
#pragma once

#include "BSplineOpen.h"

////////////////////////////////////////////////////////////////
// Packed (minimal memory footprint) representation of BSpline.
// Keeps the data in 1- or 2-byte fixed-point scaled numbers
// CAUTION: Avoid copying it too much, the data gets copied upon
// each invocation of the copy constructor.
////////////////////////////////////////////////////////////////

// interface to all representations of the packed spline
class IBSpline3Packed:
	public _reference_target<unsigned short> // we don't need the alignment of 4 bytes here
{
public:
	// apply the given scale to the spline (multiply the spline by scale
	virtual void scale (float fScale) = 0;

	// returns the size of the buffer required to pack in the spline in the current state
	virtual int getPackedSize()const = 0; // used

	// returns the size of the array that follows the descriptor structure
	virtual int getRawDataSize()const = 0;

	// returns the min corner of the bounding box for CPs
	virtual Vec3 getCPMin ()const = 0;
	// returns the max corner of the bounding box for CPs
	virtual Vec3 getCPMax ()const = 0;

	// returns the min time
	virtual float getTimeMin() const = 0;
	// returns the max time
	virtual float getTimeMax () const = 0;
	// returns the timespan range
	virtual float getTimeRange() const = 0;

	virtual int numKnots()const = 0;
	virtual int numCPs()const = 0;

	// unpacks this object out of the given storage;
	// returns the size of storage used for unpacking; 0 means unpack failed
	virtual int unpack(void* pBuffer, int nSize) = 0; // used

	// clears the contents of the spline
	virtual void clear() = 0;

	// constructs a packed version of the given unpacked bspline
	virtual void copy (BSplineVec3d* pSpline) = 0; // used

	// packs the spline into the given buffer
	virtual void pack (void* pBuffer) = 0; // used

	// calculates the value of the spline at the specified point
	virtual Vec3 getValue (float t) = 0;

	// returns the CP from the array of value triplets
	virtual Vec3 getCP (int nCP) = 0;

	// returns the time
	virtual float getKnotTime (int nKnot)const = 0;

	virtual unsigned sizeofThis()const = 0;
};

TYPEDEF_AUTOPTR(IBSpline3Packed);


////////////////////////////////////////////////////////////////////////
template <bool isOpen, typename FixedPointType>
struct TBSplineVec3dPackedBase
{
public:
	// fixed-point values.
	// may be unsigned short or unsigned char ONLY
	typedef FixedPointType fixed;

	enum
	{
		// total number of fixed values - may be overflow in the case of fixed == int
		//numFixedValues = 1<<(sizeof(fixed)*8)),
		// maximum fixed value representible
		nMaxFixedValue = ((fixed)~0)
	};
protected:
	TBSplineVec3dPackedBase():
		m_nDegree(1),
		m_numKnots(0)
		{
		}

	// degree of the spline
	short m_nDegree;
	// number of knots
	short m_numKnots;
	
	// the scale of the time values
	struct Scale
	{
		// the default scale is make all fixed values represent the interval [0,1)
		Scale():
			fBase(0),
			fScale(1.0f / (float)nMaxFixedValue)
			{
			}

		float fBase;
		float fScale;
		
		inline float unpack (fixed fVal)const
		{
			return fBase + fScale * fVal;
		}

		fixed pack (float fVal)const
		{
			float f=fScale;
			int i=nMaxFixedValue;
			
			if (fVal <= fBase)
				return 0;
			if (fVal >= fBase + f*i)
				return nMaxFixedValue;

			return (fixed)((fVal-fBase) / fScale + 0.5);
		}

		// unpacks the values, assuming that there are 2 more Scale structures after this
		Vec3 unpack (fixed*pVec)
		{
			return Vec3 (this[0].unpack(pVec[0]), this[1].unpack(pVec[1]), this[2].unpack(pVec[2]));
		}

		// initialize the structure base and scale so that the resulting fixed value range
		// maps effectively the [min,max] closed interval
		void initFromMinMax (float fMin, float fMax)
		{
			fBase  = fMin;
			fScale = (fMax-fMin) / nMaxFixedValue;
		}

		// returns the min representable value
		float getMin () const
		{
			return fBase;
		}

		// returns the max representable value
		float getMax () const
		{
			return fBase + getRange();
		}

		float getRange() const
		{
			float f=fScale;
			int i=nMaxFixedValue;
			return f * i;
		}

		void scale (float _scale)
		{
			fBase *= _scale;
			fScale *= _scale;
		}
	};
	// scale for each
	Scale m_TimeScale;
	Scale m_PosScale[3];

public:
};

//////////////////////////////////////////////////////////////////////////
// This is the descriptor of class TBSplineVec3dPacked.
// it's used to plainly pack/unpack the spline and reuse this structure
template <bool isOpen = true, typename FixedPointType = unsigned char>
struct TBSplineVec3dPackedDesc:
	public IBSpline3Packed,
	public TBSplineVec3dPackedBase<isOpen,FixedPointType>
{
	typedef TBSplineVec3dPackedBase<isOpen, FixedPointType> Base;

	// apply the given scale to the spline (multiply the spline by scale
	void scale (float fScale);

	// returns the size of the buffer required to pack in the spline in the current state
	int getPackedSize()const
	{
		return sizeof (Base) + getRawDataSize();
	}

	unsigned sizeofThis()const
	{
		return getPackedSize();
	}

	int numKnots()const
	{
		assert ((m_numKnots >= 2 && m_numKnots < 10000) || (m_numKnots==0 && m_nDegree==1));// boundary check
		return m_numKnots;
	}
	int numCPs()const
	{
		assert (m_nDegree >= 1 && m_nDegree < 5);// boundary check
		return m_numKnots + (isOpen? m_nDegree - 1 : -1);
	}
	// returns the number of raw data elements
	int numRawDataElements()const
	{
		return (numKnots() - 2 + 3*numCPs());
	}
	// returns the size of the array that follows the descriptor structure
	int getRawDataSize()const
	{
		return numRawDataElements()*sizeof(fixed);
	}

	// returns the min corner of the bounding box for CPs
	Vec3 getCPMin ()const;
	// returns the max corner of the bounding box for CPs
	Vec3 getCPMax ()const;

	// returns the min time
	float getTimeMin() const;
	// returns the max time
	float getTimeMax () const;
	// returns the timespan range
	float getTimeRange() const;
};

////////////////////////////////////////////////////////////////////////
// The spline packed implementation
template <bool isOpen = true, typename FixedPointType = unsigned char>
class TBSplineVec3dPacked:
	public TBSplineVec3dPackedDesc<isOpen, FixedPointType>
{
public:
	typedef TBSplineVec3dPackedDesc<isOpen, FixedPointType> Desc;

	// constructs an empty spline (no knots, no keys)
	// you can unpack or copy the 
	TBSplineVec3dPacked ();

	// destructs
	~TBSplineVec3dPacked (void);

	// copies the packed version of the BSpline
	TBSplineVec3dPacked (const TBSplineVec3dPacked<isOpen, FixedPointType>& that);

	// unpacks this object out of the given storage;
	// returns the size of storage used for unpacking; 0 means unpack failed
	int unpack(void* pBuffer, int nSize);

	// clears the contents of the spline
	void clear();

	// constructs a packed version of the given unpacked bspline
	void copy (BSplineVec3d* pSpline);

	// packs the spline into the given buffer
	void pack (void* pBuffer);

	// calculates the value of the spline at the specified point
	Vec3 getValue (float t);

	// returns the CP from the array of value triplets
	Vec3 getCP (int nCP);

	// returns the time
	float getKnotTime (int nKnot)const;

	// searches and returns the interval to which the given time belongs.
	// each pair of knots defines interval [k1,k2)
	// -1 is before the 0-th knot, numKnots()-1 is after the last knot
	int findInterval (float fTime)const;

	// returns the i-th basis function of degree d, given the time t
	float getBasis (int i, int d, float t) const;

	// returns the i-th basis function of degree d, given the time t and a hint, in which interval to search for it
	float getBasis (int i, int d, float t, int nIntervalT) const;
protected:
	// returns the value of the basis function of degree d, given the time t
	// the knots of the basis function start at pKnotBegin, and the first knot before t is pKnotBeforeT
	float getBasisUnsafe (int nKnotBegin, int d, float t, int nKnotBeforeT) const;

	// plain array of fixed-point values scaled between the min and max time and position
	// there are m_numKnots knot time values, immediately followed by m_numKnots+m_nDegree-1
	// control point values, each control point value is 3 fixed values
	FixedPointType* m_pData;
};

template <bool isOpen, typename FixedPointType>
TBSplineVec3dPacked<isOpen, FixedPointType>::TBSplineVec3dPacked(void):
	m_pData(NULL)
{
}

template <bool isOpen, typename FixedPointType>
TBSplineVec3dPacked<isOpen, FixedPointType>::~TBSplineVec3dPacked(void)
{
	clear();
}


// copies the packed version of the BSpline
template <bool isOpen, typename FixedPointType>
TBSplineVec3dPacked<isOpen, FixedPointType>::TBSplineVec3dPacked (const TBSplineVec3dPacked<isOpen,FixedPointType>& that):
	Desc(that)
{
	if (that.numKnots()) // if not an empty one
	{
		m_pData = new fixed[that.numRawDataElements()];
		memcpy (m_pData, that.m_pData, that.getRawDataSize());
	}
	else
		m_pData = NULL;
}

//////////////////////////////////////////////////////////////////////////
// unpacks this object out of the given storage;
// returns the size of storage used for unpacking; 0 means unpack failed
template <bool isOpen, typename FixedPointType>
int TBSplineVec3dPacked<isOpen, FixedPointType>::unpack(void* pBuffer, int nSize)
{
	clear();
	
	if (nSize < sizeof(Base))
		return 0; // failed

	memcpy (static_cast<Base*>(this), pBuffer, sizeof(Base));

	if (nSize < (int)sizeof(Base) + getRawDataSize())
	{
		clear();
		return 0;
	}

	m_pData = (new fixed[numRawDataElements()]);
	memcpy (m_pData, ((const Base*)pBuffer)+1, getRawDataSize());
	return sizeof(Base) + getRawDataSize();
}

//////////////////////////////////////////////////////////////////////////
// clears the contents of the spline
template <bool isOpen, typename FixedPointType>
void TBSplineVec3dPacked<isOpen, FixedPointType>::clear()
{
	if (m_pData)
	{
		delete[]m_pData;
		m_pData = NULL;
	}
	
	m_nDegree = 1;
	m_numKnots = 0;
}

//////////////////////////////////////////////////////////////////////////
// constructs a packed version of the given unpacked bspline
template <bool isOpen, typename FixedPointType>
void TBSplineVec3dPacked<isOpen, FixedPointType>::copy (BSplineVec3d* pSpline)
{
	int i, nCoord;
	assert (isOpen == pSpline->isOpen());

	clear();
	m_nDegree = pSpline->getDegree();
	m_numKnots = pSpline->numKnots();

	assert (numCPs() == pSpline->numCPs());

	// find the time scale
	m_TimeScale.initFromMinMax (pSpline->getKnotTime(0), pSpline->getKnotTime(m_numKnots-1));

	// find the spacial scale for the CPs - min/max CP coordinates
	Vec3 vMinCP, vMaxCP;
	vMinCP = vMaxCP = pSpline->getCP(0);

	for (i = 1; i < pSpline->numCPs(); ++i)
	{
		const Vec3& vCP = pSpline->getCP(i);
		for (nCoord = 0; nCoord < 3; ++nCoord)
		{
			if (vMinCP[nCoord] > vCP[nCoord])
				vMinCP[nCoord] = vCP[nCoord];
			else
			if (vMaxCP[nCoord] < vCP[nCoord])
				vMaxCP[nCoord] = vCP[nCoord];
		}
	}

	// map the min/max coordinates to the scales
	for (nCoord = 0; nCoord < 3; ++nCoord)
	{
		m_PosScale[nCoord].initFromMinMax(vMinCP[nCoord], vMaxCP[nCoord]);
	}

	m_pData = new fixed[numRawDataElements()];

	// copy the data (knots and CPs)
	// We ignore the first and last knot, because information about them is already present in the scale structure
	for (i = 0; i < pSpline->numKnots()-2; ++i)
		m_pData[i] = m_TimeScale.pack(pSpline->getKnotTime(i+1));

	// each CP has 3 coordinates, each mapped with different pos scale
	for (i = 0; i < pSpline->numCPs(); ++i)
		for (nCoord = 0; nCoord < 3; ++nCoord)
			m_pData[i*3+nCoord+numKnots()-2] = m_PosScale[nCoord].pack (pSpline->getCP(i)[nCoord]);
}

//////////////////////////////////////////////////////////////////////////
// packs the spline into the given buffer
template <bool isOpen, typename FixedPointType>
void TBSplineVec3dPacked<isOpen, FixedPointType>::pack (void* pBuffer)
{
	memcpy (pBuffer, static_cast<const Base*>(this), sizeof(Base));
	memcpy (static_cast<Base*>(pBuffer) + 1, m_pData, getRawDataSize());
}

//////////////////////////////////////////////////////////////////////////
// returns the CP from the array of value triplets
template <bool isOpen, typename FixedPointType>
Vec3 TBSplineVec3dPacked<isOpen, FixedPointType>::getCP (int nCP)
{
	
	if (isOpen)
	{
		if (nCP < 0)
			nCP = 0;
		else
		if (nCP >= numCPs())
			nCP = numCPs()-1;
	}
	else
	{
		nCP = nCP % numCPs();
		if (nCP < 0)
			nCP += numCPs();
	}

  assert (nCP >= 0 && nCP < numCPs());
	return m_PosScale->unpack(m_pData + numKnots()-2 + 3 * nCP);
}

//////////////////////////////////////////////////////////////////////////
// returns the normalized knot and the base - how many cycles the knot rolled back or forth
extern int PackedSplineClosedGetKnotTime(int &nKnot, int numKnots);


//////////////////////////////////////////////////////////////////////////
// returns the time
template <bool isOpen, typename FixedPointType>
float TBSplineVec3dPacked<isOpen, FixedPointType>::getKnotTime (int nKnot)const
{
	if (!isOpen)
	{
		int nBase = PackedSplineClosedGetKnotTime(nKnot, numKnots());

		if (nKnot == 0)
			return m_TimeScale.getMin() + m_TimeScale.getRange()*nBase;
		else
		{
			assert (nKnot < numKnots()-1);
			return m_TimeScale.getRange()*nBase + m_TimeScale.unpack (m_pData[nKnot-1]);
		}
	}
	else
	{
		if (nKnot <= 0)
			return m_TimeScale.getMin();
		else
		if (nKnot >= numKnots()-1)
			return m_TimeScale.getMax();
		else
		return m_TimeScale.unpack (m_pData[nKnot-1]);
	}
}


//////////////////////////////////////////////////////////////////////////
// returns the max corner of the bounding box for CPs
template <bool isOpen, typename FixedPointType>
Vec3 TBSplineVec3dPackedDesc<isOpen, FixedPointType>::getCPMax ()const
{
	return Vec3(m_PosScale[0].getMax(), m_PosScale[1].getMax(), m_PosScale[2].getMax());
}


//////////////////////////////////////////////////////////////////////////
// returns the min corner of the bounding box for CPs
template <bool isOpen, typename FixedPointType>
Vec3 TBSplineVec3dPackedDesc<isOpen, FixedPointType>::getCPMin ()const
{
	return Vec3(m_PosScale[0].getMin(), m_PosScale[1].getMin(), m_PosScale[2].getMin());
}


//////////////////////////////////////////////////////////////////////////
// returns the min time
template <bool isOpen, typename FixedPointType>
float TBSplineVec3dPackedDesc<isOpen, FixedPointType>::getTimeMin() const
{
	return m_TimeScale.getMin();
}


//////////////////////////////////////////////////////////////////////////
// returns the max time
template <bool isOpen, typename FixedPointType>
float TBSplineVec3dPackedDesc<isOpen, FixedPointType>::getTimeMax () const
{
	return m_TimeScale.getMax();
}


//////////////////////////////////////////////////////////////////////////
// returns the max-min time
template <bool isOpen, typename FixedPointType>
float TBSplineVec3dPackedDesc<isOpen, FixedPointType>::getTimeRange () const
{
	return m_TimeScale.getRange();
}


//////////////////////////////////////////////////////////////////////////
// calculates the value of the spline at the specified point
template <bool isOpen, typename FixedPointType>
Vec3 TBSplineVec3dPacked<isOpen, FixedPointType>::getValue (float fTime)
{
	int nInterval = findInterval (fTime);
	assert (nInterval < 0 || nInterval >= numKnots()-1 || (getKnotTime (nInterval) <= fTime && fTime <= getKnotTime(nInterval+1)));

	if (nInterval < 0)
		return getCP(0);

	if (nInterval >= numKnots()-1)
		return getCP(numCPs()-1);
	
	Vec3 vResult(0,0,0);
	for (int i = 0; i >= -m_nDegree; --i)
	{
		Vec3 vCP = getCP(nInterval + (isOpen?m_nDegree:0) + i);
		float fBasis = getBasis (nInterval + i, m_nDegree, fTime, nInterval);
		vResult += vCP * fBasis;
	}

	return vResult;
}


//////////////////////////////////////////////////////////////////////////
// searches and returns the interval to which the given time belongs.
// each pair of knots defines interval [k1,k2)
// -1 is before the 0-th knot, numKnots()-1 is after the last knot
template <bool isOpen, typename FixedPointType>
int TBSplineVec3dPacked<isOpen, FixedPointType>::findInterval (float fTime)const
{
	if (fTime < m_TimeScale.getMin())
		return -1;
	if (fTime < getKnotTime (1))
		return 0;
	if (fTime >= m_TimeScale.getMax())
		return numKnots()-1;
	if (fTime >= getKnotTime (numKnots()-2))
		return numKnots()-2;

	fixed fTimePacked = m_TimeScale.pack(fTime);
	// depending on how we rounded the fTIme value, we seek either the bigger value or bigger or equal
	if (fTime > m_TimeScale.unpack(fTimePacked))
		// now, the time is clamped and can be safely packed 
		// search for the value that's bigger than fTimePacked
		return std::upper_bound (m_pData, m_pData + numKnots()-2, fTimePacked) - m_pData;
	else
		// search for the fTimePacked or bigger value
		return std::lower_bound (m_pData, m_pData + numKnots()-2, fTimePacked) - m_pData;
}

// returns the i-th basis function of degree d, given the time t
template <bool isOpen, typename FixedPointType>
float TBSplineVec3dPacked<isOpen, FixedPointType>::getBasis (int i, int d, float t) const
{
	// the requested basis must have defined supporting knots
	assert (i>= -d && i < numKnots()-1);
	
	// starting and ending knots - they demark the support interval: [*begin,*(end-1)]
	int nKnotBegin = i;
	int nKnotEnd  = nKnotBegin + d + 2;
	// find the interval where the t is, among the supporting intervals
	// the upper bound of that interval is searched for
	for (int nKnotAfterT = nKnotBegin;
		nKnotAfterT < nKnotEnd && getKnotTime (nKnotAfterT) < t;
		++nKnotAfterT)
	continue;

	if (nKnotAfterT == nKnotBegin)
	{
		assert (t < getKnotTime(nKnotBegin));
		return 0; // the time t is before the supporting interval of the basis function
	}

	if (nKnotAfterT == nKnotEnd)
	{
		if (t > getKnotTime(nKnotEnd - 1))
			return 0; // the time t is after the supporting interval

		assert (t == getKnotTime (nKnotEnd - 1));
		// scan down multiple knots
		while (t == getKnotTime (nKnotAfterT - 1));
			--nKnotAfterT;
	}
	
	return getBasis (nKnotBegin, d, t, nKnotAfterT - 1);
}

// returns the i-th basis function of degree d, given the time t and a hint, in which interval to search for it
template <bool isOpen, typename FixedPointType>
float TBSplineVec3dPacked<isOpen, FixedPointType>::getBasis (int i, int d, float t, int nIntervalT) const
{
	if (nIntervalT < -d || nIntervalT >= numKnots()-1)
		return 0;
	
	assert (t >= getKnotTime(nIntervalT) && (t < getKnotTime(nIntervalT+1) || (t==getKnotTime(nIntervalT+1)&&getKnotTime(nIntervalT+1)==getKnotTime(nIntervalT))));

	// the requested basis must have defined supporting knots
	if (i < -d || i >= numKnots()-1)
		return 0;

	if (nIntervalT < i || nIntervalT > i + d)
		return 0; // the time is outside supporting base
	
	return getBasisUnsafe (i, d, t, nIntervalT);
}

// returns the value of the basis function of degree d, given the time t
// the knots of the basis function start at pKnotBegin, and the first knot before t is pKnotBeforeT
template <bool isOpen, typename FixedPointType>
float TBSplineVec3dPacked<isOpen, FixedPointType>::getBasisUnsafe (int nKnotBegin, int d, float t, int nKnotBeforeT)const
{
	assert (t >= getKnotTime(nKnotBeforeT) && t <= getKnotTime(nKnotBeforeT+1));
	assert (nKnotBegin >= -d && nKnotBegin < numKnots()-1);
	assert (nKnotBeforeT >= nKnotBegin && nKnotBeforeT <= nKnotBegin + d);

	switch (d)
	{
	case 0:
		// trivial case
		return 1;
	case 1:
		if (nKnotBeforeT == nKnotBegin)
		{
			// the t is in the first interval
			return (t - getKnotTime(nKnotBegin)) / (getKnotTime(nKnotBegin+1) - getKnotTime(nKnotBegin));
		}
		else
		{
			assert (nKnotBeforeT == nKnotBegin + 1);
			return (getKnotTime(nKnotBegin+2) - t) / (getKnotTime(nKnotBegin+2) - getKnotTime(nKnotBegin+1));
		}
	default:
		{
			float fResult = 0;
			if (nKnotBeforeT < nKnotBegin + d)
				fResult += getBasis (nKnotBegin, d-1, t, nKnotBeforeT) * (t - getKnotTime(nKnotBegin)) / (getKnotTime(nKnotBegin+d) - getKnotTime(nKnotBegin));
			if (nKnotBeforeT > nKnotBegin)
				fResult += getBasis (nKnotBegin+1, d-1, t, nKnotBeforeT) * (getKnotTime(nKnotBegin+d+1) - t) / (getKnotTime(nKnotBegin+d+1) - getKnotTime(nKnotBegin+1));
			return fResult;
		}
	}
}

// apply the given scale to the spline (multiply the spline by scale
template <bool isOpen, typename FixedPointType>
void TBSplineVec3dPackedDesc<isOpen,FixedPointType>::scale (float fScale)
{
	m_PosScale[0].scale (fScale);
	m_PosScale[1].scale (fScale);
	m_PosScale[2].scale (fScale);
}

typedef TBSplineVec3dPacked<true,unsigned char> BSplineVec3dPackedOpen;
typedef TBSplineVec3dPacked<false,unsigned char> BSplineVec3dPackedClosed;
