#pragma once

#include "precomp.h"
#include "uniconv.h"
#include "tinyxml2.h"
#include "picojson.h"
#include <map>
#include <list>

#ifndef MAX_XML_BUFFER_SIZE
#define MAX_XML_BUFFER_SIZE 4096
#endif

class PathEntry 
{
	friend class PropertyCollection;

public:
	PathEntry()
	{
		processed = false;
	}

	PathEntry(const PathEntry & copy)
	{
		processed = false;
		path = copy.path;
		nameValueCollection = copy.nameValueCollection;
	}

	PathEntry(const PathEntry * copy)
	{
		processed = false;
		path = copy->path;
		nameValueCollection = copy->nameValueCollection;
	}

private:
	string path;
	std::map<string, string> nameValueCollection;
	bool processed;
};

class PropertyCollection
{
public:
	PropertyCollection()
	{

	}

	void SetProperty(const char * path, const char * name, const char * value)
	{
		string propName = (name != NULL) ? name : "";
		std::map<string, PathEntry>::iterator entry;

		if ((entry = m_properties.find(path)) == m_properties.end())
		{
			PathEntry entry;
			entry.path = path;
			entry.nameValueCollection.insert(std::map<string, string>::value_type(propName, value));
			m_properties.insert(std::map<string, PathEntry>::value_type(path, entry));
		} else
			entry->second.nameValueCollection.insert(std::map<string, string>::value_type(propName, value));
	}

	const char * GetProperty(const char * path, const char * name) const
	{
		std::map<string, PathEntry>::const_iterator entry = m_properties.find(path);
		
		if (entry == m_properties.end())
			return NULL;

		std::map<string, string>::const_iterator nv = entry->second.nameValueCollection.find((name != NULL) ? name : "");

		if (nv == entry->second.nameValueCollection.end())
			return NULL;

		return nv->second.c_str();
	}
	
	void LoadXml(tinyxml2::XMLElement * src)
	{
		XmlToPathList("", src);
	}

	void SaveXml(std::string & xml)
	{
		PathListToXml("/", &m_properties, xml, 1);
	}

	void LoadJson(picojson::object & json) 
	{
		
		string path;
		size_t pos;

		for (picojson::object::iterator iter = json.begin(); iter != json.end(); iter++)
		{
			path = iter->first.c_str();

			if ((pos = path.find_last_of('#')) == string::npos)
				SetProperty(path.c_str(), NULL, iter->second.get<std::string>().c_str());
			else
			{
				SetProperty(path.substr(0, pos).c_str(), path.substr(pos + 1).c_str(), iter->second.get<std::string>().c_str());
			}
		}
	}

	void SaveJson(std::string & json, const char * indent = "")
	{
		
		bool first = true;
		json.append("{");

		for (std::map<string, PathEntry>::const_iterator item = m_properties.begin(); item != m_properties.end(); item++)
		{
			for (std::map<string, string>::const_iterator innerItem = item->second.nameValueCollection.begin(); innerItem != item->second.nameValueCollection.end(); innerItem++)
			{
				if (!first) json.append(",");
				json.append("\n"); 
				json.append(indent);
				json.append("\t");
				json.append("\"");
				json.append(item->first.c_str()); 
				if (innerItem->first.length() > 0) 
				{
					json.append("#"); json.append(innerItem->first.c_str());
				} 
				json.append("\":\""); json.append(innerItem->second.c_str()); json.append("\"");

				first = false;
			}
		}

		json.append("\n"); json.append(indent); json.append("}");
	}

	size_t GetSize() const
	{
		return m_properties.size();
	}

private: 
	std::map<string, PathEntry> m_properties;

	void XmlToPathList(const std::string prefix, const tinyxml2::XMLElement * node)
	{
		const char * text = node->GetText();

		if (text != NULL)
			SetProperty(prefix.c_str(), "", text);

		const tinyxml2::XMLAttribute * attr = node->FirstAttribute();

		// risk of stack overflow because _alloca is called in loop. However the current number of properties
		// is very low so it is acceptable
		while (attr)
		{
			SetProperty(prefix.c_str(), attr->Name(), attr->Value());

			attr = attr->Next();
		}

		tinyxml2::XMLElement * child;

		for (child = (tinyxml2::XMLElement *)node->FirstChildElement(); child != NULL; child = child->NextSiblingElement())
		{
			XmlToPathList(prefix + "/" + child->Name(), child);
		}
	}

	void PathListToXml(const std::string pathPrefix, std::map<string, PathEntry> * sortedPathList, std::string & xml, int indent)
	{
		std::string elementValue;
		bool startElementClosed = false;

		if (pathPrefix != "/")
		{
			if (indent > 0)
				xml.append(indent, '\t');

			xml.append("<"); xml.append(1 + strrchr(pathPrefix.c_str(), '/'));
		}

		for (std::map<string, PathEntry>::const_iterator item = sortedPathList->begin(); item != sortedPathList->end(); item++)
		{
			if (strcmp(item->second.path.c_str(), pathPrefix.c_str()) == 0)
			{
				for (std::map<string, string>::const_iterator nvItem = item->second.nameValueCollection.begin(); 
					 nvItem != item->second.nameValueCollection.end(); 
					 nvItem++)
				{
					if (nvItem->first.length() == 0)
					{
						elementValue = nvItem->second.c_str();
					} else
					{
						xml.append(" ");
						xml.append(nvItem->first.c_str());
						xml.append("=\"");
						xml.append(nvItem->second.c_str());
						xml.append("\"");
					}
				}
			}
		}

		if (pathPrefix != "/")
		{
			xml.append(">");

			if (elementValue.length() > 0)
			{
				xml.append(elementValue);
			} else
				if (indent >= 0)
					xml.append("\n");
		}

		bool finished = false;

		while (!finished)
		{
			finished = true;
			std::string firstUnprocessedPrefix;
			
			std::map<string, PathEntry> innerPathList;

			for (std::map<string, PathEntry>::iterator item = sortedPathList->begin(); item != sortedPathList->end(); item++)
			{
				if (!item->second.processed && item->second.path.length() > pathPrefix.length() && (firstUnprocessedPrefix.empty() || strncmp(item->second.path.c_str(), firstUnprocessedPrefix.c_str(), firstUnprocessedPrefix.length()) == 0))
				{
                    if (firstUnprocessedPrefix.empty())
                    {
                        int nextSep = item->second.path.find('/', pathPrefix.length() + 1);
                        firstUnprocessedPrefix = item->second.path.substr(0, (nextSep >= 0) ? nextSep : item->second.path.length()).c_str();
                    }

                    innerPathList.insert(std::map<string, PathEntry>::value_type(item->first, item->second));

                    item->second.processed = true;
                    finished = false;
				}
			}
			
			if (!finished)
				PathListToXml(firstUnprocessedPrefix, &innerPathList, xml, (indent >= 0) ? indent + 1 : indent);
		}

		if (pathPrefix != "/")
		{
			if (indent > 0 && elementValue.length() == 0)
				xml.append(indent, '\t');

			xml.append("</"); xml.append(1 + strrchr(pathPrefix.c_str(), '/')); xml.append(">");

			if (indent >= 0) xml.append("\n");
		}
	}

	void PathListToJson(const std::string pathPrefix, std::map<string, PathEntry> * sortedPathList, std::string & xml, int indent)
	{
		std::string elementValue;
		bool startElementClosed = false;

		if (pathPrefix != "/")
		{
			if (indent > 0)
				xml.append(indent, '\t');

			xml.append("\""); xml.append(1 + strrchr(pathPrefix.c_str(), '/')); xml.append("\":");
		}

		for (std::map<string, PathEntry>::const_iterator item = sortedPathList->begin(); item != sortedPathList->end(); item++)
		{
			if (strcmp(item->second.path.c_str(), pathPrefix.c_str()) == 0)
			{
				for (std::map<string, string>::const_iterator nvItem = item->second.nameValueCollection.begin();
					nvItem != item->second.nameValueCollection.end();
					nvItem++)
				{
					if (nvItem->first.length() == 0)
					{
						elementValue = nvItem->second.c_str();
					}
					else
					{
						xml.append(" ");
						xml.append(nvItem->first.c_str());
						xml.append("=\"");
						xml.append(nvItem->second.c_str());
						xml.append("\"");
					}
				}
			}
		}

		if (pathPrefix != "/")
		{
			xml.append(">");

			if (elementValue.length() > 0)
			{
				xml.append(elementValue);
			}
			else
				if (indent >= 0)
					xml.append("\n");
		}

		bool finished = false;

		while (!finished)
		{
			finished = true;
			std::string firstUnprocessedPrefix;

			std::map<string, PathEntry> innerPathList;

			for (std::map<string, PathEntry>::iterator item = sortedPathList->begin(); item != sortedPathList->end(); item++)
			{
				if (!item->second.processed && item->second.path.length() > pathPrefix.length() && (firstUnprocessedPrefix.empty() || strncmp(item->second.path.c_str(), firstUnprocessedPrefix.c_str(), firstUnprocessedPrefix.length()) == 0))
				{
					if (firstUnprocessedPrefix.empty())
					{
						int nextSep = item->second.path.find('/', pathPrefix.length() + 1);
						firstUnprocessedPrefix = item->second.path.substr(0, (nextSep >= 0) ? nextSep : item->second.path.length()).c_str();
					}

					innerPathList.insert(std::map<string, PathEntry>::value_type(item->first, item->second));

					item->second.processed = true;
					finished = false;
				}
			}

			if (!finished)
				PathListToXml(firstUnprocessedPrefix, &innerPathList, xml, (indent >= 0) ? indent + 1 : indent);
		}

		if (pathPrefix != "/")
		{
			if (indent > 0 && elementValue.length() == 0)
				xml.append(indent, '\t');

			xml.append("}");

			if (indent >= 0) xml.append("\n");
		}
	}
};
