#include "umake.h"

bool SilentMode;

String GetUmkFile(const char *fn)
{
	return GetFileOnPath(
			   fn,
			   GetHomeDirFile(".upp/umk") + ';' +
			   GetHomeDirFile(".upp/theide") + ';' +
			   GetHomeDirFile(".upp/ide") + ';' +
			   GetHomeDirectory() + ';' +
			   GetFileFolder(GetExeFilePath())
		   );
}

String GetBuildMethodPath(String method)
{
	if(GetFileExt(method) != ".bm") {
		method << ".bm";
	}

	return GetUmkFile(method);
}

String Ide::GetDefaultMethod()
{
	return "GCC";
}

VectorMap<String, String> Ide::GetMethodVars(const String& method)
{
	VectorMap<String, String> map;
	LoadVarFile(GetBuildMethodPath(method), map);
	return map;
}

void Puts(const char *s)
{
	if(!SilentMode)
	{ Cout() << s; }
}

int CommaSpace(int c)
{
	return c == ',' ? ' ' : c;
}

int IsCommaOrColon(int c)
{
	return c == ':' || c == ',' ? c : 0;
}

int ProcessResponseFile(const char * resp)
{
	return 0;
}

struct UMKConfig {
	bool clean;
	bool makefile;
	bool deletedir;
	int  exporting;
};

int ProcessOption(const char * opt, UMKConfig& cfg, Ide& ide)
{
	String x = opt;
	for(int i = 1; i < x.GetCount(); i++) {
		switch(x[i]) {
		case 'a':
			cfg.clean = true;
			break;
		case 'r':
			ide.targetmode = 1;
			break;
		case '1':
			ide.targetmode = 2;
			break;
		case '2':
			ide.targetmode = 3;
			break;
		case 'm':
			ide.release.createmap = ide.debug.createmap = true;
			break;
		case 'b':
			ide.release.def.blitz = ide.debug.def.blitz = 1;
			break;
		case 's':
			ide.debug.linkmode = ide.release.linkmode = 1;
			break;
		case 'd':
			ide.debug.def.debug = 0;
			break;
		case 'S':
			ide.debug.linkmode = ide.release.linkmode = 2;
			break;
		case 'M':
			cfg.makefile = true;
			break;
		case 'v':
			ide.console.verbosebuild = true;
			break;
		case 'l':
			break;
		case 'x':
			cfg.exporting = 1;
			break;
		case 'X':
			cfg.exporting = 2;
			break;
		case 'k':
			cfg.deletedir = false;
			break;
		case 'H':
			if(i + 1 < x.GetCount() && x[i + 1] >= '1' && x[i + 1] <= '9') {
				ide.console.SetSlots(x[++i] - '0');
			} else {
				ide.console.SetSlots(1);
			}
			break;
		default:
			SilentMode = false;
			Puts("Invalid build option(s)");
			return 3;
		}
	}
	return 0;
}

/**
 * Truncates a string at a '\n' or '\r'
 * @param str Line to truncate.
 * @retval str Used for chaining.
 */
char * EatCR(char * str)
{
	if(str) {
		for(char * p = str; '\0' != *p; ++p) {
			if('\n' == *p || '\r' == *p) {
				*p = 0;
				break;
			}
		}
	}

	return str;
}

/**
 * Removes spaces at the start and end of a string
 * @param str String.
 * @retval str
 */
char * RemoveSpaces(char * str)
{
	if(!str) {return 0;}


	size_t len = strlen(str);

	if(1 > len) {return str;}

	char * p;
	int i;

	// Remove spaces from the beggining of a string.
	// For us a space is any character less or equal to ' '
	for(i = 0, p = str; *p; ++p, ++i) {
		if(' ' < *p) {break;}
	}

	if(0 < i) {
		if(0 == (len -= i)) {
			str[0] = 0;
			return str;
		}

		// Move the string. Including the '\0' at the end.
		// Do not use memcpy as the string overlaps itself.
		memmove(str,p,len+1);
	}

	// Removes spaces from the end of a string.
	// For us a space is any character less or equal to ' '
	for(p = &str[len - 1]; p >= str; --p) {
		if(' ' < *p) {
			// Found last non space character.
			p[1] = 0;
			break;
		}
	}

	return str;
}

/**
 * Gets path to response file.
 * @param fn Name of response file.
 * @return Path to response file.
 */
inline String GetResponseFilePath(const char *fn)
{
	return GetFileOnPath(fn,GetFileFolder(GetExeFilePath()));
}


/**
 * Gets arguments from command line and response files.
 * @param v Vector to hold the arguments.
 * @retval Number of arguments (including those on the response files) on success.
 * @retval -1 on failure.
 */
int GetCommandLineArguments(Upp::Vector<String>& v)
{
	const Vector<String>& arg = CommandLine();
	int n = arg.GetCount();
	v.Clear();

	for(int i = 0; i < n; ++i) {
		if('@' == arg[i][0]) {
			// Response file.
			String x = arg[i];
			x.Remove(0);
			FILE * fp = fopen(GetResponseFilePath(x), "rt");
			if(fp) {
				char line[128];
				while(fgets(line,sizeof(line)-1, fp)) {
					(void)EatCR(line);
					(void)RemoveSpaces(line);
					if(0 == line[0] || '#' == line[0]) {
						// Skip empty lines and comments.
						continue;
					}
					v.Add(line);
				}
				(void)fclose(fp);
			} else {
				SilentMode = false;
				Puts("Could not load response file.");
				return -1;
			}
		} else {
			v.Add(arg[i]);
		}
	}

	return v.GetCount();
}

CONSOLE_APP_MAIN {
#ifdef PLATFORM_POSIX
	setlinebuf(stdout);
#endif
	Ide ide;
	TheIde(&ide);
	ide.console.SetSlots(CPU_Cores());
	ide.console.console = true;

	Vector<String> arg;

	if(0 > GetCommandLineArguments(arg))
	{
		SilentMode = false;
		SetExitCode(1);
		return;
	}

	if(arg.GetCount() >= 3)
	{
		for(int i = 3; i < arg.GetCount(); i++) {
			if(arg[i][0] == '-') {
				String x = arg[i];
				for(int i = 1; i < x.GetCount(); i++) {
					if(x[i] == 'l') {
						SilentMode = true;
					}
					if(x[i] == 'v') {
						ide.console.verbosebuild = true;
					}
				}
			}
		}
		String v = GetUmkFile(arg[0] + ".var");
		if(IsNull(v) || !FileExists(v)) {
#ifdef PLATFORM_POSIX
			Vector<String> h = Split(arg[0], IsCommaOrColon);
#else
			Vector<String> h = Split(arg[0], ',');
#endif
			for(int i = 0; i < h.GetCount(); i++) {
				h[i] = GetFullPath(TrimBoth(h[i]));
			}
			String x = Join(h, ";");
			SetVar("UPP", x, false);
			PutVerbose("Inline assembly: " + x);
			String outdir = ConfigFile("_out");
			RealizeDirectory(outdir);
			SetVar("OUTPUT", outdir, false);
		} else {
			if(!LoadVars(v)) {
				Puts("Invalid assembly\n");
				SetExitCode(2);
				return;
			}
			PutVerbose("Assembly file: " + v);
			PutVerbose("Assembly: " + GetVar("UPP"));
		}
		PutVerbose("Output directory: " + GetVar("OUTPUT"));
		v = SourcePath(arg[1], GetFileTitle(arg[1]) + ".upp");
		PutVerbose("Main package: " + v);
		if(!FileExists(v)) {
			Puts("Package does not exist\n");
			SetExitCode(2);
			return;
		}
		ide.main = arg[1];
		ide.wspc.Scan(ide.main);
		const Workspace& wspc = ide.IdeWorkspace();
		if(!wspc.GetCount()) {
			Puts("Empty assembly\n");
			SetExitCode(4);
			return;
		}
		const Array<Package::Config>& f = wspc.GetPackage(0).config;
		if(f.GetCount()) {
			ide.mainconfigparam = f[0].param;
		}
		String m = arg[2];
		String bp = GetBuildMethodPath(m);
		PutVerbose("Build method: " + bp);
		if(bp.GetCount() == 0) {
			SilentMode = false;
			Puts("Invalid build method\n");
			SetExitCode(3);
			return;
		}
		ide.method <<= m;
		ide.debug.def.blitz = ide.release.def.blitz = 0;
		ide.debug.def.debug = 2;
		ide.release.def.debug = 0;
		ide.debug.package.Clear();
		ide.release.package.Clear();
		ide.debug.linkmode = ide.release.linkmode = 0;
		ide.release.createmap = ide.debug.createmap = false;
		ide.targetmode = 0;
		UMKConfig cfg = {0};
		String mkf;

		for(int i = 3; i < arg.GetCount(); i++) {
			if(arg[i][0] == '+' || arg[i][0] == '>') {
				ide.mainconfigparam = Filter(~arg[i] + 1, CommaSpace);
			} else if(arg[i][0] == '-') {
				int err = ProcessOption(arg[i], cfg, ide);
				if(err) {
					SetExitCode(err);
					return;
				}
			} else {
				ide.debug.target_override = ide.release.target_override = true;
				ide.debug.target = ide.release.target = mkf = arg[i];
			}
		}

		if(cfg.clean) {
			ide.Clean();
		}

		if(cfg.exporting) {
			mkf = GetFullPath(mkf);
			Cout() << mkf << '\n';
			RealizeDirectory(mkf);
			if(cfg.makefile) {
				ide.ExportMakefile(mkf);
			} else {
				ide.ExportProject(mkf, cfg.exporting == 2, cfg.deletedir);
			}
		} else if(cfg.makefile) {
			ide.SaveMakeFile(IsNull(mkf) ? "Makefile" : mkf, false);
			SetExitCode(0);
		} else if(ide.Build()) {
			SetExitCode(0);
		} else {
			SetExitCode(1);
		}
	} else {
		Puts("Usage: umk assembly main_package build_method -options [+flags] [output]\n"
		"Examples: umk examples Bombs GCC -ab +GUI,SHARED ~/bombs\n"
		"          umk examples,uppsrc Bombs ~/GCC.bm -rv +GUI,SHARED ~/bin\n"
		"See http://www.ultimatepp.org/app$ide$umk$en-us.html for details\n");
	}
}
