// 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 "color_modifier.h"
#include "color_mask.h"
#include "hls_bank_texture_info.h"
#define HAS_INFO_GENERATION 0
#if HAS_INFO_GENERATION
#include "info_color_generation.h"
#include "info_mask_generation.h"
#endif

#include <nel/misc/types_nl.h>
#include <nel/misc/config_file.h>
#include <nel/misc/path.h>
#include <nel/misc/file.h>
#include <nel/misc/bitmap.h>
#include <nel/misc/debug.h>

#include <time.h>

using namespace NLMISC;
using namespace std;


string	DivideBy2Dir= "/d4/";
//string	HlsInfoDir= "hlsInfo/";


// ========================================================================================================
// This tool is for creating various colored texture from a base texture.
// Parts of a base texture can have hue, contrast, luminosity shifting etc.
// Each part is defined by a mask. The red component of it is considered as an alpha value (not the alpha, because it is faster to create a grey texture with photoshop..)
// The result is serialized in png or tga files.
// ========================================================================================================
// why this tool ? : it is useful to create various colored cloth and skin textures
// Not all hardware allow it to manage that at runtime (lack for palettized textures or pixel shaders...)
//=========================================================================================================


/// describes the building infos
struct CBuildInfo
{
	std::string					InputPath;
	std::string					OutputPath;
	std::string					HlsInfoPath;
	std::string					CachePath;
	std::vector<std::string>    BitmapExtensions; // the supported extension for bitmaps
	std::string					OutputFormat; // png or tga
	std::string					DefaultSeparator;
	TColorMaskVect				ColorMasks;
	// how to shift right the size of the src Bitmap for the .hlsinfo
	uint						LowDefShift;
	uint						OptimizeTextures; // 0 = don't optimize, 1 = check, 2 = optimize
};


/// Temporary
static void validateCgiInfo();
static void validateGtmInfo();
/// Temporary


/** Build the infos we need from a config file
  * It build a list of masks infos
  */
static void BuildMasksFromConfigFile(NLMISC::CConfigFile &cf,
									 TColorMaskVect &colorMasks);

/// Build the colored versions
static void BuildColoredVersions(const CBuildInfo &bi);

///
static void BuildColoredVersionForOneBitmap(const CBuildInfo &bi, const std::string &fileNameWithExtension, bool mustDivideBy2);
/** Check if building if reneeded by looking in the cache directory.
  * If the texture is found in the cache it is just copied
  */
static bool CheckIfNeedRebuildColoredVersionForOneBitmap(const CBuildInfo &bi, const std::string &fileNameWithExtension, bool mustDivideBy2);



/// replace slashes by the matching os value in a file name
static std::string replaceSlashes(const std::string &src)
{
	std::string result = src;
	for(uint k = 0; k < result.size(); ++k)
	#ifdef NL_OS_WINDOWS
		if (result[k] == '/') result[k] = '\\';
	#else
		if (result[k] == '\\') result[k] = '/';
	#endif
	return result;
}



///=====================================================
int main(int argc, char* argv[])
{
	// Filter addSearchPath
	NLMISC::createDebug();

	//"panoply.cfg" "gtm" "fyros"

#if HAS_INFO_GENERATION
	if(!strcmp(argv[2], "gtm") || !strcmp(argv[2], "cgi"))
	{
		NLMISC::CConfigFile cf;

		std::string _Path_Input_TexBases;
		std::string _Path_Input_Masks;
		std::string _Path_Output_MasksOptimized;
		std::string _Path_Output_Gtm;
		std::string _Path_Output_Cgi;

		try
		{
			/// load the config file
			cf.load(argv[1]);

			/// look paths
			try
			{
				NLMISC::CConfigFile::CVar &additionnal_paths = cf.getVar ("additionnal_paths");
				for (uint k = 0; k < (uint) additionnal_paths.size(); ++k)
				{
					NLMISC::CPath::addSearchPath(NLMISC::CPath::standardizePath(additionnal_paths.asString(k)),true, false);
				}
			}
			catch (const NLMISC::EUnknownVar &)
			{
			}

			/// repertory of textures bases (no-colorized)
			try
			{
				_Path_Input_TexBases = NLMISC::CPath::standardizePath(cf.getVar ("input_path_texbase").asString());
			}
			catch (const NLMISC::EUnknownVar &)
			{
			}

			/// repertory of masks (original)
			try
			{
				_Path_Input_Masks = NLMISC::CPath::standardizePath(cf.getVar ("input_path_mask").asString());
			}
			catch (const NLMISC::EUnknownVar &)
			{
			}

			/// optimized masks output directory created
			try
			{
				_Path_Output_MasksOptimized = NLMISC::CPath::standardizePath(cf.getVar ("output_path_mask_optimized").asString());
			}
			catch (const NLMISC::EUnknownVar &)
			{
			}

			/// file of infos about colorization average for the client
			try
			{
				_Path_Output_Cgi = NLMISC::CPath::standardizePath(cf.getVar ("output_path_cgi").asString());
			}
			catch (const NLMISC::EUnknownVar &)
			{
			}

			/// file of infos about multiplexing texture for the client
			try
			{
				_Path_Output_Gtm = NLMISC::CPath::standardizePath(cf.getVar ("output_path_gtm").asString());
			}
			catch (const NLMISC::EUnknownVar &)
			{
			}

		}
		catch (const std::exception &e)
		{
			nlerror("Panoply building failed: %s", e.what());
			return -1;
		}

		/// oriented program

		if( !strcmp(argv[2], "gtm"))	/// masks optimized
		{
			CInfoMaskGeneration infoMaskGen(_Path_Input_TexBases,
											_Path_Input_Masks,
											_Path_Output_MasksOptimized,
											_Path_Output_Gtm,
											argv[3],
											1);
			infoMaskGen.init();
			infoMaskGen.process();
		}
		else if( !strcmp(argv[2], "cgi")) /// colorized information
		{
			CInfoColorGeneration infoColor(_Path_Input_TexBases,
										   _Path_Input_Masks,
										   _Path_Output_Cgi,
										   argv[3],
										   1);
			infoColor.init();
			infoColor.process();
		}
	}
	else
	{
#endif
		NLMISC::InfoLog->addNegativeFilter ("adding the path");

		if (argc != 2)
		{
			nlinfo("Usage : %s [config_file name]", argv[0]);
			return -1;
		}

		CBuildInfo bi;

		/////////////////////////////////////////
		// reads infos from the config files   //
		/////////////////////////////////////////

			NLMISC::CConfigFile cf;
			try
			{
				/// load the config file
				cf.load(argv[1]);

				/// colors masks
				BuildMasksFromConfigFile(cf, bi.ColorMasks);

				/// look paths
				try
				{
					NLMISC::CConfigFile::CVar &additionnal_paths = cf.getVar ("additionnal_paths");
					for (uint k = 0; k < (uint) additionnal_paths.size(); ++k)
					{
						NLMISC::CPath::addSearchPath(NLMISC::CPath::standardizePath(additionnal_paths.asString(k)));
					}
				}
				catch (const NLMISC::EUnknownVar &)
				{
				}

				/// input
				try
				{
					bi.InputPath = NLMISC::CPath::standardizePath(cf.getVar ("input_path").asString());
				}
				catch (const NLMISC::EUnknownVar &)
				{
				}

				/// output
				try
				{
					bi.OutputPath = NLMISC::CPath::standardizePath(cf.getVar ("output_path").asString());
				}
				catch (const NLMISC::EUnknownVar &)
				{
				}

				/// hls info path
				try
				{
					bi.HlsInfoPath = NLMISC::CPath::standardizePath(cf.getVar("hls_info_path").asString());
				}
				catch (const NLMISC::EUnknownVar &)
				{
					bi.HlsInfoPath = "hlsInfo/";
				}

				/// output
				try
				{
					bi.CachePath = NLMISC::CPath::standardizePath(cf.getVar ("cache_path").asString());
				}
				catch (const NLMISC::EUnknownVar &)
				{
				}

				/// output format
				try
				{
					bi.OutputFormat = "." + cf.getVar ("output_format").asString();
				}
				catch (const NLMISC::EUnknownVar &)
				{
					bi.OutputFormat = ".tga";
				}

				/// default ascii character for unused masks
				try
				{
					bi.DefaultSeparator = cf.getVar ("default_separator").asString();
				}
				catch (const NLMISC::EUnknownVar &)
				{
					bi.DefaultSeparator = '_';
				}
				/// extension for bitmaps
				try
				{
					NLMISC::CConfigFile::CVar &bitmap_extensions = cf.getVar ("bitmap_extensions");
					for (uint k = 0; k < (uint) bitmap_extensions.size(); ++k)
					{
						std::string ext = "." + NLMISC::toLower(bitmap_extensions.asString(k));
						if (std::find(bi.BitmapExtensions.begin(), bi.BitmapExtensions.end(), ext) == bi.BitmapExtensions.end())
						{
							bi.BitmapExtensions.push_back(ext);
						}
					}
				}
				catch (const NLMISC::EUnknownVar &)
				{
					bi.BitmapExtensions[0].resize(1);
					bi.BitmapExtensions[0] = bi.OutputFormat;
				}

				try
				{
					bi.LowDefShift = cf.getVar ("low_def_shift").asInt();
				}
				catch (const NLMISC::EUnknownVar &)
				{
					// tranform 512*512 to 64*64 by default
					bi.LowDefShift= 3;
				}

				try
				{
					bi.OptimizeTextures = cf.getVar ("optimize_textures").asInt();
				}
				catch (const NLMISC::EUnknownVar &)
				{
					// don't optimize files by default
					bi.OptimizeTextures = 0;
				}
			}
			catch (const std::exception &e)
			{
				nlerror("Panoply building failed: %s", e.what());
				return -1;
			}

		////////////////////////////////
		// Build the colored versions //
		////////////////////////////////
		try
		{
			BuildColoredVersions(bi);
		}
		catch (const std::exception &e)
		{
			nlerror("Something went wrong while building bitmap: %s", e.what());
			return -1;
		}
		return 0;
#if HAS_INFO_GENERATION
	}
#endif
}

///======================================================
#if HAS_INFO_GENERATION
static void validateCgiInfo()
{
	NLMISC::CIFile f;


	vector<StrInfoTexColor> temp;
	uint version;

	try
	{
		f.open(CPath::lookup("info_color_texbase_fyros.cgi"));

		f.serialCont(temp);

	}
	catch(const std::exception &e)
	{
		nlerror("Panoply building failed: %s", e.what());
	}

	uint16 a = temp.size();

	f.close();
}

///======================================================

static void validateGtmInfo()
{

}
#endif
///======================================================
static void BuildMasksFromConfigFile(NLMISC::CConfigFile &cf,
									 TColorMaskVect &colorMasks)

{
	/// get a list of the alpha mask extensions
	NLMISC::CConfigFile::CVar &mask_extensions = cf.getVar ("mask_extensions");
	colorMasks.resize(mask_extensions.size());

	/// For each kind of mask, build a list of the color modifiers
	for (uint k = 0; k < (uint) mask_extensions.size(); ++k)
	{
		colorMasks[k].MaskExt = mask_extensions.asString(k);
		NLMISC::CConfigFile::CVar &luminosities    = cf.getVar (colorMasks[k].MaskExt + "_luminosities");
		NLMISC::CConfigFile::CVar &contrasts	   = cf.getVar (colorMasks[k].MaskExt + "_constrasts");
		NLMISC::CConfigFile::CVar &hues			   = cf.getVar (colorMasks[k].MaskExt + "_hues");
		NLMISC::CConfigFile::CVar &lightness	   = cf.getVar (colorMasks[k].MaskExt + "_lightness");
		NLMISC::CConfigFile::CVar &saturation	   = cf.getVar (colorMasks[k].MaskExt + "_saturations");
		NLMISC::CConfigFile::CVar &colorIDs		   = cf.getVar (colorMasks[k].MaskExt + "_color_id");

		if (luminosities.size() != contrasts.size()
			|| luminosities.size() != hues.size()
			|| luminosities.size() != lightness.size()
			|| luminosities.size() != saturation.size()
			|| luminosities.size() != colorIDs.size()
			)
		{
			throw NLMISC::Exception("All color descriptors must have the same number of arguments");
		}
		colorMasks[k].CMs.resize(luminosities.size());
		for (uint l = 0; l < (uint) luminosities.size(); ++l)
		{
			CColorModifier &cm = colorMasks[k].CMs[l];
			cm.Contrast		   = contrasts.asFloat(l);
			cm.Luminosity      = luminosities.asFloat(l);
			cm.Hue		       = hues.asFloat(l);
			cm.Lightness       = lightness.asFloat(l);
			cm.Saturation      = saturation.asFloat(l);

			cm.ColID = colorIDs.asString(l);
		}
	}
}

///======================================================
static void BuildColoredVersions(const CBuildInfo &bi)
{
	if (!NLMISC::CFile::isExists(bi.InputPath))
	{
		nlerror("Path not found: %s", bi.InputPath.c_str());
		return;
	}
	for(uint sizeVersion= 0; sizeVersion<2; sizeVersion++)
	{
		std::vector<std::string> files;
		if(sizeVersion==0)
			// get the original (not to dvide) dir
			NLMISC::CPath::getPathContent (bi.InputPath, false, false, true, files);
		else
			// get the dir content with texture that must be divided by 2.
			NLMISC::CPath::getPathContent (bi.InputPath+DivideBy2Dir, false, false, true, files);

		// For all files found
		for (uint k = 0;  k < files.size(); ++k)
		{
			for (uint l = 0; l < bi.BitmapExtensions.size(); ++l)
			{
				std::string fileExt = "." + NLMISC::toLower(NLMISC::CFile::getExtension(files[k]));
				if (fileExt == bi.BitmapExtensions[l])
				{
					//nlwarning("Processing : %s ", files[k].c_str());
					try
					{
						if (CheckIfNeedRebuildColoredVersionForOneBitmap(bi, NLMISC::CFile::getFilename(files[k]),
																		sizeVersion==1) )
						{
							BuildColoredVersionForOneBitmap(bi,
															NLMISC::CFile::getFilename(files[k]),
														   sizeVersion==1);
						}
						else
						{
							//nlwarning(("No need to rebuild " + NLMISC::CFile::getFilename(files[k])).c_str());
						}
					}
					catch (const std::exception &e)
					{
						nlerror("Processing of %s failed: %s", files[k].c_str(), e.what());
					}
				}
			}
		}
	}
}


/// used to loop throiugh the process, avoiding unused masks
struct CLoopInfo
{
	NLMISC::CBitmap		Mask;
	uint        Counter;
	uint        MaskID;
};


///======================================================
static bool CheckIfNeedRebuildColoredVersionForOneBitmap(const CBuildInfo &bi, const std::string &fileNameWithExtension,
	bool mustDivideBy2)
{
	if (bi.CachePath.empty()) return true;
	uint32 srcDate = (uint32) NLMISC::CFile::getFileModificationDate(replaceSlashes(bi.InputPath + fileNameWithExtension));
	static std::vector<CLoopInfo> masks;
	/// check the needed masks
	masks.clear();

	std::string fileName = NLMISC::CFile::getFilenameWithoutExtension(fileNameWithExtension);
	std::string fileExt  = NLMISC::toLower(NLMISC::CFile::getExtension(fileNameWithExtension));

	for (uint k = 0; k < bi.ColorMasks.size(); ++k)
	{
		std::string maskName = fileName + "_" + bi.ColorMasks[k].MaskExt + "." + fileExt;
		std::string maskFileName = NLMISC::CPath::lookup(maskName,
														 false, false);
		if (!maskFileName.empty()) // found the mask ?
		{
			CLoopInfo li;
			li.Counter = 0;
			li.MaskID = k;

			if (NLMISC::CFile::fileExists(maskFileName))
			{
				srcDate = std::max(srcDate, (uint32) NLMISC::CFile::getFileModificationDate(replaceSlashes(maskFileName)));
				masks.push_back(li);
			}
		}
	}

	// get hls info version that is in the cache. if not possible, must rebuild
	std::string outputHLSInfo = bi.HlsInfoPath + fileName + ".hlsinfo";
	std::string cacheHLSInfo = bi.CachePath + fileName + ".hlsinfo";
	if (!NLMISC::CFile::fileExists(cacheHLSInfo.c_str()) )
		return true;
	else
	{
		// Must now if was moved beetween normal dir and d4/ dir.
		CHLSBankTextureInfo		hlsInfo;
		// read .hlsInfo cache
		CIFile		f;
		if(!f.open(cacheHLSInfo))
			return true;
		f.serial(hlsInfo);
		f.close();
		// check if same DividedBy2 Flag.
		if(hlsInfo.DividedBy2!=mustDivideBy2)
			return true;

		// ok, can move the cache
		if (!NLMISC::CFile::moveFile(outputHLSInfo, cacheHLSInfo))
		{
			nlerror("Couldn't move %s to %s", cacheHLSInfo.c_str(), outputHLSInfo.c_str());
			return true;
		}
	}


	/// check is each generated texture has the same date or is more recent
	for(;;)
	{
		uint l;
		std::string outputFileName = fileName;

		/// build current tex name
		for (l  = 0; l < masks.size(); ++l)
		{
			uint maskID = masks[l].MaskID;
			uint colorID = masks[l].Counter;
			/// complete the file name
			outputFileName += bi.DefaultSeparator + bi.ColorMasks[maskID].CMs[colorID].ColID;
		}

		// compare date
		std::string searchName = replaceSlashes(bi.CachePath + outputFileName + bi.OutputFormat);
		if ((uint32) NLMISC::CFile::getFileModificationDate(searchName) < srcDate)
		{
			return true; // not found or more old => need rebuild
		}

		// get version that is in the cache
		std::string cacheDest = bi.OutputPath + outputFileName + bi.OutputFormat;

		if (!NLMISC::CFile::moveFile(cacheDest, searchName))
		{
			nlerror("Couldn't move %s to %s", searchName.c_str(), cacheDest.c_str());
			return true;
		}

		/// increment counters
		for (l  = 0; l < (uint) masks.size(); ++l)
		{
			++ (masks[l].Counter);

			/// check if we have done all colors for this mask
			if (masks[l].Counter == bi.ColorMasks[masks[l].MaskID].CMs.size())
			{
				masks[l].Counter = 0;
			}
			else
			{
				break;
			}
		}
		if (l == masks.size()) break; // all cases dones
	}
	return false; // nothing to rebuild
}



///======================================================
static void BuildColoredVersionForOneBitmap(const CBuildInfo &bi, const std::string &fileNameWithExtension,
	bool mustDivideBy2)
{
	uint32 depth;
	NLMISC::CBitmap srcBitmap;
	NLMISC::CBitmap resultBitmap;

	/// **** load the src bitmap
	{
		// where to load it.
		string	actualInputPath;
		if(mustDivideBy2)
			actualInputPath= bi.InputPath + DivideBy2Dir;
		else
			actualInputPath= bi.InputPath;

		// load
		std::string fullInputBitmapPath = actualInputPath + fileNameWithExtension;

		NLMISC::CIFile is;
		try
		{
			if (is.open(fullInputBitmapPath))
			{
				// 8 bits textures are grayscale
				srcBitmap.loadGrayscaleAsAlpha(false);

				depth = srcBitmap.load(is);
				is.close();

				if (depth == 0 || srcBitmap.getPixels().empty())
				{
					throw NLMISC::Exception("Failed to load bitmap");
				}

				// if bitmap is RGBA but has an alpha channel fully transparent,
				// we can save it as RGB to optimize it
				if (bi.OptimizeTextures > 0 && depth == 32)
				{
					uint8 value = 0;

					// texture can be converted if all alphas are 255
					if (srcBitmap.isAlphaUniform(&value) && value == 255)
					{
						if (bi.OptimizeTextures > 1)
						{
							// make bitmap opaque
							srcBitmap.makeOpaque();

							// original depth is now 24 bits, since we discarded alpha channel
							depth = 24;

							NLMISC::COFile os;

							if (os.open(fullInputBitmapPath))
							{
								nlwarning("Optimizing texture %s...", fullInputBitmapPath.c_str());

								std::string ext = CFile::getExtension(fullInputBitmapPath);

								// resave the texture in optimized same format
								if (ext == "png")
								{
									srcBitmap.writePNG(os, 24);
								}
								else if (ext == "tga")
								{
									srcBitmap.writeTGA(os, 24);
								}
								else
								{
									nlwarning("Don't support %s format for texture, unable to save it", ext.c_str());
								}
							}
							else
							{
								nlwarning("Unable to save texture %s", fullInputBitmapPath.c_str());
							}
						}
						else
						{
							nlwarning("Texture %s can be optimized", fullInputBitmapPath.c_str());
						}
					}
				}

				if (srcBitmap.PixelFormat != NLMISC::CBitmap::RGBA)
				{
					srcBitmap.convertToType(NLMISC::CBitmap::RGBA);
				}
			}
			else
			{
				nlerror("Unable to open %s. Processing next", fullInputBitmapPath.c_str());
				return;
			}
		}
		catch (const NLMISC::Exception &e)
		{
			nlerror("File or format error with %s (%s). Processing next...", fullInputBitmapPath.c_str(), e.what());
			return;
		}
	}

	/// **** Build and prepare build of the .hlsinfo to write.
	CHLSBankTextureInfo		hlsInfo;
	CBitmap					hlsInfoSrcBitmap;
	hlsInfoSrcBitmap= srcBitmap;
	// reduce size of the bitmap of LowDef shift
	uint	reduceShift= bi.LowDefShift;
	if(reduceShift>0)
	{
		uint	w= hlsInfoSrcBitmap.getWidth()>>reduceShift;
		uint	h= hlsInfoSrcBitmap.getHeight()>>reduceShift;
		w= max(w, 1U);
		h= max(h, 1U);
		hlsInfoSrcBitmap.resample(w, h);
	}
	// Compress DXTC5 src bitmap
	hlsInfo.SrcBitmap.build(hlsInfoSrcBitmap);
	// Store info about if where in d4/ dir or not
	hlsInfo.DividedBy2= mustDivideBy2;


	/// **** check the needed masks
	static std::vector<CLoopInfo> masks;
	masks.clear();

	std::string fileName = NLMISC::CFile::getFilenameWithoutExtension(fileNameWithExtension);
	std::string fileExt  = NLMISC::toLower(NLMISC::CFile::getExtension(fileNameWithExtension));

	uint	k;
	for (k = 0; k < bi.ColorMasks.size(); ++k)
	{
		std::string maskName = fileName + "_" + bi.ColorMasks[k].MaskExt + "." + fileExt;
		std::string maskFileName = NLMISC::CPath::lookup(maskName, false, false);

		if (!maskFileName.empty()) // found the mask ?
		{
			CLoopInfo li;
			li.Counter = 0;
			li.MaskID = k;

			/// try to load the bitmap
			NLMISC::CIFile is;
			try
			{

				if (is.open(maskFileName))
				{
					// masks are always opaque, if the mask is 8bits, it's in grayscale
					li.Mask.loadGrayscaleAsAlpha(false);

					uint8 maskDepth = li.Mask.load(is);

					is.close();

					if (maskDepth == 0 || li.Mask.getPixels().empty())
					{
						throw NLMISC::Exception("Failed to load mask");
					}

					// convert color mask to grayscale (red is the new gray value)
					if (li.Mask.getPixelFormat() == CBitmap::RGBA)
					{
						// display a warning if checks enabled
						if (bi.OptimizeTextures > 0 && !li.Mask.isGrayscale())
						{
							nlwarning("Mask %s is using colors, results may by incorrect! Use OptimizeTextures = 2 to fix it.", maskFileName.c_str());
						}

						// get a pointer on original data
						uint32 size = li.Mask.getPixels().size();
						uint32 *data = (uint32*)li.Mask.getPixels().getPtr();
						uint32 *endData = (uint32*)((uint8*)data + size);

						NLMISC::CRGBA *color = NULL;

						// process all pixels
						while(data < endData)
						{
							color = (NLMISC::CRGBA*)data;

							// copy red value to green and blue,
							// because only red is used for mask
							color->B = color->G = color->R;

							// make opaque
							color->A = 255;

							++data;
						}
					}

					// convert image to real grayscale
					if (li.Mask.PixelFormat != NLMISC::CBitmap::Luminance)
					{
						li.Mask.convertToType(NLMISC::CBitmap::Luminance);
					}

					// masks can be converted to grayscale files
					if (bi.OptimizeTextures > 0 && maskDepth > 8)
					{
						if (bi.OptimizeTextures > 1)
						{
							NLMISC::COFile os;

							if (os.open(maskFileName))
							{
								std::string ext = NLMISC::toLower(CFile::getExtension(maskFileName));

								nlwarning("Optimizing mask %s...", maskFileName.c_str());

								// resave the texture in optimized same format
								if (ext == "png")
								{
									li.Mask.writePNG(os, 8);
								}
								else if (ext == "tga")
								{
									li.Mask.writeTGA(os, 8);
								}
								else
								{
									nlwarning("Don't support %s format for mask, unable to save it", ext.c_str());
								}
							}
						}
						else
						{
							nlwarning("Mask %s can be optimized", maskFileName.c_str());
						}
					}

					/// make sure the mask has the same size
					if (li.Mask.getWidth() != srcBitmap.getWidth()
						|| li.Mask.getHeight() != srcBitmap.getHeight())
					{
						throw NLMISC::Exception("Bitmap and mask do not have the same size");
					}

					masks.push_back(li);
				}
				else
				{
					nlerror("Unable to open %s. Processing next", maskFileName.c_str());
					return;
				}
			}
			catch (const std::exception &e)
			{
				nlerror("Error with %s: %s. Aborting this bitmap processing", maskFileName.c_str(), e.what());
				return;
			}
		}
	}

	// **** Add the masks to the .hlsInfo
	hlsInfo.Masks.resize(masks.size());
	for (k = 0; k < masks.size(); ++k)
	{
		CLoopInfo &li= masks[k];
		CBitmap		tmp= li.Mask;
		tmp.resample(hlsInfoSrcBitmap.getWidth(), hlsInfoSrcBitmap.getHeight());
		hlsInfo.Masks[k].build(tmp);
	}


	// **** generate each texture
	// NB : if there are no masks the texture just will be copied
	for(;;)
	{
		resultBitmap = srcBitmap;
		uint l;
		std::string outputFileName = fileName;

		// Add an instance entry to the hlsInfo
		uint	instId= (uint)hlsInfo.Instances.size();
		hlsInfo.Instances.resize(instId+1);
		CHLSBankTextureInfo::CTextureInstance	&hlsTextInstance= hlsInfo.Instances[instId];
		hlsTextInstance.Mods.resize(masks.size());

		/// build current tex
		for (l  = 0; l < masks.size(); ++l)
		{
			uint maskID = masks[l].MaskID;
			uint colorID = masks[l].Counter;

			/// get the color modifier
			const CColorModifier &cm = bi.ColorMasks[maskID].CMs[colorID];

			/// apply the mask
			float	deltaHueApplied;
			cm.convertBitmap(resultBitmap, resultBitmap, masks[l].Mask, deltaHueApplied);

			/// save the setup in hlsInfo
			hlsTextInstance.Mods[l].DHue= deltaHueApplied;
			hlsTextInstance.Mods[l].DLum= cm.Lightness;
			hlsTextInstance.Mods[l].DSat= cm.Saturation;

			/// complete the file name
			outputFileName += bi.DefaultSeparator + bi.ColorMasks[maskID].CMs[colorID].ColID;
		}

		// save good hlsInfo instance name
		hlsTextInstance.Name = outputFileName + bi.OutputFormat;

		nlinfo("Writing %s", outputFileName.c_str());
		/// Save the result. We let propagate exceptions (if there's no more space disk it useless to continue...)
		{
			std::string fullOutputPath = bi.OutputPath + outputFileName + bi.OutputFormat;

			try
			{
				NLMISC::COFile os;
				if (os.open(fullOutputPath))
				{
					// divide by 2 when needed.
					if(mustDivideBy2)
						resultBitmap.resample( (resultBitmap.getWidth()+1)/2, (resultBitmap.getHeight()+1)/2 );
					// write the file
					if (bi.OutputFormat == ".png")
					{
						resultBitmap.writePNG(os, depth);
					}
					else
					{
						resultBitmap.writeTGA(os, depth);
					}
				}
				else
				{
					nlerror("Couldn't open %s for writing", fullOutputPath.c_str());
				}
			}
			catch(const NLMISC::EStream &e)
			{
				nlerror("Couldn't write %s: %s", fullOutputPath.c_str(), e.what());
			}
		}


		/// increment counters
		for (l  = 0; l < (uint) masks.size(); ++l)
		{
			++ (masks[l].Counter);

			/// check if we have done all colors for this mask
			if (masks[l].Counter == bi.ColorMasks[masks[l].MaskID].CMs.size())
			{
				masks[l].Counter = 0;
			}
			else
			{
				break;
			}
		}
		if (l == masks.size()) break; // all cases dones
	}

	// **** save the TMP hlsInfo
	std::string fullHlsInfoPath = bi.HlsInfoPath + fileName + ".hlsinfo";

	NLMISC::COFile os;
	if (os.open(fullHlsInfoPath))
	{
		os.serial(hlsInfo);
	}
	else
	{
		nlerror("Couldn't write %s", fullHlsInfoPath.c_str());
	}
}