#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2020 FABRIC Testbed
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
#
# Author: Komal Thareja (kthare10@renci.org)
"""
Base class for all sliver types
"""
from typing import Any, List
from abc import ABC, abstractmethod

from fim.slivers.capacities_labels import Capacities, Labels


class BaseSliver(ABC):
    """Base class for all sliver types"""

    @abstractmethod
    def __init__(self):
        self.resource_type = None
        self.resource_name = None
        self.resource_model = None
        self.capacities = None
        self.labels = None
        self.node_id = None
        self.details = None

    def set_resource_type(self, resource_type):
        """
        Could be NodeType, ComponentType, InterfaceType etc
        :param resource_type:
        :return:
        """
        self.resource_type = resource_type

    def get_resource_type(self):
        return self.resource_type

    def get_type(self):
        """
        Aliased to get_resource_type
        :return:
        """
        return self.get_resource_type()

    def set_resource_name(self, resource_name: str):
        self.resource_name = resource_name

    def set_name(self, resource_name: str):
        self.resource_name = resource_name

    def get_resource_name(self):
        return self.resource_name

    def get_name(self):
        return self.resource_name

    def set_resource_model(self, resource_model: str):
        self.resource_model = resource_model

    def set_model(self, resource_model: str):
        """
        Aliased to set_resource_model
        :param resource_model:
        :return:
        """
        self.resource_model = resource_model

    def get_resource_model(self):
        return self.resource_model

    def get_model(self):
        return self.resource_model

    def set_capacities(self, cap: Capacities) -> None:
        self.capacities = cap

    def get_capacities(self) -> Capacities:
        return self.capacities

    def set_labels(self, lab: Labels) -> None:
        self.labels = lab

    def get_labels(self) -> Labels:
        return self.labels

    def set_details(self, desc: str) -> None:
        self.details = desc

    def get_details(self) -> str:
        return self.details

    def set_properties(self, **kwargs):
        """
        Lets you set multiple properties exposed via setter methods
        :param kwargs:
        :return:
        """
        # set any property on a sliver that has a setter
        for k, v in kwargs.items():
            try:
                # we can set anything the sliver model has a setter for
                self.__getattribute__('set_' + k)(v)
            except AttributeError:
                raise RuntimeError(f'Unable to set property {k} on the sliver - no such property available')

    @classmethod
    def list_properties(cls):
        """
        List properties available for setting/getting on a sliver (those exposing
        setters)
        :return:
        """
        ret = list()
        exclude_set = {"set_property", "set_properties"}
        for k in dir(cls):
            if k.startswith('set_') and k not in exclude_set:
                ret.append(k[4:])
        return ret

    def set_property(self, prop_name: str, prop_val: Any):
        """
        Lets you set any property exposed via a setter
        :param prop_name:
        :param prop_val:
        :return:
        """
        try:
            return self.__getattribute__('set_' + prop_name)(prop_val)
        except AttributeError:
            raise RuntimeError(f'Unable to set property {prop_name} of the sliver - no such property available')

    def get_property(self, prop_name: str):
        """
        Lets you get a property that is exposed via getter method
        :param prop_name:
        :return:
        """
        try:
            return self.__getattribute__('get_' + prop_name)()
        except AttributeError:
            raise RuntimeError(f'Unable to get property {prop_name} of the sliver - no such property available')

    def __repr__(self):
        exclude_set = {"get_property"}
        print_set = list()
        for k in dir(self):
            if k.startswith('get_') and k not in exclude_set:
                print_set.append(k[4:])
        print_set.sort()
        print_vals = dict()
        for p in print_set:
            print_vals[p] = self.get_property(p)
        return str(print_vals)

    def __str__(self):
        return self.__repr__()


class BaseSliverWithDelegation(BaseSliver):
    """
    Intended to be used in advertisements
    """
    def __init__(self):
        super().__init__()
        self.capacity_delegations = None
        self.label_delegations = None

    def set_capacity_delegations(self, cap_del) -> None:
        self.capacity_delegations = cap_del

    def set_label_delegations(self, lab_del) -> None:
        self.label_delegations = lab_del
