
#include "doc_json.h"

#include "libs/jansson.h"

namespace scripting {

static void json_doc_generate_class(json_t* elObj, const DocumentationElementClass* lib)
{
	json_object_set_new(elObj, "superClass", json_string(lib->superClass.c_str()));
}
static json_t* json_doc_generate_return_type(const scripting::ade_type_info& type_info)
{
	switch (type_info.getType()) {
	case ade_type_info_type::Empty:
		return json_string("void");
	case ade_type_info_type::Simple:
		return json_string(type_info.getIdentifier());
	case ade_type_info_type::Tuple: {
		json_t* tupleTypes = json_array();

		for (const auto& type : type_info.elements()) {
			json_array_append_new(tupleTypes, json_doc_generate_return_type(type));
		}

		return json_pack("{ssso}", "type", "tuple", "elements", tupleTypes);
	}
	case ade_type_info_type::Array: {
		return json_pack("{ssso}",
			"type",
			"list",
			"element",
			json_doc_generate_return_type(type_info.elements().front()));
	}
	case ade_type_info_type::Map: {
		return json_pack("{sssoso}",
			"type",
			"map",
			"key",
			json_doc_generate_return_type(type_info.elements()[0]),
			"value",
			json_doc_generate_return_type(type_info.elements()[1]));
	}
	case ade_type_info_type::Iterator: {
		return json_pack("{ssso}",
			"type",
			"iterator",
			"element",
			json_doc_generate_return_type(type_info.elements().front()));
	}
	case ade_type_info_type::Alternative: {
		json_t* alternativeTypes = json_array();

		for (const auto& type : type_info.elements()) {
			json_array_append_new(alternativeTypes, json_doc_generate_return_type(type));
		}

		return json_pack("{ssso}", "type", "alternative", "elements", alternativeTypes);
	}
	case ade_type_info_type::Function: {
		const auto& elements = type_info.elements();

		json_t* parameterTypes = json_array();

		if (elements.size() > 1) {
			for (auto iter = elements.begin() + 1; iter != elements.end(); ++iter) {
				json_array_append_new(parameterTypes,
					json_pack("{ssso}", "name", iter->getName().c_str(), "type", json_doc_generate_return_type(*iter)));
			}
		}

		return json_pack("{sssoso}",
			"type",
			"function",
			"returnType",
			json_doc_generate_return_type(elements.front()),
			"parameters",
			parameterTypes);
	}
	case ade_type_info_type::Generic: {
		const auto& elements = type_info.elements();

		json_t* parameterTypes = json_array();

		if (elements.size() > 1) {
			for (auto iter = elements.begin() + 1; iter != elements.end(); ++iter) {
				json_array_append_new(parameterTypes, json_doc_generate_return_type(*iter));
			}
		}

		return json_pack("{sssoso}",
			"type",
			"generic",
			"baseType",
			json_doc_generate_return_type(elements.front()),
			"parameters",
			parameterTypes);
	}
	case ade_type_info_type::Varargs: {
		return json_pack("{ssso}",
			"type",
			"varargs",
			"baseType",
			json_doc_generate_return_type(type_info.elements().front()));
	}
	}

	UNREACHABLE("Unknown type type!");
	return nullptr;
}
static json_t* json_doc_function_signature(const SCP_vector<scripting::argument_def>& args)
{
	json_t* arr = json_array();

	for (const auto& arg : args) {
		json_array_append_new(arr,
			json_pack("{sosssssbss}",
				"type",
				json_doc_generate_return_type(arg.type),
				"name",
				arg.name.c_str(),
				"default",
				arg.def_val.c_str(),
				"optional",
				arg.optional,
				"description",
				arg.comment.c_str()));
	}

	return arr;
}
static void json_doc_generate_function(json_t* elObj, const DocumentationElementFunction* lib)
{
	json_object_set_new(elObj, "returnType", json_doc_generate_return_type(lib->returnType));
	json_object_set_new(elObj, "returnDocumentation", json_string(lib->returnDocumentation.c_str()));

	if (lib->overloads.size() == 1 && !lib->overloads.front().simple.empty()) {
		// Legacy argument list not filled with argument data
		json_object_set_new(elObj, "parameters", json_string(lib->overloads.front().simple.c_str()));
	} else {
		json_t* arr = json_array();

		for (const auto& overload : lib->overloads) {
			if (!overload.simple.empty()) {
				json_array_append_new(arr, json_string(overload.simple.c_str()));
			} else {
				json_array_append_new(arr, json_doc_function_signature(overload.arguments));
			}
		}

		json_object_set_new(elObj, "parameters", arr);
	}
}
static void json_doc_generate_property(json_t* elObj, const DocumentationElementProperty* lib)
{
	json_object_set_new(elObj, "getterType", json_doc_generate_return_type(lib->getterType));
	json_object_set_new(elObj, "setterType", json_doc_generate_return_type(lib->setterType));
	json_object_set_new(elObj, "returnDocumentation", json_string(lib->returnDocumentation.c_str()));
}
static json_t* json_doc_generate_elements(const SCP_vector<std::unique_ptr<DocumentationElement>>& elements);
static json_t* json_doc_generate_element(const std::unique_ptr<DocumentationElement>& element)
{
	json_t* elementsObj = json_object();

	json_object_set_new(elementsObj, "name", json_string(element->name.c_str()));
	json_object_set_new(elementsObj, "shortName", json_string(element->shortName.c_str()));
	json_object_set_new(elementsObj, "description", json_string(element->description.c_str()));

	switch (element->type) {
	case ElementType::Library:
		json_object_set_new(elementsObj, "type", json_string("library"));
		break;
	case ElementType::Class:
		json_object_set_new(elementsObj, "type", json_string("class"));
		json_doc_generate_class(elementsObj, static_cast<DocumentationElementClass*>(element.get()));
		break;
	case ElementType::Function:
		json_object_set_new(elementsObj, "type", json_string("function"));
		json_doc_generate_function(elementsObj, static_cast<DocumentationElementFunction*>(element.get()));
		break;
	case ElementType::Operator:
		json_object_set_new(elementsObj, "type", json_string("operator"));
		json_doc_generate_function(elementsObj, static_cast<DocumentationElementFunction*>(element.get()));
		break;
	case ElementType::Property:
		json_object_set_new(elementsObj, "type", json_string("property"));
		json_doc_generate_property(elementsObj, static_cast<DocumentationElementProperty*>(element.get()));
		break;
	default:
		json_object_set_new(elementsObj, "type", json_string("unknown"));
		break;
	}

	json_object_set_new(elementsObj, "children", json_doc_generate_elements(element->children));

	return elementsObj;
}

static json_t* json_doc_generate_elements(const SCP_vector<std::unique_ptr<DocumentationElement>>& elements)
{
	json_t* elementsArray = json_array();

	for (const auto& el : elements) {
		json_array_append_new(elementsArray, json_doc_generate_element(el));
	}

	return elementsArray;
}

static json_t* json_doc_generate_param_element(const HookVariableDocumentation& param)
{
	return json_pack("{s:s,s:s,s:o}",
		"name",
		param.name,
		"description",
		param.description,
		"type",
		json_doc_generate_return_type(param.type));
}

static json_t* json_doc_generate_action_element(const DocumentationAction& action)
{
	json_t* paramArray = json_array();
	json_t* conditionArray = json_array();

	for (const auto& param : action.parameters) {
		json_array_append_new(paramArray, json_doc_generate_param_element(param));
	}

	for (const auto& condition : action.conditions) {
		json_array_append_new(conditionArray, json_pack("{s:s,s:s}",
			"name",
			condition.first.c_str(),
			"description",
			condition.second->documentation.c_str()));
	}

	return json_pack("{s:s,s:s,s:o,s:o,s:b}",
		"name",
		action.name.c_str(),
		"description",
		action.description.c_str(),
		"hookVars",
		paramArray,
		"conditions",
		conditionArray,
		"overridable",
		action.overridable);
}

void output_json_doc(const ScriptingDocumentation& doc, const SCP_string& filename)
{
	std::unique_ptr<json_t> root(json_object());
	// Should be incremented every time an incompatible change is made
	json_object_set_new(root.get(), "version", json_integer(2));

	{
		json_t* actionArray = json_array();

		for (const auto& action : doc.actions) {
			json_array_append_new(actionArray, json_doc_generate_action_element(action));
		}

		json_object_set_new(root.get(), "actions", actionArray);
	}
	{
		json_t* conditionArray = json_array();

		for (const auto& cond : doc.conditions) {
			json_array_append_new(conditionArray, json_string(cond.c_str()));
		}

		json_object_set_new(root.get(), "conditions", conditionArray);
	}
	{
		json_t* optionsArray = json_array();

		for (const auto& option : doc.options) {
			json_t* optionsObject = json_object();

			json_object_set_new(optionsObject, "title", json_string(option.title.c_str()));
			json_object_set_new(optionsObject, "key", json_string(option.key.c_str()));
			json_object_set_new(optionsObject, "description", json_string(option.description.c_str()));

			json_array_append_new(optionsArray, optionsObject);
		}

		json_object_set_new(root.get(), "options", optionsArray);
	}
	{
		json_t* enumObject = json_object();

		for (const auto& enumVal : doc.enumerations) {
			json_object_set_new(enumObject, enumVal.name.c_str(), json_integer(enumVal.value));
		}

		json_object_set_new(root.get(), "enums", enumObject);
	}
	{
		json_t* globalVariables = json_array();

		for (const auto& param : doc.globalVariables) {
			json_array_append_new(globalVariables, json_doc_generate_param_element(param));
		}

		json_object_set_new(root.get(), "globalVars", globalVariables);
	}
	json_object_set_new(root.get(), "elements", json_doc_generate_elements(doc.elements));

	const auto jsonStr = json_dump_string(root.get(), JSON_INDENT(2) | JSON_SORT_KEYS);

	std::ofstream outStr(filename, std::ios::binary);
	outStr << jsonStr;
}

} // namespace scripting
