import json
import os

def resolve_enum_size(size):
    if (size == 1):
        return "byte"
    if (size == 4):
        return "int"
    raise Exception("Unknown size {}".format(size))

def write_enum(enum, enumNames):
    namespace = str.capitalize(enum["filename"][3:])
    if (enum["name"].startswith("bfd_")):
        return
    if (enum["name"].startswith("float_")):
        return
    if (enum["name"].startswith("as")):
        return
    filename = "Generated/{}/{}.cs".format(namespace, enum["name"])
    os.makedirs(os.path.dirname(filename), exist_ok=True)
    with open(filename, "w") as f:
        f.write("// AUTOMATICALLY GENERATED, DO NOT EDIT\n")
        f.write("using System.Diagnostics.CodeAnalysis;\n\n")
        f.write("namespace {}\n{{\n".format(namespace))
        f.write("    [SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\n")
        f.write("    internal enum {} : {}\n    {{\n".format(enum["name"], resolve_enum_size(enum["byteSize"]) ))
        vals = enum["values"].items()
        dict = {}
        for k, v in vals:
            dict[int(k)] = v
        for k, v in sorted(dict.items(), key=lambda item: item[0]):
            f.write("        {} = {},\n".format(v, k))
        f.write("    }\n")
        f.write("}\n")
    enumNames.add(enum["name"])

class funcDef:
    def __init__(self, name, returns, parameters):
        self.name = name
        self.returns = returns
        self.parameters = parameters

class classDef:
    def __init__(self, namespace, name, file):
        self.namespace = namespace
        self.file = file
        self.name = name
        self.functions = []

    def register_function(self, func):
        self.functions.append(func)

def parse_function(function, classDict):
    splitName = function["name"].split("_")
    classKey = "{}_{}".format(function["filename"], splitName[1])
    if (not classKey in classDict):
        classDict[classKey] = classDef(function["filename"][3:].capitalize(), splitName[1], function["filename"])

    classDict[classKey].register_function(funcDef(function["name"], function["returns"], function["parameters"]))

def parse_type(type, enumSet):
    type = type.strip()
    if (type == "void"):
        return "void"
    if (type == "unsigned char"):
        return "byte"
    if (type == "signed char"):
        return "sbyte"
    if (type == "unsigned short"):
        return "ushort"
    if (type == "bool"):
        return "byte"
    if (type == "float"):
        return "float"
    if (type == "double"):
        return "double"
    if (type == "long unsigned int"):
        return "ulong"
    if (type == "long int"):
        return "long"
    if (type == "unsigned int"):
        return "uint"
    if (type == "int"):
        return "int"
    if (type.endswith("*")):
        return "IntPtr"
    if (type.endswith("&")):
        return "ref " + parse_type(type[:-1], enumSet)
    if (type in enumSet):
        return type
    print("Unhandled type '{}'".format(type))
    return "void"

def clean_name(name):
    if (name == "out"):
        return "@out"
    return name

def write_class(c, enumSet):
    filename = "Generated/{}/{}.cs".format(c.file[3:].capitalize(), c.name)
    os.makedirs(os.path.dirname(filename), exist_ok=True)
    with open(filename, "w") as f:
        f.write("// AUTOMATICALLY GENERATED, DO NOT EDIT\n")
        f.write("using System;\n")
        f.write("using System.Runtime.InteropServices;\n\n")
        f.write("namespace {}.Generated\n{{\n".format(c.namespace))
        f.write("    internal static class {}\n    {{\n".format(c.name))
        for function in c.functions:
            for parameter in function.parameters:
                f.write("        /// <param name=\"{}\">{}</param>\n".format(parameter["name"], parameter["type"]))
            f.write("        /// <returns>{}</returns>\n".format(function.returns))
            f.write("        [DllImport(\"{}\", CallingConvention = CallingConvention.Cdecl, EntryPoint= \"{}\")]\n".format(c.file, function.name))
            f.write("        internal static extern {} {}(".format(parse_type(function.returns, enumSet), function.name.split("_")[2]))
            isFirst = True
            for parameter in function.parameters:
                if (not isFirst):
                    f.write(", ")
                else:
                    isFirst = False
                f.write("{} {}".format(parse_type(parameter["type"], enumSet), clean_name(parameter["name"])))

            f.write(");\n\n")
        f.write("    }\n")
        f.write("}\n")


def parse_create(data, filepath):
    enumSet = set()
    for enum in data["enums"]:
        write_enum(enum, enumSet)
    classDict = dict()
    for function in data["functions"]:
        parse_function(function, classDict)
    print("Registered {} classes from file {}.".format(len(classDict), filepath))
    for _, v in classDict.items():
        write_class(v, enumSet)

def handle_file(filepath):
    with open(filepath) as json_file:
        data = json.load(json_file)
        parse_create(data, filepath)

def main():
    handle_file("arbutils.json")
    handle_file("creaturelib.json")
    handle_file("pkmnlib.json")

main()