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

# -*- coding: utf-8 -*-
"""Module containing classes for VCC - Supplier debug interface.

TODO: Check if all IO parameters in SPMEMSInterfaceRequirements.xlsx defined as safety variables
 automatically have debug switches.
"""

import os
import re

from powertrain_build import build_defs
from powertrain_build.types import byte_size_string, get_bitmask, a2l_range
from powertrain_build.a2l import A2l
from powertrain_build.problem_logger import ProblemLogger


class ExtDbg(ProblemLogger):
    """Class for generating c-files.

    These declares all debug parameters in the VCC - Supplier interface.
    """

    __data_type_size = {'Float32': '4', 'UInt32': '4', 'Int32': '4',
                        'UInt16': '2', 'Int16': '2',
                        'UInt8': '1', 'Int8': '1', 'Bool': '1'}

    def __init__(self, variable_dict, prj_cfg, unit_cfg, integrity_level=build_defs.CVC_ASIL_QM):
        """Constructor.

        Args:
            variable_dict (dict): dictionary with signal information

        Variable dict shall have the following format and is generated by the
        :doc:`CsvSignalInterfaces <signal_interfaces>` class::

            {
                'CAN-Input': {
                    'signal1': {
                        'IOType': 'd',
                        'description': 'Some description',
                        'init': 0,
                        'max': 1,
                        'min': 0,
                        'type': 'UInt8',
                        'unit': '-'
                    },
                    'signal2': {
                        ...
                    }
                },
                'CAN-Output': {
                    'signal3': {
                        ...
                    }
                },
                'xxx-Input': ...,
                'xxx-Output': ...
            }

        The optional keyword arguments may override which code section
        directives to use.
        """
        super().__init__()
        self.set_integrity_level(integrity_level)
        self._prj_cfg = prj_cfg
        self._unit_cfg = unit_cfg
        self._use_volatile_globals = prj_cfg.get_use_volatile_globals()
        self.dbg_dict = self._restruct_data(variable_dict)

    @staticmethod
    def _restruct_data(variable_dict):
        """Restructure input variables per data-type.

        This will be used for declaring the variables and generating the A2L-file.
        """
        data = {'outputs': {}, 'inputs': {}}
        for inp in variable_dict.keys():
            if re.match(r'.*Output$', inp) is not None:
                iotype = 'outputs'
            else:
                iotype = 'inputs'
            for var, var_data in variable_dict[inp].items():
                data_type_size = byte_size_string(var_data['type'])
                data[iotype].setdefault(data_type_size, {})[var] = var_data
        return data

    def set_integrity_level(self, integrity_level):
        """Set integrity level of code generation.

        Args:
            integrity_level (str): integrity level of the unit from 'A' to 'D' or 'QM'
        """
        self._disp_start = integrity_level['DISP']['START']
        self._disp_end = integrity_level['DISP']['END']
        self._cal_start = integrity_level['CAL']['START']
        self._cal_end = integrity_level['CAL']['END']
        self._code_start = integrity_level['CODE']['START']
        self._code_end = integrity_level['CODE']['END']

    def _a2l_dict(self, var_dict, function):
        """Generate dict defining parameters for a2l-generation."""
        def _range(data):
            range_a2l = a2l_range(data['type'])
            if data['min'] == '-':
                a2l_min = range_a2l[0]
            else:
                a2l_min = data['min']
            if data['max'] == '-':
                a2l_max = range_a2l[1]
            else:
                a2l_max = data['max']
            return a2l_min, a2l_max

        res = {'vars': {},
               'function': function}
        for var, data in self._type_order_iterator(var_dict):
            resv = res['vars']
            a2l_min, a2l_max = _range(data)
            a2l_data = {
                'bitmask': get_bitmask(data['type']),
                'description': data['description'],
                'lsb': '2^0',
                'max': a2l_max,
                'min': a2l_min,
                'offset': '0',
                'unit': '',
                'x_axis': None,
                'y_axis': None
            }
            var_db = f'c{var[1:]}_db'
            var_sw = self._var_name_to_dbgsw_name(var)
            resv.setdefault(var_db, {})['a2l_data'] = a2l_data
            a2l_data = {
                'bitmask': get_bitmask(data['type']),
                'description': f'debug switch for {var_db} (1=bdsw act)',
                'lsb': '2^0',
                'max': '1',
                'min': '0',
                'offset': '0',
                'unit': '',
                'x_axis': None,
                'y_axis': None
            }
            resv.setdefault(var_sw, {})['a2l_data'] = a2l_data
            resv[var_db]['array'] = []
            resv[var_sw]['array'] = []
            resv[var_db]['function'] = [function]
            resv[var_sw]['function'] = [function]
            resv[var_db]['var'] = {'cvc_type': 'CVC_CAL',
                                   'type': data['type'],
                                   'var': var}
            resv[var_sw]['var'] = {'cvc_type': 'CVC_CAL',
                                   'type': 'Bool',
                                   'var': var}
        return res

    @staticmethod
    def _var_name_to_dbgsw_name(name):
        """Convert a variable name to a debug switch name."""
        # the below conversion would generate a correct name for the
        # debug switch, however the current build systemt generates one
        # like the currently returned name
        # return re.sub(r'\w(\w+?)_\w+?_(\w+)', r'c\1_B_\2_sw', name)
        return re.sub(r'\w(\w+)', r'c\1_sw', name)

    @staticmethod
    def _type_order_iterator(var_items):
        """Get iterator over all variables.

        In data type size order, and then in alphabetical order.
        """
        for _, typ_data in var_items.items():
            for var in sorted(typ_data.keys()):
                yield (var, typ_data[var])

    def _gen_dbg_c_file(self, data, filename):
        """Generate debug c-files.

        These define all the debug labels for
        the supplier input and output signals.
        """
        with open(filename, 'w', encoding="utf-8") as fh_c:
            fh_c.write(self._unit_cfg.base_types_headers)
            fh_c.write('#define CVC_DISP\n')

            # define extrern variable references
            fh_c.write(f'#include "{self._disp_start}"\n')
            for var, var_data in self._type_order_iterator(data):
                fh_c.write(f"extern CVC_DISP {var_data['type']} {var};\n")
            fh_c.write(f'#include "{self._disp_end}"\n\n')

            # define debug calibration constants
            fh_c.write(f'#include "{self._cal_start}"\n')
            fh_c.write('\n/* Debug values */\n\n')
            for var, var_data in self._type_order_iterator(data):
                initial_value = var_data['min'] if var_data['min'] != "-" and float(var_data['min']) > 0 else "0"
                if self._use_volatile_globals:
                    fh_c.write(f"volatile {var_data['type']} c{var[1:]}_db = {initial_value};\n")
                else:
                    fh_c.write(f"{var_data['type']} c{var[1:]}_db = {initial_value};\n")

            fh_c.write('\n/* Debug switches */\n\n')
            for var, var_data in self._type_order_iterator(data):
                sw_name = self._var_name_to_dbgsw_name(var)
                if self._use_volatile_globals:
                    fh_c.write(f"volatile Bool {sw_name} = 0;\n")
                else:
                    fh_c.write(f"Bool {sw_name} = 0;\n")
            fh_c.write(f'#include "{self._cal_end}"\n\n')

            # set the variable to the debug calibration constants
            fh_c.write('/***********************/\n')
            fh_c.write('/* debug functionality */\n')
            fh_c.write('/***********************/\n\n')
            _, fname_tmp = os.path.split(filename)
            func_name = fname_tmp.split('.')[-2]
            fh_c.write(f'#include "{self._code_start}"\n')
            fh_c.write(f'void {func_name}(void) {{\n')
            for var, var_data in self._type_order_iterator(data):
                sw_name = self._var_name_to_dbgsw_name(var)
                fh_c.write(f'  if ({sw_name}) {{\n')
                fh_c.write(f'    {var} = c{var[1:]}_db;\n  }}\n')
            fh_c.write(f'}}\n#include "{self._code_end}"\n\n')
            self.info('Generated %s', filename)

    def gen_dbg_files(self, file_path_inputs, file_path_outputs):
        """Generate the c-files and A2L-files.

        These declares all the supplier interface debug parameters and functions.

        Args:
            file_path_inputs (str): path to the debug inputs c-file.
            file_path_outputs (str): path to the debug outputs c-file.
        """
        _, file_name_inputs = os.path.split(file_path_inputs)
        _, file_name_outputs = os.path.split(file_path_outputs)

        if not self.dbg_dict['inputs']:
            self.info(f"Skipping {file_name_inputs} as there were no corresponding vars.")
        else:
            self._gen_dbg_c_file(self.dbg_dict['inputs'], file_path_inputs + '.c')
            a2l_dict_in = self._a2l_dict(self.dbg_dict['inputs'], file_name_inputs)
            a2l = A2l(a2l_dict_in, self._prj_cfg)
            a2l.gen_a2l(file_path_inputs + '.a2l')

        if not self.dbg_dict['outputs']:
            self.info(f"Skipping {file_name_outputs} as there were no corresponding vars.")
        else:
            self._gen_dbg_c_file(self.dbg_dict['outputs'], file_path_outputs + '.c')
            a2l_dict_out = self._a2l_dict(self.dbg_dict['outputs'], file_name_outputs)
            a2l = A2l(a2l_dict_out, self._prj_cfg)
            a2l.gen_a2l(file_path_outputs + '.a2l')
