#include "Xml\Xml.h"
#include <Windows.h>

#include <shlwapi.h>
#pragma comment( lib, "shlwapi.lib" )

#include <list>
#include <set>
#include <iostream>

using namespace std;

#define for_count(i,c)	for (int i=0; i<(c); ++i)

//////////////////////////////////////////////////////////////////////////

template<class C> 
class range: public C::iterator
{
public:
	range(C& cont)
		: C::iterator(cont.begin()), End(cont.end())
	{}
	range(typename C::iterator start, typename C::iterator end)
		: C::iterator(start), End(end)
	{}

	operator bool() const
	{
		return *this != End;
	}

private:
	typename C::iterator End;
};

#define for_all(r, type, cont)		for (range< type > r(cont); r; ++r)
#define for_all(r, type, cont)		for (range< type > r(cont); r; ++r)

template<typename C>
void construct_union(C& dest, typename C::iterator& pos, typename C::value_type const& val)
{
	if (pos == dest.end() || *pos != val)
	{
		C::iterator newpos = find(dest.begin(), dest.end(), val);
		if (newpos == dest.end())
			pos = dest.insert(pos, val);
		else
			pos = newpos;
	}
	++pos;
}

template<typename C>
void construct_intersection(C& dest, typename C::iterator& pos, typename C::value_type const& val, int pass)
{
	if (pass == 0)
		// First set.
		dest.insert(dest.end(), val);
	else if (pos != dest.end())
	{
		if (*pos == val)
			++pos;
		else
			pos = dest.erase(pos);
	}
}

typedef pair<XmlString,XmlString> StringPair;

cstr Display(XmlNode* node)
{
	static char crap[256];
	*crap = 0;
	if (node)
		sprintf(crap, "%s %s [%d]", (cstr)node->getTag(), (cstr)node->getAttr("Name"), node->getChildCount());
	return crap;
}

//////////////////////////////////////////////////////////////////////////
// XmlNode toolNames.

class XmlChildIterator
{
public:
	XmlChildIterator(XmlNode* node, cstr childTag = "")
		: Node(node), ChildTag(childTag), ChildCount(node->getChildCount()), ChildIdx(-1)
	{
		Iterate();
	}
	operator bool() const
	{
		return ChildIdx < ChildCount;
	}
	void operator++ ()
	{
		Iterate();
	}
	XmlNode* operator* ()
	{
		return Child;
	}
	XmlNode* operator-> ()
	{
		return Child;
	}

	void remove()
	{
		Node->deleteChildAt(ChildIdx);
		ChildIdx--;
		ChildCount--;
	}

private:
	XmlNode* Node;
	string ChildTag;
	int ChildCount;
	int ChildIdx;
	XmlNode* Child;

	void Iterate()
	{
		for (;;)
		{
			++ChildIdx;
			if (ChildIdx >= ChildCount)
			{
				Child = 0;
				break;
			}
			Child = Node->getChild(ChildIdx);
			if (ChildTag.empty() || ChildTag == Child->getTag())
				break;
		}
	}
};

//////////////////////////////////////////////////////////////////////////

XmlNode* MergeSections(cstr tag, cstr name, list<XmlNode*>& configs, bool bTopLevel)
{
	if (configs.empty())
		return NULL;

	XmlNode* commonConfig = new XmlNode(tag);
	commonConfig->setAttr("Name", name);

	if (bTopLevel)
	{
		cout << "MergeSections " << name << ":";
		for_all (config, list<XmlNode*>, configs)
			cout << " " << (*config)->getAttr("Name");
		cout << endl;
	}

	// Populate union of all attrs & tool children.
	list<XmlString> keyNames;
	list<StringPair> childNames;

	for_all (config, list<XmlNode*>, configs)
	{
		// Merge attrs.
		list<XmlString>::iterator cur = keyNames.begin();
		for (int a = 0; ; a++)
		{
			XmlString key, value;
			if (!(*config)->getAttributeByIndex(a, &key, &value))
				break;
			if (key == "Name")
				continue;
			construct_union(keyNames, cur, key);
		}

		// Merge common childNames.
		list<StringPair>::iterator curCh = childNames.begin();
		for (XmlChildIterator ch(*config); ch; ++ch)
		{
			StringPair pair(ch->getTag(), ch->getAttr("Name"));
			if (!pair.first.empty() && !pair.second.empty())
				construct_intersection(childNames, curCh, pair, config != configs.begin());
		}
	}

	// For each key, find the majority value.
	for_all (key, list<XmlString>, keyNames)
	{
		// Initialise the value as undefined, as a placeholder.
		commonConfig->setAttr(*key, "?");

		// Look for a majority.
		for_all (config, list<XmlNode*>, configs)
		{
			XmlString value;
			if ((*config)->getAttr(*key, value))
			{
				int valCount = 1;
				for_all (config2, list<XmlNode*>, configs)
				{
					if (&*config2 != &*config)
					{
						XmlString value2;
						if ((*config2)->getAttr(*key, value2))
							if (value2 == value)
								valCount++;
					}
				}

				if (valCount > configs.size()/2)
				{
					// Found the majority. Set in common.
					commonConfig->setAttr(*key, value);

					// Remove from majority holders.
					for_all (config2, list<XmlNode*>, configs)
					{
						XmlString value2;
						if ((*config2)->getAttr(*key, value2))
						{
							if (value2 == value)
								(*config2)->delAttr(*key);
						}
						else
							// Mark configs that do not have this setting.
							(*config2)->setAttr(*key, "~");
					}
					break;
				}
			}
		}
	}

	// Recurse on children.
	for_all (child, list<StringPair>, childNames)
	{
		// Find list of childNames in main list.
		list<XmlNode*> configSubs;
		for_all (config, list<XmlNode*>, configs)
		{
			XmlNode* node = (*config)->findChild((*child).first, "Name", (*child).second);
			if (node)
				configSubs.push_back(node);
		}

		XmlNode* commonSub = MergeSections((*child).first, (*child).second, configSubs, false);
		if (commonSub)
			commonConfig->addChild(commonSub);
	}

	// Clean up original configs.
	for_all (config, list<XmlNode*>, configs)
	{
		XmlNode* parent = (*config)->getParent();
		if (!parent)
			continue;
		if (bTopLevel)
		{
			// Move under new config.
			parent->removeChild(*config);
			commonConfig->addChild(*config);
		}
		else if ((*config)->getNumAttributes() <= 1)
			// Remove empty children.
			parent->removeChild(*config);

		XmlString name = (*config)->getAttr("Name");
		size_t pos = name.find(commonConfig->getAttr("Name"));
		if (pos != string::npos && pos > 0)
		{
			name.erase(pos);
			(*config)->setAttr("Name", name);
		}
	}

	return commonConfig;
}

void CombineConfigs(XmlNode* root)
{
	// Analyse, combine config sections.
	XmlNode* configs = root->findChild("Configurations");
	if (!configs)
		return;

	// Find platforms (including null platform).
	set<XmlString> platformNames;
	platformNames.insert("|none");
	for (XmlChildIterator conf(configs, "Configuration"); conf; ++conf)
	{
		XmlString configName = (*conf)->getAttr("Name");
		size_t pos = configName.find('|');
		if (pos != string::npos)
		{
			// Extract platform name, keeping divider.
			platformNames.insert(configName.substr(pos));
		}
	}

	// For each platform, construct common config. 
	for_all (pn, set<XmlString>, platformNames)
	{
		list<XmlNode*> platConfigs;
		for (XmlChildIterator conf(configs, "Configuration"); conf; ++conf)
		{
			XmlString configName = (*conf)->getAttr("Name");
			if (*pn == "|none")
			{
				// Null platform.
				if (configName.find('|') == string::npos)
					platConfigs.push_back(*conf);
			}
			else
			{
				if (configName.find(*pn) != string::npos)
					platConfigs.push_back(*conf);
			}
		}
		XmlNode* platCommon = MergeSections("Configuration", *pn, platConfigs, true);
		if (platCommon)
			configs->addChild(platCommon);
	}

	// Construct global config from platforms.
	list<XmlNode*> platConfigs;
	for (XmlChildIterator conf(configs, "Configuration"); conf; ++conf)
		platConfigs.push_back(*conf);
	XmlNode* platCommon = MergeSections("Configuration", "|", platConfigs, true);
	if (platCommon)
		configs->addChild(platCommon);
}

//////////////////////////////////////////////////////////////////////////

void CombineAttrs(XmlNode* targ, XmlNode* src, bool bFinal)
{
	// For all src attrs.
	for (int a = 0; ; a++)
	{
		XmlString key, value;
		if (!src->getAttributeByIndex(a, &key, &value))
			break;
		if (key == "Name")
			continue;

		// Check existing targ attr.
		XmlString valueTarg;
		if (!targ->getAttr(key, valueTarg) || valueTarg == "?" || value != "?")
			targ->setAttr(key, value);
	}

	// Recurse on toolNames.
	for (XmlChildIterator ch(src, "Tool"); ch; ++ch)
	{
		XmlString toolName = ch->getAttr("Name");
		if (toolName.empty())
			continue;

		// Combine sub-toolNames.
		XmlNode* targTool = targ->findChild("Tool", "Name", toolName);
		if (!targTool)
		{
			targTool = targ->newChild("Tool");
			targTool->setAttr("Name", toolName);
		}
		CombineAttrs(targTool, *ch, bFinal);
	}
}

void CleanAttrs(XmlNode* node)
{
	// For all attrs.
	for (int a = 0; ; a++)
	{
		XmlString key, value;
		if (!node->getAttributeByIndex(a, &key, &value))
			break;
		if (value == "?" || value == "~")
		{
			node->delAttr(key);
			a--;
		}
	}

	// Recurse on all children.
	for (XmlChildIterator ch(node); ch; ++ch)
	{
		CleanAttrs(*ch);
		if ((*ch)->getTag() == "Tool" && (*ch)->getNumAttributes() <= 1)
			ch.remove();
	}
}

void ExpandConfigs(XmlNode* root)
{
	// Analyse, combine config sections.
	XmlNode* configs = root->findChild("Configurations");
	if (!configs)
		return;

	// Separate combined from final configs.
	list<XmlNode*> configsCombined;
	list<XmlNode*> configsFinal;
	XmlNode* configGlobal = 0;

	for (XmlChildIterator c(configs, "Configuration"); c; ++c)
	{
		XmlString name = (*c)->getAttr("Name");
		if (name.empty())
			continue;
		if (name == "|")
			configGlobal = *c;
		else if (name[0U] == '|' || name[name.size()-1] == '|')
			configsCombined.push_back(*c);
		else
			configsFinal.push_back(*c);
	}

	// Remove existing configs, populate with expanded ones.
	configs->removeAllChilds();

	for_all (f, list<XmlNode*>, configsFinal)
	{
		// Create a new version of final config.
		XmlString nameFinal = (*f)->getAttr("Name");
		XmlNode* newConfig = configs->newChild("Configuration");
		newConfig->setAttr("Name", nameFinal);

		// Populate with common attrs first, then custom.
		if (configGlobal)
			CombineAttrs(newConfig, configGlobal, false);
		for_all (c, list<XmlNode*>, configsCombined)
		{
			XmlString nameCombined = (*c)->getAttr("Name");
			if (nameFinal.find(nameCombined) != string::npos)
				CombineAttrs(newConfig, *c, false);
		}
		CombineAttrs(newConfig, *f, true);

		// Finally, remove any ? attrs.
		CleanAttrs(newConfig);
	}
}

//////////////////////////////////////////////////////////////////////////

int main(int argc, char* argv[])
{
	if (argc < 3)
	{
		cout << "Not enough arguments specified." << endl << endl;

		cout << "Usage:" << endl;
		cout << "  " << argv[0] << " OrigProject.vcproj NewProject.xml [CheckProject.vcproj]" << endl;
		return 0;
	}

	cstr srcFile = argv[1];
	cstr trgFile = argv[2];
	cstr chkFile = argc > 3 ? argv[3] : 0;

	cout << "Parsing " << srcFile << endl;

	XmlParser xmlParse;
	XmlNode* root = xmlParse.parse( srcFile );

	if (!root)
	{
		cout << "Error, Cannot parse XML from file " << srcFile << endl;
		return 1;
	}

	//////////////////////////////////////////////////////////////////////////

	CombineConfigs(root);
	cout << "Saving " << trgFile << endl;
	root->saveToFile(trgFile);

	//////////////////////////////////////////////////////////////////////////

	if (chkFile)
	{
		ExpandConfigs(root);
		cout << "Saving " << chkFile << endl;
		root->saveToFile(chkFile);
	}

	return 0;
}
