/*
 *  The contents of this file are subject to the Initial
 *  Developer's Public License Version 1.0 (the "License");
 *  you may not use this file except in compliance with the
 *  License. You may obtain a copy of the License at
 *  http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl.
 *
 *  Software distributed under the License is distributed AS IS,
 *  WITHOUT WARRANTY OF ANY KIND, either express or implied.
 *  See the License for the specific language governing rights
 *  and limitations under the License.
 *
 *  The Original Code was created by Adriano dos Santos Fernandes.
 *
 *  Copyright (c) 2014 Adriano dos Santos Fernandes <adrianosf at gmail.com>
 *  and all contributors signed below.
 *
 *  All Rights Reserved.
 *  Contributor(s): ______________________________________.
 */

#include "Generator.h"
#include "Expr.h"
#include <deque>
#include <set>
#include <stdexcept>
#include <string>
#include <vector>

using std::deque;
using std::runtime_error;
using std::set;
using std::string;
using std::vector;


//--------------------------------------


const char* const Generator::AUTOGEN_MSG =
	"This file was autogenerated by cloop - Cross Language Object Oriented Programming";


//--------------------------------------


FileGenerator::FileGenerator(const string& filename, const string& prefix)
	: prefix(prefix)
{
	out = fopen(filename.c_str(), "w+");
}

FileGenerator::~FileGenerator()
{
	fclose(out);
}


//--------------------------------------


CBasedGenerator::CBasedGenerator(const string& filename, const string& prefix, bool cPlusPlus)
	: FileGenerator(filename, prefix),
	  cPlusPlus(cPlusPlus)
{
}

string CBasedGenerator::convertType(const TypeRef& typeRef)
{
	string ret(typeRef.isConst ? "const " : "");

	switch (typeRef.token.type)
	{
		case Token::TYPE_BOOLEAN:
			ret += "FB_BOOLEAN";	// seems to be more portable than bool, specially thinking on pointers
			break;

		case Token::TYPE_INT:
			ret += "int";
			break;

		case Token::TYPE_INT64:
			ret += "ISC_INT64";		//int64_t
			break;

		case Token::TYPE_INTPTR:
			ret += "intptr_t";
			break;

		case Token::TYPE_STRING:
			ret += "char*";
			break;

		case Token::TYPE_UCHAR:
			ret += "unsigned char";
			break;

		case Token::TYPE_UINT:
			ret += "unsigned";
			break;

		case Token::TYPE_UINT64:
			ret += "ISC_UINT64";	//uint64_t
			break;

		case Token::TYPE_IDENTIFIER:
			ret += string(cPlusPlus || typeRef.type == BaseType::TYPE_TYPEDEF ? "" : "struct ") +
				(typeRef.type == BaseType::TYPE_INTERFACE ? prefix : "") + typeRef.token.text;

			if (typeRef.type == BaseType::TYPE_INTERFACE)
				ret += "*";
			break;

		default:
			ret += typeRef.token.text;
			break;
	}

	if (typeRef.isPointer)
		ret += "*";

	return ret;
}


//--------------------------------------


CppGenerator::CppGenerator(const string& filename, const string& prefix, Parser* parser,
		const string& headerGuard, const string& nameSpace)
	: CBasedGenerator(filename, prefix, true),
	  parser(parser),
	  headerGuard(headerGuard),
	  nameSpace(nameSpace)
{
}

void CppGenerator::generate()
{
	fprintf(out, "// %s\n\n", AUTOGEN_MSG);

	fprintf(out, "#ifndef %s\n", headerGuard.c_str());
	fprintf(out, "#define %s\n\n", headerGuard.c_str());
	///fprintf(out, "#include <stdint.h>\n\n");

	fprintf(out, "#ifndef CLOOP_CARG\n");
	fprintf(out, "#define CLOOP_CARG\n");
	fprintf(out, "#endif\n\n\n");

	fprintf(out, "namespace %s\n", nameSpace.c_str());
	fprintf(out, "{\n");
	fprintf(out, "\tclass DoNotInherit\n");
	fprintf(out, "\t{\n");
	fprintf(out, "\t};\n");
	fprintf(out, "\n");
	fprintf(out, "\ttemplate <typename T>\n");
	fprintf(out, "\tclass Inherit : public T\n");
	fprintf(out, "\t{\n");
	fprintf(out, "\tpublic:\n");
	fprintf(out, "\t\tInherit(DoNotInherit = DoNotInherit())\n");
	fprintf(out, "\t\t\t: T(DoNotInherit())\n");
	fprintf(out, "\t\t{\n");
	fprintf(out, "\t\t}\n");
	fprintf(out, "\t};\n");
	fprintf(out, "\n");

	fprintf(out, "\t// Forward interfaces declarations\n\n");

	for (vector<Interface*>::iterator i = parser->interfaces.begin();
		 i != parser->interfaces.end();
		 ++i)
	{
		Interface* interface = *i;

		fprintf(out, "\tclass %s%s;\n", prefix.c_str(), interface->name.c_str());
	}

	fprintf(out, "\n");
	fprintf(out, "\t// Interfaces declarations\n\n");

	for (vector<Interface*>::iterator i = parser->interfaces.begin();
		 i != parser->interfaces.end();
		 ++i)
	{
		Interface* interface = *i;

		deque<Method*> methods;

		for (Interface* p = interface; p; p = p->super)
			methods.insert(methods.begin(), p->methods.begin(), p->methods.end());

		if (!interface->super)
			fprintf(out, "\tclass %s%s\n", prefix.c_str(), interface->name.c_str());
		else
		{
			fprintf(out, "\tclass %s%s : public %s%s\n",
				prefix.c_str(), interface->name.c_str(),
				prefix.c_str(), interface->super->name.c_str());
		}

		fprintf(out, "\t{\n");
		fprintf(out, "\tpublic:\n");

		if (!interface->super)
		{
			fprintf(out, "\t\tstruct VTable\n");
			fprintf(out, "\t\t{\n");
			fprintf(out, "\t\t\tvoid* cloopDummy[%d];\n", DUMMY_VTABLE);
			fprintf(out, "\t\t\tuintptr_t version;\n");
		}
		else
		{
			fprintf(out, "\t\tstruct VTable : public %s%s::VTable\n",
				prefix.c_str(), interface->super->name.c_str());
			fprintf(out, "\t\t{\n");
		}

		for (vector<Method*>::iterator j = interface->methods.begin();
			 j != interface->methods.end();
			 ++j)
		{
			Method* method = *j;

			fprintf(out, "\t\t\t%s (CLOOP_CARG *%s)(%s%s%s* self",
				convertType(method->returnTypeRef).c_str(),
				method->name.c_str(),
				(method->isConst ? "const " : ""),
				prefix.c_str(),
				interface->name.c_str());

			for (vector<Parameter*>::iterator k = method->parameters.begin();
				 k != method->parameters.end();
				 ++k)
			{
				Parameter* parameter = *k;

				fprintf(out, ", %s %s",
					convertType(parameter->typeRef).c_str(), parameter->name.c_str());
			}

			fprintf(out, ") throw();\n");
		}

		fprintf(out, "\t\t};\n");
		fprintf(out, "\n");

		if (!interface->super)
		{
			fprintf(out, "\t\tvoid* cloopDummy[%d];\n", DUMMY_INSTANCE);
			fprintf(out, "\t\tVTable* cloopVTable;\n");
			fprintf(out, "\n");
		}

		fprintf(out, "\tprotected:\n");
		fprintf(out, "\t\t%s%s(DoNotInherit)\n", prefix.c_str(), interface->name.c_str());

		if (interface->super)
		{
			fprintf(out, "\t\t\t: %s%s(DoNotInherit())\n",
				prefix.c_str(), interface->super->name.c_str());
		}

		fprintf(out, "\t\t{\n");
		fprintf(out, "\t\t}\n");
		fprintf(out, "\n");
		fprintf(out, "\t\t~%s%s()\n", prefix.c_str(), interface->name.c_str());
		fprintf(out, "\t\t{\n");
		fprintf(out, "\t\t}\n");
		fprintf(out, "\n");

		fprintf(out, "\tpublic:\n");
		fprintf(out, "\t\tstatic const unsigned VERSION = %u;\n", interface->version);

		if (!interface->constants.empty())
			fprintf(out, "\n");

		for (vector<Constant*>::iterator j = interface->constants.begin();
			 j != interface->constants.end();
			 ++j)
		{
			Constant* constant = *j;

			fprintf(out, "\t\tstatic const %s %s = %s;\n",
				convertType(constant->typeRef).c_str(),
				constant->name.c_str(),
				constant->expr->generate(LANGUAGE_CPP, prefix).c_str());
		}

		for (vector<Method*>::iterator j = interface->methods.begin();
			 j != interface->methods.end();
			 ++j)
		{
			Method* method = *j;

			fprintf(out, "\n\t\t");

			string statusName;

			if (!method->parameters.empty() &&
				parser->exceptionInterface &&
				method->parameters.front()->typeRef.token.text == parser->exceptionInterface->name)
			{
				statusName = method->parameters.front()->name;
				fprintf(out, "template <typename StatusType> ");
			}

			fprintf(out, "%s %s(",
				convertType(method->returnTypeRef).c_str(), method->name.c_str());

			for (vector<Parameter*>::iterator k = method->parameters.begin();
				 k != method->parameters.end();
				 ++k)
			{
				Parameter* parameter = *k;

				if (k != method->parameters.begin())
					fprintf(out, ", ");

				if (k == method->parameters.begin() && !statusName.empty())
					fprintf(out, "StatusType* %s", parameter->name.c_str());
				else
				{
					fprintf(out, "%s %s",
						convertType(parameter->typeRef).c_str(), parameter->name.c_str());
				}
			}

			fprintf(out, ")%s\n", (method->isConst ? " const" : ""));
			fprintf(out, "\t\t{\n");

			if (method->version - (interface->super ? interface->super->version : 0) != 1)
			{
				fprintf(out, "\t\t\tif (cloopVTable->version < %d)\n", method->version);
				fprintf(out, "\t\t\t{\n");

				if (!statusName.empty())
				{
					fprintf(out,
						"\t\t\t\tStatusType::setVersionError(%s, \"%s%s\", cloopVTable->version, %d);\n",
						statusName.c_str(),
						prefix.c_str(),
						interface->name.c_str(),
						method->version);

					fprintf(out, "\t\t\t\tStatusType::checkException(%s);\n", statusName.c_str());
				}

				fprintf(out, "\t\t\t\treturn");

				if (method->returnTypeRef.token.type != Token::TYPE_VOID ||
					method->returnTypeRef.isPointer)
				{
					fprintf(out, " %s",
						(method->notImplementedExpr ?
							method->notImplementedExpr->generate(LANGUAGE_CPP, prefix).c_str() :
							"0"));
				}

				fprintf(out, ";\n");
				fprintf(out, "\t\t\t}\n");
			}

			if (!statusName.empty())
			{
				fprintf(out, "\t\t\t");

				fprintf(out, "StatusType::clearException(%s)", statusName.c_str());

				fprintf(out, ";\n");
			}

			fprintf(out, "\t\t\t");

			if (method->returnTypeRef.token.type != Token::TYPE_VOID ||
				method->returnTypeRef.isPointer)
			{
				fprintf(out, "%s ret = ", convertType(method->returnTypeRef).c_str());
			}

			fprintf(out, "static_cast<VTable*>(this->cloopVTable)->%s(this",
				method->name.c_str());

			for (vector<Parameter*>::iterator k = method->parameters.begin();
				 k != method->parameters.end();
				 ++k)
			{
				Parameter* parameter = *k;
				fprintf(out, ", %s", parameter->name.c_str());
			}

			fprintf(out, ")");
			fprintf(out, ";\n");

			if (!method->parameters.empty() &&
				parser->exceptionInterface &&
				method->parameters.front()->typeRef.token.text == parser->exceptionInterface->name)
			{
				fprintf(out, "\t\t\tStatusType::checkException(%s);\n",
					method->parameters.front()->name.c_str());
			}

			if (method->returnTypeRef.token.type != Token::TYPE_VOID ||
				method->returnTypeRef.isPointer)
			{
				fprintf(out, "\t\t\treturn ret;\n");
			}

			fprintf(out, "\t\t}\n");
		}

		fprintf(out, "\t};\n\n");
	}

	fprintf(out, "\t// Interfaces implementations\n");

	for (vector<Interface*>::iterator i = parser->interfaces.begin();
		 i != parser->interfaces.end();
		 ++i)
	{
		Interface* interface = *i;

		deque<Method*> methods;

		for (Interface* p = interface; p; p = p->super)
			methods.insert(methods.begin(), p->methods.begin(), p->methods.end());

		fprintf(out, "\n");
		fprintf(out, "\ttemplate <typename Name, typename StatusType, typename Base>\n");
		fprintf(out, "\tclass %s%sBaseImpl : public Base\n",
			prefix.c_str(), interface->name.c_str());
		fprintf(out, "\t{\n");
		fprintf(out, "\tpublic:\n");
		fprintf(out, "\t\ttypedef %s%s Declaration;\n", prefix.c_str(), interface->name.c_str());
		fprintf(out, "\n");
		fprintf(out, "\t\t%s%sBaseImpl(DoNotInherit = DoNotInherit())\n",
			prefix.c_str(), interface->name.c_str());
		fprintf(out, "\t\t{\n");
		fprintf(out, "\t\t\tstatic struct VTableImpl : Base::VTable\n");
		fprintf(out, "\t\t\t{\n");
		fprintf(out, "\t\t\t\tVTableImpl()\n");
		fprintf(out, "\t\t\t\t{\n");
		fprintf(out, "\t\t\t\t\tthis->version = Base::VERSION;\n");

		for (deque<Method*>::iterator j = methods.begin(); j != methods.end(); ++j)
		{
			Method* method = *j;

			fprintf(out, "\t\t\t\t\tthis->%s = &Name::cloop%sDispatcher;\n",
				method->name.c_str(), method->name.c_str());
		}

		fprintf(out, "\t\t\t\t}\n");
		fprintf(out, "\t\t\t} vTable;\n");
		fprintf(out, "\n");

		fprintf(out, "\t\t\tthis->cloopVTable = &vTable;\n");
		fprintf(out, "\t\t}\n");

		// We generate all bases dispatchers so indirect overrides work. At the same time, we
		// inherit from all bases impls, so pure virtual methods are introduced and required to
		// be overriden in the user's implementation.

		for (Interface* p = interface; p; p = p->super)
		{
			for (vector<Method*>::iterator j = p->methods.begin(); j != p->methods.end(); ++j)
			{
				Method* method = *j;

				fprintf(out, "\n");
				fprintf(out, "\t\tstatic %s CLOOP_CARG cloop%sDispatcher(%s%s%s* self",
					convertType(method->returnTypeRef).c_str(),
					method->name.c_str(),
					(method->isConst ? "const " : ""),
					prefix.c_str(),
					p->name.c_str());

				for (vector<Parameter*>::iterator k = method->parameters.begin();
					 k != method->parameters.end();
					 ++k)
				{
					Parameter* parameter = *k;

					fprintf(out, ", %s %s",
						convertType(parameter->typeRef).c_str(), parameter->name.c_str());
				}

				Parameter* exceptionParameter =
					(!method->parameters.empty() &&
					 parser->exceptionInterface &&
					 method->parameters.front()->typeRef.token.text == parser->exceptionInterface->name
					) ? method->parameters.front() : NULL;

				fprintf(out, ") throw()\n");
				fprintf(out, "\t\t{\n");

				if (exceptionParameter)
				{
					fprintf(out, "\t\t\tStatusType %s2(%s);\n",
						exceptionParameter->name.c_str(),
						exceptionParameter->name.c_str());
					fprintf(out, "\n");
				}

				fprintf(out, "\t\t\ttry\n");
				fprintf(out, "\t\t\t{\n");

				fprintf(out, "\t\t\t\t");

				if (method->returnTypeRef.token.type != Token::TYPE_VOID ||
					method->returnTypeRef.isPointer)
				{
					fprintf(out, "return ");
				}

				fprintf(out, "static_cast<%sName*>(self)->Name::%s(",
					(method->isConst ? "const " : ""),
					method->name.c_str());

				for (vector<Parameter*>::iterator k = method->parameters.begin();
					 k != method->parameters.end();
					 ++k)
				{
					Parameter* parameter = *k;

					if (k != method->parameters.begin())
						fprintf(out, ", ");

					if (parameter == exceptionParameter)
						fprintf(out, "&%s2", parameter->name.c_str());
					else
						fprintf(out, "%s", parameter->name.c_str());
				}

				fprintf(out, ");\n");

				fprintf(out, "\t\t\t}\n");
				fprintf(out, "\t\t\tcatch (...)\n");
				fprintf(out, "\t\t\t{\n");
				fprintf(out, "\t\t\t\tStatusType::catchException(%s);\n",
					(exceptionParameter ? ("&" + exceptionParameter->name + "2").c_str() : "0"));

				if (method->returnTypeRef.token.type != Token::TYPE_VOID ||
					method->returnTypeRef.isPointer)
				{
					const char* ret = "\t\t\t\treturn";
					if (method->onErrorFunction.length())
					{
						fprintf(out, "%s %s();\n",
							ret, method->onErrorFunction.c_str());
					}
					else
					{
						fprintf(out, "%s static_cast<%s>(0);\n",
							ret, convertType(method->returnTypeRef).c_str());
					}
				}

				fprintf(out, "\t\t\t}\n");

				fprintf(out, "\t\t}\n");
			}
		}

		fprintf(out, "\t};\n\n");

		if (!interface->super)
		{
			fprintf(out, "\ttemplate <typename Name, typename StatusType, typename Base = Inherit<%s%s> >\n",
				prefix.c_str(), interface->name.c_str());
		}
		else
		{
			string base;
			unsigned baseCount = 0;

			for (Interface* p = interface->super; p; p = p->super)
			{
				base += prefix + p->name + "Impl<Name, StatusType, Inherit<";
				++baseCount;
			}

			base += prefix.c_str() + interface->name;

			while (baseCount-- > 0)
				base += "> > ";

			fprintf(out, "\ttemplate <typename Name, typename StatusType, typename Base = %s>\n", base.c_str());
		}

		fprintf(out, "\tclass %s%sImpl : public %s%sBaseImpl<Name, StatusType, Base>\n",
			prefix.c_str(), interface->name.c_str(), prefix.c_str(), interface->name.c_str());
		fprintf(out, "\t{\n");
		fprintf(out, "\tprotected:\n");
		fprintf(out, "\t\t%s%sImpl(DoNotInherit = DoNotInherit())\n",
			prefix.c_str(), interface->name.c_str());
		fprintf(out, "\t\t{\n");
		fprintf(out, "\t\t}\n");
		fprintf(out, "\n");
		fprintf(out, "\tpublic:\n");
		fprintf(out, "\t\tvirtual ~%s%sImpl()\n", prefix.c_str(), interface->name.c_str());
		fprintf(out, "\t\t{\n");
		fprintf(out, "\t\t}\n");
		fprintf(out, "\n");

		for (vector<Method*>::iterator j = interface->methods.begin();
			 j != interface->methods.end();
			 ++j)
		{
			Method* method = *j;

			Parameter* exceptionParameter =
				(!method->parameters.empty() &&
				 parser->exceptionInterface &&
				 method->parameters.front()->typeRef.token.text == parser->exceptionInterface->name
				) ? method->parameters.front() : NULL;

			fprintf(out, "\t\tvirtual %s %s(",
				convertType(method->returnTypeRef).c_str(), method->name.c_str());

			for (vector<Parameter*>::iterator k = method->parameters.begin();
				 k != method->parameters.end();
				 ++k)
			{
				Parameter* parameter = *k;

				if (k != method->parameters.begin())
					fprintf(out, ", ");

				if (parameter == exceptionParameter)
					fprintf(out, "StatusType* %s", parameter->name.c_str());
				else
				{
					fprintf(out, "%s %s",
						convertType(parameter->typeRef).c_str(), parameter->name.c_str());
				}
			}

			fprintf(out, ")%s = 0;\n", (method->isConst ? " const" : ""));
		}

		fprintf(out, "\t};\n");
	}

	fprintf(out, "};\n\n");
	fprintf(out, "\n");

	fprintf(out, "#endif\t// %s\n", headerGuard.c_str());
}


//--------------------------------------


CHeaderGenerator::CHeaderGenerator(const string& filename, const string& prefix, Parser* parser,
		const string& headerGuard)
	: CBasedGenerator(filename, prefix, false),
	  parser(parser),
	  headerGuard(headerGuard)
{
}

void CHeaderGenerator::generate()
{
	fprintf(out, "/* %s */\n\n", AUTOGEN_MSG);

	fprintf(out, "#ifndef %s\n", headerGuard.c_str());
	fprintf(out, "#define %s\n\n", headerGuard.c_str());
	fprintf(out, "#include <stdint.h>\n\n");

	fprintf(out, "#ifndef CLOOP_EXTERN_C\n");
	fprintf(out, "#ifdef __cplusplus\n");
	fprintf(out, "#define CLOOP_EXTERN_C extern \"C\"\n");
	fprintf(out, "#else\n");
	fprintf(out, "#define CLOOP_EXTERN_C\n");
	fprintf(out, "#endif\n");
	fprintf(out, "#endif\n\n\n");

	for (vector<Interface*>::iterator i = parser->interfaces.begin();
		 i != parser->interfaces.end();
		 ++i)
	{
		Interface* interface = *i;

		fprintf(out, "struct %s%s;\n", prefix.c_str(), interface->name.c_str());
	}

	fprintf(out, "\n\n");

	for (vector<Interface*>::iterator i = parser->interfaces.begin();
		 i != parser->interfaces.end();
		 ++i)
	{
		Interface* interface = *i;

		deque<Method*> methods;

		for (Interface* p = interface; p; p = p->super)
			methods.insert(methods.begin(), p->methods.begin(), p->methods.end());

		fprintf(out, "#define %s%s_VERSION %d\n\n",
			prefix.c_str(), interface->name.c_str(), interface->version);

		for (vector<Constant*>::iterator j = interface->constants.begin();
			 j != interface->constants.end();
			 ++j)
		{
			Constant* constant = *j;

			fprintf(out, "#define %s%s_%s ((%s) (%s))\n",
				prefix.c_str(),
				interface->name.c_str(),
				constant->name.c_str(),
				convertType(constant->typeRef).c_str(),
				constant->expr->generate(LANGUAGE_C, prefix).c_str());
		}

		if (!interface->constants.empty())
			fprintf(out, "\n");

		fprintf(out, "struct %s%s;\n\n", prefix.c_str(), interface->name.c_str());

		fprintf(out, "struct %s%sVTable\n", prefix.c_str(), interface->name.c_str());
		fprintf(out, "{\n");
		fprintf(out, "\tvoid* cloopDummy[%d];\n", DUMMY_VTABLE);
		fprintf(out, "\tuintptr_t version;\n");

		for (deque<Method*>::iterator j = methods.begin(); j != methods.end(); ++j)
		{
			Method* method = *j;

			fprintf(out, "\t%s (*%s)(%sstruct %s%s* self",
				convertType(method->returnTypeRef).c_str(),
				method->name.c_str(),
				(method->isConst ? "const " : ""),
				prefix.c_str(),
				interface->name.c_str());

			for (vector<Parameter*>::iterator k = method->parameters.begin();
				 k != method->parameters.end();
				 ++k)
			{
				Parameter* parameter = *k;

				fprintf(out, ", %s %s", convertType(parameter->typeRef).c_str(),
					parameter->name.c_str());
			}

			fprintf(out, ");\n");
		}

		fprintf(out, "};\n\n");

		fprintf(out, "struct %s%s\n", prefix.c_str(), interface->name.c_str());
		fprintf(out, "{\n");
		fprintf(out, "\tvoid* cloopDummy[%d];\n", DUMMY_INSTANCE);
		fprintf(out, "\tstruct %s%sVTable* vtable;\n", prefix.c_str(), interface->name.c_str());
		fprintf(out, "};\n\n");

		for (deque<Method*>::iterator j = methods.begin(); j != methods.end(); ++j)
		{
			Method* method = *j;

			fprintf(out, "CLOOP_EXTERN_C %s %s%s_%s(%sstruct %s%s* self",
				convertType(method->returnTypeRef).c_str(),
				prefix.c_str(),
				interface->name.c_str(),
				method->name.c_str(),
				(method->isConst ? "const " : ""),
				prefix.c_str(),
				interface->name.c_str());

			for (vector<Parameter*>::iterator k = method->parameters.begin();
				 k != method->parameters.end();
				 ++k)
			{
				Parameter* parameter = *k;

				fprintf(out, ", %s %s",
					convertType(parameter->typeRef).c_str(), parameter->name.c_str());
			}

			fprintf(out, ");\n");
		}

		fprintf(out, "\n");
	}

	fprintf(out, "\n");
	fprintf(out, "#endif\t// %s\n", headerGuard.c_str());
}


//--------------------------------------


CImplGenerator::CImplGenerator(const string& filename, const string& prefix, Parser* parser,
		const string& includeFilename)
	: CBasedGenerator(filename, prefix, false),
	  parser(parser),
	  includeFilename(includeFilename)
{
}

void CImplGenerator::generate()
{
	fprintf(out, "/* %s */\n\n", AUTOGEN_MSG);

	fprintf(out, "#include \"%s\"\n\n\n", includeFilename.c_str());

	for (vector<Interface*>::iterator i = parser->interfaces.begin();
		 i != parser->interfaces.end();
		 ++i)
	{
		Interface* interface = *i;

		deque<Method*> methods;

		for (Interface* p = interface; p; p = p->super)
			methods.insert(methods.begin(), p->methods.begin(), p->methods.end());

		for (deque<Method*>::iterator j = methods.begin(); j != methods.end(); ++j)
		{
			Method* method = *j;

			fprintf(out, "CLOOP_EXTERN_C %s %s%s_%s(%sstruct %s%s* self",
				convertType(method->returnTypeRef).c_str(),
				prefix.c_str(),
				interface->name.c_str(),
				method->name.c_str(),
				(method->isConst ? "const " : ""),
				prefix.c_str(),
				interface->name.c_str());

			for (vector<Parameter*>::iterator k = method->parameters.begin();
				 k != method->parameters.end();
				 ++k)
			{
				Parameter* parameter = *k;

				fprintf(out, ", %s %s",
					convertType(parameter->typeRef).c_str(), parameter->name.c_str());
			}

			fprintf(out, ")\n");
			fprintf(out, "{\n");
			fprintf(out, "\t");

			//// TODO: checkVersion

			if (method->returnTypeRef.token.type != Token::TYPE_VOID ||
				method->returnTypeRef.isPointer)
			{
				fprintf(out, "return ");
			}

			fprintf(out, "self->vtable->%s(self", method->name.c_str());

			for (vector<Parameter*>::iterator k = method->parameters.begin();
				 k != method->parameters.end();
				 ++k)
			{
				Parameter* parameter = *k;
				fprintf(out, ", %s", parameter->name.c_str());
			}

			fprintf(out, ");\n");
			fprintf(out, "}\n\n");
		}
	}
}


//--------------------------------------


PascalGenerator::PascalGenerator(const string& filename, const string& prefix, Parser* parser,
		const string& unitName, const std::string& additionalUses, const std::string& interfaceFile,
		const std::string& implementationFile, const std::string& exceptionClass, const std::string& functionsFile)
	: FileGenerator(filename, prefix),
	  parser(parser),
	  unitName(unitName),
	  additionalUses(additionalUses),
	  interfaceFile(interfaceFile),
	  implementationFile(implementationFile),
	  exceptionClass(exceptionClass),
	  functionsFile(functionsFile)
{
}

void PascalGenerator::generate()
{
	fprintf(out, "{ %s }\n\n", AUTOGEN_MSG);

	fprintf(out, "{$IFDEF FPC}\n{$MODE DELPHI}\n{$OBJECTCHECKS OFF}\n{$ENDIF}\n\n");

	fprintf(out, "unit %s;\n\n", unitName.c_str());
	fprintf(out, "interface\n\n");
	fprintf(out, "uses Classes");

	if (!additionalUses.empty())
		fprintf(out, ", %s", additionalUses.c_str());

	fprintf(out, ";\n\n");

	fprintf(out, "type\n");
	fprintf(out, "{$IFNDEF FPC}\n");
	fprintf(out, "\tQWord = UInt64;\n");
	fprintf(out, "{$ENDIF}\n\n");

	for (vector<Interface*>::iterator i = parser->interfaces.begin();
		 i != parser->interfaces.end();
		 ++i)
	{
		Interface* interface = *i;
		fprintf(out, "\t%s = class;\n", escapeName(interface->name, true).c_str());
	}

	fprintf(out, "\n");

	insertFile(interfaceFile);

	// Pass at every type to fill pointerTypes. We need it in advance.

	for (vector<Interface*>::iterator i = parser->interfaces.begin();
		 i != parser->interfaces.end();
		 ++i)
	{
		Interface* interface = *i;

		for (vector<Method*>::iterator j = interface->methods.begin();
			 j != interface->methods.end();
			 ++j)
		{
			Method* method = *j;

			convertType(method->returnTypeRef);

			for (vector<Parameter*>::iterator k = method->parameters.begin();
				 k != method->parameters.end();
				 ++k)
			{
				Parameter* parameter = *k;
				convertParameter(*parameter);
			}
		}
	}

	for (set<string>::iterator i = pointerTypes.begin(); i != pointerTypes.end(); ++i)
	{
		string type = *i;
		fprintf(out, "\t%sPtr = ^%s;\n", type.c_str(), type.c_str());
	}

	if (!pointerTypes.empty())
		fprintf(out, "\n");

	for (vector<Interface*>::iterator i = parser->interfaces.begin();
		 i != parser->interfaces.end();
		 ++i)
	{
		Interface* interface = *i;

		for (vector<Method*>::iterator j = interface->methods.begin();
			 j != interface->methods.end();
			 ++j)
		{
			Method* method = *j;

			bool isProcedure = method->returnTypeRef.token.type == Token::TYPE_VOID &&
				 !method->returnTypeRef.isPointer;

			fprintf(out, "\t%s_%sPtr = %s(this: %s",
				escapeName(interface->name, true).c_str(), escapeName(method->name).c_str(),
				(isProcedure ? "procedure" : "function"),
				escapeName(interface->name, true).c_str());

			for (vector<Parameter*>::iterator k = method->parameters.begin();
				 k != method->parameters.end();
				 ++k)
			{
				Parameter* parameter = *k;
				fprintf(out, "; %s", convertParameter(*parameter).c_str());
			}

			fprintf(out, ")");

			if (!isProcedure)
				fprintf(out, ": %s", convertType(method->returnTypeRef).c_str());

			fprintf(out, "; cdecl;\n");
		}
	}

	fprintf(out, "\n");

	for (vector<Interface*>::iterator i = parser->interfaces.begin();
		 i != parser->interfaces.end();
		 ++i)
	{
		Interface* interface = *i;

		fprintf(out, "\t%sVTable = class", escapeName(interface->name).c_str());

		if (interface->super)
			fprintf(out, "(%sVTable)", escapeName(interface->super->name).c_str());

		fprintf(out, "\n");

		if (!interface->super)
			fprintf(out, "\t\tversion: NativeInt;\n");

		for (vector<Method*>::iterator j = interface->methods.begin();
			 j != interface->methods.end();
			 ++j)
		{
			Method* method = *j;

			fprintf(out, "\t\t%s: %s_%sPtr;\n", escapeName(method->name).c_str(),
				escapeName(interface->name, true).c_str(), escapeName(method->name).c_str());
		}

		fprintf(out, "\tend;\n\n");

		fprintf(out, "\t%s = class", escapeName(interface->name, true).c_str());

		if (interface->super)
			fprintf(out, "(%s)", escapeName(interface->super->name, true).c_str());

		fprintf(out, "\n");

		if (!interface->super)
			fprintf(out, "\t\tvTable: %sVTable;\n\n", escapeName(interface->name).c_str());

		fprintf(out, "\t\tconst VERSION = %d;\n", interface->version);

		for (vector<Constant*>::iterator j = interface->constants.begin();
			 j != interface->constants.end();
			 ++j)
		{
			Constant* constant = *j;

			fprintf(out, "\t\tconst %s = %s(%s);\n",
				constant->name.c_str(),
				convertType(constant->typeRef).c_str(),
				constant->expr->generate(LANGUAGE_PASCAL, prefix).c_str());
		}

		fprintf(out, "\n");

		for (vector<Method*>::iterator j = interface->methods.begin();
			 j != interface->methods.end();
			 ++j)
		{
			Method* method = *j;

			bool isProcedure = method->returnTypeRef.token.type == Token::TYPE_VOID &&
				 !method->returnTypeRef.isPointer;

			fprintf(out, "\t\t%s %s(",
				(isProcedure ? "procedure" : "function"),
				escapeName(method->name).c_str());

			for (vector<Parameter*>::iterator k = method->parameters.begin();
				 k != method->parameters.end();
				 ++k)
			{
				Parameter* parameter = *k;

				if (k != method->parameters.begin())
					fprintf(out, "; ");

				fprintf(out, "%s", convertParameter(*parameter).c_str());
			}

			fprintf(out, ")");

			if (!isProcedure)
				fprintf(out, ": %s", convertType(method->returnTypeRef).c_str());

			fprintf(out, ";\n");
		}

		fprintf(out, "\tend;\n\n");

		fprintf(out, "\t%sImpl = class(%s)\n",
			escapeName(interface->name, true).c_str(), escapeName(interface->name, true).c_str());
		fprintf(out, "\t\tconstructor create;\n\n");

		deque<Method*> methods;

		for (Interface* p = interface; p; p = p->super)
			methods.insert(methods.begin(), p->methods.begin(), p->methods.end());

		for (deque<Method*>::iterator j = methods.begin(); j != methods.end(); ++j)
		{
			Method* method = *j;

			bool isProcedure = method->returnTypeRef.token.type == Token::TYPE_VOID &&
				 !method->returnTypeRef.isPointer;

			fprintf(out, "\t\t%s %s(",
				(isProcedure ? "procedure" : "function"),
				escapeName(method->name).c_str());

			for (vector<Parameter*>::iterator k = method->parameters.begin();
				 k != method->parameters.end();
				 ++k)
			{
				Parameter* parameter = *k;

				if (k != method->parameters.begin())
					fprintf(out, "; ");

				fprintf(out, "%s", convertParameter(*parameter).c_str());
			}

			fprintf(out, ")");

			if (!isProcedure)
				fprintf(out, ": %s", convertType(method->returnTypeRef).c_str());

			fprintf(out, "; virtual; abstract;\n");
		}

		fprintf(out, "\tend;\n\n");
	}

	insertFile(functionsFile);

	fprintf(out, "implementation\n\n");

	for (vector<Interface*>::iterator i = parser->interfaces.begin();
		 i != parser->interfaces.end();
		 ++i)
	{
		Interface* interface = *i;

		for (vector<Method*>::iterator j = interface->methods.begin();
			 j != interface->methods.end();
			 ++j)
		{
			Method* method = *j;

			bool isProcedure = method->returnTypeRef.token.type == Token::TYPE_VOID &&
				 !method->returnTypeRef.isPointer;

			fprintf(out, "%s %s.%s(",
				(isProcedure ? "procedure" : "function"),
				escapeName(interface->name, true).c_str(),
				escapeName(method->name).c_str());

			for (vector<Parameter*>::iterator k = method->parameters.begin();
				 k != method->parameters.end();
				 ++k)
			{
				Parameter* parameter = *k;

				if (k != method->parameters.begin())
					fprintf(out, "; ");

				fprintf(out, "%s", convertParameter(*parameter).c_str());
			}

			fprintf(out, ")");

			if (!isProcedure)
				fprintf(out, ": %s", convertType(method->returnTypeRef).c_str());

			fprintf(out, ";\n");
			fprintf(out, "begin\n");
			fprintf(out, "\t");

			//// TODO: checkVersion

			if (!isProcedure)
				fprintf(out, "Result := ");

			fprintf(out, "%sVTable(vTable).%s(Self",
				escapeName(interface->name).c_str(), escapeName(method->name).c_str());

			for (vector<Parameter*>::iterator k = method->parameters.begin();
				 k != method->parameters.end();
				 ++k)
			{
				Parameter* parameter = *k;
				fprintf(out, ", %s", escapeName(parameter->name).c_str());
			}

			fprintf(out, ");\n");

			if (!method->parameters.empty() &&
				parser->exceptionInterface &&
				method->parameters.front()->typeRef.token.text == parser->exceptionInterface->name &&
				!exceptionClass.empty())
			{
				fprintf(out, "\t%s.checkException(%s);\n", exceptionClass.c_str(),
					escapeName(method->parameters.front()->name).c_str());
			}

			fprintf(out, "end;\n\n");
		}
	}

	for (vector<Interface*>::iterator i = parser->interfaces.begin();
		 i != parser->interfaces.end();
		 ++i)
	{
		Interface* interface = *i;

		deque<Method*> methods;

		for (Interface* p = interface; p; p = p->super)
			methods.insert(methods.begin(), p->methods.begin(), p->methods.end());

		for (deque<Method*>::iterator j = methods.begin(); j != methods.end(); ++j)
		{
			Method* method = *j;

			bool isProcedure = method->returnTypeRef.token.type == Token::TYPE_VOID &&
				 !method->returnTypeRef.isPointer;

			fprintf(out, "%s %sImpl_%sDispatcher(this: %s",
				(isProcedure ? "procedure" : "function"),
				escapeName(interface->name, true).c_str(),
				escapeName(method->name).c_str(),
				escapeName(interface->name, true).c_str());

			for (vector<Parameter*>::iterator k = method->parameters.begin();
				 k != method->parameters.end();
				 ++k)
			{
				Parameter* parameter = *k;

				fprintf(out, "; %s", convertParameter(*parameter).c_str());
			}

			fprintf(out, ")");

			if (!isProcedure)
				fprintf(out, ": %s", convertType(method->returnTypeRef).c_str());

			fprintf(out, "; cdecl;\n");
			fprintf(out, "begin\n");

			if (!exceptionClass.empty())
				fprintf(out, "\ttry\n\t");

			fprintf(out, "\t");

			if (!isProcedure)
				fprintf(out, "Result := ");

			fprintf(out, "%sImpl(this).%s(", escapeName(interface->name, true).c_str(),
				escapeName(method->name).c_str());

			for (vector<Parameter*>::iterator k = method->parameters.begin();
				 k != method->parameters.end();
				 ++k)
			{
				Parameter* parameter = *k;

				if (k != method->parameters.begin())
					fprintf(out, ", ");

				fprintf(out, "%s", escapeName(parameter->name).c_str());
			}

			fprintf(out, ");\n");

			if (!exceptionClass.empty())
			{
				Parameter* exceptionParameter =
					(!method->parameters.empty() &&
					 parser->exceptionInterface &&
					 method->parameters.front()->typeRef.token.text == parser->exceptionInterface->name
					) ? method->parameters.front() : NULL;

				fprintf(out, "\texcept\n");
				fprintf(out, "\t\ton e: Exception do %s.catchException(%s, e);\n",
					exceptionClass.c_str(),
					(exceptionParameter ? escapeName(exceptionParameter->name).c_str() : "nil"));

				fprintf(out, "\tend\n");
			}

			fprintf(out, "end;\n\n");
		}

		fprintf(out, "var\n");
		fprintf(out, "\t%sImpl_vTable: %sVTable;\n\n",
			escapeName(interface->name, true).c_str(), escapeName(interface->name).c_str());

		fprintf(out, "constructor %sImpl.create;\n", escapeName(interface->name, true).c_str());
		fprintf(out, "begin\n");
		fprintf(out, "\tvTable := %sImpl_vTable;\n", escapeName(interface->name, true).c_str());
		fprintf(out, "end;\n\n");
	}

	insertFile(implementationFile);

	fprintf(out, "initialization\n");

	for (vector<Interface*>::iterator i = parser->interfaces.begin();
		 i != parser->interfaces.end();
		 ++i)
	{
		Interface* interface = *i;

		deque<Method*> methods;

		for (Interface* p = interface; p; p = p->super)
			methods.insert(methods.begin(), p->methods.begin(), p->methods.end());

		fprintf(out, "\t%sImpl_vTable := %sVTable.create;\n",
			escapeName(interface->name, true).c_str(), escapeName(interface->name).c_str());
		fprintf(out, "\t%sImpl_vTable.version := %d;\n",
			escapeName(interface->name, true).c_str(), (int) methods.size());

		for (deque<Method*>::iterator j = methods.begin(); j != methods.end(); ++j)
		{
			Method* method = *j;

			fprintf(out, "\t%sImpl_vTable.%s := @%sImpl_%sDispatcher;\n",
				escapeName(interface->name, true).c_str(),
				escapeName(method->name).c_str(),
				escapeName(interface->name, true).c_str(),
				escapeName(method->name).c_str());
		}

		fprintf(out, "\n");
	}

	fprintf(out, "finalization\n");

	for (vector<Interface*>::iterator i = parser->interfaces.begin();
		 i != parser->interfaces.end();
		 ++i)
	{
		Interface* interface = *i;
		fprintf(out, "\t%sImpl_vTable.destroy;\n", escapeName(interface->name, true).c_str());
	}

	fprintf(out, "\n");
	fprintf(out, "end.\n");
}

string PascalGenerator::convertParameter(const Parameter& parameter)
{
	return escapeName(parameter.name) + ": " + convertType(parameter.typeRef);
}

string PascalGenerator::convertType(const TypeRef& typeRef)
{
	string name;

	switch (typeRef.token.type)
	{
		case Token::TYPE_BOOLEAN:
			name = "Boolean";
			break;

		case Token::TYPE_INT:
			name = "Integer";
			break;

		case Token::TYPE_INT64:
			name = "Int64";
			break;

		case Token::TYPE_INTPTR:
			name = "NativeInt";
			break;

		case Token::TYPE_STRING:
			name = "PAnsiChar";
			break;

		case Token::TYPE_UCHAR:
			name = "Byte";
			break;

		case Token::TYPE_UINT:
			name = "Cardinal";
			break;

		case Token::TYPE_UINT64:
			name = "QWord";
			break;

		case Token::TYPE_IDENTIFIER:
			name = (typeRef.type == BaseType::TYPE_INTERFACE ? prefix : "") + typeRef.token.text;
			break;

		default:
			name = typeRef.token.text;
			break;
	}

	if (typeRef.isPointer)
	{
		if (name == "void")
			return "Pointer";

		if (pointerTypes.find(name) == pointerTypes.end())
			pointerTypes.insert(name);

		name += "Ptr";
	}

	return name;
}

string PascalGenerator::escapeName(string name, bool interfaceName)
{
	//// TODO: Create a table of keywords.

	if (name == "file" ||
		name == "function" ||
		name == "procedure" ||
		name == "record" ||
		name == "set" ||
		name == "to" ||
		name == "type")
	{
		name += "_";
	}

	if (interfaceName)
		name = prefix + name;

	return name;
}

void PascalGenerator::insertFile(const string& filename)
{
	if (filename.empty())
		return;

	FILE* in = fopen(filename.c_str(), "r");

	if (!in)
		throw runtime_error(string("Error opening input file '") + filename + "'.");

	char buffer[1024];
	int count;

	while ((count = fread(buffer, 1, sizeof(buffer), in)) > 0)
		fwrite(buffer, 1, count, out);

	fclose(in);
}
