# *****************************************************************************
# 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:
#   Paul M. Neves <pmneves@mit.edu>
# *****************************************************************************

from frappy.core import Readable, Parameter, FloatRange, HasIO, StringIO, Property, IntRange,\
    IDLE, BUSY, WARN, ERROR, Drivable, BoolType, Attached
from ast import literal_eval


class RP100IO(StringIO):
    """communication with RP100"""
    end_of_line = '\n'
    #wait_before = 0.05
    identification = [('*IDN?', r'Razorbill,.*')]


class VoltageChannel(HasIO, Drivable):
    """a voltage output with loop"""

    temp = Attached()

    # define the communication class for automatic creation of the IO module
    ioClass = RP100IO

    # internal property to configure the channel
    channel = Property('the voltage channel', datatype=IntRange(1,2))

    # modifying a property of inherited parameters (unit is propagated to the FloatRange datatype)
    value = Parameter('output voltage', FloatRange(-210, 210, unit='V'),
                             readonly=True)
    target = Parameter('target voltage', FloatRange(-210, 210, unit='V'),
                            readonly=False)
    meas_voltage = Parameter('measured output voltage', FloatRange(-250, 250, unit='V'),
                             readonly=True)
    meas_current = Parameter('measured output current', FloatRange(-0.007, 0.007, unit='A'),
                             readonly=True)
    max_target = Parameter('max. target', FloatRange(0, 210, unit='V'), readonly=False)
    min_target = Parameter('max. target', FloatRange(-210, 0, unit='V'), readonly=False)
    slew_rate = Parameter('voltage slew rate', FloatRange(0.1e-3, 100e3, unit='V/s'), readonly=False)
    output_state = Parameter('output on or off', BoolType(), readonly=False)

    def doPoll(self):
        super().doPoll()

        # calculate temperature dependent voltage limits
        temp = self.temp.target
        if temp > 250:
            self.max_target = 120
            self.min_target = -20
        elif temp >= 100:
            self.max_target = 120
            self.min_target = -50 + (temp-100)/5
        elif temp >= 10:
            self.max_target = 200 - 8*(temp-10)/9
            self.min_target = -200 + 5*(temp-10)/3
        elif temp < 10:
            self.max_target = 200
            self.min_target = -200

        # if the current voltage exceeds these limits, reduce voltage to max/min
        if self.target > self.max_target:
            self.write_target(self.max_target)
        if self.target < self.min_target:
            self.write_target(self.min_target)

    def read_value(self):
        # using the inherited HasIO.communicate method to send a command and get the reply
        reply = self.communicate(f'SOUR{self.channel}:VOLT:NOW?')
        return float(reply)
    
    def read_status(self):
        while 1:
            code, text = literal_eval(self.communicate(f'SYST:ERR?'))
            if code == 0:
                break
            self.log.warning('got error %d %s', code, text)
        return IDLE, ''

    def read_target(self):
        # read back the target value 
        target = float(self.communicate(f'SOUR{self.channel}:VOLT?'))
        return target

    def write_target(self, target):
        # write here the target to the hardware
        if target > self.max_target:
            target = self.max_target
            self.log.warning('Attempted to set voltage above maximum allowed voltage. Setting to max allowed instead.')
        if target < self.min_target:
            target = self.min_target
            self.log.warning('Attempted to set voltage below minimum allowed voltage. Setting to min allowed instead.')
        self.communicate(f'SOUR{self.channel}:VOLT {target};*OPC?')
        return self.read_target()  # return the read back value

    def read_slew_rate(self):
        return float(self.communicate(f'SOUR{self.channel}:VOLT:SLEW?'))

    def write_slew_rate(self, slew_rate):
        self.communicate(f'SOUR{self.channel}:VOLT:SLEW {slew_rate};*OPC?')
        return self.read_slew_rate()

    def read_output_state(self):
        return bool(self.communicate(f'OUTP{self.channel}?'))

    def write_output_state(self, output_state):
        self.communicate(f'OUTP{self.channel} {int(output_state)};*OPC?')
        return self.read_slew_rate()

    def read_meas_voltage(self):
        return float(self.communicate(f'MEAS{self.channel}:VOLT?'))

    def read_meas_current(self):
        return float(self.communicate(f'MEAS{self.channel}:CURR?'))