// 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 "std_afx.h"
#include <shlwapi.h>
//
#include "particle_workspace.h"
#include "object_viewer.h"
//
#include "nel/3d/shape_bank.h"
#include "nel/3d/particle_system_model.h"
#include "nel/3d/particle_system_shape.h"
#include "nel/3d/skeleton_model.h"
//
#include "nel/misc/o_xml.h"
#include "nel/misc/i_xml.h"
#include "nel/misc/file.h"
//



//***********************************************************************************************
CParticleWorkspace::CNode::CNode()
{
	_PS = NULL;
	_PSM = NULL;
	_ShapeBank = NULL;
	_Modified = false;
	_ParentSkel = NULL;
	_ResetAutoCount = false;
	_WS = NULL;
}

//***********************************************************************************************
CParticleWorkspace::CNode::~CNode()
{	
	unload();
	
}

//***********************************************************************************************
void CParticleWorkspace::CNode::memorizeState()
{
	nlassert(_WS);
	if (!_PS) return;
	_InitialPos.copySystemInitialPos(_PS);
}

//***********************************************************************************************
void CParticleWorkspace::CNode::restoreState()
{
	nlassert(_WS);
	if (!_PS) return;
	_InitialPos.restoreSystem();
}

//**************************************************************************************************************************
bool CParticleWorkspace::CNode::isStateMemorized() const
{
	return _InitialPos.isStateMemorized();
}

//**************************************************************************************************************************
void CParticleWorkspace::CNode::stickPSToSkeleton(NL3D::CSkeletonModel *skel,
												  uint bone,
												  const std::string &parentSkelName,
												  const std::string &parentBoneName)
{
	nlassert(_WS);
	if (!_PSM) return;
	unstickPSFromSkeleton();
	_ParentSkelName = parentSkelName;
	_ParentBoneName = parentBoneName;
	if (skel)
	{
		skel->stickObject(_PSM, bone);
		_PSM->setMatrix(NLMISC::CMatrix::Identity);
		_ParentSkel = skel;		
	}
	if (_WS->getModificationCallback())
	{
		_WS->getModificationCallback()->nodeSkelParentChanged(*this);
	}
}

//**************************************************************************************************************************
void CParticleWorkspace::CNode::unstickPSFromSkeleton()
{
	nlassert(_WS);
	_ParentSkelName = "";
	_ParentBoneName = "";
	if (!_PSM) return;
	if (_ParentSkel)
	{
		_ParentSkel->detachSkeletonSon(_PSM);
		_ParentSkel = NULL;		
	}	
}

//***********************************************************************************************
void CParticleWorkspace::CNode::removeLocated(NL3D::CPSLocated *loc)
{	
	nlassert(_WS);
	if (_InitialPos.isStateMemorized())
	{	
		_InitialPos.removeLocated(loc);
	}
}

//***********************************************************************************************
void CParticleWorkspace::CNode::removeLocatedBindable(NL3D::CPSLocatedBindable *lb)
{	
	nlassert(_WS);
	if (_InitialPos.isStateMemorized())
	{
		_InitialPos.removeLocatedBindable(lb);
	}
}

//***********************************************************************************************
void CParticleWorkspace::CNode::setModified(bool modified)
{
	nlassert(_WS);
	if (_Modified == modified) return;		
	_Modified = modified;
	_WS->nodeModified(*this);
}

//***********************************************************************************************
void CParticleWorkspace::CNode::unload()
{
	nlassert(_WS);
	if (_PSM)
	{
		NL3D::CShapeBank *oldSB = NL3D::CNELU::Scene->getShapeBank();
		NL3D::CNELU::Scene->setShapeBank(_ShapeBank);
		NL3D::CNELU::Scene->deleteInstance(_PSM);
		NL3D::CNELU::Scene->setShapeBank(oldSB);
		_PSM = NULL;
	}
	delete _ShapeBank;	
	_ShapeBank = NULL;
	_PS = NULL;
	_ParentSkel = NULL;
}


//***********************************************************************************************
void CParticleWorkspace::CNode::setup(NL3D::CParticleSystemModel &psm)
{
	nlassert(_WS);
	psm.setTransformMode(NL3D::CTransform::DirectMatrix);
	psm.setMatrix(NLMISC::CMatrix::Identity);					
	nlassert(_WS->getObjectViewer());
	psm.setEditionMode(true); // this also force the system instanciation
	for(uint k = 0; k < NL3D::MaxPSUserParam; ++k)
	{
		psm.bypassGlobalUserParamValue(k);
	}
	psm.enableAutoGetEllapsedTime(false);
	psm.setEllapsedTime(0.f); // system is paused	
	// initialy, the ps is hidden
	psm.hide();
	// link to the root for manipulation
	_WS->getObjectViewer()->getSceneRoot()->hrcLinkSon(&psm);
	NL3D::CParticleSystem  *ps  = psm.getPS();
	nlassert(ps);
	ps->setFontManager(_WS->getFontManager());
	ps->setFontGenerator(_WS->getFontGenerator());
	ps->stopSound();		
	// flush textures
	psm.Shape->flushTextures(*NL3D::CNELU::Driver, 0);	
}

//***********************************************************************************************
void CParticleWorkspace::CNode::setTriggerAnim(const std::string &anim)
{
	nlassert(_WS);
	if (anim == _TriggerAnim) return;
	_WS->touch();
	_TriggerAnim = anim;
}


//***********************************************************************************************
void CParticleWorkspace::CNode::createEmptyPS()
{	
	nlassert(_WS);
	NL3D::CParticleSystem emptyPS;	
	NL3D::CParticleSystemShape *pss = new NL3D::CParticleSystemShape;
	pss->buildFromPS(emptyPS);	
	std::auto_ptr<NL3D::CShapeBank> sb(new NL3D::CShapeBank);
	std::string shapeName = NLMISC::CFile::getFilename(_RelativePath);
	sb->add(shapeName, pss);
	NL3D::CShapeBank *oldSB = NL3D::CNELU::Scene->getShapeBank();
	NL3D::CNELU::Scene->setShapeBank(sb.get());
	NL3D::CParticleSystemModel *psm = NLMISC::safe_cast<NL3D::CParticleSystemModel *>(NL3D::CNELU::Scene->createInstance(shapeName));
	nlassert(psm);	
	NL3D::CNELU::Scene->setShapeBank(oldSB);				
	setup(*psm);
	unload();
	// commit new values
	_PS = psm->getPS();
	_PSM = psm;
	_ShapeBank = sb.release();
	_Modified = false;
}

//***********************************************************************************************
void CParticleWorkspace::CNode::init(CParticleWorkspace *ws)		
{		
	nlassert(ws);
	_WS = ws;
}

//***********************************************************************************************
void CParticleWorkspace::CNode::setRelativePath(const std::string &relativePath)
{
	nlassert(_WS);
	_RelativePath = relativePath;
}

//***********************************************************************************************
void CParticleWorkspace::CNode::serial(NLMISC::IStream &f)
{	
	nlassert(_WS);
	f.xmlPush("PROJECT_FILE");
		sint version = f.serialVersion(2);
		f.xmlSerial(_RelativePath, "RELATIVE_PATH");
		if (version >= 1)
		{
			f.xmlSerial(_TriggerAnim, "TRIGGER_ANIMATION");
		}
		if (version >= 2)
		{			
			f.xmlSerial(_ParentSkelName, "PARENT_SKEL_NAME");
			f.xmlSerial(_ParentBoneName, "PARENT_BONE_NAME");
		}
	f.xmlPop();		
}

//***********************************************************************************************
void CParticleWorkspace::CNode::savePS()
{
	savePSAs(getFullPath());
}


//***********************************************************************************************
void CParticleWorkspace::CNode::savePSAs(const std::string &fullPath) throw(NLMISC::EStream)
{
	nlassert(_WS);
	if (!_PS) return;	
	// build a shape from our system, and save it
	NL3D::CParticleSystemShape psc;	
	psc.buildFromPS(*_PS);
	NL3D::CShapeStream st(&psc);
	NLMISC::COFile oFile(fullPath);
	oFile.serial(st);	
}

//***********************************************************************************************
std::string CParticleWorkspace::CNode::getFullPath() const
{
	nlassert(_WS);
	char fullPath[MAX_PATH];
	std::string basePath = NLMISC::CPath::standardizeDosPath(_WS->getPath());
	if (PathCombine(fullPath, basePath.c_str(), _RelativePath.c_str()))
	{	
		return fullPath;
	}
	else
	{
		return _RelativePath.c_str();
	}
}

//***********************************************************************************************
bool CParticleWorkspace::CNode::loadPS()
{	
	nlassert(_WS);
	// manually load the PS shape (so that we can deal with exceptions)
	NL3D::CShapeStream ss;
	NLMISC::CIFile inputFile;
	// collapse name
	inputFile.open(getFullPath());
	ss.serial(inputFile);
	std::auto_ptr<NL3D::CShapeBank> sb(new NL3D::CShapeBank);
	std::string shapeName = NLMISC::CFile::getFilename(_RelativePath);
	sb->add(shapeName, ss.getShapePointer());
	NL3D::CShapeBank *oldSB = NL3D::CNELU::Scene->getShapeBank();
	NL3D::CNELU::Scene->setShapeBank(sb.get());
	NL3D::CTransformShape *trs = NL3D::CNELU::Scene->createInstance(shapeName);
	if (!trs)
	{
		NL3D::CNELU::Scene->setShapeBank(oldSB);
		return false;
	}
	NL3D::CParticleSystemModel *psm = dynamic_cast<NL3D::CParticleSystemModel *>(trs);
	if (!psm)
	{
		// Not a particle system
		NL3D::CNELU::Scene->deleteInstance(trs);
		return false;
	}
	NL3D::CNELU::Scene->setShapeBank(oldSB);		
	setup(*psm);
	unload();
	// commit new values
	_PS = psm->getPS();
	_PSM = psm;
	_ShapeBank = sb.release();
	_Modified = false;
	return true; 
}


//***********************************************************************************************
CParticleWorkspace::CParticleWorkspace()
{
	_OV = NULL;
	_Modified = false;
	_FontManager = NULL;
	_FontGenerator = NULL;
	_ModificationCallback = NULL;
}

//***********************************************************************************************
CParticleWorkspace::~CParticleWorkspace()
{
}

//***********************************************************************************************
void CParticleWorkspace::init(CObjectViewer *ov,
							  const std::string &filename,
							  NL3D::CFontManager	*fontManager, 
							  NL3D::CFontGenerator	*fontGenerator 
							 )
{	
	nlassert(!_OV);
	nlassert(ov);
	_OV = ov;
	_Filename = filename;
	_FontManager   = fontManager;
	_FontGenerator = fontGenerator;
}
//***********************************************************************************************
void CParticleWorkspace::setName(const std::string &name)
{
	_Name = name;
	touch();
}


//***********************************************************************************************
CParticleWorkspace::CNode *CParticleWorkspace::addNode(const std::string &filenameWithFullPath)  throw( NLMISC::EStream)
{	
	nlassert(_OV);				
	// Check that file is not already inserted 	
	std::string fileName = NLMISC::CFile::getFilename(filenameWithFullPath);
	for(uint k = 0; k < _Nodes.size(); ++k)
	{
		if (NLMISC::nlstricmp(_Nodes[k]->getFilename(), fileName) == 0) return NULL;		
	}	
	char resultPath[MAX_PATH];
	std::string dosPath = NLMISC::CPath::standardizeDosPath(getPath());
	std::string relativePath;
	if (!PathRelativePathTo(resultPath, dosPath.c_str(), FILE_ATTRIBUTE_DIRECTORY, filenameWithFullPath.c_str(), 0))
	{
		relativePath = filenameWithFullPath; 
	}
	else
	{
		relativePath = resultPath;
	}
	if (relativePath.size() >= 2)
	{
		if (relativePath[0] == '\\' && relativePath[1] != '\\')
		{
			relativePath = relativePath.substr(1);
		}
	}
	CNode *newNode = new CNode;
	newNode->init(this);
	newNode->setRelativePath(relativePath);
	_Nodes.push_back(newNode);
	setModifiedFlag(true);
	return newNode;	
}

//***********************************************************************************************
void CParticleWorkspace::removeNode(uint index)
{
	nlassert(_OV);
	nlassert(index < _Nodes.size());
	_Nodes[index] = NULL; // delete the smart-ptr target
	_Nodes.erase(_Nodes.begin() + index);
	touch();
}

//***********************************************************************************************
void CParticleWorkspace::removeNode(CNode *ptr)
{
	sint index = getIndexFromNode(ptr);
	nlassert(index != -1);
	removeNode((uint) index);
}

//***********************************************************************************************
void CParticleWorkspace::save() throw(NLMISC::EStream)
{
	NLMISC::COFile stream;
	stream.open(_Filename);
	NLMISC::COXml xmlStream;
	xmlStream.init(&stream);
	this->serial(xmlStream);
	clearModifiedFlag();
}

//***********************************************************************************************
void CParticleWorkspace::load()
{
	NLMISC::CIFile stream;
	stream.open(_Filename);
	NLMISC::CIXml xmlStream;
	xmlStream.init(stream);
	this->serial(xmlStream);
	clearModifiedFlag();
}

//***********************************************************************************************
void CParticleWorkspace::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
{
	f.xmlPush("PARTICLE_WORKSPACE");
	f.serialVersion(0);
	f.xmlSerial(_Name, "NAME");	
	f.xmlPush("PS_LIST");
	uint32 numNodes = (uint32)_Nodes.size();
	// TODO : avoid to store the number of nodes
	f.xmlSerial(numNodes, "NUM_NODES");		
	if (f.isReading())
	{
		for(uint k = 0; k < numNodes; ++k)
		{		
			_Nodes.push_back(new CNode());						
			_Nodes.back()->init(this);
			f.serial(*_Nodes.back());
		}	
	}
	else
	{
		for(uint k = 0; k < numNodes; ++k)
		{
			f.serial(*_Nodes[k]);
		}		
	}
	f.xmlPop();
	f.xmlPop();
}

//***********************************************************************************************
std::string CParticleWorkspace::getPath() const
{
	return NLMISC::CPath::standardizePath(NLMISC::CFile::getPath(_Filename));
}

//***********************************************************************************************
sint CParticleWorkspace::getIndexFromNode(CNode *node) const
{
	nlassert(node);
	nlassert(node->getWorkspace() == this);
	for(uint k = 0; k < _Nodes.size(); ++k)
	{
		if (node == _Nodes[k]) return (sint) k;
	}
	return -1;
}

//***********************************************************************************************
bool CParticleWorkspace::containsFile(std::string filename) const
{
	for(uint k = 0; k < _Nodes.size(); ++k)
	{
		if (NLMISC::nlstricmp(filename, _Nodes[k]->getFilename()) == 0) return true;
	}
	return false;
}

//***********************************************************************************************
void CParticleWorkspace::nodeModified(CNode &node)
{
	nlassert(node.getWorkspace() == this);
	if (_ModificationCallback)
	{
		_ModificationCallback->nodeModifiedFlagChanged(node);
	}
}

//***********************************************************************************************
CParticleWorkspace::CNode *CParticleWorkspace::getNodeFromPS(NL3D::CParticleSystem *ps) const
{
	for(uint k = 0; k < _Nodes.size(); ++k)
	{
		if (_Nodes[k]->getPSPointer() == ps) return _Nodes[k];
	}
	return NULL;
}

//***********************************************************************************************
void CParticleWorkspace::setModifiedFlag(bool modified)
{
	if (_Modified == modified) return;
	_Modified = modified;
	if (_ModificationCallback) _ModificationCallback->workspaceModifiedFlagChanged(*this);
}

//***********************************************************************************************
// predicate for workspace sorting
class CParticleWorkspaceSorter
{
public:
	CParticleWorkspace::ISort *Sorter;
	bool operator()(const NLMISC::CSmartPtr<CParticleWorkspace::CNode> &lhs, const NLMISC::CSmartPtr<CParticleWorkspace::CNode> &rhs)
	{
		return Sorter->less(*lhs, *rhs);
	}
};

//***********************************************************************************************
void CParticleWorkspace::sort(ISort &predicate)
{
	CParticleWorkspaceSorter ws;
	ws.Sorter = &predicate;
	std::sort(_Nodes.begin(), _Nodes.end(), ws);
	setModifiedFlag(true);
}

//***********************************************************************************************
bool CParticleWorkspace::isContentModified() const
{
	for(uint k = 0; k < _Nodes.size(); ++k)
	{
		if (_Nodes[k]->isModified()) return true;
	}
	return false;
}

//***********************************************************************************************
void CParticleWorkspace::restickAllObjects(CObjectViewer *ov)
{
	for(uint k = 0; k < _Nodes.size(); ++k)
	{
		std::string parentSkelName = _Nodes[k]->getParentSkelName();
		std::string parentBoneName = _Nodes[k]->getParentBoneName();
		//
		_Nodes[k]->unstickPSFromSkeleton();
		if (!parentSkelName.empty())
		// find instance to stick to in the scene
		for(uint l = 0; l < ov->getNumInstance(); ++l)
		{
			CInstanceInfo *ii = ov->getInstance(l);
			if (ii->TransformShape && ii->Saved.ShapeFilename == parentSkelName)
			{
				NL3D::CSkeletonModel *skel = dynamic_cast<NL3D::CSkeletonModel *>(ii->TransformShape);
				if (skel)
				{
					sint boneID = skel->getBoneIdByName(parentBoneName);
					if (boneID != -1)
					{
						_Nodes[k]->stickPSToSkeleton(skel, (uint) boneID, parentSkelName, parentBoneName);
						break;
					}
				}
			}
		}
	}
}