/*
    PIP - Platform Independent Primitives
    Code model generator
    Ivan Pelipenko peri4ko@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser 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 Lesser General Public License for more details.

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

#include "enum.h"
#include "getter.h"
#include "json.h"
#include "metainfo.h"
#include "picli.h"
#include "picodeparser.h"
#include "piiostream.h"
#include "pitranslator.h"
#include "stream.h"

using namespace PICoutManipulators;


bool writeModel(PICodeParser & parser,
                PICLI & cli,
                const PIString out,
                bool meta,
                bool enums,
                bool streams,
                bool json,
                bool texts,
                bool getters) {
	PIString defname = "CCM_" + PIString::fromNumber(out.hash()) + "_H";
	PISet<PIString> inc_files;
	for (const PICodeParser::Entity * e: parser.entities)
		if (!e->name.startsWith("_PI")) inc_files << e->file;
	PIString inc_string;
	PIVector<PIString> incf = inc_files.toVector();
	for (const PIString & i: incf) {
		if ((i != parser.mainFile()) && (streams || json || texts || getters)) inc_string += "\n#include \"" + i + "\"";
	}

	PIFile f(out + ".cpp");
	f.clear();
	if (!f.open(PIIODevice::WriteOnly)) {
		piCerr << "Error: can`t open output file \"%1\""_tr("pip_cmg").arg(f.path());
		return false;
	}
	PIIOTextStream ts(&f);
	ts << "// Generated by \"PIP Code model generator\" " << PIDateTime::current().toString("dd.MM.yyyy hh:mm:ss\n\n");
	ts << "#include <string.h>\n";
	ts << "#include \"" << out << ".h\"\n";
	ts << "\nusing namespace PICodeInfo;\n";

	Runtime rt{parser, cli, ts};

	if (meta || enums || getters) {
		if (getters) {
			ts << "\n\n// Getter funtions\n";
			ts << "\n#define PICODEINFO_OFFSET(type, member) reinterpret_cast<size_t>(&reinterpret_cast<const char &>((((type "
				  "*)nullptr)->member)))\n";
			for (const PICodeParser::Entity * e: parser.entities) {
				if (e->is_anonymous || e->name.startsWith("_PI")) continue;
				makeGetterType(rt, e);
				makeGetterValue(rt, e);
				makeGetterOffset(rt, e);
			}
			ts << "\n#undef PICODEINFO_OFFSET\n";
		}
		ts << "\n\n// Metainformation\n\n__ClassInfo_" << defname << "_Initializer__::__ClassInfo_" << defname << "_Initializer__() {\n";
		ts << "\tstatic __ClassInfo_" << defname << "_Initializer__::Content content;\n";
		ts << "}\n\n";
		ts << "__ClassInfo_" << defname << "_Initializer__::Content::Content() {\n";

		ts << "\tauto * ci_ins = PICodeInfo::__Storage__::instance();\n";
		if (meta) ts << "\tauto & ci_ci(*ci_ins->classesInfo);\n";
		if (enums) ts << "\tauto & ci_ei(*ci_ins->enumsInfo);\n";
		if (getters) {
			ts << "\tauto & ci_avf(*ci_ins->accessValueFunctions);\n";
			ts << "\tauto & ci_atf(*ci_ins->accessTypeFunctions);\n";
			ts << "\tauto & ci_aof(*ci_ins->accessOffsetFunctions);\n";
		}

		if (meta) {
			ts << "\tClassInfo * pci = new ClassInfo();\n";
			ts << "\tci_ci[\"\"] = pci;\n";
		}
		if (enums && !parser.enums.isEmpty()) {
			ts << "\tEnumInfo * ei;\n";
		}
		if (meta) {
			ts << "\n\n// Classes\n";
			for (const PICodeParser::Entity * e: parser.entities) {
				if (e->name.startsWith("_PI") || e->is_anonymous) continue;
				makeClassInfo(rt, e);
			}
		}
		if (enums) {
			ts << "\n// Enums\n";
			for (const PICodeParser::Enum & e: parser.enums)
				makeEnumInfo(rt, &e);
		}
		if (getters) {
			ts << "\n// Getters\n";
			for (const PICodeParser::Entity * e: parser.entities) {
				if (!needClassStream(e)) continue;
				if (e->is_anonymous || e->name.startsWith("_PI")) continue;
				auto cname = toCName(e->name);
				ts << "\tci_avf[\"" << e->name << "\"] = getterValue" << cname << ";\n";
				ts << "\tci_atf[\"" << e->name << "\"] = getterType" << cname << ";\n";
				ts << "\tci_aof[\"" << e->name << "\"] = getterOffset" << cname << ";\n";
			}
		}
		ts << "}\n\n";
		ts << "__ClassInfo_" << defname << "_Initializer__::Content::~Content() {\n";
		ts << "\tauto * ci_ins = PICodeInfo::__Storage__::instance();\n";
		if (meta) ts << "\tauto & ci_ci(*ci_ins->classesInfo);\n";
		if (enums) ts << "\tauto & ci_ei(*ci_ins->enumsInfo);\n";
		if (getters) {
			ts << "\tauto & ci_avf(*ci_ins->accessValueFunctions);\n";
			ts << "\tauto & ci_atf(*ci_ins->accessTypeFunctions);\n";
			ts << "\tauto & ci_aof(*ci_ins->accessOffsetFunctions);\n";
		}
		if (meta) {
			ts << "\n// Classes clean\n";
			for (const PICodeParser::Entity * e: parser.entities) {
				if (e->name.startsWith("_PI") || e->is_anonymous) continue;
				ts << "\tpiDeleteSafety(ci_ci[\"" << e->name << "\"]);\n";
				ts << "\tci_ins->classesInfo->remove(\"" << e->name << "\");\n";
			}
		}
		if (enums) {
			ts << "\n// Enums clean\n";
			for (const PICodeParser::Enum & e: parser.enums) {
				if (e.name.isNotEmpty()) {
					ts << "\tpiDeleteSafety(ci_ei[\"" << e.name << "\"]);\n";
					ts << "\tci_ins->enumsInfo->remove(\"" << e.name << "\");\n";
				}
			}
		}
		if (getters) {
			ts << "\n// Getters clean\n";
			for (const PICodeParser::Entity * e: parser.entities) {
				if (!needClassStream(e)) continue;
				if (e->is_anonymous || e->name.startsWith("_PI")) continue;
				ts << "\tci_avf.remove(\"" << e->name << "\");\n";
				ts << "\tci_atf.remove(\"" << e->name << "\");\n";
				ts << "\tci_aof.remove(\"" << e->name << "\");\n";
			}
		}
		ts << "}\n";
	}

	f.close();


	f.setPath(out + ".h");
	f.clear();
	if (!f.open(PIIODevice::WriteOnly)) {
		piCerr << "Error: can`t open output file \"%1\""_tr("pip_cmg").arg(f.path());
		return false;
	}
	ts << "// Generated by \"PIP Code model generator\" " << PIDateTime::current().toString("dd.MM.yyyy hh:mm:ss\n");
	ts << "// Execute command:\n";
	for (const PIString & _a: cli.rawArguments())
		ts << "//   \"" << _a << "\"\n";
	ts << "\n";
	ts << "#ifndef " << defname << "\n#define " << defname << "\n\n";
	ts << "#include <pivariant.h>\n#include <picodeinfo.h>";
	if (streams || texts) ts << "\n#include <pichunkstream.h>";
	if (json) ts << "\n#include <pijsonserialization.h>";
	ts << inc_string << "\n";
	if (streams) {
		ts << "\n\n// Stream operators\n";
		for (const PICodeParser::Entity * e: parser.entities) {
			if (e->is_anonymous || e->name.startsWith("_PI") ||
			    !(e->visibility == PICodeParser::Global || e->visibility == PICodeParser::Public))
				continue;
			if (!makeClassStream(rt, e)) return false;
		}
	}
	if (json) {
		ts << "\n\n// JSON serialization\n";
		for (const PICodeParser::Entity * e: parser.entities) {
			if (e->is_anonymous || e->name.startsWith("_PI") ||
				!(e->visibility == PICodeParser::Global || e->visibility == PICodeParser::Public))
				continue;
			if (!makeClassJSON(rt, e)) return false;
		}
	}
	if (meta || enums || getters) {
		ts << "\n\n// Metainformation\n\nclass __ClassInfo_" << defname << "_Initializer__ {\n";
		ts << "public:\n";
		ts << "\t__ClassInfo_" << defname << "_Initializer__();\n";
		ts << "private:\n";
		ts << "\tclass Content {\n";
		ts << "\tpublic:\n";
		ts << "\t\tContent();\n";
		ts << "\t\t~Content();\n";
		ts << "\t};\n";
		ts << "};\n";
		ts << "\nstatic __ClassInfo_" << defname << "_Initializer__ __classinfo_" << defname.toLowerCase() << "_initializer__;\n";
	}
	ts << "\n\n#endif // " << defname << "\n";
	f.close();

	return true;
}


int main(int argc, char * argv[]) {
	PICLI cli(argc, argv);
	// piCout << cli.rawArguments();
	cli.setOptionalArgumentsCount(-1);
	cli.addArgument("output", true);
	cli.addArgument("help");
	cli.addArgument("Help");
	cli.addArgument("quiet");
	cli.addArgument("All");
	cli.addArgument("Metainfo");
	cli.addArgument("Enum");
	cli.addArgument("Stream");
	cli.addArgument("JSON");
	cli.addArgument("Getter");
	cli.addArgument("Text");
	cli.addArgument("print");
	cli.addArgument("Print");
	cli.addArgument("single");
	if (cli.hasArgument("Help")) {
		help();
		return 0;
	}
	if (cli.hasArgument("help") || cli.argumentValue("output").isEmpty() || cli.optionalArguments().isEmpty()) {
		usage();
		return 0;
	}
	piDebug = !cli.hasArgument("quiet");
	PICodeParser parser;
	for (const 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;
	for (const PIString & a: cli.optionalArguments())
		if (!a.startsWith("-")) files << a;
	piCout << Cyan << Bold << "Parse files" << files << "...";
	parser.parseFiles(files, !cli.hasArgument("single"));
	piCout << Cyan << Bold << "Parsing done";
	piCout << Cyan << Bold << "Writing code model ...";
	bool all = cli.hasArgument("All");
	if (!writeModel(parser,
	                cli,
	                cli.argumentValue("output"),
	                cli.hasArgument("Metainfo") || all,
	                cli.hasArgument("Enum") || all,
	                cli.hasArgument("Stream") || all,
					cli.hasArgument("JSON") || all,
	                cli.hasArgument("Text") || all,
	                cli.hasArgument("Getter") || all))
		return 1;
	piCout << Cyan << Bold << "Writing done";
	if (cli.hasArgument("print") || cli.hasArgument("Print")) {
		bool womain = cli.hasArgument("print");
		piDebug     = true;
		PIStringList pf(parser.parsedFiles());
		for (const PIString & f: pf) {
			if (!womain || (f != parser.mainFile())) piCout << f;
		}
	}
	return 0;
}
