# Copyright (c) 2020-2024 by Fraunhofer Institute for Energy Economics
# and Energy System Technology (IEE), Kassel. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

import copy

import pandas as pd
from numpy import dtype
from pandapower.auxiliary import ADict, pandapowerNet

from pandapipes import __version__
from pandapipes import pandapipesNet

try:
    import pandaplan.core.pplog as logging
except ImportError:
    import logging

logger = logging.getLogger(__name__)


class MultiNet(ADict):
    """
    A 'MultiNet' is a frame for different pandapipes & pandapower nets and coupling controllers.

    Usually, a multinet is a multi energy net which one net per energy carrier. 
    The coupled simulation can be run with
    pandapipes.multinet.control.run_control_multinet.run_control()
    The nets are stored with a unique key in a dictionary in multinet['nets'].
    Controllers that connect to nets are stored in multinet['controller'].
    """

    def __init__(self, *args, **kwargs):
        """

        :param args: item of the ADict
        :type args: variable
        :param kwargs: item of the ADict with corresponding name
        :type kwargs: dict
        """
        super().__init__(*args, **kwargs)
        if isinstance(args[0], self.__class__):
            net = args[0]
            self.clear()
            self.update(**net.deepcopy())

    def deepcopy(self):
        return copy.deepcopy(self)

    def __repr__(self):  # pragma: no cover
        """
        defines the representation of the multinet in the console

        :return: representation
        :rtype: str
        """

        r = "This multi net includes following nets:"
        for cat in self.nets:
            if isinstance(self['nets'][cat], pandapowerNet):
                r += "\n   - %s (%s pandapowerNet)" % (cat, 1)
            elif isinstance(self['nets'][cat], pandapipesNet):
                r += "\n   - %s (%s pandapipesNet)" % (cat, 1)
            else:
                r += "\n   - %s (%s nets)" % (cat, len(self['nets'][cat]))

        par = []
        for tb in list(self.keys()):
            if isinstance(self[tb], pd.DataFrame) and len(self[tb]) > 0:
                par.append(tb)
            elif tb == 'std_type':
                par.append(tb)
        if par:
            r += "\nand the following parameter tables:"
            for tb in par:
                r += "\n   - %s (%s elements)" % (tb, len(self[tb]))
        return r


def get_default_multinet_structure():
    """
    Return the default structure of an empty multinet with categories and data types.

    :return: default structure of an empty multinet
    :rtype: dict
    """
    default_multinet_structure = {
        # structure data
        # f8, u4 etc. are probably referencing numba or numpy data types
        "name": "",
        "nets": dict(),
        "version": __version__,
        "controller": [('object', dtype(object)),
                       ('in_service', "bool"),
                       ('order', "float64"),
                       ('level', dtype(object)),
                       ('initial_run', "bool"),
                       ("recycle", "bool")]}
    return default_multinet_structure
