# !/usr/bin/env python3
# coding=utf-8
"""
* Copyright (c) 2022 Shenzhen Kaihong Digital Industry Development Co., Ltd.
*
* HDF is dual licensed: you can use it either under the terms of
* the GPL, or the BSD license, at your option.
* See the LICENSE file in the root of this repository for complete details.
"""

import json
import os
import re
from shutil import copyfile, move

import CppHeaderParser


class HeaderParser:
    """
    Extract file path, file name, includes, enums, unions, structs, interfaces and callbacks
    from CppHeaderParser
    """

    def __init__(self):
        self._header_dict = {
            "name": "",
            "path": "",
            "import": [],
            "enum": [],
            "union": [],
            "struct": [],
            "typedef": [],
            "interface": [],
            "callback": []
        }
        self._rand_name_count = 0

    def parse(self, header_file):
        try:
            hjson = json.loads(CppHeaderParser.CppHeader(header_file).toJSON())
        except CppHeaderParser.CppParseError:
            back_file = self._pre_handle(header_file)
            hjson = json.loads(CppHeaderParser.CppHeader(header_file).toJSON())
            move(back_file, header_file)

        try:
            self._extract_path_and_file(header_file)
            self._extract_include(hjson["includes"])
            self._extract_enum(hjson["enums"])
            for i in hjson["classes"]:
                self._extract_union(hjson["classes"][i])
                self._extract_struct(hjson["classes"][i])
                self._extract_interface(hjson["classes"][i])
            self._extract_typedef(hjson["typedefs"])
            return self._header_dict
        except KeyError:
            pass

    @staticmethod
    def _pre_handle(header_file):
        back_file = header_file + ".back"
        copyfile(header_file, back_file)
        f = open(header_file, 'r')
        new_lines = ""
        for line in f:
            tt = re.match(r".*enum *((\w+) *\*) *\w* *;", line)
            if tt:
                new_line = line.replace(tt[1], tt[2] + "_ENUM_POINTER ")
                new_lines += new_line
                continue
            tt = re.match(r".*(\[\w* *\(.*\) *]) *", line)
            if tt:
                new_line = line.replace(tt[1], "[]")
                new_lines += new_line
                continue
            else:
                new_lines += line
        f.close()
        f = open(header_file, 'w')
        f.writelines(new_lines)
        f.close()
        return back_file

    @staticmethod
    def _has_function_pointer(jvs):
        for jv in jvs:
            if jv["function_pointer"] > 0:
                return True
        return False

    def _checkout_function_pointer_param(self, params):
        if params == '':
            return []
        tt = re.match(r"(typedef )* *\w+ \( \* \) \(( .* )\)", params)
        if tt:
            params = tt[2].strip() + ","
        else:
            print("error cant analyze function pointer params: ", params)
            return []
        ret = []
        while params:
            tt = re.match(" *(const )*(struct |enum |union )* *", params)
            if tt:  # 去掉结构类型头
                params = params[tt.regs[0][1]:]
                tt = re.match(r"((unsigned )*[a-zA-Z0-9_:]+( *\** *\**)) *", params)
                if tt:
                    param_type = params[tt.regs[1][0]:tt.regs[1][1]]  # 参数类型
                    params = params[tt.regs[0][1]:]
                    tt = re.match("([a-zA-Z0-9_]+) *(,)", params)
                    if tt:
                        param_name = params[tt.regs[1][0]:tt.regs[1][1]]
                        params = params[tt.regs[0][1]:]
                    else:  # 没有定义变量名的,设置一个默认变量名
                        param_name = "rand_name_%d" % self._rand_name_count
                        self._rand_name_count += 1
                        params = params[params.index(",") + 1:]
                    ret.append({"name": param_name, "type": param_type.strip()})
                else:
                    ret.append({"name": '', "type": params.strip(',').strip()})
                    break
            else:
                print("error cant analyze param :[%s]" % params)
                break
        return ret

    def _extract_path_and_file(self, path):
        self._header_dict["path"], self._header_dict["name"] = os.path.split(path)

    def _extract_include(self, includes):
        """
        Extract imports from includes
        """
        for include in includes:
            if '<' not in include:  # ignore system files
                self._header_dict.get("import").append(include[1:-1])

    def _extract_enum(self, enums):
        """
        Extract enums from enums
        """
        for enm in enums:
            if "name" in enm:
                enum_dict = {"name": enm["name"], "members": []}
            else:
                enum_dict = {"name": "LostName_%d" % self._rand_name_count, "members": []}
                self._rand_name_count += 1
            for value in enm["values"]:
                v_value = value["value"]
                if not isinstance(v_value, int):
                    tt = re.match(r"0x|0X|\(|-", v_value)
                    if tt:
                        errmsg = "unexpected '%s'" % tt[0]
                        v_value = v_value + " // " + errmsg
                        print("[HeaderParser]: %s[line %d] " % (enm["filename"], enm["line_number"]), errmsg)
                enum_dict["members"].append({"name": value["name"], "value": v_value})
            self._header_dict.get("enum").append(enum_dict)

    def _extract_union(self, stack):
        """
        Extract unions from classes
        """
        union_dict = {}
        if stack["declaration_method"] == "union":
            union_dict["name"] = stack["name"].split(" ")[-1]
            union_dict["type"] = "union"
            union_dict["members"] = []
            file_name = self._header_dict.get("path") + "/" + self._header_dict.get("name")
            for mb in stack["members"]:
                union_dict.get("members").append({"file_name": file_name,
                                                  "line_number": mb["line_number"],
                                                  "name": mb["name"], "type": mb["type"]})
            self._header_dict.get("union").append(union_dict)

    def _extract_struct(self, stack):
        """
        Extract structs from structs or classes that have no methods
        """
        struct_dict = {}
        if stack["declaration_method"] in ["struct", "class"]:
            if len(stack["methods"]["public"]) == 0:  # 不带函数
                if not self._has_function_pointer(stack["properties"]["public"]):  # 变量中没有函数指针
                    struct_dict["name"] = stack["name"]
                    struct_dict["type"] = "struct"
                    struct_dict["members"] = []
                    file_name = self._header_dict.get("path") + "/" + self._header_dict.get("name")
                    for mb in stack["properties"]["public"]:
                        if "enum_type" in mb:
                            struct_dict.get("members").append({"file_name": file_name,
                                                               "line_number": mb["line_number"],
                                                               "name": mb["name"],
                                                               "type": mb["enum_type"]["name"]})
                        elif mb["array"] == 1:
                            struct_dict.get("members").append({"file_name": file_name,
                                                               "line_number": mb["line_number"],
                                                               "name": mb["name"],
                                                               "type": mb["type"] + " *"})
                        else:
                            struct_dict.get("members").append({"file_name": file_name,
                                                               "line_number": mb["line_number"],
                                                               "name": mb["name"],
                                                               "type": mb["type"]})
                    self._header_dict.get("struct").append(struct_dict)

    def _extract_interface(self, stack):
        """
        Extract interfaces from structs or classes which have some methods
        """
        interface_dict = {}
        if stack["declaration_method"] in ["struct", "class"]:
            # 带函数,或变量中包含函数指针
            if len(stack["methods"]["public"]) > 0 or self._has_function_pointer(stack["properties"]["public"]):
                interface_dict["name"] = stack["name"]
                interface_dict["members"] = []
                for mb in stack["methods"]["public"]:
                    if mb["name"] in (stack["name"], "DECLARE_INTERFACE_DESCRIPTOR"):
                        continue
                    params = []
                    for param in mb["parameters"]:
                        para_name = param["name"]
                        if para_name == '':
                            para_name = "rand_name_%d" % self._rand_name_count
                            self._rand_name_count += 1
                        params.append({"name": para_name, "type": param["type"]})
                    interface_dict.get("members").append(
                        {"name": mb["name"],
                         "params": params,
                         "file_name":
                             self._header_dict.get("path") + "/" + self._header_dict.get("name"),
                         "line_number": mb["line_number"]})
                for mb in stack["properties"]["public"]:
                    if mb["function_pointer"] > 0:
                        interface_dict.get("members").append(
                            {"name": mb["name"],
                             "params": self._checkout_function_pointer_param(mb["type"]),
                             "file_name":
                                 self._header_dict.get("path") + "/" + self._header_dict.get("name"),
                             "line_number": mb["line_number"]})
                self._header_dict.get("interface").append(interface_dict)

    def _extract_typedef(self, typedefs):
        """
        Extract typedef from global typedefs
        e.g.
        "typedefs": {
            "HotPlugCallback": "typedef void ( * ) ( uint32_t devId , bool connected , void * data )",
            "AudioHandle": "void *"
        }
        """
        for td in typedefs:
            if "typedef" in typedefs[td]:
                self._header_dict.get("typedef").append({"name": td,
                                                         "type": "/* unsupported function pointer type: " + td + " */"})
            else:
                self._header_dict.get("typedef").append({"name": td, "type": typedefs[td]})