#include <Core/Core.h>
using namespace Upp;

#include <iostream>

using namespace std;

// Begin U++ extend

template <class T, class U>
inline bool Contains(const T& container, const U& element)
{
	return container.Find(element) != -1;
}

template <class T, class U>
inline bool Replace(T& container, const U& search, const U& replace)
{
	int len = container.GetCount();
	for (int i = 0; i < len; ++i)
		if (container[i] == search) container.Set(i, replace);
}

template <class T, class U>
inline bool Replace(T& container, const T& search, const U& replace)
{
	int len = container.GetCount();
	for (int i = 0; i < len; ++i)
		if (Contains(search, container[i])) container.Set(i, replace);
}

typedef Vector<String> StringV;
typedef Index<String> StringI;

StringV Tokenize(const String& source, String sep, String ignore = "", char rep = 0)
{
	// ANSI (non-UTF-8) Strings only
	
	int iStart = 0, iToken, lenToken, lenSrc = source.GetCount(), lenSep = sep.GetCount();
	StringV retval;
	
	do
	{
		iToken = source.Find(sep, iStart);
		String token = source.Mid(iStart, ((iToken != -1) ? iToken : lenSrc) - iStart);
		lenToken = token.GetCount();
		
		String add;
		if (rep) // Replace ignored characters
		{
			add = token;
			Replace(add, ignore, rep);
		}
		else // Remove ignored characters
		{
			add.Reserve(lenToken);
			for (int i = 0; i < lenToken; ++i)
				if (!Contains(ignore, token[i])) add.Cat(token[i]);
		}
		if (!add.IsEmpty()) retval.Add(add);
		iStart = iToken + lenSep;
	}
	while (iToken != -1);
	
	return retval;
}

// End U++ extend

struct Use
{
	StringV conds;
	StringV pkgs;
};

struct Package
{
	String dir, mainheader;
	Array<Use> uses;
	StringV headers, sources, icpps, extras;	
};

void NormalizePkg(StringV& files)
{
	int i, j, k;
	for (i = 0; i < files.GetCount(); ++i)
	{
		if (Contains(files[i], "readonly") && Contains(files[i], "separator"))
		{
			files.Remove(i--);
			continue;	
		}
		for (j = 0; files[i][j] == ' '; ++j); // Skip spaces
		k = files[i].Find(' ', j); // Next space
		if (k == -1)
			files[i] = files[i].Mid(j);
		else
			files[i] = files[i].Mid(j, k - j);
		if (files[i][0] == '\"')
			files[i] = files[i].Mid(1, files[i].GetCount() - 2);
	}
}

void ReadPackage(String file, Package& package)
{
	static const int iInf = 99999;
	
	String pkginfo = LoadFile(file);
	if (pkginfo.GetLength() == 0) throw Exc("Invalid package: " + file);
	pkginfo.Insert(0, '\n');
	Replace(pkginfo, '\\', '/');
	
	package.dir = file.Left(file.ReverseFind('/'));
	int iCur = 0, iNext, iUses, iFile;
	
	while (1)
	{
		iUses = pkginfo.Find("\nuses", iCur); if (iUses == -1) iUses = iInf;
		iFile = pkginfo.Find("\nfile", iCur); if (iFile == -1) iFile = iInf;
		if (iUses == iInf && iFile == iInf) break;
		if (iUses < iFile)
		{
			iCur = iUses + 5;
			package.uses.Add(Use());
			iUses = package.uses.GetCount() - 1;
			
			// Conditions
			if (pkginfo[iCur] == '(')
			{
				++iCur;
				iNext = pkginfo.Find(')', iCur);
				package.uses[iUses].conds = Tokenize(pkginfo.Mid(iCur, iNext - iCur), " ");
				iCur = iNext + 1;
			}
			
			// Packages
			iNext = pkginfo.Find(';', iCur);
			package.uses[iUses].pkgs = Tokenize(pkginfo.Mid(iCur, iNext - iCur), ",", " \t\n\r");
			iCur = iNext + 1;
		}
		else
		{
			iCur = iFile + 5;
			
			iNext = pkginfo.Find(';', iCur);
			StringV allfiles = Tokenize(pkginfo.Mid(iCur, iNext - iCur), ",", "\t\n\r", ' ');
			iCur = iNext + 1;
			
			NormalizePkg(allfiles);
			
			if (package.mainheader.IsEmpty() &&
				(allfiles[0].EndsWith(".h") || allfiles[0].EndsWith(".hpp") || allfiles[0].EndsWith(".hxx")))
			{
				package.mainheader = allfiles[0];	
			}
			
			for (int i = 0; i < allfiles.GetCount(); ++i)
			{
				if (allfiles[i].EndsWith(".h") || allfiles[i].EndsWith(".hpp") || allfiles[i].EndsWith(".hxx"))
				{
					package.headers.Add(allfiles[i]);
				}
				else if (allfiles[i].EndsWith(".c") || allfiles[i].EndsWith(".cpp") || allfiles[i].EndsWith(".cxx"))
				{
					package.sources.Add(allfiles[i]);
				}
				else if (allfiles[i].EndsWith(".icpp"))
				{
					package.icpps.Add(allfiles[i]);
				}
				else
				{
					package.extras.Add(allfiles[i]);
				}
			}
		}
	}
}

inline String InvParam(const String& param)
{
	if (param.StartsWith("!")) return param.Mid(1);
	else return "!" + param;	
}

StringI PackageUses(const Package& pkg, const StringI& params)
{
	StringI retval;
	for (int i = 0; i < pkg.uses.GetCount(); ++i)
	{
		bool IsOK = true;
		// Check conditions
		for (int j = 0; IsOK && j < pkg.uses[i].conds.GetCount(); ++j)
		{
			if (!Contains(params, pkg.uses[i].conds[j])) IsOK = false;
			if (Contains(params, InvParam(pkg.uses[i].conds[j]))) IsOK = false;
		}
		if (!IsOK) continue;
		// Add packages
		for (int j = 0; j < pkg.uses[i].pkgs.GetCount(); ++j)
			if (!Contains(retval, pkg.uses[i].pkgs[j])) retval.Add(pkg.uses[i].pkgs[j]);
	}
	return retval;
}

String Package2Include(const Package& pkg)
{
	// plugin/z -> plugin_z
	// package:			plugin/z/z.upp
	// filename:		Upp/plugin_z.h
	// include guard:	UPP_PLUGIN_Z_H
	String retval = pkg.dir;
	Replace(retval, '/', '_');
	return retval;
}

String DefsHeader(String HeaderName, const StringV& cmdline)
{
	String HeaderNameU = ToUpper(HeaderName);
	String Header = Format("#ifndef %s_H\n#define %s_H\n\n", HeaderNameU, HeaderNameU);
	Header.Cat("#ifdef __GNUC__\n#define flagGCC\n#endif\n");
	Header.Cat("#if !defined(NDEBUG) && !defined(__OPTIMIZE__)\n");
	Header.Cat("#define flagDEBUG\n#define flagDEBUG_FULL\n#endif\n\n");
	for (int i = 0; i < cmdline.GetCount(); ++i)
	{
		
		if (cmdline[i] == "WIN32" || cmdline[i] == "LINUX" || cmdline[i] == "SOLARIS" ||
			cmdline[i] == "FREEBSD" || cmdline[i] == "OSX11" || cmdline[i] == "CPU_X86" ||
			cmdline[i] == "SPARC" || cmdline[i] == "ARM" || cmdline[i] == "POWERPC")
		{
			Header.Cat("#define flag" + cmdline[i] + "\n");
		}
	}
	return Header;
}

//#define ASSERT() (void)0
CONSOLE_APP_MAIN
{
	const StringV& cmdline = CommandLine();
	
	String srcDir = GetCurrentDirectory();
	for (int i = 0; i < srcDir.GetCount(); ++i)
		if (srcDir[i] == DIR_SEP) srcDir.Set(i, '/');
	cout << "U++ sources dir: " << (const char*)srcDir << endl << endl;
	srcDir.Cat('/');
	
	String genPCH, genUse = LoadFile("pkggen.txt");
	StringV UsePackages = Tokenize(genUse, "\n", " \t\r");
	StringI ExcludePackages;
	for (int i = 0; i < UsePackages.GetCount(); ++i)
	{
		if (UsePackages[i].StartsWith("//"))
		{
			UsePackages.Remove(i--);
			continue;	
		}
		if (UsePackages[i].StartsWith("/"))
		{
			UsePackages[i] = UsePackages[i].Mid(1);
			ExcludePackages.Add(UsePackages[i]);
		}
		if (UsePackages[i].EndsWith(".h") || UsePackages[i].EndsWith(".hpp") || UsePackages[i].EndsWith(".hxx"))
		{
			if (genPCH.IsEmpty()) genPCH = UsePackages[i];
			UsePackages.Remove(i--);
			continue;
		}
	}	
	int nPkgs = UsePackages.GetCount();
	if (nPkgs == 0) throw Exc("No packages selected.");
	
	cout << "Precompiled header: " << (const char*)genPCH << endl;
	cout << "U++ packages:";
	for (int i = 0; i < nPkgs; ++i)
		cout << " " << (const char*)UsePackages[i];
	cout << endl << endl;
	
	Array<Package> packages; packages.Reserve(nPkgs);
	StringI Refs;
	for (int i = 0; i < nPkgs; ++i)
	{
		packages.Add(Package());
		ReadPackage(UsePackages[i], packages[i]);
		if (!Contains(ExcludePackages, UsePackages[i]))
			cout << "Package " << (const char*)packages[i].dir
				<< " { " << (const char*)packages[i].mainheader << " }";
		else
			cout << "Package (" << (const char*)packages[i].dir
				<< ") { " << (const char*)packages[i].mainheader << " }";
		for (int j = 0; j < packages[i].uses.GetCount(); ++j)
		{
			cout << " ifdef [ ";
			for (int k = 0; k < packages[i].uses[j].conds.GetCount(); ++k)
				cout << (const char*)packages[i].uses[j].conds[k] << " ";
			cout << "] { ";
			for (int k = 0; k < packages[i].uses[j].pkgs.GetCount(); ++k)
				cout << (const char*)packages[i].uses[j].pkgs[k] << " ";
			cout << "} ";
		}
		cout << endl;
		if (!packages[i].headers.IsEmpty())
		{
			cout << "headers: ";
			for (int j = 0; j < packages[i].headers.GetCount(); ++j)
				cout << (const char*)packages[i].headers[j] << " ";
			cout << endl;
		}
		if (!packages[i].sources.IsEmpty())
		{
			cout << "sources: ";
			for (int j = 0; j < packages[i].sources.GetCount(); ++j)
				cout << (const char*)packages[i].sources[j] << " ";
			cout << endl;
		}
		if (!packages[i].icpps.IsEmpty())
		{
			cout << "icpps: ";
			for (int j = 0; j < packages[i].icpps.GetCount(); ++j)
				cout << (const char*)packages[i].icpps[j] << " ";
			cout << endl;
		}
		if (!packages[i].extras.IsEmpty())
		{
			cout << "extras: ";
			for (int j = 0; j < packages[i].extras.GetCount(); ++j)
				cout << (const char*)packages[i].extras[j] << " ";
			cout << endl;
		}
		cout << endl;
		for (int j = 0; j < packages[i].uses.GetCount(); ++j)
		{
			if (packages[i].uses[j].pkgs.IsEmpty()) continue;
			for (int k = 0; k < packages[i].uses[j].pkgs.GetCount(); ++k)
			{
				if (!Contains(Refs, packages[i].uses[j].pkgs[k]))
					Refs.Add(packages[i].uses[j].pkgs[k]);
			}
		}
	}
	
	StringI Exist;
	for (int i = 0; i < packages.GetCount(); ++i)
		Exist.Add(packages[i].dir);
	Vector<int> RefsSO = GetSortOrder(Refs);
	cout << "Used packages:" << endl;
	for (int i = 0; i < RefsSO.GetCount(); ++i)
	{
		cout << "\t" << (const char*)Refs[RefsSO[i]];
		if (!Contains(Exist, Refs[RefsSO[i]])) cout << " (!)";
		cout << endl;
	}
	
	String FileList;
	String inDir;
	for (int i = 0; i < nPkgs; ++i)
	{
		inDir = Format("%s%s/", srcDir, packages[i].dir);
		if (!Contains(ExcludePackages, UsePackages[i]))
			FileList.Cat(Format(":: %s\n", packages[i].dir));
		else
			FileList.Cat(Format(":: (%s)\n", packages[i].dir));
		int j = packages[i].dir.ReverseFind('/');
		if (j == -1)
			FileList.Cat(Format("%s%s.upp\n", inDir, packages[i].dir));
		else
			FileList.Cat(Format("%s%s.upp\n", inDir, packages[i].dir.Mid(j + 1)));
		for (int j = 0; j < packages[i].headers.GetCount(); ++j)
			FileList.Cat(Format("%s%s\n", inDir, packages[i].headers[j]));
		for (int j = 0; j < packages[i].sources.GetCount(); ++j)
			FileList.Cat(Format("%s%s\n", inDir, packages[i].sources[j]));
		for (int j = 0; j < packages[i].icpps.GetCount(); ++j)
			FileList.Cat(Format("%s%s\n", inDir, packages[i].icpps[j]));
		for (int j = 0; j < packages[i].extras.GetCount(); ++j)
			FileList.Cat(Format("%s%s\n", inDir, packages[i].extras[j]));
		FileList.Cat("\n");
	}
	Replace(FileList, '/', DIR_SEP);
	SaveFile("pkggenlog.txt", FileList);
	
	StringI iparams;
	for (int i = 0; i < cmdline.GetCount(); ++i) iparams.Add(cmdline[i]);
	
	const String PkgDir = "UppPkg";
	DirectoryCreate(PkgDir);
	for (int i = 0; i < nPkgs; ++i)
	{
		String PkgName = Package2Include(packages[i]);
		String PkgDef = ToUpper(PkgDir + "_" + PkgName + "_h");
		String PkgFile = Format("#ifndef flagSCU\n\n#ifndef %s\n#define %s\n\n", PkgDef, PkgDef);
		StringI PkgRef = PackageUses(packages[i], iparams);
		for (int j = 0; j < PkgRef.GetCount(); ++j)
		{
			int ipkg = Exist.Find(PkgRef[j]);
			if (ipkg != -1)
				PkgFile.Cat(Format("#include <%s/%s.h>\n", PkgDir, Package2Include(packages[ipkg])));
			else
			{
				String errinc = PkgRef[j]; Replace(errinc, '/', '_');
				PkgFile.Cat(Format("// ERROR: #include <%s/%s.h>\n", PkgDir, errinc));
			}
		}
		PkgFile.Cat('\n');
		if (!packages[i].mainheader.IsEmpty())
			PkgFile.Cat(Format("#include <%s/%s>\n", packages[i].dir, packages[i].mainheader));
		PkgFile.Cat(Format("\n#endif\n\n#else\n\n#ifdef %s\n\n", PkgDef));
		for (int j = 0; j < packages[i].icpps.GetCount(); ++j)
			PkgFile.Cat(Format("#include <%s/%s>\n", packages[i].dir, packages[i].icpps[j]));
		for (int j = 0; j < packages[i].sources.GetCount(); ++j)
			PkgFile.Cat(Format("#include <%s/%s>\n", packages[i].dir, packages[i].sources[j]));
		PkgFile.Cat("\n#endif\n\n#endif\n");
		SaveFile(PkgDir + "/" + PkgName + ".h", PkgFile);
	}
	
	const String LibDir = "UppLib";
	DirectoryCreate(LibDir);
	String IncHeader = "#include \"" + LibDir + ".h\"\n\n";
	String LibHeader = DefsHeader(LibDir, cmdline);
	if (!genPCH.IsEmpty()) LibHeader.Cat("\n#include <" + genPCH + ">\n");
	LibHeader.Cat("\n#endif\n");
	SaveFile(LibDir + '/' + LibDir + ".h", LibHeader);
	for (int i = 0; i < nPkgs; ++i)
	{
		if (Contains(ExcludePackages, UsePackages[i])) continue;
		String BaseCName = packages[i].dir; BaseCName.Cat('/');
		String BaseFName = BaseCName; Replace(BaseFName, '/', '_');
		for (int j = 0; j < packages[i].icpps.GetCount(); ++j)
		{
			String FName = packages[i].icpps[j]; Replace(FName, '/', '_');
			FName.Set(FName.ReverseFind('.'), '_'); FName.Cat(".cpp");
			String FText = "#include <" + BaseCName + packages[i].icpps[j] + ">\n";
			SaveFile(LibDir + '/' + BaseFName + FName, IncHeader + FText);
		}
		for (int j = 0; j < packages[i].sources.GetCount(); ++j)
		{
			String FName = packages[i].sources[j]; Replace(FName, '/', '_');
			FName.Set(FName.ReverseFind('.'), '_'); FName.Cat(".cpp");
			String FText = "#include <" + BaseCName + packages[i].sources[j] + ">\n";
			SaveFile(LibDir + '/' + BaseFName + FName, IncHeader + FText);
		}
	} 
	
	String UppBaseCpp = "#include \"UppBase.h\"\n\n#define flagSCU\n";
	for (int i = 0; i < nPkgs; ++i)
	{
		if (Contains(ExcludePackages, UsePackages[i])) continue;
		String PkgName = Package2Include(packages[i]);
		UppBaseCpp.Cat("#include <" + PkgDir + "/" + PkgName + ".h>\n");
	}
	UppBaseCpp.Cat("#undef flagSCU\n");
	SaveFile("UppBase.cpp", UppBaseCpp);
	
	String UppBaseH = DefsHeader("UppBase", cmdline);
	UppBaseH.Cat("\n//#define flagGUI\n//#define APP_MAIN GUI_APP_MAIN\n");
	UppBaseH.Cat("//#define APP_MAIN CONSOLE_APP_MAIN\n//#define APP_MAIN OCX_APP_MAIN\n\n");
	for (int i = 0; i < nPkgs; ++i)
	{
		if (Contains(ExcludePackages, UsePackages[i])) continue;
		String PkgName = Package2Include(packages[i]);
		UppBaseH.Cat("//#include <" + PkgDir + "/" + PkgName + ".h>\n");
	}
	UppBaseH.Cat("\n#endif\n\nusing namespace Upp;\n");
	SaveFile("UppBase.h", UppBaseH);
	
	cout << endl << "Done, enter anything to exit . . . ";
	char ret;
	cin >> ret;
}
