/*
    PIP - Platform Independent Primitives
    Code model generator
    Copyright (C) 2014  Ivan Pelipenko peri4ko@gmail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "picli.h"
#include "picodeparser.h"


void usage() {
	piCout << Bold << "PIP Code model generator";
	piCout << Cyan << "Version" << Bold << PIPVersion() << NewLine;
	piCout << Green << Bold << "Usage:" << Default << "\"pip_cmg [-hqPp] -o <output_file> [-I<include_dir1>] [-I<include_dir1>] [...] [-D<define1>] [-D<define1>] [...] <file1> [<file2>] [<file3>] [...]\"" << NewLine;
	piCout << Green << Bold << "Details:";
	piCout << "-h               " << Green << "- display this message and exit";
	piCout << "-q               " << Green << "- quiet, no debug output to console";
	piCout << "-P               " << Green << "- print list of all parsed files to console before exit";
	piCout << "-p               " << Green << "- print list of all parsed files without file with \"main\" function to console before exit";
	piCout << "-o <output_file> " << Green << "- output file for code model without extension (e.g. \"ccm\" - files \"ccm.h\" and \"ccm.cpp\" will be created)";
	piCout << "-I<include_dir>  " << Green << "- add include dir (e.g. -I.. -I../some_dir -I/usr/include)";
	piCout << "-D<define>       " << Green << "- add define to preprocessor, define PICODE is always defined (e.g. -DMY_DEFINE will add MY_DEFINE define)";
	piCout << "<file>           " << Green << "- add file to code model, all includes of this file will be proceed (e.g. \"main.cpp\")";
}


void makeClassInfo(PIFile & f, const PICodeParser::Entity * e) {
	f << "\n\tci = new ClassInfo();\n";
	f << "\t(*classesInfo)[\"" << e->name << "\"] = ci;\n";
	f << "\tci->name = \"" << e->name << "\";\n";
	piForeachC (PICodeParser::Entity * p, e->parents)
		f << "\tci->parents << \"" << p->name << "\";\n";
	piForeachC (PICodeParser::Member & m, e->members) {
		f << "\tci->variables << TypeInfo(\"" << m.name << "\", \"" << m.type << "\"";
		if (m.attributes != 0) {
			bool fir = true;
			f << ", ";
			if (m.attributes[PICodeParser::Const]) {if (fir) fir = false; else f << " | "; f << "Const";}
			if (m.attributes[PICodeParser::Static]) {if (fir) fir = false; else f << " | "; f << "Static";}
			if (m.attributes[PICodeParser::Mutable]) {if (fir) fir = false; else f << " | "; f << "Mutable";}
			if (m.attributes[PICodeParser::Volatile]) {if (fir) fir = false; else f << " | "; f << "Volatile";}
			if (m.attributes[PICodeParser::Inline]) {if (fir) fir = false; else f << " | "; f << "Inline";}
			if (m.attributes[PICodeParser::Virtual]) {if (fir) fir = false; else f << " | "; f << "Virtual";}
		}
		f << ");\n";
	}
	PIString arg;
	piForeachC (PICodeParser::Member & m, e->functions) {
		if (e->name.findCWord(m.name) >= 0) continue;
		f << "\tci->functions.push_back(FunctionInfo()); fi = &(ci->functions.back());\n";
		f << "\tfi->name = \"" << m.name << "\";";
		f << " fi->return_type = TypeInfo(\"\", \"" << m.type << "\"";
		if (m.attributes[PICodeParser::Const] || m.attributes[PICodeParser::Static]) {
			bool fir = true;
			f << ", ";
			if (m.attributes[PICodeParser::Const]) {if (fir) fir = false; else f << " | "; f << "Const";}
			if (m.attributes[PICodeParser::Static]) {if (fir) fir = false; else f << " | "; f << "Static";}
		}
		f << ");\n";
		//piCout << "write func" << m.name;
		piForeachC (PIString & a, m.arguments_full) {
			//piCout << "write arg" << a;
			f << "\tfi->arguments << TypeInfo(";
			arg = a;
			bool con = false;
			arg.prepend(" ");
			if (arg.find(" const ") >= 0) {
				arg.replaceAll(" const ", " ");
				con = true;
			}
			arg.trim();
			int ts = 0;
			for (ts = arg.size_s() - 1; ts > 0; --ts)
				if (!_isCChar(arg[ts]) && !(arg[ts].isDigit())) break;
			f << "\"" << arg.takeRight(arg.size_s() - ts - 1).trim() << "\", ";
			f << "\"" << arg.trim() << "\"";
			if (con) f << ", Const";
			f << ");\n";
		}
	}
}


void makeEnumInfo(PIFile & f, const PICodeParser::Enum * e) {
	f << "\n\tei = new EnumInfo();\n";
	f << "\t(*enumsInfo)[\"" << e->name << "\"] = ei;\n";
	f << "\tei->name = \"" << e->name << "\";\n";
	piForeachC (PICodeParser::Enumerator & m, e->members)
		f << "\tei->members << PICodeInfo::EnumeratorInfo(\"" << m.first << "\", " << m.second << ");\n";
}


void writeModel(PICodeParser & parser, const PIString out) {
	PIVector<const PICodeParser::Entity * > ventities;
	PIString defname = out.replaceAll(".", "_").replaceAll("/", "_").toUpperCase() + "_H";
	
	PISet<PIString> inc_files;
	piForeachC (PICodeParser::Entity * e, parser.entities)
		if (e->name.find("::") < 0 && !e->name.startsWith("_PI"))
			inc_files << e->file;
	
	PIFile f(out + ".cpp");
	f.clear();
	f.open(PIIODevice::WriteOnly);
	f << "// Generated by \"PIP Code model generator\" " << PIDateTime::current().toString("dd.MM.yyyy hh:mm:ss\n\n");
	f << "#include <string.h>\n#include \"" << out << ".h\"\n\nusing namespace PICodeInfo;\n\n";
	piForeachC (PIString & i, inc_files) {
		if (i != parser.mainFile())
			f << "#include \"" << i << "\"\n";
	}
	f << "\n";
	PIString entry, argtype, rettype, args;
	piForeachC (PICodeParser::Entity * e, parser.entities) {
		if (e->name.find("::") >= 0 || e->name.startsWith("_PI")) continue;
		entry.clear();
		entry << "\nPIVariant execFunction(" << e->name << " * object, const char * function, const PIVariant & arg0, const PIVariant & arg1, const PIVariant & arg2, const PIVariant & arg3) {\n";
		const PIVector<PICodeParser::Member> & fl(e->functions);
		bool efunc = true;
		piForeachC (PICodeParser::Member & m, fl) {
			if (m.name.startsWith("__stat") || m.attributes[PICodeParser::Static]) continue;
			//piCout << e->name << m.name << m.visibility;
			args.clear();
			rettype = m.type;
			if (rettype.startsWith("const") && rettype.endsWith("&"))
				rettype.cutLeft(5).cutRight(1).trim();
			if (rettype.endsWith("&")) continue;
			bool aok = true, ret = (m.type != "void");
			if (m.arguments_full.size() > 4 || m.visibility != PICodeParser::Public) continue;
			for (int i = 0; i < m.arguments_full.size_s(); ++i) {
				if (i > 0) args << ", ";
				argtype = m.arguments_type[i];
				if (argtype.startsWith("const") && argtype.endsWith("&"))
					argtype.cutLeft(5).cutRight(1).trim();
				if (argtype.endsWith("&")) {
					aok = false;
					continue;
				}
				//entry << "(" << m.arguments_type[i] << ")";
				//if (parser.isEnum(m.arguments_type[i])) entry << "(int)";
				args << "arg" << i << ".toValue<" << argtype << " >()";
			}
			if (!aok) continue;
			efunc = false;
			entry << "\tif (strcmp(function, \"" << m.name << "\") == 0) {";
			if (ret) entry << "return PIVariant::fromValue<" << rettype << " >(";
			entry << "object->" << m.name << "(" << args << ")";
			if (ret) entry << ");";
			else entry << "; return PIVariant();";
			entry << "}\n";
		}
		if (efunc) continue;
		f << entry << "\tPICout(AddNewLine) << \"Can`t find function \\\"\" << function << \"\\\" in "
		  << e->type << " \\\"" << e->name << "\\\"!\";\n\treturn PIVariant();\n}\n";
		ventities << e;
	}
	f << "\n\n__ClassInfo_" << defname << "_Initializer__::__ClassInfo_" << defname << "_Initializer__() {\n";
	f << "\tif (_inited_) return;\n\t_inited_ = true;\n\n";
	f << "\tClassInfo * ci;\n\tTypeInfo * ni;\n\tFunctionInfo * fi;\n\tEnumInfo * ei;";
	f << "\n\n// Classes\n";
	piForeachC (PICodeParser::Entity * e, parser.entities) {
		if (e->name.startsWith("_PI")) continue;
		makeClassInfo(f, e);
	}
	f << "\n// Enums\n";
	piForeachC (PICodeParser::Enum & e, parser.enums)
		makeEnumInfo(f, &e);
	f << "}\n";
	f << "\n\nbool __ClassInfo_" << defname << "_Initializer__::_inited_ = false;\n";
	f.close();
	
	
	f.setPath(out + ".h");
	f.clear();
	f.open(PIIODevice::WriteOnly);
	f << "// Generated by \"PIP Code model generator\" " << PIDateTime::current().toString("dd.MM.yyyy hh:mm:ss\n\n");
	f << "#ifndef " << defname << "\n#define " << defname << "\n\n";
	f << "#include \"pivariant.h\"\n#include \"picodeinfo.h\"\n\n";
	piForeachC (PICodeParser::Entity * e, ventities)
		f << e->type << " " << e->name << ";\n";
	f << "\n";
	piForeachC (PICodeParser::Entity * e, ventities) {
		f << "\nPIVariant execFunction(" << e->name << " * object, const char * function, const PIVariant & arg0 = PIVariant(), \
const PIVariant & arg1 = PIVariant(), const PIVariant & arg2 = PIVariant(), const PIVariant & arg3 = PIVariant());";
	}
	f << "\n\n\nclass __ClassInfo_" << defname << "_Initializer__ {\n";
	f << "public:\n\t__ClassInfo_" << defname << "_Initializer__();\n\tstatic bool _inited_;\n};\n";
	f << "\nstatic __ClassInfo_" << defname << "_Initializer__ __classinfo_" << defname.toLowerCase() << "_initializer__;\n";
	f << "\n\n#endif // " << defname << "\n";
	f.close();
}


int main(int argc, char * argv[]) {
	PICLI cli(argc, argv);
	cli.setOptionalArgumentsCount(-1);
	cli.addArgument("output", true);
	cli.addArgument("help");
	cli.addArgument("quiet");
	cli.addArgument("print");
	cli.addArgument("Print");
	if (cli.hasArgument("help") || cli.argumentValue("output").isEmpty() || cli.optionalArguments().isEmpty()) {
		usage();
		return 0;
	}
	piDebug = !cli.hasArgument("quiet");
	PICodeParser parser;
	piForeachC (PIString & a, cli.rawArguments()) {
		if (a.startsWith("-I")) parser.includeDirectory(a.mid(2));
		if (a.startsWith("-D")) parser.addDefine(a.mid(2), PIString());
	}
	PIStringList files;
	piForeachC (PIString & a, cli.optionalArguments())
		if (!a.startsWith("-")) files << a;
	piCout << Cyan << Bold << "Parse files" << files << "...";
	parser.parseFiles(files);
	piCout << Cyan << Bold << "Parsing done";
	piCout << Cyan << Bold << "Writing code model ...";
	writeModel(parser, cli.argumentValue("output"));
	piCout << Cyan << Bold << "Writing done";
	if (cli.hasArgument("print") || cli.hasArgument("Print")) {
		bool womain = cli.hasArgument("print");
		piDebug = true;
		PIStringList pf(parser.parsedFiles());
		piForeachC (PIString & f, pf) {
			if ((womain && (f != parser.mainFile())) || !womain)
				piCout << f;
		}
	}
	return 0;
};
