#!/usr/bin/env python
#  -*- coding: utf-8 -*-
# *****************************************************************************
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program 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 General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Module authors:
#   Oksana Shliakhtun <oksana.shliakhtun@psi.ch>
# *****************************************************************************
""" RUFS Command: Description of Bits

        ======  ========================================================  ==============================================
        Value    Description
        ======  ========================================================  ==============================================
        V1
                 B6: warning, rtd1 (internal temp. sensor) is shorted
                                                                           B0 --> 1
                 B7: warning, rtd1 is open
                                                                           B1 --> 2
        V2
                 B0: error, HTC (high temperature cutout) fault            B2 --> 4

                 B1: error, high RA (refrigeration) temperature fault      B3 --> 8

        V3                                                                 B4 --> 16
                 B0: warning, low level in the bath
                                                                           B5 --> 32
                 B1: warning, low temperature                                         
                                                                           B6 --> 64
                 B2: warning, high temperature
                                                                           B7 --> 128
                 B3: error, low level in the bath

                 B4: error, low temperature fault

                 B5: error, high temperature fault                         

                 B6: error, low temperature fixed* fault

                 B7: error, high temperature fixed** fault

        V4
                 B3: idle, circulator** is running

                 B5: error, circulator** fault

        V5
                 B0: error, pump speed fault

                 B1: error, motor overloaded

                 B2: error, high pressure cutout

                 B3: idle, maximum cooling

                 B4: idle, cooling

                 B5: idle, maximum heating

                 B6: idle, heating
        ======  ========================================================  ==============================================

"""

from frappy.core import StringIO, Parameter, HasIO, \
    Drivable, FloatRange, IDLE, BUSY, ERROR, WARN, BoolType
from frappy_psi.convergence import HasConvergence


class ThermFishIO(StringIO):
    end_of_line = '\r'
    identification = [('RVER', r'.*')]  # Firmware Version


class TemperatureLoopA10(HasConvergence, HasIO, Drivable):
    ioClass = ThermFishIO
    value = Parameter('internal temperature', unit='degC')
    value = Parameter('temperature', unit='degC')
    target = Parameter('setpoint/target', datatype=FloatRange, unit='degC', default=0)
    circ_on = Parameter('is circulation running', BoolType(), readonly=False, default=False)
    # pids
    p_heat = Parameter('proportional heat parameter', FloatRange(), readonly=False)
    i_heat = Parameter('integral heat parameter', FloatRange(), readonly=False)
    d_heat = Parameter('derivative heat parameter', FloatRange(), readonly=False)
    p_cool = Parameter('proportional cool parameter', FloatRange(), readonly=False)
    i_cool = Parameter('integral cool parameter', FloatRange(), readonly=False)
    d_cool = Parameter('derivative cool parameter', FloatRange(), readonly=False)

    status_messages = [
        (ERROR, 'high tempr. cutout fault', 2, 0),
        (ERROR, 'high RA tempr. fault', 2, 1),
        (ERROR, 'high temperature fixed fault', 3, 7),
        (ERROR, 'low temperature fixed fault', 3, 6),
        (ERROR, 'high temperature fault', 3, 5),
        (ERROR, 'low temperature fault', 3, 4),
        (ERROR, 'low level fault', 3, 3),
        (ERROR, 'circulator fault', 4, 5),
        (ERROR, 'high press. cutout', 5, 2),
        (ERROR, 'motor overloaded', 5, 1),
        (ERROR, 'pump speed fault', 5, 0),
        (WARN, 'open internal sensor', 1, 7),
        (WARN, 'shorted internal sensor', 1, 6),
        (WARN, 'high temperature warn', 3, 2),
        (WARN, 'low temperature warn', 3, 1),
        (WARN, 'low level warn', 3, 0),
        (IDLE, 'max. heating', 5, 5),
        (IDLE, 'heating', 5, 6),
        (IDLE, 'cooling', 5, 4),
        (IDLE, 'max cooling', 5, 3),
        (IDLE, '', 4, 3),
    ]

    def get_par(self, cmd):
        """
        All the reading commands starts with 'R', in the source code all the commands are written without 'R' (except
        'RUFS').The result of a reading command is a value in the format '20C', without spaces.

        :param cmd: any hardware command

        :return: 'R'+cmd
        """
        new_cmd = 'R' + cmd
        reply = self.communicate(new_cmd)
        if any(unit.isalpha() for unit in reply):
            reply = ''.join(unit for unit in reply if not unit.isalpha())
        return float(reply)

    def read_value(self):
        """
        Reading internal temperature sensor value.
        """
        return self.get_par('T')

    def read_status(self):
        result_str = self.communicate('RUFS')  # read unit fault status
        values_str = result_str.strip().split()
        values_int = [int(val) for val in values_str]

        for status_type, status_msg, vi, bit in self.status_messages:
            if values_int[vi-1] & (1 << bit):
                conv_status = HasConvergence.read_status(self)
                if self.isBusy(conv_status):
                    # use 'inside tolerance' and 'outside tolerance' from HasConvergence,
                    # else our own status
                    return BUSY, conv_status[1] if 'tolerance' in conv_status[1] else status_msg
                return status_type, status_msg
        return WARN, 'circulation off'

    def read_circ_on(self):
        return self.communicate('RO')

    def write_circ_on(self, circ_on):
        circ_on_str = '1' if circ_on else '0'
        self.communicate(f'SO {circ_on_str}')
        return self.read_circ_on()

    def read_target(self):
        return self.get_par('S')

    def write_target(self, target):
        """
        :param target: here, it serves as an equivalent to a setpoint.
        """
        self.write_circ_on('1')
        self.communicate(f'SS {target}')
        self.convergence_start()
        return target

        ##  heat PID
    def read_p_heat(self):
        p_heat = self.get_par('PH')
        return float(p_heat)

    def write_p_heat(self, p_heat):
        self.communicate(f'SPH {p_heat}')
        return p_heat

    def read_i_heat(self):
        i_heat = self.get_par('IH')
        return float(i_heat)

    def write_i_heat(self, i_heat):
        self.communicate(f'SIH {i_heat}')
        return i_heat

    def read_d_heat(self):
        d_heat = self.get_par('DH')
        return float(d_heat)

    def write_d_heat(self, d_heat):
        self.communicate(f'SDH {d_heat}')
        return d_heat

    ##  cool PID
    def read_p_cool(self):
        p_cool = self.get_par('PC')
        return float(p_cool)

    def write_p_cool(self, p_cool):
        self.communicate(f'SPC {p_cool}')
        return p_cool

    def read_i_cool(self):
        i_cool = self.get_par('IC')
        return float(i_cool)

    def write_i_cool(self, i_cool):
        self.communicate(f'SIC {i_cool}')
        return i_cool

    def read_d_cool(self):
        d_cool = self.get_par('DC')
        return float(d_cool)

    def write_d_cool(self, d_cool):
        self.communicate(f'SDC {d_cool}')
        return d_cool
