// 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 <nel/misc/types_nl.h>
#include "browser_model.h"

#include <string>

#include <nel/3d/tile_bank.h>
#include <nel/misc/stream.h>
#include <nel/misc/file.h>

#include <QtGui/QMessageBox>

#include "pic/readpic.h"

using namespace std;
using namespace NL3D;
using namespace NLMISC;

extern CTileBank tileBankBrowser;
static const char* comp[]={"Red", "Green", "Blue", "Alpha", ""};

bool RemovePath (std::string& path, const char* absolutePathToRemplace);

// Rotate a buffer
void rotateBuffer (std::vector<NLMISC::CBGRA> &Buffer, uint &Width, uint &Height)
{
	// Make a copy
	std::vector<NLMISC::CBGRA> copy = Buffer;

	// Rotate
	for (uint y=0; y<Width; y++)
	{
		// Line offset
		uint tmp=y*Width;
		for (uint x=0; x<Width; x++)
		{
			Buffer[y+(Width-x-1)*Height]=copy[x+tmp];
		}
	}

	// New size
	uint tmp=Width;
	Width=Height;
	Height=tmp;
}

/////////////////////////////////////////////////////////////////////////////
// Load a Pic and apply Alpha & Rotation
int loadPixmapBuffer(const std::string& path, std::vector<NLMISC::CBGRA>& Buffer, std::vector<NLMISC::CBGRA>* Alpha, int rot)
{	
	uint Width;
	uint Height;
	if (PIC_LoadPic(path, Buffer, Width, Height))
	{
		while (rot)
		{
			// Rotate the buffer
			rotateBuffer (Buffer, Width, Height);
			rot--;
		}

		if ( (Alpha) && (Alpha->size()==Buffer.size()) )
		{
			// Pre mul RGB componates by Alpha one
			int nPixelCount=(int)(Width*Height);
			for (int p=0; p<nPixelCount; p++)
			{
				// Invert alpha ?
				int alpha=(*Alpha)[p].A;
				Buffer[p].R=(uint8)(((int)Buffer[p].R*alpha)>>8);
				Buffer[p].G=(uint8)(((int)Buffer[p].G*alpha)>>8);
				Buffer[p].B=(uint8)(((int)Buffer[p].B*alpha)>>8);
			}
		}

		return 1;
	}
	else
		return 0;
}


/////////////////////////////////////////////////////////////////////////////
//TileInfo


TileInfo::TileInfo():tileType(UnSet)
{
	this->loaded = 0;
	this->nightLoaded = 0;
	this->alphaLoaded = 0;
}



TileInfo::TileInfo(int id, TileType tileType):id(id), tileType(tileType)
{
	this->loaded = 0;
	this->nightLoaded = 0;
	this->alphaLoaded = 0;
}

void TileInfo::Init(int id, TileType tileType)
{
	this->id = id;
	this->tileType = tileType;
	this->loaded = 0;
	this->nightLoaded = 0;
	this->alphaLoaded = 0;
}


bool TileInfo::Load (int index, std::vector<NLMISC::CBGRA>* Alpha)
{
	bool bRes=true;
	if (!loaded && getRelativeFileName (Diffuse, index)!="")
	{
		path = fixPath(tileBankBrowser.getAbsPath() + getRelativeFileName (Diffuse, index));
		
		
	
		if ( !loadPixmapBuffer( path, Bits, Alpha, 0))
		{
			bRes=false;
			QMessageBox::information( NULL, QObject::tr("Can't load Diffuse file"),  QString( (path.c_str()) ));

		}
		else
			loaded=1;
	}
	if (!nightLoaded && getRelativeFileName (Additive, index)!="")
	{
		nightPath = fixPath(tileBankBrowser.getAbsPath() + getRelativeFileName (Additive, index));
		if (!loadPixmapBuffer( nightPath, nightBits, Alpha, 0))
		{
			bRes=false;
			QMessageBox::information( NULL, QObject::tr("Can't load Additive file"),  QString( nightPath.c_str() ) );
		}
		else
			nightLoaded=1;
	}
	if (!alphaLoaded && getRelativeFileName (::Alpha, index)!="")
	{
		alphaPath = fixPath(tileBankBrowser.getAbsPath() + getRelativeFileName (::Alpha, index));
		if (!loadPixmapBuffer( alphaPath, alphaBits, NULL, tileBankBrowser.getTile (index)->getRotAlpha ()))
		{
			bRes=false;
			QMessageBox::information( NULL, QObject::tr("Can't load Alpha file"),  QString( alphaPath.c_str() ) );

		}
		else
			alphaLoaded=1;
	}

	return bRes;
}

std::string TileInfo::fixPath(const std::string &path) {
	// Fix the Windows path issues for subpaths.
	std::string searchStr("\\");
	std::string replaceStr("/");
	string::size_type pos = 0;
	std::string pathStr = path;

	while( (pos = pathStr.find(searchStr, pos)) != std::string::npos) {
		pathStr.replace(pos, searchStr.size(), replaceStr);
		pos++;
	}
	return pathStr;
}

void TileInfo::Delete ()
{
	loaded=0;
	Bits.resize(0);

	nightLoaded=0;
	nightBits.resize(0);
	
	alphaLoaded=0;
	alphaBits.resize(0);
}

const std::string TileInfo::getRelativeFileName (TileTexture texture, int index)
{
	nlassert(this->tileType != UnSet);

	std::string currentPath;
	if (tileType != Displace)
	{
		currentPath = tileBankBrowser.getTile(index)->getRelativeFileName ((CTile::TBitmap)texture);
	}
	else
	{
		if (texture == Diffuse)
		{
			currentPath = tileBankBrowser.getDisplacementMap(index);

			//TODO titegus: remove MAGIC STRING "EmptyDisplacementMap" in Nel \tile_bank.cpp\void CTileNoise::setEmpty () or add an IsEmpty() method !!!!
			if (currentPath == "EmptyDisplacementMap")
				currentPath = "";
		}	
	}

	return currentPath;
}



//TileList
TileList::TileList()
{
	_tileSet = -1;

	// Add 48 transitions
	int i;
	for (i=0; i<CTileSet::count; i++)
	{
		TileInfo info(i, Transition);
		theListTransition.push_back (info);
	}

	// Add 16 displacements
	for (i=0; i<CTileSet::CountDisplace; i++)
	{
		TileInfo info(i, Displace);
		theListDisplacement.push_back (info);
	}
}


int TileList::addTile128 ()
{

	int index;
	tileBankBrowser.getTileSet (_tileSet)->addTile128 (index, tileBankBrowser);
	nlassert (index==(sint)theList128.size());

	TileInfo info(index, _128x128);
	theList128.push_back (info);

	return index;
}

int TileList::addTile256 ()
{
	int index;
	tileBankBrowser.getTileSet (_tileSet)->addTile256 (index, tileBankBrowser);
	nlassert (index==(sint)theList256.size());

	TileInfo info(index, _256x256);
	theList256.push_back (info);

	return index;
}

bool TileList::setTile128 (int tile, const std::string& name, NL3D::CTile::TBitmap type)
{
	// Remove the absolute path from the path name
	std::string troncated=name;
	if (RemovePath (troncated, tileBankBrowser.getAbsPath ().c_str()))
	{
		vector<NLMISC::CBGRA> tampon;
		uint Width;
		uint Height;
		if (!PIC_LoadPic(tileBankBrowser.getAbsPath ()+troncated, tampon, Width, Height))
		{
			return ( QMessageBox::Yes == QMessageBox::question( NULL, QObject::tr("Can't load bitmap."),  QString( ((tileBankBrowser.getAbsPath ()+troncated)+"\nContinue ?").c_str() ), QMessageBox::Yes | QMessageBox::No));
		}
		else
		{
			CTileBorder border;
			border.set (Width, Height, tampon);

			CTileSet::TError error;
			int pixel=-1;
			int composante=4;
			if (type == CTile::additive)
				error=CTileSet::ok;
			else
				error=tileBankBrowser.getTileSet(_tileSet)->checkTile128 (type, border, pixel, composante);

			if ((error!=CTileSet::ok)&&(error!=CTileSet::addFirstA128128))
			{
				QString pixelMessage = QObject::tr("%1\nPixel: %2(%3).\nContinue ?").arg(CTileSet::getErrorMessage (error)).arg(pixel).arg(comp[composante]);
				return ( QMessageBox::Yes == QMessageBox::question( NULL, QObject::tr("Can't set Bitmap"),  pixelMessage, QMessageBox::Yes | QMessageBox::No) );
			}
			else
			{
				if (error==CTileSet::addFirstA128128)
					tileBankBrowser.getTileSet(_tileSet)->setBorder (type, border);

				tileBankBrowser.getTileSet(_tileSet)->setTile128 (tile, troncated, type, tileBankBrowser);
				switch (type)
				{
				case CTile::diffuse:
					theList128[tile].loaded=0;
					break;
				case CTile::additive:
					theList128[tile].nightLoaded=0;
					break;
				case CTile::alpha:
					theList128[tile].alphaLoaded=0;
					break;
				}
				Reload(tile, tile + 1, _128x128);
			}
		}
	}
	else
	{
		// Error: bitmap not in the absolute path..
		QString notInAbsolutePathMessage = QObject::tr("The bitmap %1 is not in the absolute path %2.\nContinue ?").arg(name.c_str()).arg(tileBankBrowser.getAbsPath ().c_str());
		return ( QMessageBox::Yes == QMessageBox::question( NULL, QObject::tr("Load error"),  notInAbsolutePathMessage, QMessageBox::Yes | QMessageBox::No));
	}

	return true;
}

bool TileList::setTile256 (int tile, const std::string& name, NL3D::CTile::TBitmap type)
{
	// Remove the absolute path from the path name
	std::string troncated=name;
	if (RemovePath (troncated, tileBankBrowser.getAbsPath ().c_str()))
	{
		vector<NLMISC::CBGRA> tampon;
		uint Width;
		uint Height;
		if (!PIC_LoadPic(tileBankBrowser.getAbsPath ()+troncated, tampon, Width, Height))
		{
			return ( QMessageBox::Yes == QMessageBox::question( NULL, QObject::tr("Can't load bitmap."),  QString( ((tileBankBrowser.getAbsPath ()+troncated)+"\nContinue ?").c_str() ), QMessageBox::Yes | QMessageBox::No) );

		}
		else
		{
			CTileBorder border;
			border.set (Width, Height, tampon);
			
			CTileSet::TError error;
			int pixel=-1;
			int composante=4;

			if (type == CTile::additive)
				error=CTileSet::ok;
			else
				error=tileBankBrowser.getTileSet(_tileSet)->checkTile256 (type, border, pixel, composante);
			if ((error!=CTileSet::ok))
			{
				QString pixelMessage = QObject::tr("%1\nPixel: %2(%3).\nContinue ?").arg(CTileSet::getErrorMessage (error)).arg(pixel).arg(comp[composante]);
				return ( QMessageBox::Yes == QMessageBox::question( NULL, QObject::tr("Can't set Bitmap"),  pixelMessage, QMessageBox::Yes | QMessageBox::No) );
			}
			else
			{
				tileBankBrowser.getTileSet(_tileSet)->setTile256 (tile, troncated, type, tileBankBrowser);
						switch (type)
				{
				case CTile::diffuse:
					theList256[tile].loaded=0;
					break;
				case CTile::additive:
					theList256[tile].nightLoaded=0;
					break;
				case CTile::alpha:
					theList256[tile].alphaLoaded=0;
					break;
				}
				Reload(tile, tile + 1, _256x256);
			}
		}
	}
	else
	{
		// Error: bitmap not in the absolute path..
		QString notInAbsolutePathMessage = QObject::tr("The bitmap %1 is not in the absolute path %2.\nContinue ?").arg(name.c_str()).arg(tileBankBrowser.getAbsPath ().c_str());
		return ( QMessageBox::Yes == QMessageBox::question( NULL, QObject::tr("Load error"),  notInAbsolutePathMessage, QMessageBox::Yes | QMessageBox::No));
	}

	return true;
}

bool TileList::setTileTransition (int tile, const std::string& name, NL3D::CTile::TBitmap type)
{
	// Remove the absolute path from the path name
	std::string troncated=name;
	if (RemovePath (troncated, tileBankBrowser.getAbsPath ().c_str()))
	{
		// No alpha, use setTileTransitionAlpha
		nlassert (CTile::alpha!=type);

		vector<NLMISC::CBGRA> tampon;
		uint Width;
		uint Height;
		if (!PIC_LoadPic(tileBankBrowser.getAbsPath ()+troncated, tampon, Width, Height))
		{
			return ( QMessageBox::Yes == QMessageBox::question( NULL, QObject::tr("Can't load bitmap."),  QString( ((tileBankBrowser.getAbsPath ()+troncated)+"\nContinue ?").c_str() ), QMessageBox::Yes | QMessageBox::No) );
		}
		else
		{
			CTileBorder border;
			border.set (Width, Height, tampon);
			
			CTileSet::TError error;
			int pixel=-1;
			int composante=4;
			if (type == CTile::additive)
				error=CTileSet::ok;
			else
				error=tileBankBrowser.getTileSet(_tileSet)->checkTile128 (type, border, pixel, composante);
			if ((error!=CTileSet::ok)&&(error!=CTileSet::addFirstA128128))
			{
				QString pixelMessage = QObject::tr("%1\nPixel: %2(%3).\nContinue ?").arg(CTileSet::getErrorMessage (error)).arg(pixel).arg(comp[composante]);
				return ( QMessageBox::Yes == QMessageBox::question( NULL, QObject::tr("Can't set Bitmap"),  pixelMessage, QMessageBox::Yes | QMessageBox::No) );
			}
			else
			{
				if (error==CTileSet::addFirstA128128)
					tileBankBrowser.getTileSet(_tileSet)->setBorder (type, border);
				tileBankBrowser.getTileSet(_tileSet)->setTileTransition ((CTileSet::TTransition)tile, troncated, type, tileBankBrowser, border);
				switch (type)
				{
				case CTile::diffuse:
					theListTransition[tile].loaded=0;
					break;
				case CTile::additive:
					theListTransition[tile].nightLoaded=0;
					break;
				case CTile::alpha:
					theListTransition[tile].alphaLoaded=0;
					break;
				}
				Reload(tile, tile + 1, Transition);
			}
		}
	}
	else
	{
		// Error: bitmap not in the absolute path..
		QString notInAbsolutePathMessage = QObject::tr("The bitmap %1 is not in the absolute path %2.\nContinue ?").arg(name.c_str()).arg(tileBankBrowser.getAbsPath ().c_str());
		return ( QMessageBox::Yes == QMessageBox::question( NULL, QObject::tr("Load error"),  notInAbsolutePathMessage, QMessageBox::Yes | QMessageBox::No) );
	}

	return true;
}

bool TileList::setDisplacement (int tile, const std::string& name, NL3D::CTile::TBitmap type)
{
	// Remove the absolute path from the path name
	std::string troncated=name;
	if (RemovePath (troncated, tileBankBrowser.getAbsPath ().c_str()))
	{

		vector<NLMISC::CBGRA> tampon;
		uint Width;
		uint Height;
		if (!PIC_LoadPic(tileBankBrowser.getAbsPath ()+troncated, tampon, Width, Height))
		{
			return ( QMessageBox::Yes == QMessageBox::question( NULL, QObject::tr("Can't load bitmap"),  QString( ((tileBankBrowser.getAbsPath ()+troncated)+"\nContinue ?").c_str() ), QMessageBox::Yes | QMessageBox::No) );
		}
		else
		{
			// Check the size
			if ( (Width!=32) || (Height!=32) )
			{
				// Error message
				return ( QMessageBox::Yes == QMessageBox::question( NULL, QObject::tr("Can't set Bitmap"), QString( (troncated+"\nInvalid size: displacement map must be 32x32 8 bits.\nContinue ?").c_str()), QMessageBox::Yes | QMessageBox::No) );
			}
			else
			{
				// change the file name of the displacement map
				tileBankBrowser.getTileSet(_tileSet)->setDisplacement ((CTileSet::TDisplacement)tile, troncated, tileBankBrowser);

				// Loaded
				theListDisplacement[tile].loaded=0;
				Reload(tile, tile + 1, Displace);
			}
		}

	}
	else
	{
		// Error: bitmap not in the absolute path..
		QString notInAbsolutePathMessage = QObject::tr("The bitmap %1 is not in the absolute path %2.\nContinue ?").arg(name.c_str()).arg(tileBankBrowser.getAbsPath ().c_str());
		return ( QMessageBox::Yes == QMessageBox::question( NULL, QObject::tr("Load error"),  notInAbsolutePathMessage, QMessageBox::Yes | QMessageBox::No) );
	}

	return true;
}

bool TileList::setTileTransitionAlpha (int tile, const std::string& name, int rot)
{
	// Remove the absolute path from the path name
	std::string troncated=name;
	if (RemovePath (troncated, tileBankBrowser.getAbsPath ().c_str()))
	{
		vector<NLMISC::CBGRA> tampon;
		uint Width;
		uint Height;
		if (!PIC_LoadPic(tileBankBrowser.getAbsPath ()+troncated, tampon, Width, Height))
		{
			return ( QMessageBox::Yes == QMessageBox::question( NULL, QObject::tr("Can't load bitmap"),  QString( ((tileBankBrowser.getAbsPath ()+troncated)+"\nContinue ?").c_str() ), QMessageBox::Yes | QMessageBox::No) );
		}
		else
		{
			CTileBorder border;
			border.set (Width, Height, tampon);

			// rotate the border
			int rotBis=rot;
			while (rotBis)
			{
				border.rotate ();
				rotBis--;
			}
			
			CTileSet::TError error;
			int indexError;
			int pixel=-1;
			int composante=4;
			if (((error=tileBankBrowser.getTileSet(_tileSet)->checkTileTransition ((CTileSet::TTransition)tile, CTile::alpha, border, indexError,
				pixel, composante))!=CTileSet::ok))
			{
				QString pixelMessage;
				if ((error==CTileSet::topInterfaceProblem)||(error==CTileSet::bottomInterfaceProblem)||(error==CTileSet::leftInterfaceProblem)
					||(error==CTileSet::rightInterfaceProblem)||(error==CTileSet::topBottomNotTheSame)||(error==CTileSet::rightLeftNotTheSame)
					||(error==CTileSet::topInterfaceProblem))
				{
					if (indexError!=-1)
						pixelMessage = QObject::tr("%1.\nIncompatible with tile nb %4.\nPixel: %2(%3).\nContinue ?").arg(CTileSet::getErrorMessage (error)).arg(pixel).arg(comp[composante]).arg(indexError);
					else
						pixelMessage = QObject::tr("%1.\nIncompatible with the 128x128 tile.\nPixel: %2(%3).\nContinue ?").arg(CTileSet::getErrorMessage (error)).arg(pixel).arg(comp[composante]);

				}
				else
					pixelMessage = QObject::tr("%1.\nIncompatible filled tile.\nContinue ?").arg(CTileSet::getErrorMessage (error));
				
				return ( QMessageBox::Yes == QMessageBox::question( NULL, QObject::tr("Can't set Bitmap"),  pixelMessage, QMessageBox::Yes | QMessageBox::No) );
			}
			else
			{
				tileBankBrowser.getTileSet(_tileSet)->setTileTransitionAlpha ((CTileSet::TTransition)tile, troncated, tileBankBrowser, border, rot);
				theListTransition[tile].alphaLoaded=0;
				theListTransition[tile].Load (tileBankBrowser.getTileSet(_tileSet)->getTransition(tile)->getTile(), NULL);
			}
		}
	}
	else
	{
		// Error: bitmap not in the absolute path..
		QString notInAbsolutePathMessage = QObject::tr("The bitmap %1 is not in the absolute path %2.\nContinue ?").arg(name.c_str()).arg(tileBankBrowser.getAbsPath ().c_str());
		return ( QMessageBox::Yes == QMessageBox::question( NULL, QObject::tr("Load error"),  notInAbsolutePathMessage, QMessageBox::Yes | QMessageBox::No) );
	}

	return true;
}

void TileList::removeTile128 (int index)
{
	tileBankBrowser.getTileSet (_tileSet)->removeTile128 (index, tileBankBrowser);
	theList[0].erase (theList[0].begin()+index);
	for (int i=0; i<(sint)theList[0].size(); i++)
	{
		theList[0][i].setId(i);
	}
}

void TileList::removeTile256 (int index)
{
	tileBankBrowser.getTileSet (_tileSet)->removeTile256 (index, tileBankBrowser);
	theList[1].erase (theList[1].begin()+index);
	for (int i=0; i<(sint)theList[1].size(); i++)
	{
		theList[1][i].setId(i);
	}
}

void TileList::clearTile128 (int index, CTile::TBitmap bitmap)
{
	switch (bitmap)
	{
		case CTile::diffuse:
			theList128[index].loaded=0;
			theList128[index].Bits.resize(0);
			break;
		case CTile::additive:
			theList128[index].nightLoaded=0;
			theList128[index].nightBits.resize(0);
			break;
		case CTile::alpha:
			nlassert(0);
			break;
	}
	tileBankBrowser.getTileSet (_tileSet)->clearTile128 (index, bitmap, tileBankBrowser);
}

void TileList::clearTile256 (int index, CTile::TBitmap bitmap)
{
	switch (bitmap)
	{
		case CTile::diffuse:
			theList256[index].loaded=0;
			theList256[index].Bits.resize(0);
			break;
		case CTile::additive:
			theList256[index].nightLoaded=0;
			theList256[index].nightBits.resize(0);
			break;
		case CTile::alpha:
			nlassert(0);
			break;
	}
	tileBankBrowser.getTileSet (_tileSet)->clearTile256 (index, bitmap, tileBankBrowser);
}

void TileList::clearTransition (int index, CTile::TBitmap bitmap)
{
	switch (bitmap)
	{
		case CTile::diffuse:
			theListTransition[index].loaded=0;
			theListTransition[index].Bits.resize(0);
			break;
		case CTile::additive:
			theListTransition[index].nightLoaded=0;
			theListTransition[index].nightBits.resize(0);
			break;
		case CTile::alpha:
			theListTransition[index].alphaLoaded=0;
			theListTransition[index].alphaBits.resize(0);
			break;
	}
	tileBankBrowser.getTileSet (_tileSet)->clearTransition ((CTileSet::TTransition)index, bitmap, tileBankBrowser);
}

void TileList::clearDisplacement (int index, CTile::TBitmap bitmap)
{
	switch (bitmap)
	{
		case CTile::diffuse:
			theListDisplacement[index].loaded=0;
			theListDisplacement[index].Bits.resize(0);
			break;
		case CTile::additive:
			nlassert(0);
			break;
		case CTile::alpha:
			nlassert(0);
			break;
	}

	tileBankBrowser.getTileSet (_tileSet)->clearDisplacement ((CTileSet::TDisplacement)index, tileBankBrowser);
}

tilelist::iterator TileList::GetFirst(int n)
{
	return theList[n].begin();
}

tilelist::iterator TileList::GetLast(int n)
{
	return theList[n].end();
}

tilelist::iterator TileList::Get(int i, int n)
{
	return theList[n].begin()+i;
}


int TileList::GetSize(int n)
{
	return (int)theList[n].size();
}


void TileList::Reload(int first, int last, TileType n) //recharge en memoire une tranche de tiles
{
	for (int i=first; i<last; i++)
	{
		switch (n)
		{
			case _128x128:
			{
				theList[n][i].Load (tileBankBrowser.getTileSet(_tileSet)->getTile128 (i), NULL);
				break;
			}
			case _256x256:
			{
				theList[n][i].Load (tileBankBrowser.getTileSet(_tileSet)->getTile256 (i), NULL);
				break;
			}
			case Transition:
			{
				int index=tileBankBrowser.getTileSet(_tileSet)->getTransition (i)->getTile();
				if (index!=-1)
					theList[n][i].Load (index, &theListTransition[i].alphaBits);
				break;
			}
			case Displace:
			{
				int index=tileBankBrowser.getTileSet(_tileSet)->getDisplacementTile ((CTileSet::TDisplacement)i);
				if (index!=-1)
					theList[n][i].Load (index, NULL);
				break;
			}

		}
	}
}