#pragma once

#include "Palette.h"

/// <summary>
/// PaletteList class.
/// </summary>

namespace EmberNs
{
/// <summary>
/// Holds a list of palettes read from an Xml file. Since the default list from flam3-palettes.xml is fairly large at 700 palettes,
/// the list member is kept as a static. This class derives from EmberReport in order to report any errors that occurred while reading the Xml.
/// Note that although the Xml color values are expected to be 0-255, they are converted and stored as normalized colors, with values from 0-1.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API PaletteList : public EmberReport
{
public:
	static const char* m_DefaultFilename;

	/// <summary>
	/// Empty constructor which initializes the palette map with the default palette file.
	/// </summary>
	PaletteList()
	{
		Add(string(m_DefaultFilename));
	}

	~PaletteList() = default;
	PaletteList(const PaletteList<T>& paletteList) = delete;

	/// <summary>
	/// Read an Xml palette file into memory.
	/// This must be called before any palette file usage.
	/// </summary>
	/// <param name="filename">The full path to the file to read</param>
	/// <param name="force">If true, override the initialization state and force a read, else observe the initialization state.</param>
	/// <returns>Whether anything was read</returns>
	bool Add(const string& filename, bool force = false)
	{
		bool added = true;
		auto palettes = s_Palettes.insert(make_pair(filename, vector<Palette<T>>()));

		if (force || palettes.second)
		{
			string buf;
			const char* loc = __FUNCTION__;

			if (ReadFile(filename.c_str(), buf))
			{
				xmlDocPtr doc = xmlReadMemory(static_cast<const char*>(buf.data()), int(buf.size()), filename.c_str(), nullptr, XML_PARSE_NONET);

				if (doc)
				{
					auto rootNode = xmlDocGetRootElement(doc);
					auto pfilename = shared_ptr<string>(new string(filename));
					palettes.first->second.clear();
					palettes.first->second.reserve(buf.size() / 2048);//Roughly what it takes per palette.
					ParsePalettes(rootNode, pfilename, palettes.first->second);
					xmlFreeDoc(doc);

					if (palettes.first->second.empty())
					{
						added = false;//Reading failed, likely not a valid palette file.
						s_Palettes.erase(filename);
						AddToReport(string(loc) + " : Couldn't parse xml doc");
					}
				}
				else
				{
					added = false;
					s_Palettes.erase(filename);
					AddToReport(string(loc) + " : Couldn't load xml doc");
				}
			}
			else
			{
				added = false;
				s_Palettes.erase(filename);
				AddToReport(string(loc) + " : Couldn't read palette file " + filename);
			}
		}

		return added;
	}

	/// <summary>
	/// Get the palette at a random index in a random file in the map.
	/// </summary>
	Palette<T>* GetRandomPalette()
	{
		auto p = s_Palettes.begin();
		size_t i = 0, paletteFileIndex = QTIsaac<ISAAC_SIZE, ISAAC_INT>::LockedRand() % Size();

		//Move p forward i elements.
		while (i < paletteFileIndex && p != s_Palettes.end())
		{
			++i;
			++p;
		}

		if (i < Size())
		{
			size_t paletteIndex = QTIsaac<ISAAC_SIZE, ISAAC_INT>::LockedRand() % p->second.size();

			if (paletteIndex < p->second.size())
				return &p->second[paletteIndex];
		}

		return nullptr;
	}

	/// <summary>
	/// Get the palette at a specified index in the specified file in the map.
	/// </summary>
	/// <param name="filename">The filename of the palette to retrieve</param>
	/// <param name="i">The index of the palette to read. A value of -1 indicates a random palette.</param>
	/// <returns>A pointer to the requested palette if the index was in range, else nullptr.</returns>
	Palette<T>* GetPalette(const string& filename, size_t i)
	{
		auto& palettes = s_Palettes[filename];

		if (!palettes.empty() && i < palettes.size())
			return &palettes[i];

		return nullptr;
	}

	/// <summary>
	/// Get a pointer to a palette with a specified name in the specified file in the map.
	/// </summary>
	/// <param name="filename">The filename of the palette to retrieve</param>
	/// <param name="name">The name of the palette to retrieve</param>
	/// <returns>A pointer to the palette if found, else nullptr</returns>
	Palette<T>* GetPaletteByName(const string& filename, const string& name)
	{
		for (auto& palettes : s_Palettes)
			if (palettes.first == filename)
				for (auto& palette : palettes.second)
					if (palette.m_Name == name)
						return &palette;

		return nullptr;
	}

	/// <summary>
	/// Get a copy of the palette at a specified index in the specified file in the map
	/// with its hue adjusted by the specified amount.
	/// </summary>
	/// <param name="filename">The filename of the palette to retrieve</param>
	/// <param name="i">The index of the palette to read.</param>
	/// <param name="hue">The hue adjustment to apply</param>
	/// <param name="palette">The palette to store the output</param>
	/// <returns>True if successful, else false.</returns>
	bool GetHueAdjustedPalette(const string& filename, size_t i, T hue, Palette<T>& palette)
	{
		bool b = false;

		if (Palette<T>* unadjustedPal = GetPalette(filename, i))
		{
			unadjustedPal->MakeHueAdjustedPalette(palette, hue);
			b = true;
		}

		return b;
	}

	/// <summary>
	/// Clear the palette list and reset the initialization state.
	/// </summary>
	void Clear()
	{
		s_Palettes.clear();
	}

	/// <summary>
	/// Get the size of the palettes map.
	/// This will be the number of files read.
	/// </summary>
	/// <returns>The size of the palettes map</returns>
	size_t Size() { return s_Palettes.size(); }

	/// <summary>
	/// Get the size of specified palette vector in the palettes map.
	/// </summary>
	/// <param name="index">The index of the palette in the map to retrieve</param>
	/// <returns>The size of the palette vector at the specified index in the palettes map</returns>
	size_t Size(size_t index)
	{
		size_t i = 0;
		auto p = s_Palettes.begin();

		while (i < index && p != s_Palettes.end())
		{
			++i;
			++p;
		}

		return p->second.size();
	}

	/// <summary>
	/// Get the size of specified palette vector in the palettes map.
	/// </summary>
	/// <param name="s">The filename of the palette in the map to retrieve</param>
	/// <returns>The size of the palette vector at the specified index in the palettes map</returns>
	size_t Size(const string& s)
	{
		return s_Palettes[s].size();
	}

	/// <summary>
	/// Get the name of specified palette in the palettes map.
	/// </summary>
	/// <param name="index">The index of the palette in the map to retrieve</param>
	/// <returns>The name of the palette vector at the specified index in the palettes map</returns>
	const string& Name(size_t index)
	{
		size_t i = 0;
		auto p = s_Palettes.begin();

		while (i < index && p != s_Palettes.end())
		{
			++i;
			++p;
		}

		return p->first;
	}

private:
	/// <summary>
	/// Parses an Xml node for all palettes present and stores them in the passed in palette vector.
	/// Note that although the Xml color values are expected to be 0-255, they are converted and
	/// stored as normalized colors, with values from 0-1.
	/// </summary>
	/// <param name="node">The parent note of all palettes in the Xml file.</param>
	/// <param name="filename">The name of the Xml file.</param>
	/// <param name="palettes">The vector to store the paresed palettes associated with this file in.</param>
	void ParsePalettes(xmlNode* node, const shared_ptr<string>& filename, vector<Palette<T>>& palettes)
	{
		char* val;
		const char* loc = __FUNCTION__;
		xmlAttrPtr attr;

		while (node)
		{
			if (node->type == XML_ELEMENT_NODE && !Compare(node->name, "palette"))
			{
				attr = node->properties;
				Palette<T> palette;

				while (attr)
				{
					val = reinterpret_cast<char*>(xmlGetProp(node, attr->name));

					if (!Compare(attr->name, "data"))
					{
						string s1, s;
						size_t tmp, colorCount = 0, colorIndex = 0;
						stringstream ss, temp(val); ss >> std::hex;
						s.reserve(2048);

						while (temp >> s1)
							s += s1;

						auto length = s.size();

						for (size_t strIndex = 0; strIndex < length;)
						{
							strIndex += 2;//Skip past the 00 at the beginning of each RGB.

							for (glm::length_t i = 0; i < 3 && colorCount < palette.Size(); i++)
							{
								const char tmpStr[3] = { s[strIndex++], s[strIndex++], 0 };//Read out and convert the string two characters at a time.
								ss.clear();//Reset and fill the string stream.
								ss.str(tmpStr);
								ss >> tmp;//Do the conversion.
								palette.m_Entries[colorCount][i] = T(tmp) / T(255);//Hex palette is [0..255], convert to [0..1].
							}

							colorCount++;
						}
					}
					else if (!Compare(attr->name, "number"))
					{
						palette.m_Index = atoi(val);
					}
					else if (!Compare(attr->name, "name"))
					{
						palette.m_Name = string(val);
					}

					xmlFree(val);
					attr = attr->next;
				}

				palette.m_Filename = filename;
				palettes.push_back(palette);
			}
			else
			{
				ParsePalettes(node->children, filename, palettes);
			}

			node = node->next;
		}
	}

	static map<string, vector<Palette<T>>> s_Palettes;//The map of filenames to vectors that store the palettes.
};
}