// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include "std3d.h"

#include "nel/3d/ps_float.h"
#include "nel/3d/ps_register_float_attribs.h"
#include "nel/misc/fast_floor.h"

namespace NL3D {


/////////////////////////////////////
// CPSFloatGradient implementation //
/////////////////////////////////////

CPSFloatGradient::CPSFloatGradient(const float *floatTab, uint32 nbValues, uint32 nbStages, float nbCycles)
				: CPSValueGradient<float>(nbCycles)
{
	_F.setValues(floatTab, nbValues, nbStages) ;
}


////////////////////////////////////////////
// CPSFloatBezierCurve implementation     //
////////////////////////////////////////////

CPSFloatCurveFunctor::CPSFloatCurveFunctor() : _NumSamples(0), _Smoothing(true)
{
	/*
	_CtrlPoints.push_back(CCtrlPoint(0, 0.5f));
	_CtrlPoints.push_back(CCtrlPoint(1, 0.5f));
	updateTab();
	*/
}

///=======================================================================================
void CPSFloatCurveFunctor::sortPoints(void)
{
	std::sort(_CtrlPoints.begin(), _CtrlPoints.end());
}

///=======================================================================================
void CPSFloatCurveFunctor::addControlPoint(const CCtrlPoint &ctrlPoint)
{
	_CtrlPoints.push_back(ctrlPoint);
	sortPoints();
	updateTab();
}

///=======================================================================================
const CPSFloatCurveFunctor::CCtrlPoint &CPSFloatCurveFunctor::getControlPoint(uint index) const
{
	return _CtrlPoints[index];
}

///=======================================================================================
void CPSFloatCurveFunctor::setCtrlPoint(uint index, const CCtrlPoint &ctrlPoint)
{
	nlassert(ctrlPoint.Date >= 0 && ctrlPoint.Date <= 1);
	_CtrlPoints[index] = ctrlPoint;
	sortPoints();
	updateTab();
}

///=======================================================================================
void CPSFloatCurveFunctor::removeCtrlPoint(uint index)
{
	nlassert(_CtrlPoints.size() > 1);
	_CtrlPoints.erase(_CtrlPoints.begin() + index);
	updateTab();
}

///=======================================================================================
void CPSFloatCurveFunctor::setNumSamples(uint32 numSamples)
{
	nlassert(numSamples > 0);
	_NumSamples = numSamples;
	updateTab();
}

///=======================================================================================
float CPSFloatCurveFunctor::getValue(float date) const
{
	if (_CtrlPoints.empty()) return 0.f;
	NLMISC::clamp(date, 0, 1);
	// find a key that has a higher value
	CPSVector<CCtrlPoint>::V::const_iterator it = _CtrlPoints.begin();
	while ( it != _CtrlPoints.end() && it->Date <= date ) ++it;

	if (it == _CtrlPoints.begin()) return _CtrlPoints[0].Value;
	if (it == _CtrlPoints.end()) return _CtrlPoints[_CtrlPoints.size() - 1].Value;
	CPSVector<CCtrlPoint>::V::const_iterator precIt = it - 1;
	if (precIt->Date == it->Date) return 0.5f * (precIt->Value + it->Value);
	const float lambda = (date - precIt->Date) / (it->Date - precIt->Date);
	if (!_Smoothing) // linear interpolation
	{
		return lambda * it->Value + (1.f - lambda) * precIt->Value;
	}
	else // hermite interpolation
	{
		float width = it->Date - precIt->Date;
		uint index = (uint)(precIt - _CtrlPoints.begin());
		float t1 = getSlope(index) * width, t2 = getSlope(index + 1) * width;
		const float lambda2 = NLMISC::sqr(lambda);
		const float lambda3 = lambda2 * lambda;
		const float h1 = 2 * lambda3 - 3 * lambda2 + 1;
		const float h2 = - 2 * lambda3 + 3 * lambda2;
		const float h3 = lambda3 - 2 * lambda2 + lambda;
		const float h4 = lambda3 - lambda2;

		return h1 * precIt->Value + h2 * it->Value + h3 * t1 + h4 * t2;
	}
}

///=======================================================================================
void CPSFloatCurveFunctor::updateTab(void)
{
	float step  = 1.f / _NumSamples;
	float d = 0.f;
	_Tab.resize(_NumSamples + 1);
	uint k;
	for (k = 0; k <= _NumSamples; ++k)
	{
		_Tab[k] = getValue(d);
		d += step;
	}
	_MinValue = _MaxValue = _Tab[0];
	for (k = 1; k <= _NumSamples; ++k)
	{
		_MinValue = std::min(_MinValue, _Tab[k]);
		_MaxValue = std::max(_MaxValue, _Tab[k]);
	}
}

///=======================================================================================
void CPSFloatCurveFunctor::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
{
	f.serialVersion(1);
	f.serial(_NumSamples, _Smoothing);
	f.serialCont(_CtrlPoints);
	if (f.isReading())
	{
		updateTab();
	}
}

///=======================================================================================
float CPSFloatCurveFunctor::getSlope(uint index) const
{
	// tangent for first point
	if (index == 0)
	{
		return _CtrlPoints[1].Date != _CtrlPoints[0].Date ? (_CtrlPoints[1].Value - _CtrlPoints[0].Value)
															 / (_CtrlPoints[1].Date - _CtrlPoints[0].Date)
														  : 1e6f;
	}

	// tangent for last point
	if (index == _CtrlPoints.size() - 1)
	{
		return _CtrlPoints[index].Date != _CtrlPoints[index - 1].Date ? (_CtrlPoints[index].Value - _CtrlPoints[index - 1].Value)
																		/ (_CtrlPoints[index].Date - _CtrlPoints[index - 1].Date)
																	  : 1e6f;
	}

	// tangent for other points
	return _CtrlPoints[index + 1].Date != _CtrlPoints[index - 1].Date ? (_CtrlPoints[index + 1].Value - _CtrlPoints[index - 1].Value)
																		/ (_CtrlPoints[index + 1].Date - _CtrlPoints[index - 1].Date)
																	  : 1e6f;
}

///=======================================================================================
void CPSFloatCurveFunctor::enableSmoothing(bool enable /* = true*/)
{
	_Smoothing = enable;
	updateTab();
}

///=======================================================================================
void PSRegisterFloatAttribs()
{
	NLMISC_REGISTER_CLASS(CPSFloatBlender);
	NLMISC_REGISTER_CLASS(CPSFloatGradient);
	NLMISC_REGISTER_CLASS(CPSFloatMemory);
	NLMISC_REGISTER_CLASS(CPSFloatBinOp);
	NLMISC_REGISTER_CLASS(CPSFloatCurve);
}

} // NL3D