# Copyright 2024 Volvo Car Corporation
# Licensed under Apache 2.0.

# -*- coding: utf-8 -*-
"""Module containing classes for generating RTE dummy code.

These files are needed for building test SW,
where the RTE is not available.
For example, when running Silver tests.
"""

from powertrain_build.problem_logger import ProblemLogger

from pathlib import Path


class RteDummy(ProblemLogger):
    """A class for RTE dummy file generation."""

    def __init__(self, build_cfg, core, nvm_def, calib_data):
        """Init.

        Args:
            build_cfg (BuildProjConfig): Object with build configuration settings.
            core (ZCCore): Object representing the core configuration data.
            nvm_def (ZCNVMDef): Object with NVM definition information.
            calib_data (dict): Dictionary containing calibration data for a ZoneController project.
        """
        super().__init__()
        self.build_cfg = build_cfg
        self.dtcs = core.project_dtcs
        self.nvm_def = nvm_def
        self.calibration_variables = calib_data.get("class_info", {})
        mem_map_config = self.build_cfg.get_memory_map_config()
        self.mem_map_header_file_name = f"{mem_map_config['memMapPrefix']}_MemMap"
        self.swc_header_file_name = f"Rte_{self.build_cfg.get_composition_config('softwareComponentName')}"
        self.type_header_file_name = "Rte_Type"
        self.source_file_name = "Rte_Dummy"

    def _get_common_header(self):
        """Get common header for the RTE dummy files."""
        return (
            "/*\n"
            " * This file is generated by the Powertrain Build System.\n"
            " * It defines RTE dummy types and/or functions.\n"
            " * Do not modify this file manually.\n"
            " */\n"
        )

    def _get_mem_map_header_content(self):
        """Get content for the dummy memory map header."""
        return (
            f"{self._get_common_header()}"
            f"#ifndef {self.mem_map_header_file_name.upper()}_H\n"
            f"#define {self.mem_map_header_file_name.upper()}_H\n\n"
            f"#endif /* {self.mem_map_header_file_name.upper()}_H */\n"
        )

    def _get_swc_header_content(self):
        """Get content for the SWC RTE dummy header."""
        return (
            f"{self._get_common_header()}"
            f"#ifndef {self.swc_header_file_name.upper()}_H\n"
            f"#define {self.swc_header_file_name.upper()}_H\n\n"
            f'#include "{self.type_header_file_name}.h"\n'
            f"#endif /* {self.swc_header_file_name.upper()}_H */\n"
        )

    def _get_type_header_header(self):
        """Get header for the RTE type dummy header."""
        include_nvm_header = ""
        if not self.build_cfg.get_code_generation_config("useRteNvmStructs"):
            nvm_defs = self.build_cfg.get_nvm_defs()
            nvm_file_name = nvm_defs["fileName"]
            include_nvm_header = f'#include "{nvm_file_name}.h"\n'
        return (
            f"{self._get_common_header()}"
            f"#ifndef {self.type_header_file_name.upper()}_H\n"
            f"#define {self.type_header_file_name.upper()}_H\n\n"
            '#include "tl_basetypes.h"\n'
            f"{include_nvm_header}\n"
            "#define FALSE 0U\n"
            "#define TRUE 1U\n\n"
        )

    def _get_type_header_footer(self):
        """Get footer for the RTE type dummy header."""
        return f"\n#endif /* {self.type_header_file_name.upper()}_H */\n"

    def _get_source_header(self):
        """Get header for the RTE dummy source."""
        return (
            f"{self._get_common_header()}"
            f'#include "{self.swc_header_file_name}.h"\n\n'
        )

    def _get_typedef_dummy(self):
        """Get typedef dummy code."""
        typedefs = []

        function_name = self.build_cfg.get_composition_config("compositionName")
        if function_name is None:
            function_name = self.build_cfg.get_composition_config("softwareComponentName")

        # MAPs get typedef:ed to structs and need special data type mapping in calibration.py
        for signal_name, signal_data in self.calibration_variables.items():
            if isinstance(signal_data["width"], list):
                if signal_name.startswith("m") and signal_name[-2:] not in ["_r", "_c"]:
                    typedefs.append(
                        f"typedef {signal_data['type']} {signal_data['autosar_type']}_e[{signal_data['width'][0]}];\n"
                    )
                    typedefs.append(
                        "typedef struct \n"
                        "{\n"
                        f"  {signal_data['autosar_type']}_e {signal_data['autosar_type']}[{signal_data['width'][1]}];\n"
                        f"}} {signal_data['autosar_type']};\n\n"
                    )

        return "".join(typedefs)

    def _get_nvm_header_dummy(self):
        """Get NVM dummy header code."""
        struct_defines = []
        function_declarations = []
        prefix = self.build_cfg.get_scheduler_prefix()
        use_rte_nvm_structs = self.build_cfg.get_code_generation_config("useRteNvmStructs")

        for memory_area in self.nvm_def._nvm_memory_areas:
            nvm_name = f"{prefix}{memory_area}"
            struct_name = f"dt_{nvm_name}" if use_rte_nvm_structs else f"struct {nvm_name}"
            function_declarations.append(f"{struct_name} *Rte_Pim_{nvm_name}(void);")
            function_declarations.append(f"void Rte_Read_{nvm_name.upper()}_{nvm_name.upper()}({struct_name} *block);")
            function_declarations.append(f"void Rte_Write_{nvm_name.upper()}_{nvm_name.upper()}({struct_name} *block);")

            if use_rte_nvm_structs:
                struct_defines.append("typedef struct\n{")
                memory_area_index = self.nvm_def._get_nvm_areas_index(memory_area)
                nr_of_unused_signals = self.nvm_def.nvm_definitions[memory_area_index]["size"]
                signals = self.nvm_def.nvm_definitions[memory_area_index]["signals"]
                for signal in signals:
                    signal_string = ""
                    nr_of_unused_signals -= signal["x_size"] * signal["y_size"]
                    signal_string += f'  {signal["type"]} {self.nvm_def.struct_member_prefix}{signal["name"]}'
                    size = max(signal["x_size"], 1) * max(signal["y_size"], 1)
                    if size > 1:
                        if signal["x_size"] > 1:
                            signal_string += f'[{signal["x_size"]}]'
                        if signal["y_size"] > 1:
                            signal_string += f'[{signal["y_size"]}]'
                    signal_string += ";"
                    struct_defines.append(signal_string)
                if nr_of_unused_signals > 0:
                    struct_defines.append(
                        f'  {self.nvm_def.nvm_definitions[memory_area_index]["default_datatype"]} '
                        f'unused[{nr_of_unused_signals}];'
                    )
                struct_defines.append(f"}} {struct_name};\n")

        return "\n".join(struct_defines + function_declarations)

    def _get_nvm_source_dummy(self):
        """Generate NVM source dummy code."""
        lines_to_write = []
        prefix = self.build_cfg.get_scheduler_prefix()
        use_rte_nvm_structs = self.build_cfg.get_code_generation_config("useRteNvmStructs")
        for memory_area in self.nvm_def._nvm_memory_areas:
            nvm_name = f"{prefix}{memory_area}"
            struct_name = f"dt_{nvm_name}" if use_rte_nvm_structs else f"struct {nvm_name}"
            return_string = f"{struct_name} dummy; {struct_name}* dummyPtr = &dummy; return dummyPtr;"
            lines_to_write.append(f"{struct_name} *Rte_Pim_{nvm_name}(void) {{ {return_string} }}")
            lines_to_write.append(f"void Rte_Read_{nvm_name.upper()}_{nvm_name.upper()}({struct_name} *block) {{}}")
            lines_to_write.append(f"void Rte_Write_{nvm_name.upper()}_{nvm_name.upper()}({struct_name} *block) {{}}")
        lines_to_write.append("")
        return "\n".join(lines_to_write)

    def _get_calibration_function_data(self, signal_name, signal_data):
        """"Get calibration function data.

        Args:
            signal_name (str): Name of the signal.
            signal_data (dict): Dictionary containing signal data.
        Returns:
            return_type, return_string (tuple): Tuple containing the return type and return string.
        """
        if isinstance(signal_data["width"], list):
            if signal_data["width"][0] == 1:
                dummy_variable = f"dummy[{signal_data['width'][1]}]"
            else:
                dummy_variable = f"dummy[{signal_data['width'][0]}][{signal_data['width'][1]}]"
            # MAPs get typedef:ed to structs and need special data type mapping in calibration.py
            if signal_name.startswith("m") and signal_name[-2:] not in ["_r", "_c"]:
                return_type = f'const {signal_data["autosar_type"]}*'
                return_string = (
                    f"const {signal_data['autosar_type']} {dummy_variable}; "
                    f"{return_type} dummyPtr = &dummy; "
                    "return dummyPtr;"
                )
            else:
                return_type = f'const {signal_data["type"]}*'
                return_string = (
                    f"{signal_data['type']} {dummy_variable}; "
                    f"{return_type} dummyPtr = &dummy; "
                    "return dummyPtr;"
                )
        else:
            return_type = f'{signal_data["type"]}'
            return_string = f"return ({return_type})0;"
        return return_type, return_string

    def _get_calibration_header_dummy(self):
        """Get calibration dummy header code."""
        if not self.calibration_variables:
            return ""

        lines_to_write = []
        swc_name = self.build_cfg.get_composition_config("softwareComponentName")
        for signal_name, signal_data in self.calibration_variables.items():
            return_type = self._get_calibration_function_data(signal_name, signal_data)[0]
            lines_to_write.append(f"{return_type} Rte_CData_{swc_name}_{signal_name}(void);")
        lines_to_write.append("")
        return "\n".join(lines_to_write)

    def _get_calibration_source_dummy(self):
        """Get calibration source dummy code."""
        if not self.calibration_variables:
            return ""

        lines_to_write = []
        swc_name = self.build_cfg.get_composition_config("softwareComponentName")
        for signal_name, signal_data in self.calibration_variables.items():
            return_type, return_string = self._get_calibration_function_data(signal_name, signal_data)
            lines_to_write.append(f"{return_type} Rte_CData_{swc_name}_{signal_name}(void) {{ {return_string} }}")
        lines_to_write.append("")
        return "\n".join(lines_to_write)

    def _get_core_header_dummy(self):
        """Get core header dummy code."""
        if not self.dtcs:
            return ""

        lines_to_write = []
        for dtc in self.dtcs:
            lines_to_write.append(f"UInt8 Rte_Call_Event_{dtc}_SetEventStatus(UInt8 EventStatus);")
        lines_to_write.append("")
        return "\n".join(lines_to_write)

    def _get_core_source_dummy(self):
        """Get core source dummy code."""
        if not self.dtcs:
            return ""

        lines_to_write = []
        for dtc in self.dtcs:
            lines_to_write.append(f"UInt8 Rte_Call_Event_{dtc}_SetEventStatus(UInt8 EventStatus) {{ return 0U; }}")
        lines_to_write.append("")
        return "\n".join(lines_to_write)

    def generate_rte_dummy(self):
        """Generate RTE dummy files."""
        src_code_dest_dir = self.build_cfg.get_src_code_dst_dir()
        mem_map_header_file = Path(src_code_dest_dir, self.mem_map_header_file_name + ".h")
        swc_header_file = Path(src_code_dest_dir, self.swc_header_file_name + ".h")
        type_header_file = Path(src_code_dest_dir, self.type_header_file_name + ".h")
        source_file = Path(src_code_dest_dir, self.source_file_name + ".c")
        with mem_map_header_file.open(mode="w", encoding="utf-8") as mem_map_header_fh:
            mem_map_header_fh.write(self._get_mem_map_header_content())
        with swc_header_file.open(mode="w", encoding="utf-8") as swc_header_fh:
            swc_header_fh.write(self._get_swc_header_content())
        with type_header_file.open(mode="w", encoding="utf-8") as type_header_fh:
            type_header_fh.write(self._get_type_header_header())
            type_header_fh.write(self._get_typedef_dummy())
            type_header_fh.write(self._get_core_header_dummy())
            type_header_fh.write(self._get_nvm_header_dummy())
            type_header_fh.write(self._get_calibration_header_dummy())
            type_header_fh.write(self._get_type_header_footer())
        with source_file.open(mode="w", encoding="utf-8") as source_fh:
            source_fh.write(self._get_source_header())
            source_fh.write(self._get_core_source_dummy())
            source_fh.write(self._get_nvm_source_dummy())
            source_fh.write(self._get_calibration_source_dummy())
