from typing import List, Tuple, Any, Mapping, Optional
from itertools import chain
from dataclasses import dataclass, field, astuple

from clang.cindex import (
    CursorKind,
    TypeKind,
    AccessSpecifier,
    Cursor,
    Type,
    PrintingPolicy,
)
from path import Path

from .type_parser import parse_type
from .translation_unit import parse_tu
from .utils import current_platform

EXCLUDE_NS: List[str] = []


def paths_approximately_equal(p1: str, p2: str):
    """Approximate path equality. This is due to
    """
    return any([Path(p1).name.split(".")[0] == Path(p).name.split(".")[0] for p in p2])


def is_public(el):

    return (el.acess_specifier == AccessSpecifier.PUBLIC) or (
        el.access_specifier is None
    )


def get_symbols(
    tu,
    kind,
    ignore_forwards=True,
    search_in=(CursorKind.NAMESPACE,),
    exclude_ns: List[str] = [],
):
    """
    Symbols defined locally (i.e. without includes) and are not forward declarations
    Search_in allows to explore nested entities as well.

    """
    tu_path = tu.path

    def _get_symbols(cursor, kind):

        for child in cursor.get_children():
            if (
                paths_approximately_equal(
                    Path(child.location.file.name), tu_path)
                and child.kind == kind
            ):
                if ignore_forwards:
                    if child.get_definition() is None:
                        pass  # forward declaration
                    elif not paths_approximately_equal(
                        Path(child.get_definition().location.file.name), tu_path
                    ):
                        pass  # forward declaration but declared in an include
                    else:
                        yield child  # legitimate
                else:
                    yield child
            if (
                paths_approximately_equal(
                    Path(child.location.file.name), tu_path)
                and child.kind in search_in
                and child.spelling not in exclude_ns
            ):
                for nested in _get_symbols(child, kind):
                    if nested.access_specifier in (
                        AccessSpecifier.PUBLIC,
                        AccessSpecifier.INVALID,
                    ):
                        yield nested

    yield from _get_symbols(tu.cursor, kind)


def get_forward_declarations(tu):
    """Get all symbols that are forward declared"""
    tu_path = tu.path

    for child in tu.cursor.get_children():
        if paths_approximately_equal(Path(child.location.file.name), tu_path):
            if child.get_definition() is None:
                yield child


def get_all_symbols(tu, kind):
    """All defined symbols of given kind
    """
    tu_path = tu.path

    for child in tu.cursor.get_children():
        if (
            paths_approximately_equal(Path(child.location.file.name), tu_path)
            and child.kind is kind
        ):
            yield child


def get_all_symbols_multi(tu, kinds):
    """All defined symbols of given kinds
    """
    tu_path = tu.path

    for child in tu.cursor.get_children():
        if paths_approximately_equal(Path(child.location.file.name), tu_path) and any(
            (child.kind is kind for kind in kinds)
        ):
            yield child


def get_functions(tu):
    """Functions defined locally (i.e. without includes)
    """

    return (
        f
        for f in get_symbols(tu, CursorKind.FUNCTION_DECL, False)
        if "operator" not in f.spelling
    )


def get_function_templates(tu):
    """Function templates defined locally (i.e. without includes)
    """

    return (
        f
        for f in get_symbols(tu, CursorKind.FUNCTION_TEMPLATE, False)
        if "operator" not in f.spelling
    )


def get_operators(tu):
    """Functions defined locally (i.e. without includes)
    """

    return (
        f
        for f in get_symbols(tu, CursorKind.FUNCTION_DECL, False)
        if "operator" in f.spelling
    )


def get_operator_templates(tu):
    """Operator templates defined locally (i.e. without includes)
    """

    return (
        f
        for f in get_symbols(tu, CursorKind.FUNCTION_TEMPLATE, False)
        if "operator" in f.spelling
    )


def get_enums(tu):
    """Enums defined locally (i.e. without includes)
    """

    return get_symbols(tu, CursorKind.ENUM_DECL)


def get_enum_values(cur: Cursor):
    """Gets enum values
    """

    for child in cur.get_children():
        if child.kind is CursorKind.ENUM_CONSTANT_DECL:
            yield child


def get_typedefs(tu):
    """Typedefs defined locally (i.e. without includes)
    """

    return get_symbols(tu, CursorKind.TYPEDEF_DECL)


def get_classes(tu):
    """Classes defined locally (i.e. without includes)
    """

    return chain(
        get_symbols(tu, CursorKind.CLASS_DECL), get_symbols(
            tu, CursorKind.STRUCT_DECL)
    )


def get_class_templates(tu):
    """Class templates defined locally (i.e. without includes)
    """

    return get_symbols(tu, CursorKind.CLASS_TEMPLATE)


def get_namespaces(tu, ignore_ns=[]):
    """Namepspaces defined locally (i.e. without includes).
    """

    for el in get_symbols(tu, CursorKind.NAMESPACE):
        if el.spelling not in ignore_ns+[""]:
            yield el


def get_x(cls, kind):
    """Get children entities of the specified type excluding forward declataions
    """

    for child in cls.get_children():
        if child.kind is kind and child.get_definition():
            yield child


def get_x_multi(cls, kinds):
    """Get children entities of the specified types excluding forward declataions
    """

    for child in cls.get_children():
        if any((child.kind is kind for kind in kinds)) and child.get_definition():
            yield child


def get_xx(cls, kind, access):
    """Get children entities of the specified type with given access specifier
    """

    for child in cls.get_children():
        if child.kind is kind and child.access_specifier is access:
            yield child


def get_template_type_params(cls):
    """Get all template type params
    """

    for t in get_x_multi(
        cls,
        (CursorKind.TEMPLATE_TYPE_PARAMETER,
         CursorKind.TEMPLATE_NON_TYPE_PARAMETER),
    ):
        if len(list(t.get_children())) == 0:
            yield t, None
        else:

            default = "".join([tok.spelling for tok in t.get_tokens()][3:])

            # workaround - needs to be investigated
            if default.endswith(">>"):
                default = default[:-1]

            yield t, default


def get_base_class(c):
    """Get all class-baseclass pairs with public or protected inheritance
    """

    if c.get_definition():
        rv = [
            el
            for el in get_x(c.get_definition(), CursorKind.CXX_BASE_SPECIFIER)
            # how to handle  AccessSpecifier.PROTECTED?
            if el.access_specifier in (AccessSpecifier.PUBLIC,)
        ]
    else:
        rv = [
            el
            for el in get_x(c, CursorKind.CXX_BASE_SPECIFIER)
            # how to handle  AccessSpecifier.PROTECTED?
            if el.access_specifier in (AccessSpecifier.PUBLIC,)
        ]

    if len(rv) == 0:
        return []
    else:
        return [el.type.spelling for el in rv]


def get_inheritance_relations(tu):
    """Inheritance relations pairs
    """

    all_classes = get_x(tu.cursor, CursorKind.CLASS_DECL)

    for c in all_classes:
        yield c.spelling, get_base_class((c))

    all_templates = get_x(tu.cursor, CursorKind.CLASS_TEMPLATE)

    for c in all_templates:
        yield c.spelling, get_base_class((c))


def get_public_fields(cls):
    """Public methods of a given class
    """

    for child in get_xx(cls, CursorKind.FIELD_DECL, AccessSpecifier.PUBLIC):
        yield child


def get_public_enums(cls):
    """Public enums of a given class
    """

    for child in get_xx(cls, CursorKind.ENUM_DECL, AccessSpecifier.PUBLIC):
        yield child


def get_public_methods(cls):
    """Public methods of a given class
    """

    for child in get_xx(cls, CursorKind.CXX_METHOD, AccessSpecifier.PUBLIC):
        if not child.is_static_method() and not child.spelling.startswith("operator"):
            yield child


def get_protected_pure_virtual_methods(cls):
    """Protected pure virtual methods of a given class
    """

    for child in get_xx(cls, CursorKind.CXX_METHOD, AccessSpecifier.PROTECTED):
        if (
            not child.is_static_method()
            and not child.spelling.startswith("operator")
            and child.is_pure_virtual_method()
        ):
            yield child


def get_private_pure_virtual_methods(cls):
    """Private pure virtual methods of a given class
    """

    for child in get_xx(cls, CursorKind.CXX_METHOD, AccessSpecifier.PRIVATE):
        if (
            not child.is_static_method()
            and not child.spelling.startswith("operator")
            and child.is_pure_virtual_method()
        ):
            yield child


def get_public_static_methods(cls):
    """Public static methods of a given class
    """

    for child in get_xx(cls, CursorKind.CXX_METHOD, AccessSpecifier.PUBLIC):
        if child.is_static_method() and not child.spelling.startswith("operator"):
            yield child


def get_public_operators(cls):
    """Public operators of a given class
    """

    for child in get_xx(cls, CursorKind.CXX_METHOD, AccessSpecifier.PUBLIC):
        if not child.is_static_method() and child.spelling.startswith("operator"):
            yield child


def get_public_static_operators(cls):
    """Public static operators of a given class
    """

    for child in get_xx(cls, CursorKind.CXX_METHOD, AccessSpecifier.PUBLIC):
        if child.is_static_method() and child.spelling.startswith("operator"):
            yield child


def get_public_constructors(cls):
    """Public constructors of a given class
    """

    for child in get_xx(cls, CursorKind.CONSTRUCTOR, AccessSpecifier.PUBLIC):
        yield child


def get_private_constructors(cls):
    """Private constructors of a given class
    """

    for child in get_xx(cls, CursorKind.CONSTRUCTOR, AccessSpecifier.PRIVATE):
        yield child


def get_protected_constructors(cls):
    """Protected constructors of a given class
    """

    for child in get_xx(cls, CursorKind.CONSTRUCTOR, AccessSpecifier.PROTECTED):
        yield child


def get_public_destructors(cls):
    """Public destructors of a given class
    """

    for child in get_xx(cls, CursorKind.DESTRUCTOR, AccessSpecifier.PUBLIC):
        yield child


def get_private_destructors(cls):
    """Private destructors of a given class
    """

    for child in get_xx(cls, CursorKind.DESTRUCTOR, AccessSpecifier.PRIVATE):
        yield child


def get_protected_destructors(cls):
    """Private destructors of a given class
    """

    for child in get_xx(cls, CursorKind.DESTRUCTOR, AccessSpecifier.PROTECTED):
        yield child


def get_free_method_definitions(tu):
    """Free method definitions
    """

    return (
        el
        for el in get_symbols(tu, CursorKind.CXX_METHOD)
        if not el.is_static_method() and el.access_specifier == AccessSpecifier.PUBLIC
    )


def get_template_arg_dict(cur) -> dict[str, str]:
    """
    Retrun a dict mapping canonical to real template names.
    """

    rv = {}

    for ch in cur.get_children():
        if ch.kind == CursorKind.TEMPLATE_TYPE_PARAMETER:
            rv[ch.type.get_canonical().spelling] = ch.spelling
        else:
            break

    return rv


def get_named_type(T: Type) -> Type:
    """
    Strips const and &/ptr from Type.
    """

    rv = T

    if rv.kind in (TypeKind.LVALUEREFERENCE, TypeKind.POINTER):
        rv = rv.get_pointee()

    if rv.kind == TypeKind.ELABORATED:
        rv = rv.get_named_type()

    return rv


class BaseInfo(object):
    """Base class for the info objects
    """

    name: str
    comment: str

    def __init__(self, cur: Cursor):

        self.name = cur.spelling
        self.comment = cur.brief_comment


class FieldInfo(BaseInfo):
    """Container for field parsing reults
    """

    type: str
    const: bool
    pod: bool

    def __init__(self, cur: Cursor):

        super(FieldInfo, self).__init__(cur)

        self.type = cur.type.spelling
        self.const = cur.type.is_const_qualified()
        self.pod = cur.type.is_pod()


class EnumInfo(BaseInfo):
    """Container for enum parsing results
    """

    comment: str
    values: List[str]
    anonymous: bool

    def __init__(self, cur: Cursor):

        super(EnumInfo, self).__init__(cur)

        self.comment = cur.brief_comment
        self.values = [el.spelling for el in get_enum_values(cur)]
        self.anonymous = False
        self.name = cur.type.spelling

        if any(x in self.name for x in ["anonymous", "unnamed"]):
            self.anonymous = True
            self.name = "::".join(self.name.split(
                "::")[:-1])  # get rid of anonymous


@dataclass
class ArgInfo:

    arg_name: str
    arg_type: str
    arg_defualt: str
    arg_py_name: str = field(init=False)

    def __post_init__(self):

        if self.arg_name in ('def',):  # extend with more reserved words
            self.arg_py_name = f'{self.arg_name}_'
        else:
            self.arg_py_name = self.arg_name

    def __iter__(self):

        return iter(astuple(self))


class FunctionInfo(BaseInfo):
    """Container for function parsing results
    """

    namespace: Optional[str]
    full_name: str
    mangled_name: str
    return_type: str
    inline: bool
    pointer_by_ref: bool
    args: List[ArgInfo]
    default_value_types: List[str]

    KIND_DICT = {
        TypeKind.LVALUEREFERENCE: " &",
        TypeKind.RVALUEREFERENCE: " &&",
        TypeKind.POINTER: " *",
    }

    def __init__(self, cur: Cursor):

        super(FunctionInfo, self).__init__(cur)

        self.comment = cur.brief_comment
        self.full_name = cur.displayname
        self.mangled_name = cur.mangled_name
        self.return_type = self._underlying_type(cur.result_type, cur)

        if cur.semantic_parent.kind == CursorKind.NAMESPACE:
            self.namespace = cur.semantic_parent.spelling
        else:
            self.namespace = None

        self.inline = (
            cur.get_definition().is_inline() if cur.get_definition() else False
        )
        self.pointer_by_ref = any(
            self._pointer_by_ref(el) for el in cur.get_arguments()
        )
        self.args = [
            ArgInfo(el.spelling, self._underlying_type(
                el, cur), self._default_value(el))
            for el in cur.get_arguments()
        ]
        self.default_value_types = [
            self._underlying_type(el, cur, False)
            for el in cur.get_arguments()
            if self._default_value(el)
        ]

    def _pointer_by_ref(self, cur: Cursor) -> bool:
        """Check is type is a Pointer passed by reference
        """

        rv = False
        t = cur.type

        # if passed by ref
        if t.kind == TypeKind.LVALUEREFERENCE:
            tp = t.get_pointee().get_canonical()
            # if underlying type is a pointer
            if tp.kind in (TypeKind.POINTER,):
                rv = True

        return rv

    def _underlying_type(
        self, cur: Cursor | Type, ctx: Cursor, add_qualifiers=True
    ) -> str:
        """Tries to resolve the underlying type. Needed for typedefed templates.
        """

        T = cur.type if isinstance(cur, Cursor) else cur

        ptr = self.KIND_DICT.get(T.kind, "")

        policy = PrintingPolicy.create(ctx)

        # if lvaule,rvalue or pointer type
        if ptr:
            const_ptr = " const " if T.is_const_qualified() else ""
            pointee = T.get_pointee()
            const = " const " if pointee.is_const_qualified() else ""
            typ = pointee
        else:
            const_ptr = ""
            const = " const " if T.is_const_qualified() else ""
            typ = T

        if typ.kind == TypeKind.ELABORATED:
            typ = typ.get_named_type()

        rv = typ.pretty_printed(policy)

        # strip constness if still present
        if typ.is_const_qualified():
            rv = rv.removeprefix("const ")

        # handle typdefs that are not pods
        if typ.kind == TypeKind.TYPEDEF and not typ.is_pod():
            decl = typ.get_declaration()
            # replace the spelling with displayname to add template arguments - this is very brittle
            rv = rv.replace(
                decl.semantic_parent.spelling + "::",
                decl.semantic_parent.displayname + "::",
            )

            if decl.semantic_parent.kind != CursorKind.TRANSLATION_UNIT:
                rv = "typename " + rv

        # additional postprocessing related to templates
        if typ.kind == TypeKind.UNEXPOSED:
            typ_can = typ.get_canonical().spelling

            # handle missing std::
            if typ_can.startswith("std::") and not rv.startswith("std::"):
                rv = "std::" + rv

            # expand template argument names if needed
            n_args = typ.get_num_template_arguments()
            if n_args != -1:
                # construct a short:long name dict
                args = {}
                for ix in range(n_args):
                    arg = typ.get_template_argument_type(ix)
                    args[arg.spelling] = self._underlying_type(arg, ctx)

                # transform the result
                for k, v in args.items():
                    rv = rv.replace(k, v)

        if add_qualifiers:
            rv = const + rv + ptr + const_ptr

        # strip possible opencascade::handle<T> when
        if not add_qualifiers:
            rv = parse_type(rv)

        return rv

    def _default_value(self, cur: Cursor) -> Optional[str]:
        """Tries to extract default value
        """

        rv = None
        tokens = [t.spelling for t in cur.get_tokens()]
        if "=" in tokens:
            rv = " ".join(tokens[tokens.index("=") + 1:])

            # handle default initalization of complex types
            if "{ }" == rv:
                rv = f"{get_named_type(cur.type).spelling}{rv}"

        return rv


class MethodInfo(FunctionInfo):
    """Container for method parsing results
    """

    const: bool
    virtual: bool
    pure_virtual: bool

    def __init__(self, cur: Cursor):

        super(MethodInfo, self).__init__(cur)

        self.const = cur.is_const_method()
        self.virtual = cur.is_virtual_method()
        self.pure_virtual = cur.is_pure_virtual_method()


class ConstructorInfo(MethodInfo):
    """Container for constructor parsing results
    """

    pass


class DestructorInfo(FunctionInfo):
    """Container for destructor parsing results
    """

    pass


class ClassInfo(object):
    """Container for class parsing results
    """

    name: str
    comment: str
    abstract: bool

    constructors: List[ConstructorInfo]
    nonpublic_constructors: List[ConstructorInfo]

    fields: List[FieldInfo]

    enums: List[EnumInfo]

    methods: List[MethodInfo]
    protected_virtual_methods: List[MethodInfo]
    private_virtual_methods: List[MethodInfo]
    static_methods: List[MethodInfo]
    static_methods_byref: List[MethodInfo]
    methods_byref: List[MethodInfo]
    methods_return_byref: List[MethodInfo]

    operators: List[MethodInfo]
    static_operators: List[MethodInfo]

    destructors: List[DestructorInfo]
    nonpublic_destructors: List[DestructorInfo]

    ptr: Any  # is this needed?

    superclass: List[str]
    rootclass: List[str]
    superclasses: List[str]

    methods_dict: Mapping[str, MethodInfo]
    protected_virtual_methods_dict: Mapping[str, MethodInfo]
    private_virtual_methods_dict: Mapping[str, MethodInfo]

    def __init__(self, cur: Cursor):

        self.name = cur.type.spelling
        self.comment = cur.brief_comment
        self.abstract = cur.is_abstract_record()

        self.constructors = self.filter_rvalues(
            (ConstructorInfo(el) for el in get_public_constructors(cur))
        )
        self.nonpublic_constructors = [
            ConstructorInfo(el) for el in get_private_constructors(cur)
        ] + [ConstructorInfo(el) for el in get_protected_constructors(cur)]

        self.fields = [FieldInfo(el) for el in get_public_fields(cur)]
        self.enums = [EnumInfo(el) for el in get_public_enums(cur)]

        self.methods = self.filter_rvalues(
            (MethodInfo(el) for el in get_public_methods(cur))
        )
        self.protected_virtual_methods = self.filter_rvalues(
            (MethodInfo(el) for el in get_protected_pure_virtual_methods(cur))
        )
        self.private_virtual_methods = self.filter_rvalues(
            (MethodInfo(el) for el in get_private_pure_virtual_methods(cur))
        )

        self.static_methods = self.filter_rvalues(
            (MethodInfo(el) for el in get_public_static_methods(cur))
        )

        self.static_methods_byref = []
        self.methods_byref = []
        self.methods_return_byref = []

        self.operators = [MethodInfo(el) for el in get_public_operators(cur)]
        self.static_operators = [
            MethodInfo(el) for el in get_public_static_operators(cur)
        ]

        self.destructors = [DestructorInfo(el)
                            for el in get_public_destructors(cur)]
        self.nonpublic_destructors = [
            DestructorInfo(el) for el in get_private_destructors(cur)
        ] + [DestructorInfo(el) for el in get_protected_destructors(cur)]

        self.ptr = None
        self.superclass = []
        self.rootclass = []
        self.superclasses = []

        self.methods_dict = {m.name: m for m in self.methods}
        self.protected_virtual_methods_dict = {
            m.name: m for m in self.protected_virtual_methods
        }
        self.private_virtual_methods_dict = {
            m.name: m for m in self.private_virtual_methods
        }

    def filter_rvalues(self, funcs):

        return [f for f in funcs if not any(("&&" in arg for _, arg, _, _ in f.args))]

    def extend_defintion(self, other):

        new_comment = ""
        if self.comment:
            new_comment += self.comment
        if other.comment:
            new_comment += other.comment
        self.comment = new_comment

        self.constructors += other.constructors
        self.nonpublic_constructors += other.nonpublic_constructors
        self.methods += other.methods
        self.protected_virtual_methods += other.protected_virtual_methods
        self.private_virtual_methods += other.private_virtual_methods
        self.static_methods += other.static_methods
        self.operators += other.operators
        self.static_operators += other.static_operators
        self.destructors += other.destructors
        self.nonpublic_destructors += other.nonpublic_destructors

        self.fields += other.fields
        self.enums += other.enums

        self.methods_dict = {**self.methods_dict, **other.methods_dict}
        self.protected_virtual_methods_dict = {
            **self.protected_virtual_methods_dict,
            **other.protected_virtual_methods_dict,
        }
        self.private_virtual_methods_dict = {
            **self.protected_virtual_methods_dict,
            **other.protected_virtual_methods_dict,
        }


class ClassTemplateInfo(ClassInfo):

    type_params: List[Tuple[Optional[str], str, str]]

    def __init__(self, cur: Cursor):
        super(ClassTemplateInfo, self).__init__(cur)
        self.name = cur.spelling
        self.type_params = [
            (
                None if el.spelling == el.type.spelling else el.type.spelling,
                el.spelling,
                default,
            )
            for el, default in get_template_type_params(cur)
        ]


class TypedefInfo(BaseInfo):

    type: str
    pod: bool
    template_base: List[str]
    template_args: List[str]

    def __init__(self, cur: Cursor):

        super(TypedefInfo, self).__init__(cur)

        t = cur.underlying_typedef_type

        self.type = t.spelling
        self.pod = t.is_pod()

        if not self.pod:
            self.template_base = [
                ch.spelling
                for ch in cur.get_children()
                if ch.kind == CursorKind.TEMPLATE_REF
            ]
            self.template_args = [
                ch.spelling
                for ch in cur.get_children()
                if ch.kind == CursorKind.TYPE_REF
            ]


class ForwardInfo(BaseInfo):

    pass


class HeaderInfo(object):
    """Container for header parsing results
    """

    name: str
    short_name: str
    dependencies: List[str]
    classes: Mapping[str, ClassInfo]
    class_templates: Mapping[str, ClassTemplateInfo]
    functions: List[FunctionInfo]
    operators: List[FunctionInfo]
    enums: List[EnumInfo]
    methods: List[MethodInfo]
    inheritance: Mapping[str, str]
    typedefs: List[TypedefInfo]
    typedef_dict: Mapping[str, str]
    forwards: List[ForwardInfo]
    namespaces: List[str]

    def __init__(self):

        self.name = ""
        self.short_name = ""
        self.dependencies = []
        self.classes = {}
        self.class_templates = {}
        self.funstions = []
        self.operators = []
        self.enums = []
        self.methods = []
        self.inheritance = {}
        self.typedefs = []
        self.typdef_dict = {}
        self.forwards = []
        self.namepspaces = []

    def resolve_inheritance(self, cls):

        inheritance = self.inheritance
        superclasses = cls.superclasses
        rootclass = cls.rootclass

        cls.superclass = inheritance.get(cls.name, [])

        def _resolve_inheritance(node):
            """Recurisvely traverse the inheritance tree"""

            for el in node:
                superclasses.append(el)

                if el in inheritance:
                    _resolve_inheritance(inheritance[el])
                else:
                    rootclass.append(el)

        _resolve_inheritance(cls.superclass)

    def parse(self, path, input_folder, settings, module_name, target_platform):

        module_settings = settings["Modules"].get(module_name)

        if module_settings:
            tu_parsing_header = "\n".join(
                (
                    module_settings["module_parsing_header"],
                    module_settings["parsing_headers"].get(path.name, ""),
                )
            )
        else:
            tu_parsing_header = ""

        tr_unit = parse_tu(
            path,
            input_folder,
            prefix=settings[current_platform()]["prefix"],
            platform_includes=settings[current_platform()]["includes"],
            parsing_header=settings["parsing_header"],
            tu_parsing_header=tu_parsing_header,
            platform_parsing_header=settings[current_platform(
            )]["parsing_header"],
            target_platform=target_platform,
        )

        self.name = path
        self.short_name = path.splitpath()[-1]
        self.dependencies = [
            el.location.file.name for el in tr_unit.get_includes()]
        self.enums = [EnumInfo(el) for el in get_enums(tr_unit)]
        self.functions = [FunctionInfo(el) for el in get_functions(tr_unit)]
        self.operators = [FunctionInfo(el) for el in get_operators(tr_unit)]

        self.classes = {}

        for el in get_classes(tr_unit):
            ci = ClassInfo(el)
            if ci.name in self.classes:
                self.classes[ci.name].extend_defintion(ci)
            else:
                self.classes[ci.name] = ci

        self.class_dict = {k: self.name for k in self.classes}
        self.class_templates = {
            el.displayname: ClassTemplateInfo(el) for el in get_class_templates(tr_unit)
        }
        self.class_template_dict = {k: self.name for k in self.class_templates}
        self.inheritance = {k: v for k,
                            v in get_inheritance_relations(tr_unit) if v}
        self.typedefs = [TypedefInfo(el) for el in get_typedefs(tr_unit)]
        self.typedef_dict = {t.name: self.name for t in self.typedefs}
        self.forwards = [ForwardInfo(el)
                         for el in get_forward_declarations(tr_unit)]

        self.namespaces = [el.spelling for el in get_namespaces(tr_unit)]

        # handle freely defined methods
        methods = [el for el in get_free_method_definitions(tr_unit)]

        # match methods to classes
        for m in methods:
            cls = self.classes.get(m.semantic_parent.displayname, None)
            if cls:
                cls.methods.append(MethodInfo(m))

        # resolve inheritance relations
        for c in self.classes.values():
            self.resolve_inheritance(c)

        for c in self.class_templates.values():
            self.resolve_inheritance(c)

        return tr_unit


def process_header(path, input_folder, settings, module_name, target_platform):
    """Main function from this module
    """

    hi = HeaderInfo()
    hi.parse(path, input_folder, settings, module_name, target_platform)

    return hi


__all__ = [
    process_header,
]

if __name__ == "__main__":

    from os import getenv
    from .utils import init_clang

    init_clang()

    conda_prefix = Path(getenv("CONDA_PREFIX"))

    gp_Ax1 = process_header(conda_prefix / "include" /
                            "opencascade" / "gp_Ax1.hxx")

    for el in gp_Ax1.classes.values():
        print(el.name)

    # try public methods
    for el in el.methods:
        print(el.name, el.args, el.return_type)

    # try enums
    gp_TrsfForm = process_header(
        conda_prefix / "include" / "opencascade" / "gp_TrsfForm.hxx"
    )

    for el in gp_TrsfForm.enums:
        print(el.name)
        print(el.values)

    # try functions
    gp_Vec2d = process_header(
        conda_prefix / "include" / "opencascade" / "gp_Vec2d.hxx")

    for el in gp_Vec2d.functions:
        print(el.name)

    for el in gp_Vec2d.operators:
        print(el.name)

    # try inheritance
    TopoDS = process_header(
        conda_prefix / "include" / "opencascade" / "TopoDS_Solid.hxx"
    )

    for k in TopoDS.inheritance:
        print(k, TopoDS.inheritance[k])
