#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# SPDX-License-Identifier: BSD-3-Clause
# gnpy.core.equipment: functionality for specifying equipment
# Copyright (C) 2025 Telecom Infra Project and GNPy contributors
# see AUTHORS.rst for a list of contributors

"""
gnpy.core.equipment
===================

This module contains functionality for specifying equipment.
"""
from collections import defaultdict
from functools import reduce
from typing import List

from gnpy.core.exceptions import EquipmentConfigError, ConfigurationError


def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=False):
    """return the trx and SI parameters from eqpt_config for a given type_variety and mode (ie format)

    if the type or mode do no match an existing transceiver in the library, then the function
    raises an error if error_message is True else returns a default mode based on equipment['SI']['default']
    If trx_mode is None (but type is valid), it returns an undetermined mode whatever the error message:
    this is a special case for automatic mode selection.
    """
    trx_params = {}
    default_si_data = equipment['SI']['default']
    # default transponder characteristics
    # mainly used with transmission_main_example.py
    default_trx_params = {
        'f_min': default_si_data.f_min,
        'f_max': default_si_data.f_max,
        'baud_rate': default_si_data.baud_rate,
        'spacing': default_si_data.spacing,
        'OSNR': None,
        'penalties': {},
        'bit_rate': None,
        'cost': None,
        'roll_off': default_si_data.roll_off,
        'tx_osnr': default_si_data.tx_osnr,
        'min_spacing': None,
        'equalization_offset_db': 0
    }
    # Undetermined transponder characteristics
    # mainly used with path_request_run.py for the automatic mode computation case
    undetermined_trx_params = {
        "format": "undetermined",
        "baud_rate": None,
        "OSNR": None,
        "penalties": None,
        "bit_rate": None,
        "roll_off": None,
        "tx_osnr": None,
        "min_spacing": None,
        "cost": None,
        "equalization_offset_db": 0
    }

    trxs = equipment['Transceiver']
    if trx_type_variety in trxs:
        modes = {mode['format']: mode for mode in trxs[trx_type_variety].mode}
        trx_frequencies = {'f_min': trxs[trx_type_variety].frequency['min'],
                           'f_max': trxs[trx_type_variety].frequency['max']}
        if trx_mode in modes:
            # if called from transmission_main.py, trx_mode is ''
            trx_params = {**modes[trx_mode], **trx_frequencies}
            if trx_params['baud_rate'] > trx_params['min_spacing']:
                # sanity check: baudrate must be smaller than min spacing
                raise EquipmentConfigError(f'Inconsistency in equipment library:\n Transponder "{trx_type_variety}" '
                                           + f'mode "{trx_params["format"]}" has baud rate '
                                           + f'{trx_params["baud_rate"] * 1e-9:.2f} GHz greater than min_spacing '
                                           + f'{trx_params["min_spacing"] * 1e-9:.2f}.')
            trx_params['equalization_offset_db'] = trx_params.get('equalization_offset_db', 0)
            return trx_params
        if trx_mode is None:
            # if called from path_requests_run.py, trx_mode is filled with None when not specified by user
            trx_params = {**undetermined_trx_params, **trx_frequencies}
            return trx_params
    if trx_type_variety in trxs and error_message:
        raise EquipmentConfigError(f'Could not find transponder "{trx_type_variety}" with mode "{trx_mode}" '
                                   + 'in equipment library')
    if error_message:
        raise EquipmentConfigError(f'Could not find transponder "{trx_type_variety}" in equipment library')

    trx_params = {**default_trx_params}
    return trx_params


def find_type_variety(amps: List[str], equipment: dict) -> List[str]:
    """Returns the multiband type_variety associated with a list of single band type_varieties
    Args:
    amps (List[str]): A list of single band type_varieties.
    equipment (dict): A dictionary containing equipment information.

    Returns:
    str: an amplifier type variety
    """
    listes = find_type_varieties(amps, equipment)

    _found_type = list(reduce(lambda x, y: set(x) & set(y), listes))
    # Given a list of single band amplifiers, find the multiband amplifier whose multi_band group
    # matches. For example, if amps list contains ["a1_LBAND", "a2_CBAND"], with a1.multi_band = [a1_LBAND, a1_CBAND]
    # and a2.multi_band = [a1_LBAND, a2_CBAND], then:
    # possible_type_varieties = {"a1_LBAND": ["a1", "a2"], "a2_CBAND": ["a2"]}
    # listes = [["a1", "a2"],  ["a2"]]
    # and _found_type = [a2]
    if not _found_type:
        msg = f'{amps} amps do not belong to the same amp type {listes}'
        raise ConfigurationError(msg)
    return _found_type


def find_type_varieties(amps: List[str], equipment: dict) -> List[List[str]]:
    """Returns the multiband list of type_varieties associated with a list of single band type_varieties
    Args:
    amps (List[str]): A list of single band type_varieties.
    equipment (dict): A dictionary containing equipment information.

    Returns:
    List[List[str]]: A list of lists containing the multiband type_varieties
    associated with each single band type_variety.
    """
    possible_type_varieties = defaultdict(list)
    for amp_name, amp in equipment['Edfa'].items():
        if amp.multi_band is not None:
            for elem in amp.multi_band:
                # possible_type_varieties stores the list of multiband amp names that list this elem as
                # a possible amplifier of the multiband group. For example, if "std_medium_gain_multiband"
                # and "std_medium_gain_multiband_new" contain "std_medium_gain_C" in their "multi_band" list, then:
                # possible_type_varieties["std_medium_gain_C"] =
                # ["std_medium_gain_multiband", "std_medium_gain_multiband_new"]
                possible_type_varieties[elem].append(amp_name)
    return [possible_type_varieties[a] for a in amps]
