#!/usr/bin/env python3
import os
import sys
import shutil
import json

import stringreader
import lex
import parse
import nativetypes

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))

NATIVE_TYPE_MAP = {}
for t in nativetypes.NATIVE_TYPES:
    NATIVE_TYPE_MAP[t] = True

def typeToJsonData(t):
    if isinstance(t, parse.NamedType):
        if t.name in NATIVE_TYPE_MAP:
            return {"kind":"native","name":t.name}
        return {"kind":"alias","name":t.name}
    if isinstance(t, parse.SinglePtrType):
        return {"kind":"singleptr","const":t.const, "subtype":typeToJsonData(t.sub_type)}
    if isinstance(t, parse.ArrayPtrType):
        return {"kind":"arrayptr","const":t.const,"subtype":typeToJsonData(t.sub_type)}
    if isinstance(t, parse.FuncPtrType):
        return {"kind":"funcptr","return_type":typeToJsonData(t.return_type),"args":[
            {"name":arg.name,"type":typeToJsonData(arg.type)} for arg in t.args]}
    if isinstance(t, parse.FixedLenArrayType):
        return {"kind":"fixedlenarray","len":t.len,"subtype":typeToJsonData(t.sub_type)}
    raise Exception("unhandled type class {}".format(type(t).__name__))

def constValueToJsonData(val):
    if isinstance(val, parse.Integer):
        return val.int_value
    if isinstance(val, parse.NamedValue):
        return val.name
    raise Exception("unhandled ConstValue class {}".format(type(val).__name__))

def toJsonData(nodes):
    includes = []
    types = []
    constants = []
    functions = []
    unicode_names = []
    for node in nodes:
        if isinstance(node, parse.TypedefNode):
            types.append({"kind":"typedef","name":node.name,"definition":typeToJsonData(node.def_type)})
        elif isinstance(node, parse.StructNode):
            types.append({"kind":"struct","name":node.name,
                                "fields":[{"type":typeToJsonData(field.type),"name":field.name} for field in node.fields]})
        elif isinstance(node, parse.ConstNode):
            constants.append({"name":node.name,"type":typeToJsonData(node.type),"value":constValueToJsonData(node.value)})
        elif isinstance(node, parse.FuncNode):
            functions.append({"name":node.name,"return_type":typeToJsonData(node.return_type),
                              "args":[{"type":typeToJsonData(arg.type),"name":arg.name} for arg in node.args]})
        elif isinstance(node, parse.UnicodeNode):
            unicode_names.append({"name":node.name})
        elif isinstance(node, parse.IncludeNode):
            includes.append({"filename":node.filename_no_ext})
        else:
            raise Exception("unhandled node {}".format(type(node).__name__))
    return {"includes":includes,"types":types,"constants":constants,"functions":functions,"unicode_names":unicode_names}

def processFile(out_dir, filename):
    name = os.path.basename(filename)[:-4]
    out_filename = os.path.join(out_dir, name + ".json")
    print("generating: {}".format(out_filename))

    with open(filename) as file:
        text = file.read()

    parser = parse.Parser(lex.Lexer(stringreader.StringReader(text, filename)))
    nodes = []
    parser.parseInto(nodes)

    with open(out_filename, "w") as out_file:
        json.dump(toJsonData(nodes), out_file)

def main():
    out_dir = os.path.join(SCRIPT_DIR, "out", "json")
    if os.path.exists(out_dir):
        shutil.rmtree(out_dir)
    os.makedirs(out_dir)
    api_dir = os.path.join(SCRIPT_DIR, "api")
    for entry_basename in os.listdir(api_dir):
        if entry_basename.endswith(".api"):
            processFile(out_dir, os.path.join(api_dir, entry_basename))

main()
