# *****************************************************************************
# 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:
#   Anik Stark <anik.stark@psi.ch>
# *****************************************************************************
"""dilution refrigerator: Leiden Cryogenics GHS-2T-1T-700

! writing limits does not work (reading does)
! communication with turbo pumps is not implemented
"""

from frappy.core import StringIO, HasIO, Readable, Writable, Parameter, Property, EnumType, BoolType, FloatRange
from frappy.errors import CommunicationFailedError

P_SCALE = 2**14 / 1e4


class IO(StringIO):
    end_of_line = '\n'
    identification = [('ID?', '0\\tGHS-2T-1T-700-CF.*')]
    default_settings = {'baudrate': 9600}
    _key_status = None
    _pressure = None
    _pressure_limits = None
    pollinterval = Parameter('poll interval', FloatRange(0.1,10), default=0.5)

    def initModule(self):
        self.modules = []
        super().initModule()

    def doPoll(self):
        # read key (valve) states
        reply = self.communicate('KEYS?')
        status, key_status = reply.split('\t')
        self._key_status = key_status.split(',')
        if status != '0':
            raise CommunicationFailedError(f'bad reply: {reply}')
        # read pressure values
        reply = self.communicate('ADC?')
        status, pressure = reply.split('\t')
        if status != '0':
            raise CommunicationFailedError(f'bad reply: {reply}')
        self._pressure = pressure.split(',')
        # read pressure limits
        status, reply = self.communicate('SETTINGS?').split('\t')
        if status != '0':
            raise CommunicationFailedError(f'bad reply: {reply}')
        self._pressure_limits = reply.split(',')
        # read value for all registred modules
        for module in self.modules:
            module.update_values()

    def register(self, module):
        self.modules.append(module)


class Base(HasIO):

    def initModule(self):
        self.io.register(self)
        super().initModule()

    def change(self, cmd, key):
        reply = self.communicate(f'{cmd} {key}')
        if not reply.startswith('0'):
            raise CommunicationFailedError(f'bad reply: {reply}')

    def get_key(self, key):
        return self.io._key_status[key - 1] == '2'

    def get_pressure(self, addr):
        return float(self.io._pressure[addr])
 
    def update_values(self):
        self.read_value()


KEY_MAP = {'mixcompr': 1,    # mixture compressor
            'bypass': 2,
            'V15': 3,
            'V16': 4,
            'AUX1': 5,
            'gate18': 6,
            'V4': 7,
            'V5': 9,
            'reset': 10,
            'V9': 12,
            'V14': 13,
            'V13': 15,
            'ledtest': 16,
            'V12': 18,
            'V10': 19,
            'V11': 21,
            'A0': 22,
            'V7': 24,
            'S3': 25,
            'V6': 27,
            'V17': 28,
            'V8': 30,
            'V1': 31,
            'S2': 33,
            'V3': 34,
            'V2': 36,
            'V0': 37,
            'S1': 39,
            'A9': 40,
            'A8': 42,
            'A2': 43,
            'S5': 45,
            'A10': 46,
            'start': 48,
            'A5': 49,
            'A7': 51,
            'A1': 52,
            'auto': 54,
            'A4': 55,
            'A6': 57,
            'S4': 58,
            'A3': 60,
            'He3': 61,    # condense He3
            'He4': 62,    # condense He4
            'circulation': 63,   # normal circulation
            'recovery': 64,
            }


class Valve(Base, Writable):

    ioClass = IO

    target = Parameter('target state of valve', datatype=BoolType(), readonly=False)
    value = Parameter('status of valve (open/close)', datatype=BoolType())
    key = Property('key (button) number', datatype=EnumType(KEY_MAP))

    def write_target(self, target):
        """toggle button state (as if pressed manually on the panel)"""
        self.io.doPoll()
        state = self.read_value()
        if state != target:
            self.change('DEVMAN', self.key.value)
            self.target = target
            self.io.doPoll()
            self.read_value()

    def read_value(self):
        if self.io._key_status is None:
            self.io.doPoll()
        value = self.get_key(self.key.value)
        if not self.value and not self.target and value:
            self.log.info('valve is on, was it a ghost? %r %r %r', self.value, self.target, value)
        return value



PRESSURE_MAP = {'p0': 0,
                'p2': 1,
                'p3': 2,
                'p4': 3,
                'p5': 4,
                'p6': 5,
                'p7': 6,
                'flow': 7,
                }


class Pressure(Base, Readable):

    ioClass = IO

    value = Parameter('pressure and flow values', datatype=FloatRange(unit='mbar'))
    addr = Property('address of pressure sensor', datatype=EnumType(PRESSURE_MAP))
    scale = Parameter('scale factor from raw value', datatype=FloatRange(), default=P_SCALE)

    def read_value(self):
        value = self.get_pressure(self.addr.value) / self.scale
        return value


class PressureLimit(Pressure, Readable):
    """P6 and P7 pressure sensors of dilution refrigerator (Leiden Cryogenics GHS-2T-1T-700)
        have pressure limit settings"""

    ioClass = IO

    value = Parameter('pressure values for sensors with limit', datatype=FloatRange(unit='mbar', fmtstr='%.1f'))
    offset = Property('address of pressure sensor', datatype=EnumType({'P6': 0, 'P7': 2}))
    limit_low = Parameter('low pressure limit', datatype=FloatRange(0, 2000, unit='mbar'), readonly=False)
    limit_high = Parameter('high pressure limit', datatype=FloatRange(0, 2000, unit='mbar'), readonly=False)

    def get_pressure_limit(self, lim):
        return float(self.io._pressure_limits[lim])

    def set_limit(self, pos, limit):
        limit_list = self.io._pressure_limits
        limit_list[pos] = f'{limit:.0f}'
        reply = self.communicate(f'SETTINGS {",".join(limit_list)},0')
        if reply != '0':
            raise CommunicationFailedError(f'bad reply: {reply}')
        self.io.doPoll()

    def read_limit_low(self):
        return self.get_pressure_limit(self.offset.value)

    def read_limit_high(self):
        return self.get_pressure_limit(self.offset.value + 1)
        #self._P6_low, self._P6_high, self._P7_low, self._P7_high = reply.split(',')

    def write_limit_low(self, limit):
        return self.set_limit(self.offset.value, limit)

    def write_limit_high(self, limit):
        return self.set_limit(self.offset.value + 1, limit)

    def update_values(self):
        self.read_value()
        self.read_limit_low()
        self.read_limit_high()


class Turbo(Base, Writable):

    ioClass = IO

    target = Parameter('target state of turbo pump', datatype=EnumType(off=0, on=1))
    value = Parameter('state of turbo pump', datatype=EnumType(off=0, on=1))
    addr = Parameter('turbo pump address', datatype=EnumType(turbo1=1, turbo2=2))

    # def write_target(self, target):
    #     self.change(f'TURBO ON/OFF {target}', addr)
