#!/usr/bin/env python3
# coding=utf-8
#
# Copyright (c) 2024-2025 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Any, Dict, Union
from line_iterator import LineIterator
from runtime_collections import add_to_statistics, add_to_custom_yamls
from parse_namespace import parse_namespace
from parse_enum import parse_enum_class
from parse_struct import parse_struct
from parse_using import parse_using
from parse_define import parse_define_macros, is_known_macros
from parse_class import parse_class, parse_friend_class, parse_template_prefix
from parse_method import parse_method_or_constructor
from parse_arguments import parse_argument
from log_tools import debug_log


def deep_copy(data: Any) -> Any:
    if isinstance(data, (dict, list)):
        return data.copy()
    return data


class CppParser:
    def __init__(self, data: str, namespace: str = "", parent_class_name: str = ""):
        self.it = LineIterator(data)
        self.parsed: Any = None
        self.res: Dict[str, Any] = {}
        self.template: Union[str, None] = None

        self.parent_class_name = parent_class_name
        self.namespace = namespace
        self.current_modifier = ""

    def parse(self) -> Dict[str, Any]:  # pylint: disable=R0912

        while self.it.next_line():

            if self.it.is_access_modifier():
                self.update_access_modifier()

            elif self.it.is_skip_line() or self.current_modifier in ["private", "protected"]:
                add_to_statistics("skip", self.it.current_line)

            elif self.it.is_template():
                self.it.end, self.template = parse_template_prefix(self.it.data, self.it.start)

            elif self.it.is_using():
                self.it.end, self.parsed = parse_using(self.it.data, self.it.start)
                self.res_append_in_modifier("usings")

            elif self.it.is_namespace():
                self.it.end, self.parsed = parse_namespace(self.it.data, self.it.start)
                self.res_update()

            elif self.it.is_enum():
                self.it.end, self.parsed = parse_enum_class(self.it.data, self.it.start)
                self.res_append_namespace()

            elif self.it.is_struct():
                self.it.end, self.parsed = parse_struct(self.it.data, self.it.start)
                self.res_append("structs")

            elif self.it.is_define_macro():
                self.it.end, self.parsed = parse_define_macros(self.it.data, self.it.start)
                self.res_append("macros")

            elif is_known_macros(self.it.current_line):
                self.parsed = self.it.current_line
                self.res_append("known_macroses")

            elif self.it.is_firend_class():
                self.it.end, self.parsed = parse_friend_class(self.it.data, self.it.start)
                self.res_append("friends")

            elif self.it.is_class_forward_decl():
                self.parsed = self.it.current_line.replace("class", "").strip(" ;")
                self.res_append("class_forward_declaration")

            elif self.it.is_class_definition():
                self.it.end, self.parsed = parse_class(
                    self.it.data, self.it.start, self.namespace, self.parent_class_name
                )
                self.res_append_class_definition()

            elif self.it.is_method_or_constructor():
                self.it.end, self.parsed = parse_method_or_constructor(self.it.data, self.it.start)
                self.res_append_method_or_constructor()

            elif self.it.is_field():
                self.parsed = parse_argument(self.it.data[self.it.start : self.it.next_semicolon])
                self.it.end = self.it.next_semicolon
                self.res_append_field()

            else:
                add_to_statistics("unreachable", self.it.current_line)

        return self.res

    def res_append(self, key: str) -> None:
        if not self.parsed:
            return

        self.parsed_update_template()
        if key not in self.res:
            self.res[key] = []
        self.res[key].append(deep_copy(self.parsed))

    def res_append_in_modifier(self, key: str) -> None:
        if not self.parsed:
            return

        self.parsed_update_template()
        if self.current_modifier == "":
            if key == "usings":
                self.res_append("usings")
                return
            raise RuntimeError("Unreachable")

        if key not in self.res[self.current_modifier]:  # CC-OFF(G.TYP.07) dict key exist
            self.res[self.current_modifier][key] = []  # CC-OFF(G.TYP.07) dict key exist

        self.res[self.current_modifier][key].append(deep_copy(self.parsed))  # CC-OFF(G.TYP.07) dict key exist

    def res_update(self) -> None:
        if self.parsed:
            self.parsed_update_template()
            self.res.update(self.parsed)

    def res_append_namespace(self) -> None:
        if not self.parsed:
            return

        self.parsed["namespace"] = self.namespace

        if self.parent_class_name != "":
            self.parsed["parent_class_name"] = self.parent_class_name

        if "flags" in self.parsed or "flag_unions" in self.parsed:
            self.res_append("enums")
            add_to_custom_yamls("allEnums", "enums", self.parsed)

    def update_access_modifier(self) -> None:
        if self.parent_class_name == "":
            raise RuntimeError("Found modifier not in class")
        self.current_modifier = self.it.current_line.strip(" :")
        if self.current_modifier not in self.res:
            self.res[self.current_modifier] = {}

    def res_append_method_or_constructor(self) -> None:
        if not self.parsed:
            return

        # Constructor
        if self.parsed["name"] == self.parent_class_name:  # CC-OFF(G.TYP.07) dict key exist
            self.res_append_in_modifier("constructors")

        # Destructor
        elif self.parsed["name"] == "~" + self.parent_class_name:  # CC-OFF(G.TYP.07) dict key exist
            self.res_append_in_modifier("destructors")

        # Method
        elif self.current_modifier != "":
            self.res_append_in_modifier("methods")

        # Function
        else:
            self.res_append("functions")

    def res_append_field(self) -> None:
        # Class field
        if self.current_modifier != "":
            self.res_append_in_modifier("fields")

        # Top level variable
        else:
            self.res_append("vars")

    def res_append_class_definition(self) -> None:
        self.res_append("class_definitions")

    def parsed_update_template(self) -> None:
        if self.template and self.parsed:
            if isinstance(self.parsed, dict):
                self.parsed["template"] = self.template
                self.template = None
            else:
                debug_log(f"Skipping template for '{self.parsed}'")