# Copyright (c) 2014  Kontron Europe GmbH
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA

from .errors import DecodingError, EncodingError
from .msgs import create_request_by_name
from .msgs import picmg
from .utils import check_completion_code
from .state import State

from .msgs.picmg import \
        FRU_CONTROL_COLD_RESET, FRU_CONTROL_WARM_RESET, \
        FRU_CONTROL_GRACEFUL_REBOOT, FRU_CONTROL_ISSUE_DIAGNOSTIC_INTERRUPT, \
        FRU_ACTIVATION_FRU_ACTIVATE, FRU_ACTIVATION_FRU_DEACTIVATE


class Picmg(object):
    def get_picmg_properties(self):
        return self.send_message_with_name('GetPicmgProperties')

    def fru_control(self, fru_id, option):
        rsp = self.send_message_with_name('FruControl', fru_id=fru_id,
                                          option=option)
        return rsp.rsp_data

    def fru_control_cold_reset(self, fru_id=0):
        self.fru_control(fru_id, FRU_CONTROL_COLD_RESET)

    def fru_control_warm_reset(self, fru_id=0):
        self.fru_control(fru_id, FRU_CONTROL_WARM_RESET)

    def fru_control_graceful_reboot(self, fru_id=0):
        self.fru_control(fru_id, FRU_CONTROL_GRACEFUL_REBOOT)

    def fru_control_diagnostic_interrupt(self, fru_id=0):
        return self.fru_control(fru_id, FRU_CONTROL_ISSUE_DIAGNOSTIC_INTERRUPT)

    def get_power_level(self, fru_id, power_type):
        rsp = self.send_message_with_name('GetPowerLevel',
                                          fru_id=fru_id,
                                          power_type=power_type)
        return PowerLevel(rsp)

    def get_fan_speed_properties(self, fru_id):
        rsp = self.send_message_with_name('GetFanSpeedProperties',
                                          fru_id=fru_id)
        return FanSpeedProperties(rsp)

    def set_fan_level(self, fru_id, fan_level):
        self.send_message_with_name('SetFanLevel',
                                    fru_id=fru_id,
                                    fan_level=fan_level)

    def get_fan_level(self, fru_id):
        rsp = self.send_message_with_name('GetFanLevel', fru_id=fru_id)
        local_control_fan_level = None
        if rsp.data:
            local_control_fan_level = rsp.data[0]
        return (rsp.override_fan_level, local_control_fan_level)

    def get_led_state(self, fru_id, led_id):
        rsp = self.send_message_with_name('GetFruLedState',
                                          fru_id=fru_id,
                                          led_id=led_id)
        return LedState(rsp)

    def set_led_state(self, led):
        req = create_request_by_name('SetFruLedState')
        req = led.to_request(req)
        rsp = self.send_message(req)
        check_completion_code(rsp.completion_code)

    def _set_fru_activation(self, fru_id, control):
        self.send_message_with_name('SetFruActivation',
                                    fru_id=fru_id,
                                    control=control)

    def set_fru_activation(self, fru_id):
        self._set_fru_activation(fru_id, FRU_ACTIVATION_FRU_ACTIVATE)

    def set_fru_deactivation(self, fru_id):
        self._set_fru_activation(fru_id, FRU_ACTIVATION_FRU_DEACTIVATE)

    ACTIVATION_LOCK_SET = 0
    ACTIVATION_LOCK_CLEAR = 1
    DEACTIVATION_LOCK_SET = 2
    DEACTIVATION_LOCK_CLEAR = 3

    def set_fru_activation_policy(self, fru_id, ctrl):
        req = create_request_by_name('SetFruActivationPolicy')
        req.fru_id = fru_id

        if ctrl == self.ACTIVATION_LOCK_SET:
            req.mask.activation_locked = 1
            req.set.activation_locked = 1
        elif ctrl == self.ACTIVATION_LOCK_CLEAR:
            req.mask.activation_locked = 1
            req.set.activation_locked = 0
        elif ctrl == self.DEACTIVATION_LOCK_SET:
            req.mask.deactivation_locked = 1
            req.set.deactivation_locked = 1
        elif ctrl == self.DEACTIVATION_LOCK_CLEAR:
            req.mask.deactivation_locked = 1
            req.set.deactivation_locked = 0

        rsp = self.send_message(req)
        check_completion_code(rsp.completion_code)

    def set_fru_activation_lock(self, fru_id):
        self.set_fru_activation_policy(fru_id, self.ACTIVATION_LOCK_SET)

    def clear_fru_activation_lock(self, fru_id):
        self.set_fru_activation_policy(fru_id, self.ACTIVATION_LOCK_CLEAR)

    def set_fru_deactivation_lock(self, fru_id):
        self.set_fru_activation_policy(fru_id, self.DEACTIVATION_LOCK_SET)

    def clear_fru_deactivation_lock(self, fru_id):
        self.set_fru_activation_policy(fru_id, self.DEACTIVATION_LOCK_CLEAR)

    def set_port_state(self, link_descr, state):
        req = create_request_by_name('SetPortState')
        req.link_info.channel = link_descr.channel
        req.link_info.interface = link_descr.interface
        req.link_info.port_0 = (link_descr.link_flags >> 0) & 1
        req.link_info.port_1 = (link_descr.link_flags >> 1) & 1
        req.link_info.port_2 = (link_descr.link_flags >> 2) & 1
        req.link_info.port_3 = (link_descr.link_flags >> 3) & 1
        req.link_info.type = link_descr.type
        req.link_info.sig_class = link_descr.sig_class
        req.link_info.type_extension = link_descr.extension
        req.link_info.grouping_id = link_descr.grouping_id
        req.state = state
        rsp = self.send_message(req)
        check_completion_code(rsp.completion_code)

    def get_port_state(self, channel_number, channel_interface):
        req = create_request_by_name('GetPortState')
        req.channel.number = channel_number
        req.channel.interface = channel_interface
        rsp = self.send_message(req)
        check_completion_code(rsp.completion_code)

        if len(rsp.data) > 4:
            link = LinkDescriptor()
            link.channel = rsp.data[0] & 0x3F
            link.interface = rsp.data[0] >> 6 & 0x3
            link.link_flags = rsp.data[1] & 0xf
            link.type = rsp.data[1] >> 4 & 0xf
            link.sig_class = rsp.data[2] & 0xf
            link.extension = rsp.data[2] >> 4 & 0xf
            link.grouping_id = rsp.data[3]
            state = rsp.data[4]

        return (link, state)

    def get_pm_global_status(self):
        rsp = self.send_message_with_name('GetPowerChannelStatus',
                                          starting_power_channel_number=1,
                                          power_channel_count=1)
        return GlobalStatus(rsp)

    def get_power_channel_status(self, start):
        rsp = self.send_message_with_name('GetPowerChannelStatus',
                                          starting_power_channel_number=start,
                                          power_channel_count=1)
        return PowerChannelStatus(rsp)

    def send_channel_power(self, channel, enable, current_limit, primary_pm=1, backup_pm=0):
        rsp = self.send_message_with_name('SendPowerChannelControl',
                                          channel=channel,
                                          control=5 if enable else 4,
                                          current_limit=int(current_limit * 10),
                                          primary_pm=primary_pm,
                                          backup_pm=backup_pm
                                          )
        return rsp

    def send_pm_heartbeat(self):
        rsp = self.send_message_with_name('SendPmHeartbeat')
        return rsp

    def set_signaling_class(self, interface, channel, signaling_class):
        req = create_request_by_name('SetSignalingClass')
        req.channel_info.channel_number = channel
        req.channel_info.interface = interface
        req.channel_signaling.class_capability = signaling_class
        rsp = self.send_message(req)
        check_completion_code(rsp.completion_code)

    def get_signaling_class(self, interface, channel):
        req = create_request_by_name('GetSignalingClass')
        req.channel_info.channel_number = channel
        req.channel_info.interface = interface
        rsp = self.send_message(req)
        check_completion_code(rsp.completion_code)
        return rsp.channel_signaling.class_capability


class LinkDescriptor(State):
    # TODO dont duplicate exports, import them instead
    INTERFACE_BASE = picmg.LINK_INTERFACE_BASE
    INTERFACE_FABRIC = picmg.LINK_INTERFACE_FABRIC
    INTERFACE_UPDATE_CHANNEL = picmg.LINK_INTERFACE_UPDATE_CHANNEL

    TYPE_BASE = picmg.LINK_TYPE_BASE
    TYPE_ETHERNET_FABRIC = picmg.LINK_TYPE_ETHERNET_FABRIC
    TYPE_INFINIBAND_FABRIC = picmg.LINK_TYPE_INFINIBAND_FABRIC
    TYPE_STARFABRIC_FABRIC = picmg.LINK_TYPE_STARFABRIC_FABRIC
    TYPE_PCIEXPRESS_FABRIC = picmg.LINK_TYPE_PCIEXPRESS_FABRIC
    TYPE_OEM0 = picmg.LINK_TYPE_OEM0
    TYPE_OEM1 = picmg.LINK_TYPE_OEM1
    TYPE_OEM2 = picmg.LINK_TYPE_OEM2
    TYPE_OEM3 = picmg.LINK_TYPE_OEM3

    TYPE_EXT_BASE0 = picmg.LINK_TYPE_EXT_BASE0
    TYPE_EXT_BASE1 = picmg.LINK_TYPE_EXT_BASE1

    SIGNALING_CLASS_BASIC = picmg.LINK_SIGNALING_CLASS_BASIC
    SIGNALING_CLASS_10_3125_GBD = picmg.LINK_SIGNALING_CLASS_10_3125_GBD

    TYPE_EXT_ETHERNET_FIX1000_BX = picmg.LINK_TYPE_EXT_ETHERNET_FIX1000_BX
    TYPE_EXT_ETHERNET_FIX10G_BX4 = picmg.LINK_TYPE_EXT_ETHERNET_FIX10G_BX4
    TYPE_EXT_ETHERNET_FCPI = picmg.LINK_TYPE_EXT_ETHERNET_FCPI
    TYPE_EXT_ETHERNET_FIX1000_KX = picmg.LINK_TYPE_EXT_ETHERNET_FIX1000_KX
    TYPE_EXT_ETHERNET_FIX10G_KX4 = picmg.LINK_TYPE_EXT_ETHERNET_FIX10G_KX4

    TYPE_EXT_ETHERNET_FIX10G_KR = picmg.LINK_TYPE_EXT_ETHERNET_FIX10G_KR
    TYPE_EXT_ETHERNET_FIX40G_KR4 = picmg.LINK_TYPE_EXT_ETHERNET_FIX40G_KR4

    TYPE_EXT_OEM_LINK_TYPE_EXT_0 = picmg.LINK_TYPE_EXT_OEM_LINK_TYPE_EXT_0

    FLAGS_LANE0 = picmg.LINK_FLAGS_LANE0
    FLAGS_LANE0123 = picmg.LINK_FLAGS_LANE0123

    STATE_DISABLE = picmg.LINK_STATE_DISABLE
    STATE_ENABLE = picmg.LINK_STATE_ENABLE

    __properties__ = [
        # (property, description)
        ('channel', ''),
        ('interface', ''),
        ('link_flags', ''),
        ('type', ''),
        ('sig_class', ''),
        ('extension', ''),
        ('grouping_id', ''),
    ]

    INTERFACE_DESCR_STRING = [
        # Interface, 'STRING'
        (INTERFACE_BASE, 'Base'),
        (INTERFACE_FABRIC, 'Fabric'),
        (INTERFACE_UPDATE_CHANNEL, 'Update Channel'),
    ]

    def get_interface_string(self, interf):
        for desc in self.INTERFACE_DESCR_STRING:
            if desc[0] == interf:
                return desc[1]
        return 'unknown'

    LINK_TYPE_DESCR_STRING = [
        # Type, Extension, class, 'STRING'
        (TYPE_BASE,
         TYPE_EXT_BASE0,
         SIGNALING_CLASS_BASIC,
         '10/100/1000 BASE-T'),
        (TYPE_BASE,
         TYPE_EXT_BASE1,
         SIGNALING_CLASS_BASIC,
         '10/100 BASE-T ShMC Cross-connect'),
        (TYPE_ETHERNET_FABRIC,
         TYPE_EXT_ETHERNET_FIX1000_BX,
         SIGNALING_CLASS_BASIC,
         'Fixed 1000BASE-BX'),
        (TYPE_ETHERNET_FABRIC,
         TYPE_EXT_ETHERNET_FIX10G_BX4,
         SIGNALING_CLASS_BASIC,
         'Fixed 10GBASE-BX4 (XAUI)'),
        (TYPE_ETHERNET_FABRIC,
         TYPE_EXT_ETHERNET_FCPI,
         SIGNALING_CLASS_BASIC,
         'FC-PI'),
        (TYPE_ETHERNET_FABRIC,
         TYPE_EXT_ETHERNET_FIX1000_KX,
         SIGNALING_CLASS_BASIC,
         'Fixed 1000BASE-KX'),
        (TYPE_ETHERNET_FABRIC,
         TYPE_EXT_ETHERNET_FIX10G_KX4,
         SIGNALING_CLASS_BASIC,
         'Fixed 10GBASE-KX4'),
        (TYPE_ETHERNET_FABRIC,
         TYPE_EXT_ETHERNET_FIX10G_KR,
         SIGNALING_CLASS_10_3125_GBD,
         'Fixed 10GBASE-KR'),
        (TYPE_ETHERNET_FABRIC,
         TYPE_EXT_ETHERNET_FIX40G_KR4,
         SIGNALING_CLASS_10_3125_GBD,
         'Fixed 40GBASE-KR4'),
    ]

    def get_link_type_string(self, link_type, ext, cls=0):
        for desc in self.LINK_TYPE_DESCR_STRING:
            if desc[0] == link_type and desc[1] == ext and desc[2] == cls:
                return desc[3]
        return 'unknown'


class PowerLevel(State):

    def _from_response(self, rsp):
        self.dynamic_power_configuration = \
                rsp.properties.dynamic_power_configuration
        self.power_level = rsp.properties.power_level
        self.delay_to_stable = rsp.delay_to_stable_power
        self.power_mulitplier = rsp.power_multiplier
        self.power_levels = rsp.power_draw


class FanSpeedProperties(State):

    def _from_response(self, rsp):
        self.minimum_speed_level = rsp.minimum_speed_level
        self.maximum_speed_level = rsp.maximum_speed_level
        self.normal_operation_level = rsp.normal_operation_level
        self.local_control_supported = rsp.properties.local_control_supported


class LedState(State):
    COLOR_BLUE = picmg.LED_COLOR_BLUE
    COLOR_RED = picmg.LED_COLOR_RED
    COLOR_GREEN = picmg.LED_COLOR_GREEN
    COLOR_AMBER = picmg.LED_COLOR_AMBER
    COLOR_ORANGE = picmg.LED_COLOR_ORANGE
    COLOR_WHITE = picmg.LED_COLOR_WHITE

    FUNCTION_OFF = 1
    FUNCTION_BLINKING = 2
    FUNCTION_ON = 3
    FUNCTION_LAMP_TEST = 4

    __properties__ = [
        # (property, description)
        ('fru_id', ''),
        ('led_id', ''),
        ('local_state_available', ''),
        ('override_enabled', ''),
        ('lamp_test_enabled', ''),
        ('local_function', ''),
        ('local_off_duration', ''),
        ('local_on_duration', ''),
        ('local_color', ''),
        ('override_function', ''),
        ('override_off_duration', ''),
        ('override_on_duration', ''),
        ('override_color', ''),
        ('lamp_test_duration', ''),
    ]

    def __init__(self, rsp=None, fru_id=None, led_id=None, color=None,
                 function=None):
        super(LedState, self).__init__(rsp)
        if fru_id is not None:
            self.fru_id = fru_id
        if led_id is not None:
            self.led_id = led_id
        if color is not None:
            self.override_color = color
        if function is not None:
            self.override_function = function

    def __str__(self):
        string = '[flags '
        string += self.local_state_available and ' LOCAL_STATE' or ''
        string += self.override_enabled and ' OVR_EN' or ''
        string += self.lamp_test_enabled and ' LAMP_TEST_EN' or ''
        if not self.local_state_available and not self.override_enabled \
                and not self.lamp_test_enabled:
            string += ' NONE'
        if self.local_state_available:
            string += ' local_function %s local_color %s' % (
                self.local_function, self.local_color)
        if self.override_enabled:
            string += ' override_function %s override_color %s' % (
                self.override_function, self.override_color)
        string += ']'
        return string

    def _from_response(self, res):
        self.local_state_available = bool(res.led_states.local_avail)
        self.override_enabled = bool(res.led_states.override_en)
        self.lamp_test_enabled = bool(res.led_states.lamp_test_en)

        if res.local_function == picmg.LED_FUNCTION_OFF:
            self.local_function = self.FUNCTION_OFF
        elif res.local_function == picmg.LED_FUNCTION_ON:
            self.local_function = self.FUNCTION_ON
        elif res.local_function in picmg.LED_FUNCTION_BLINKING_RANGE:
            self.local_function = self.FUNCTION_BLINKING
            self.local_off_duration = res.local_function * 10
            if res.local_on_duration not in picmg.LED_FUNCTION_BLINKING_RANGE:
                raise DecodingError()
            self.local_on_duration = res.local_on_duration * 10
        else:
            raise DecodingError()

        self.local_color = res.local_color

        if self.override_enabled:
            if res.override_function == picmg.LED_FUNCTION_OFF:
                self.override_function = self.FUNCTION_OFF
            elif res.override_function == picmg.LED_FUNCTION_ON:
                self.override_function = self.FUNCTION_ON
            elif res.override_function in picmg.LED_FUNCTION_BLINKING_RANGE:
                self.override_function = self.FUNCTION_BLINKING
                self.override_off_duration = res.local_function * 10
            else:
                raise DecodingError()

            self.override_off_duration = res.override_on_duration * 10
            self.override_color = res.override_color

        if self.lamp_test_enabled:
            self.lamp_test_duration = res.lamp_test_duration * 100

    def to_request(self, req):
        req.fru_id = self.fru_id
        req.led_id = self.led_id
        req.color = self.override_color

        if self.override_function == self.FUNCTION_ON:
            req.led_function = picmg.LED_FUNCTION_ON
            req.on_duration = 0
        elif self.override_function == self.FUNCTION_OFF:
            req.led_function = picmg.LED_FUNCTION_OFF
            req.on_duration = 0
        elif self.override_function == self.FUNCTION_BLINKING:
            if self.override_off_duration not in \
                    picmg.LED_FUNCTION_BLINKING_RANGE:
                raise EncodingError()
            req.led_function = self.override_off_duration
            req.on_duration = self.override_on_duration
        elif self.override_function == self.FUNCTION_LAMP_TEST:
            req.led_function = picmg.LED_FUNCTION_LAMP_TEST
            req.on_duration = self.lamp_test_duration
        else:
            raise AssertionError()

        return req


class GlobalStatus(State):
    __properties__ = [
        # (property, description)
        ('role', ''),
        ('management_power_good', ''),
        ('payload_power_good', ''),
        ('unidentified_fault', ''),
    ]

    def _from_response(self, rsp):
        self.role = rsp.global_status.role
        self.management_power_good = \
            bool(rsp.global_status.management_power_good)
        self.payload_power_good = \
            bool(rsp.global_status.payload_power_good)
        self.unidentified_fault = \
            bool(rsp.global_status.unidentified_fault)


class PowerChannelStatus(State):
    __properties__ = [
        # (property, description)
        ('present', ''),
        ('management_power', ''),
        ('management_power_overcurrent', ''),
        ('enable', ''),
        ('payload_power', ''),
        ('payload_power_overcurrent', ''),
        ('pwr_on', ''),
    ]

    def _from_response(self, rsp):
        data = rsp.data[0]
        self.present = (data >> 0) & 1
        self.management_power = (data >> 1) & 1
        self.management_power_overcurrent = (data >> 2) & 1
        self.enable = (data >> 3) & 1
        self.payload_power = (data >> 4) & 1
        self.payload_power_overcurrent = (data >> 5) & 1
        self.pwr_on = (data >> 6) & 1
