# *****************************************************************************
# 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:
#   Daniel Margineda <daniel.margineda@psi.ch>
# *****************************************************************************
"""Signal Recovery SR7270: lockin amplifier for AC susceptibility"""

from frappy.core import Readable, Parameter, Command, FloatRange, TupleOf, \
    HasIO, StringIO, Attached, IntRange, BoolType, EnumType


class SR7270(StringIO):
    end_of_line = b'\x00'

    def communicate(self, command):  # remove dash from terminator
        reply = super().communicate(command)
        status = self._conn.readbytes(2, 0.1)  # get the 2 status bytes
        return reply + ';%d;%d' % tuple(status)


class XY(HasIO, Readable):
    x = Attached()
    y = Attached()
    freq_arg = Attached()
    amp_arg = Attached()
    tc_arg = Attached()
    phase_arg = Attached()
    dac_arg = Attached()

    # parameters required an initial value but initwrite write the default value for polled parameters
    value = Parameter('X, Y', datatype=TupleOf(FloatRange(unit='V'), FloatRange(unit='V')))

    # separate module (Writable)
    freq = Parameter('exc_freq_int',
                     FloatRange(0.001, 250e3, unit='Hz'),
                     readonly=False, default=1000)  # initwrite=True,
    # separate module (Writable)
    amp = Parameter('exc_volt_int',
                    FloatRange(0.00, 5, unit='Vrms'),
                    readonly=False, default=0.1)  # initwrite=True,
    # unify the following
    range = Parameter('sensitivity value', FloatRange(0.00, 1, unit='V'), default=1)
    irange = Parameter('sensitivity index', IntRange(0, 27), readonly=False, default=25)

    autorange = Parameter('autorange_on', EnumType('autorange', off=0, soft=1, hard=2),
                          readonly=False, default=0)  # , initwrite=True

    # unify the following
    tc = Parameter('time constant value', FloatRange(10e-6, 100, unit='s'), default=0.1)
    itc = Parameter('time constant index', IntRange(0, 30), readonly=False, default=14)

    nm = Parameter('noise mode', BoolType(), readonly=False, default=0)
    phase = Parameter('Reference phase control', FloatRange(-360, 360, unit='deg'),
                      readonly=False, default=0)

    # convert to enum
    vmode = Parameter('Voltage input configuration', IntRange(0, 3), readonly=False, default=3)
    # dac = Parameter('output DAC channel value', datatype=TupleOf(IntRange(1, 4), FloatRange(0.0, 5000, unit='mV')),
    #                 readonly=False, initwrite=True, default=(3,0))
    # dac = Parameter('output DAC channel value', FloatRange(-10000, 10000, unit='mV'),
    #                readonly=False, initwrite=True, default=0)

    ioClass = SR7270

    def comm(self, command):
        reply, status, overload = self.communicate(command).split(';')
        if overload != '0':
            self.status = self.Status.WARN, 'overload %s' % overload
        else:
            self.status = self.Status.IDLE, ''
        return reply

    def read_value(self):
        reply = self.comm('XY.').split(',')
        x = float(reply[0])
        y = float(reply[1])
        if self.autorange == 1:  # soft
            if max(abs(x), abs(y)) >= 0.9*self.range and self.irange < 27:
                self.write_irange(self.irange+1)
            elif max(abs(x), abs(y)) <= 0.3*self.range and self.irange > 1:
                self.write_irange(self.irange-1)
        self._x.value = x  # to update  X,Y classes which will be the collected data.
        self._y.value = y
        return x, y

    def read_freq(self):
        reply = self.comm('OF.')
        return reply

    def write_freq(self, value):
        self.comm('OF. %g' % value)
        return value

    def write_autorange(self, value):
        if value == 2:  # hard
            self.comm('AS')  # put hardware autorange on
            self.comm('AUTOMATIC. 1')
        else:
            self.comm('AUTOMATIC. 0')
        return value

    def read_autorange(self):
        reply = self.comm('AUTOMATIC')
        # determine hardware autorange
        if reply == 1:  # "hardware auto range is on"
            return 2  # hard
        if self.autorange == 0:  # soft
            return self.autorange()  # read autorange
        return reply  # off

    # oscillator amplitude module
    def read_amp(self):
        reply = self.comm('OA.')
        return reply

    def write_amp(self, value):
        self.comm('OA. %g' % value)
        return value

    # external output DAC
    #def read_dac(self):
    #    # reply = self.comm('DAC  %g' % channel) # failed to add the DAC channel you want to control
    #    reply = self.comm('DAC 3')  # stack to channel 3
    #    return reply

    #def write_dac(self, value):
    #    # self.comm('DAC %g %g' % channel % value)
    #    self.comm('DAC 3 %g' % value)
    #    return value

    # sensitivity module
    def read_range(self):
        reply = self.comm('SEN.')
        return reply

    def write_irange(self, value):
        self.comm('SEN %g' % value)
        self.read_range()
        return value

    def read_irange(self):
        reply = self.comm('SEN')
        return reply

    # time constant module/ noisemode off or 0 allows to use all the time constant range
    def read_nm(self):
        reply = self.comm('NOISEMODE')
        return reply

    def write_nm(self, value):
        self.comm('NOISEMODE %d' % int(value))
        self.read_nm()
        return value

    def read_tc(self):
        reply = self.comm('TC.')
        return reply

    def write_itc(self, value):
        self.comm('TC %g' % value)
        self.read_tc()
        return value

    def read_itc(self):
        reply = self.comm('TC')

        return reply

    # phase and autophase
    def read_phase(self):
        reply = self.comm('REFP.')
        return reply

    def write_phase(self, value):
        self.comm('REFP %d' % round(1000*value, 0))
        self.read_phase()
        return value

    @Command()
    def aphase(self):
        """auto phase"""
        self.read_phase()
        reply = self.comm('AQN')
        self.read_phase()

    # voltage input configuration 0:grounded,1=A,2=B,3=A-B
    # def read_vmode(self):
    #     reply = self.comm('VMODE')
    #     return reply

    def write_vmode(self, value):
        self.comm('VMODE %d' % value)
        # self.read_vmode()
        return value


class Comp(Readable):
    enablePoll = False
    value = Parameter(datatype=FloatRange(unit='V'))


class arg(Readable):
    enablePoll = False
    value = Parameter(datatype=FloatRange(unit=''))
