// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
// 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 "stdpch.h"
#include "precipitation.h"
#include "time_client.h"
//
#include "nel/misc/matrix.h"
#include "nel/misc/vector.h"
#include "nel/3d/u_particle_system_instance.h"

extern NL3D::UScene *Scene;


H_AUTO_DECL(RZ_Precipitation)

CPrecipitation::TClipGridMap CPrecipitation::_PrecipitationClipGripMap;

static const float DEFAUT_PRECIPITATION_TIMEOUT = 10.f; // at most 10 second before precipitations are removed


//============================================================
CPrecipitation::CPrecipitation() : _Strenght(0), _XSize(0), _YSize(0), _ClipGrid(NULL), _Touched(false), _OldX(0), _OldY(0), _TimeOut(0.f)
{
	H_AUTO_USE(RZ_Precipitation)
}

//============================================================
void CPrecipitation::init(const CPrecipitationDesc &desc)
{
	H_AUTO_USE(RZ_Precipitation)
	nlassert(desc.GridSize > 0);
	release();
	_Strenght = 0;
	_Desc = desc;
	if (!_Desc.ReleasableModel)
	{
		allocateFXs(); // keep fxs allocated;
		forceSetStrenght(0); // no precipitation is the default
	}
	_ClipGrid = NULL;
	_Touched = true;
}

//============================================================
void CPrecipitation::allocateFXs()
{
	H_AUTO_USE(RZ_Precipitation)
	if (!Scene) return;
	nlassert(_Blocks.empty()); // should be called only once
	_Blocks.resize(_Desc.GridSize * _Desc.GridSize);
	std::fill(_Blocks.begin(), _Blocks.end(), NL3D::UParticleSystemInstance ());
	for(uint k = 0; k < _Desc.GridSize * _Desc.GridSize; ++k)
	{
		_Blocks[k].cast (Scene->createInstance(_Desc.FxName.c_str()));
		if (_Blocks[k].empty())
		{
			nlwarning("can't find %s", _Desc.FxName.c_str());
			release();
			return;
		}
		_Blocks[k].forceInstanciate();
	}
	if (_Desc.UseBBoxSize)
	{
		NLMISC::CAABBox bbox;
		getBlock(0, 0).getShapeAABBox(bbox);
		_XSize = 2.f * bbox.getHalfSize().x;
		_YSize = 2.f * bbox.getHalfSize().y;
	}
	else
	{
		_XSize = _YSize = _Desc.Size;
	}

	// get the associated precipitation clipGrid if it hasn't yet
	/*
	if (!_ClipGrid)
	{
		NLMISC::CVector2f gridSize(_XSize, _YSize);
		TClipGridMap::iterator it = _PrecipitationClipGripMap.find(gridSize);
		if (it != _PrecipitationClipGripMap.end())
		{
			_ClipGrid = &it->second;
		}
		else
		{
			_ClipGrid = &(_PrecipitationClipGripMap[gridSize]);
			_ClipGrid->initGrid(_Desc.GridSize, _XSize, _YSize);
		}
	}
	*/
}

//============================================================
void CPrecipitation::deallocateFXs()
{
	H_AUTO_USE(RZ_Precipitation)
	if (Scene)
	{
		for(uint k = 0; k < _Blocks.size(); ++k)
		{
			Scene->deleteInstance(_Blocks[k]);
		}
		NLMISC::contReset(_Blocks);
	}
	_TimeOut = 0.f;
	_Touched = true;
}


//============================================================
void CPrecipitation::setStrenght(float strenght)
{
	H_AUTO_USE(RZ_Precipitation)
	if (strenght == _Strenght) return;
	forceSetStrenght(strenght);
}

//============================================================
void CPrecipitation::forceSetStrenght(float strenght)
{
	H_AUTO_USE(RZ_Precipitation)
	if (_Desc.ReleasableModel)
	{
		if (strenght > 0)
		{
			if (!areFXsAllocated())
			{
				allocateFXs();
			}
		}
	}
	 NLMISC::clamp(strenght, 0, 1);
	if (_Desc.GridSize == 0) return;
	if (!areFXsAllocated()) return;
	if (strenght <= 0.f)
	{
		if (_Strenght != 0.f)
		{
			_TimeOut = DEFAUT_PRECIPITATION_TIMEOUT;
		}
		for(uint k = 0; k < _Blocks.size(); ++k)
		{
			_Blocks[k].activateEmitters(false);
			_Blocks[k].setUserParam(0, strenght);
		}
	}
	else
	{
		_TimeOut = 0.f;
		for(uint k = 0; k < _Blocks.size(); ++k)
		{
			if (_Strenght == 0.f)
			{
				_Blocks[k].activateEmitters(true);
			}
			_Blocks[k].setUserParam(0, strenght);

		}
	}
	_Strenght = strenght;
}

//============================================================
void CPrecipitation::release()
{
	H_AUTO_USE(RZ_Precipitation)
	deallocateFXs();
}

//============================================================
bool CPrecipitation::isRunning() const
{
	H_AUTO_USE(RZ_Precipitation)
	if (!areFXsAllocated()) return false;
	// if the last particles have been removed, we can remove the models
	for(uint k = 0; k < _Blocks.size(); ++k)
	{
		if (_Blocks[k].hasParticles()) return true;
	}
	return false;
}

//============================================================
// snap a float by the given factor
static inline float fsnapf(float v, float mod)
{
	H_AUTO_USE(RZ_Precipitation)
	return mod * floorf(v / mod);
}

//============================================================
void CPrecipitation::update(const NLMISC::CMatrix &camMat, NLPACS::UGlobalRetriever * /* retriever */)
{
	H_AUTO_USE(RZ_Precipitation)
	if (_TimeOut != 0.f)
	{
		nlassert(_Strenght == 0.f);
		_TimeOut -= DT;
		if (_TimeOut <= 0.f)
		{
			_TimeOut = 0.f;
			deallocateFXs();
			return;
		}
	}
	if (_Desc.ReleasableModel)
	{
		if (_Strenght == 0.f && areFXsAllocated()) // no more precipitations & FXs are still allocated ? This means that there are particles left
		{
			if (!isRunning())
			{
				deallocateFXs();
				return;
			}
		}
	}
	if (!areFXsAllocated()) return;
	NLMISC::CVector userPos = camMat.getPos();
	//
	if (_XSize == 0 || _YSize == 0) return;

	// get world position of the user in grid units
	sint worldX = (sint) floorf(userPos.x / _XSize);
	sint worldY = (sint) floorf(userPos.y / _YSize);

	// See if we need to update pos
	if (_Touched || worldX != _OldX || worldY != _OldY)
	{
		_Touched = false;
		_OldX = worldX;
		_OldY = worldY;

		/*
		if (_ClipGrid)
		{
			_ClipGrid->updateGrid(userPos, retriever);
		}
		*/

		//
		// snap the user pos
		//
		NLMISC::CVector snappedPos(fsnapf(userPos.x, _XSize), fsnapf(userPos.y, _YSize), userPos.z);
		snappedPos -= NLMISC::CVector((float(_Desc.GridSize >> 1) - 0.5f) * _XSize, (float(_Desc.GridSize >> 1) - 0.5f) * _YSize, 0);
		// get world position of grid corner
//		sint gridCornerX = worldX - (_Desc.GridSize >> 1);
//		sint gridCornerY = worldY - (_Desc.GridSize >> 1);
		// move / show / hide each block
		for(uint k = 0; k < _Desc.GridSize; ++k)
		{
			NLMISC::CVector hPos = snappedPos;
			for(uint l = 0; l < _Desc.GridSize; ++l)
			{
				nlassert(!getBlock(l, k).empty());
				/*if (!_ClipGrid)
				{*/
					getBlock(l, k).setPos(hPos);
					hPos.x += _XSize;
				/*
				}
				else // use the clip grid to see if that block should be activated, and at what height it should be put
				{
					typedef CPrecipitationClipGrid::CGridPoint CGridPoint; // alias CGridPoint
					const CGridPoint *p0 = _ClipGrid->get(l + gridCornerX, k + gridCornerY);
					const CGridPoint *p1 = _ClipGrid->get(l + gridCornerX + 1, k + gridCornerY);
					const CGridPoint *p2 = _ClipGrid->get(l + gridCornerX + 1, k + gridCornerY + 1);
					const CGridPoint *p3 = _ClipGrid->get(l + gridCornerX, k + gridCornerY + 1);
					if (p0 && p1 && p2 && p3)
					{
						// if one of the 4 corners is in an interior, don't display the precipiations
						if (p0->Clipped || p1->Clipped || p2->Clipped || p3->Clipped)
						{
							getBlock(l, k).hide();
						}
						else
						{
							NL3D::UParticleSystemInstance psi = getBlock(l, k);
							psi.show();
							// take the mean height
							hPos.z = 0.25f * (p0->MeanHeight + p1->MeanHeight + p2->MeanHeight + p3->MeanHeight);
							psi.setPos(hPos);
						}
					}
					else
					{
						getBlock(l, k).setPos(hPos);
					}
					hPos.x += _XSize;
				}
				*/
			}
			snappedPos.y += _YSize;
		}
	}
}

//============================================================
void CPrecipitation::drawClipGrid(NL3D::UDriver &drv) const
{
	if (!_ClipGrid) return;
	_ClipGrid->display(drv);
}