
#include "peglib.h"
#include <assert.h>
#include <iostream>
#include <iterator>
#include <fstream>
#include <sstream>
#include <filesystem>
#include <regex>
#include <typeinfo>
#include <typeindex>
#include <unordered_map>

using namespace std;
using namespace peg;

#define ANY_CAST(TYPE, VAL) \
[&](){ \
	try{ \
		return any_cast<TYPE>(VAL); \
	} \
	catch(const bad_any_cast& e){ \
		cout << "line " << __LINE__ << ": bad any cast\n"; \
		cout << "from: " << (VAL).type().name() << "\n"; \
		cout << "to: " << typeid(TYPE).name() << "\n"; \
		throw e; \
	} \
}()

struct IndentationChange {
	int delta;
};
struct IndentingBreak {
	unsigned int numLines = 1;
	IndentingBreak operator*(unsigned int nl) const {
		return { nl };
	}
};

class IndentingStream {
public:
	int indent = 0;
	ostream& os;
	IndentingStream(ostream& stream) : os{ stream } {}
};

template<typename T>
IndentingStream& operator<< (IndentingStream& s, const T& msg) {
	s.os << msg;
	return s;
}

template<>
IndentingStream& operator<< (IndentingStream& s, const IndentationChange& ic) {
	s.indent += ic.delta;
	return s;
}

template<>
IndentingStream& operator<< (IndentingStream& s, const IndentingBreak& br) {
	for (size_t i = 0; i < br.numLines; i++) {
		s.os << "\n";
	}
	for (size_t i = 0; i < s.indent; i++) {
		s.os << "\t";
	}
	return s;
}

const IndentationChange INDENT{ 1 };
const IndentationChange OUTDENT{ -1 };
const IndentingBreak BR;

class SkipFirst {
public:
	bool first = true;
	string s;
	SkipFirst(string txt) :s{ txt } {}
	string operator() () {
		if (first) {
			first = false;
			return "";
		}
		else {
			return s;
		}
	}
	void reset() { first = true; }
};

// load string from file
inline string load(const string& filename, bool binary = false) {
	ifstream ifs(filename, binary ? std::ios::in | std::ios::binary : std::ios::in);
	if (!ifs) { throw std::runtime_error(filename + " could not be read"); }
	return std::string(
		(std::istreambuf_iterator<char>(ifs)),
		(std::istreambuf_iterator<char>()));
}

// save string to file
inline void save(const string& filename, const string& content) {
	ofstream ofs(filename);
	if (!ofs) {
		throw runtime_error(filename + " could not be written");
	}
	ofs << content;
}

inline void replaceInplace(
	std::string& str,
	const std::string& search,
	const std::string& repl
) {
	size_t start_pos = 0;
	while ((start_pos = str.find(search, start_pos)) != std::string::npos) {
		str.replace(start_pos, search.length(), repl);
		start_pos += repl.length(); // Handles case where 'repl' is a substring of 'search'
	}
}

inline std::string replace(
	std::string str,
	const std::string& search,
	const std::string& repl
) {
	replaceInplace(str, search, repl);
	return str;
}

int main(int argc, char* argv[]) {

	if (argc < 4) {
		cout << "usage:\ng++ -E myFile.h | " << argv[0]
			<< " GRAMMAR_FILE OUTPUT_FILE CLASS_NAMES\n";
		return EXIT_FAILURE;
	}

	try {
		istreambuf_iterator<char> streamBegin(std::cin), streamEnd;
		string input(streamBegin, streamEnd);

		string grammar = load(argv[1]);
		string outputFile = argv[2];
		vector<string> classNames;
		for (size_t i = 3; i < argc; i++) {
			classNames.push_back(argv[i]);
		}

		bool isStruct = false;
		bool isPublic = false;
		struct Var {
			string type;
			string name;
			string deflt;
		};
		struct Method {
			string returnType;
			string name;
			vector<Var> args;
		};

		stringstream ss;
		IndentingStream out(ss);
		out << R"(

// This file is automatically generated by the coding assistants. Don't edit it manually.
// The declarations are generated from the class declarations in the C++ source.

#include "bindings.h"
#include "pybind11/pybind11.h"
#include "pybind11/stl.h"

namespace py = pybind11;

PYBIND11_MODULE(PyGestaltPlanner, m) {)" << INDENT << BR * 2;

		vector<Var> fields;
		vector<vector<Var> > ctorsArgs;
		vector<Method> methods;
		unordered_map<string, string> nakedTypes;
		vector<pair<string, string>> conversions;

		string currentClass;

		string classNamesPeg = "(";
		SkipFirst sep(" / ");
		for (const auto& c : classNames) {
			classNamesPeg += sep() + '"' + c + '"' + " !Identifier ";
		}
		classNamesPeg += ")";

		grammar = replace(grammar, "{CLASS_NAMES}", classNamesPeg);

		parser parser;

		parser.log = [](size_t line, size_t col, const string& msg) {
			cout << "grammar error (" << line << ":" << col << "): " << msg << "\n";
		};

		parser.load_grammar(grammar);
		if (static_cast<bool>(parser) == false) {
			return EXIT_FAILURE;
		}

		for (const auto& rule : parser.get_rule_names()) {
			parser[rule.c_str()] = [](const SemanticValues& vs) {
				if (vs.tokens.size() == 0) {
					stringstream ss;
					for (const auto& v : vs) {
						ss << ANY_CAST(string, v);
					}
					return string(ss.str());
				}
				else {
					return string(vs.token_to_string());
				}
			};
		}

		parser["ClassKeyword"] = [&](const SemanticValues& vs) {
			isStruct = vs.token_to_string() == "struct";
			isPublic = isStruct;
			return string("");
		};

		parser["SideEffectsClassName"] = [&](const SemanticValues& vs) {
			currentClass = vs.token_to_string();
			ctorsArgs.clear();
			fields.clear();
			methods.clear();
		};

		parser["TargetClass"] = [&](const SemanticValues& vs) {

			auto init = [&](const vector<Var>& args) {
				out << INDENT << BR << ".def("
					<< INDENT << BR << "py::init<" << INDENT;
				SkipFirst sep(",");
				for (const auto& arg : args) {
					out << sep() << BR << arg.type;
				}
				out << OUTDENT;
				if (args.size() > 0) { out << BR; }
				out << ">()," << BR
					<< "py::kw_only()";
				for (const auto& arg : args) {
					out << "," << BR << "py::arg(\""
						<< arg.name << "\")";
					if (arg.deflt[0] == '{') {
						out << " = "
							<< nakedTypes.at(arg.type)
							<< arg.deflt;
					}
					else if (arg.deflt != "") {
						out << " = " << arg.deflt;
					}
				}
				out << OUTDENT << BR << ")" << OUTDENT;
			};

			out << "py::class_<"
				<< currentClass
				<< ">(m, \""
				<< currentClass
				<< "\")";

			if (isStruct) {
				if (fields.size() > 0) {
					init(fields);
				}

				for (const auto& field : fields) {
					out << INDENT << BR << ".def_readwrite(\""
						<< field.name << "\", &"
						<< currentClass << "::"
						<< field.name << ")" << OUTDENT;
				}
			}
			else {
				for (const auto& args : ctorsArgs) {
					init(args);
				}

				for (const auto& method : methods) {
					out << INDENT << BR << ".def("
						<< "\"" << method.name << "\"," << INDENT << BR
						<< "&" << currentClass << "::" << method.name;
					if (method.args.size() > 0) {
						out << "," << BR << "py::kw_only()";
					}
					for (const auto& arg : method.args) {
						out << "," << BR << "py::arg(\""
							<< arg.name << "\")";
						if (arg.deflt[0] == '{') {
							out << " = "
								<< nakedTypes.at(arg.type)
								<< arg.deflt;
						}
						else if (arg.deflt != "") {
							out << " = " << arg.deflt;
						}
					}
					out << OUTDENT << BR << ")" << OUTDENT;
				}
			}
			out << ";" << BR * 2;

			return string("");
		};

		parser["Access"] = [&](const SemanticValues& vs) {
			isPublic = vs.token_to_string() == "public";
			return string("");
		};

		parser["Var"] = [&](const SemanticValues& vs) {
			return Var{
				ANY_CAST(string, vs[0]),
				ANY_CAST(string, vs[1]),
				vs.size() > 2 ? ANY_CAST(string, vs[2]) : string("")
			};
		};

		parser["Arg"] = [&](const SemanticValues& vs) {
			return vs[0];
		};

		parser["Ctor"] = [&](const SemanticValues& vs) {
			if (isPublic && !isStruct) {
				vector<Var> args;
				for (const auto& arg : vs) {
					args.push_back(ANY_CAST(Var, arg));
				}
				ctorsArgs.push_back(args);
			}
			return string("");
		};

		parser["Conversion"] = [&](const SemanticValues& vs) {
			if (isPublic) {
				conversions.push_back({ currentClass, vs.token_to_string() });
			}
			return string("");
		};

		parser["Field"] = [&](const SemanticValues& vs) {
			if (isPublic && isStruct) {
				fields.push_back(ANY_CAST(Var, vs[0]));
			}
			return string("");
		};

		parser["Method"] = [&](const SemanticValues& vs) {
			if (isPublic && !isStruct) {
				Method method;
				method.returnType = ANY_CAST(string, vs[0]);
				method.name = ANY_CAST(string, vs[1]);
				for (size_t i = 2; i < vs.size(); i++) {
					method.args.push_back(ANY_CAST(Var, vs[i]));
				}
				methods.push_back(method);
			}
			return string("");
		};

		parser["Type"] = [&](const SemanticValues& vs) {
			string cnst = ANY_CAST(string, vs[0]);
			if (cnst != "") { cnst += " "; }
			string nakedType = ANY_CAST(string, vs[1]);
			string refs = ANY_CAST(string, vs[2]);
			if (refs != "") { refs = " " + refs; }
			string fullType = cnst + nakedType + refs;
			nakedTypes[fullType] = nakedType;
			return fullType;
		};

		parser.enable_packrat_parsing();

		parser.parse(input);

		for (const auto& c : conversions) {
			out << "py::implicitly_convertible<"
				<< c.first << ", " << c.second << ">();" << BR;
		}

		out << OUTDENT << BR << "}\n";

		string output = ss.str();

		for (const auto& c : conversions) {
			string search = string() + "py::class_<" + c.second + ">(m, \"" + c.second + "\")";
			string insert = string() + "\n\t\t.def(py::init<" + c.first + ">())";
			replaceInplace(output, search, search + insert);
		}

		bool create = true;
		try {
			string old = load(outputFile);
			if (output == old) {
				cout << outputFile << " would not change, not overwriting\n";
				create = false;
			}
		}
		catch (runtime_error& e) {}
		if (create) {
			save(outputFile, output);
		}


		return EXIT_SUCCESS;
	}
	catch (runtime_error& e) {
		cout << e.what() << "\n";
		return EXIT_FAILURE;
	}
}