#include "Core.h"

bool IsUppValueChar(int c)
{
	return c > ' ' && c != ',' && c != ';';
}

String ReadValue(CParser& p)
{
	p.Spaces();
	if(p.IsString())
		return p.ReadOneString();
	StringBuffer v;
	while(IsUppValueChar(p.PeekChar()))
		v.Cat(p.GetChar());
	p.Spaces();
	return String(v);
}

static bool sMatchOr(CParser& p, const Vector<String>& flag);

static bool sMatchFlag(CParser& p, const Vector<String>& flag)
{
	if(p.Char('!'))
		return !sMatchFlag(p, flag);
	if(p.Char('(')) {
		bool b = sMatchOr(p, flag);
		p.PassChar(')');
		return b;
	}
	if(p.IsEof())
		return true;
	return FindIndex(flag, p.ReadId()) >= 0;
}

static bool sMatchAnd(CParser& p, const Vector<String>& flag)
{
	bool b = sMatchFlag(p, flag);
	while(p.IsId() || p.IsChar('!') || p.IsChar('(') || p.Char2('&', '&') || p.Char('&'))
		b = sMatchFlag(p, flag) && b;
	return b;
}

static bool sMatchOr(CParser& p, const Vector<String>& flag)
{
	bool b = sMatchAnd(p, flag);
	while(p.Char2('|', '|') || p.Char('|'))
		b = sMatchFlag(p, flag) || b;
	return b;
}

bool MatchWhen_X(const String& when, const Vector<String>& flag)
{
	CParser p(when);
	bool b = sMatchOr(p, flag);
	if(!p.IsEof())
		p.ThrowError("expected end of expression");
	return b;
}

bool MatchWhen(const String& when, const Vector<String>& flag)
{
	try {
		return MatchWhen_X(when, flag);
	}
	catch(CParser::Error e) {
		PutConsole(String().Cat()
		           << "Invalid When expression: " << AsCString(when) << ": " << e);
		return false;
	}
}

String ReadWhen(CParser& p) {
	String when;
	if(p.Char('(')) {
		if(p.IsString())
			when = p.ReadString();
		else {
			const char *b = p.GetPtr();
			int lvl = 0;
			for(;;) {
				if(p.IsEof() || lvl == 0 && p.IsChar(')'))
					break;
				if(p.Char('('))
					lvl++;
				else
				if(p.Char(')'))
					lvl--;
				else
					p.SkipTerm();
			}
			when = String(b, p.GetPtr());
			p.Char(')');
		}
	}
	return when;
}

String AsStringWhen(const String& when) {
	String out;
	try {
		Vector<String> x;
		MatchWhen_X(when, x);
		out << '(' << when << ')';
	}
	catch(CParser::Error) {
		out << '(' << AsCString(when) << ')';
	}
	return out;
}

String CustomStep::AsString() const {
	return "custom" + AsStringWhen(when) + ' ' + AsCString(ext) + ",\n\t" +
					  AsCString(command, 70, "\t") + ",\n\t" +
					  AsCString(output, 70, "\t") + ";\n\n";
}

void CustomStep::Load(CParser& p)
{
	when = ReadWhen(p);
	ext = p.ReadString();
	p.PassChar(',');
	command = p.ReadString();
	p.PassChar(',');
	output = p.ReadString();
	p.PassChar(';');
}

String CustomStep::GetExt() const {
	return ext[0] != '.' ? "." + ToLower(ext) : ToLower(ext);
}

bool   CustomStep::MatchExt(const char *fn) const {
	return ToLower(GetFileExt(fn)) == GetExt();
}

bool LoadOpt(CParser& p, const char *key, Array<OptItem>& v) {
	if(p.Id(key)) {
		String when = ReadWhen(p);
		do {
			OptItem& m = v.Add();
			m.when = when;
			m.text = ReadValue(p);
		}
		while(p.Char(','));
		return true;
	}
	return false;
}

bool LoadFOpt(CParser& p, const char *key, Array<OptItem>& v) {
	if(p.Id(key)) {
		OptItem& m = v.Add();
		m.when = ReadWhen(p);
		m.text = ReadValue(p);
		return true;
	}
	return false;
}

void Package::Reset()
{
	charset = 0;
	noblitz = nowarnings = false;
	bold = italic = false;
	ink = Null;
	spellcheck_comments = Null;
	tabsize = Null;
#ifdef PLATFORM_WIN32
	cr = true;
#else
	cr = false;
#endif
}

Package::Package()
{
	Reset();
}

bool StdResolver(const String& error, const String& path, int line)
{
	PutConsole("Invalid package: " + path);
	exit(1);
	return false;
}

static bool (*sResolve)(const String& error, const String& path, int line) = StdResolver;

void Package::SetPackageResolver(bool (*Resolve)(const String& error, const String& path, int line))
{
	sResolve = Resolve;
}

byte CharsetByNameX(const String& s)
{
	return decode(s, "UTF-8-BOM", CHARSET_UTF8_BOM,
	                 "UTF-16-LE", CHARSET_UTF16_LE,
	                 "UTF-16-BE", CHARSET_UTF16_BE,
	                 "UTF-16-LE-BOM", CHARSET_UTF16_LE_BOM,
	                 "UTF-16-BE-BOM", CHARSET_UTF16_BE_BOM,
	                 CharsetByName(s));
}

void Package::Option(bool& option, const char *name)
{
	File& f = file.Top();
	for(int i = 0; i < f.option.GetCount(); i++) // Ugly BW compatibility hack
		if(f.option[i].text == name) {
			f.option.Remove(i);
			option = true;
			break;
		}
}

bool Package::Load(const char *path)
{
	for(;;) {
		Reset();
		library.Clear();
		static_library.Clear();
		target.Clear();
		flag.Clear();
		option.Clear();
		link.Clear();
		uses.Clear();
		include.Clear();
		pkg_config.Clear();
		accepts.Clear();
		file.Clear();
		config.Clear();
		custom.Clear();
		description.Clear();
		String f = LoadFile(path);
		cr = f.Find('\r') >= 0;
		time = FileGetTime(path);
		if(IsNull(time))
			return false;
		CParser p(f);
		p.NoSkipComments(); // allow file path like //home/user/project/something.cpp
		try {
			while(!p.IsEof()) {
				if(!LoadOpt(p, "options", option) &&
				   !LoadOpt(p, "link", link) &&
				   !LoadOpt(p, "library", library) &&
				   !LoadOpt(p, "static_library", static_library) &&
				   !LoadOpt(p, "flags", flag) &&
				   !LoadOpt(p, "target", target) &&
				   !LoadOpt(p, "uses", uses) &&
				   !LoadOpt(p, "include", include) &&
				   !LoadOpt(p, "pkg_config", pkg_config)) {
					if(p.Id("charset"))
						charset = CharsetByNameX(p.ReadString());
					else
					if(p.Id("tabsize"))
						tabsize = minmax(p.ReadInt(), 1, 20);
					else
					if(p.Id("description")) {
						description = p.ReadString();
						const char *q = strchr(description, 255);
						ink = Null;
						bold = italic = false;
						if(q) {
							CParser p(q + 1);
							bold = p.Char('B');
							italic = p.Char('I');
							if(p.IsNumber()) {
								RGBA c = Black();
								c.r = p.ReadInt();
								p.Char(',');
								if(p.IsNumber())
									c.g = p.ReadInt();
								p.Char(',');
								if(p.IsNumber())
									c.b = p.ReadInt();
								ink = c;
							}
							description = String(~description, q);
						}
					}
					else
					if(p.Id("acceptflags")) {
						do
							accepts.Add(ReadValue(p));
						while(p.Char(','));
					}
					else
					if(p.Id("noblitz"))
					   noblitz = true;
					else
					if(p.Id("optimize_speed"))
						; // Legacy option ignored
					else
					if(p.Id("optimize_size"))
						; // Legacy option ignored
					else
					if(p.Id("file")) {
						do {
							File fv(ReadValue(p));
							if(fv.GetCount()) {
								File& f = file.Add();
								f = pick(fv);
								while(!p.IsChar(',') && !p.IsChar(';') && !p.IsEof()) {
									if(!LoadFOpt(p, "options", f.option) &&
									   !LoadFOpt(p, "depends", f.depends)) {
										if(p.Id("optimize_speed"))
											; // Legacy option ignored
										else
										if(p.Id("optimize_size"))
											; // Legacy option ignored
										else
										if(p.Id("pch"))
											f.pch = true;
										else
										if(p.Id("nopch"))
											f.nopch = true;
										else
										if(p.Id("noblitz"))
											f.noblitz = true;
										else
										if(p.Id("readonly"))
											f.readonly = true;
										else
										if(p.Id("separator"))
											f.separator = true;
										else
										if(p.Id("charset"))
											f.charset = CharsetByNameX(p.ReadString());
										else
										if(p.Id("tabsize"))
											f.tabsize = minmax(p.ReadInt(), 1, 20);
										else
										if(p.Id("font"))
											f.font = minmax(p.ReadInt(), 0, 3);
										else
										if(p.Id("highlight"))
											f.highlight = p.ReadId();
										else
										if(p.Id("spellcheck_comments"))
											f.spellcheck_comments = LNGFromText(p.ReadString());
										else
											p.SkipTerm();
									}
								}
								Option(f.pch, "PCH");
								Option(f.nopch, "NOPCH");
								Option(f.noblitz, "NOBLITZ");
							}
						}
						while(p.Char(','));
					}
					else
					if(p.Id("mainconfig")) {
						do {
							String c = p.ReadString();
							p.Char('=');
							p.Id("external"); // Backward compatibility...
							p.Id("console");
							p.Id("remotelinux");
							p.Id("normal");
							String f = p.ReadString();
							Config& cf = config.Add();
							cf.name = c;
							cf.param = f;
						}
						while(p.Char(','));
					}
					else
					if(p.Id("custom"))
						custom.Add().Load(p);
					else
					if(p.Id("spellcheck_comments"))
						spellcheck_comments = LNGFromText(p.ReadString());
					else
						p.SkipTerm();
				}
				p.Char(';');
			}
			for(int i = 0; i < option.GetCount(); i++)
				if(option[i].when == "BUILDER_OPTION" && option[i].text == "NOWARNINGS") {
					nowarnings = true;
					option.Remove(i);
					break;
				}
			return true;
		}
		catch(CParser::Error error) {
			if(sResolve(error, path, p.GetLine() - 1))
				return false;
			Save(path);
			return true;
		}
	}
}

String WriteValue(const String& x) {
	for(const char *s = x; s < x.End(); s++)
		if(!IsUppValueChar(*s))
			return AsCString(x);
	return x;
}

void putopt(Stream& out, const char *key, const Array<OptItem>& m) {
	bool was = false;
	for(int i = 0; i < m.GetCount(); i++)
		if(IsNull(m[i].when)) {
			if(was)
				out << ",\n\t";
			else
				out << key << "\n\t";
			out << WriteValue(m[i].text);
			was = true;
		}
	if(was)
		out << ";\n\n";
	for(int i = 0; i < m.GetCount(); i++)
		if(!IsNull(m[i].when))
			out << key << AsStringWhen(m[i].when) << ' ' << WriteValue(m[i].text) << ";\n\n";
}

void putp(Stream& out, const char *key, const Vector<String>& v)
{
	if(v.GetCount()) {
		out << key << "\n";
		for(int i = 0; i < v.GetCount(); i++) {
			if(i) out << ",\n";
			out << '\t' << WriteValue(v[i]);
		}
		out << ";\n\n";
	}
}

void putfopt(Stream& out, const char *key, const Array<OptItem>& m)
{
	for(int i = 0; i < m.GetCount(); i++)
		out << "\n\t\t" << key << AsStringWhen(m[i].when) << ' ' << WriteValue(m[i].text);
}

String IdeCharsetName(byte charset) {
	return decode(charset, CHARSET_UTF8_BOM, "UTF-8-BOM",
		                   CHARSET_UTF16_LE, "UTF-16-LE",
		                   CHARSET_UTF16_BE, "UTF-16-BE",
		                   CHARSET_UTF16_LE_BOM, "UTF-16-LE-BOM",
		                   CHARSET_UTF16_BE_BOM, "UTF-16-BE-BOM",
		                   CharsetName(charset));
}

void PutSpellCheckComments(StringStream& out, int sc)
{
	if(!IsNull(sc))
		out << " spellcheck_comments " << AsCString(sc ? LNGAsText(sc) : String());
}

bool Package::Save(const char *path) const {
	StringStream out;
	if(description.GetCount() || italic || bold || !IsNull(ink)) {
		String d = description;
		d.Cat(255);
		if(bold)
			d << 'B';
		if(italic)
			d << 'I';
		if(!IsNull(ink))
			d << (int)ink.GetR() << ',' << (int)ink.GetG() << ',' << (int)ink.GetB();
		out << "description " << AsCString(d) << ";\n\n";
	}
	if(charset > 0)
		out << "charset " << AsCString(IdeCharsetName(charset)) << ";\n\n";
	if(!IsNull(tabsize))
		out << "tabsize " << tabsize << ";\n\n";
	if(noblitz)
		out << "noblitz;\n\n";
	if(nowarnings)
		out << "options(BUILDER_OPTION) NOWARNINGS;\n\n";
	putp(out, "acceptflags", accepts);
	putopt(out, "flags", flag);
	putopt(out, "uses", uses);
	putopt(out, "target", target);
	putopt(out, "library", library);
	putopt(out, "static_library", static_library);
	putopt(out, "options", option);
	putopt(out, "link", link);
	putopt(out, "include", include);
	putopt(out, "pkg_config", pkg_config);
	if(file.GetCount()) {
		out << "file\n";
		int i;
		for(i = 0; i < file.GetCount(); i++) {
			if(i) out << ",\n";
			const File& f = file[i];
			out << '\t' << WriteValue(f);
			if(f.readonly)
				out << " readonly";
			if(f.separator)
				out << " separator";
			if(f.tabsize > 0)
				out << " tabsize " << f.tabsize;
			if(f.font > 0)
				out << " font " << f.font;
			if(f.pch)
				out << " options(BUILDER_OPTION) PCH";
			if(f.nopch)
				out << " options(BUILDER_OPTION) NOPCH";
			if(f.noblitz)
				out << " options(BUILDER_OPTION) NOBLITZ";
			if(f.charset > 0)
				out << " charset " << AsCString(IdeCharsetName(f.charset));
			if(!IsNull(f.highlight))
				out << " highlight " << f.highlight;
			PutSpellCheckComments(out, f.spellcheck_comments);
			putfopt(out, "options", f.option);
			putfopt(out, "depends", f.depends);
		}
		out << ";\n\n";
	}
	if(config.GetCount()) {
		out << "mainconfig\n";
		for(int i = 0; i < config.GetCount(); i++) {
			const Config& f = config[i];
			if(i) out << ",\n";
			out << '\t' << AsCString(f.name) << " = " << AsCString(f.param);
		}
		out << ";\n\n";
	}
	PutSpellCheckComments(out, spellcheck_comments);
	for(int i = 0; i < custom.GetCount(); i++)
		out << custom[i].AsString();
	String content = out.GetResult();
	if(cr)
		content.Replace("\n", "\r\n");
	return SaveChangedFile(path, content);
}