#!/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>
# *****************************************************************************
"""Hewlett-Packard HP34401A Multimeter (not finished)"""

import re
from frappy.core import HasIO, Readable, Parameter, FloatRange, EnumType, StatusType, IDLE, ERROR, WARN


def string_to_value(value):
    """
    Converting the value to float, removing the units, converting the prefix into the number.
    :param value: value
    :return: float value without units
    """
    value_with_unit = re.compile(r'(\d+)([pnumkMG]?)')
    value, pfx = value_with_unit.match(value).groups()
    pfx_dict = {'p': 1e-12, 'n': 1e-9, 'u': 1e-6, 'm': 1e-3, 'k': 1e3, 'M': 1e6, 'G': 1e9}
    if pfx in pfx_dict:
        value = round(float(value) * pfx_dict[pfx], 12)
    return float(value)


class HP_IO(HasIO):
    end_of_line = b'\n'
    identification = [('*IDN?', r'HEWLETT-PACKARD,34401A,0,.*')]


class HP34401A(HP_IO):
    status = Parameter(datatype=StatusType(Readable, 'BUSY'))
    autorange = Parameter('autorange_on', EnumType('autorange', off=0, on=1), readonly=False, default=0)

    def comm(self, cmd):  # read until \n
        string = f'{cmd}\n'
        n_string = string.encode()
        response = self.communicate(n_string)

        if response:
            return response

        response = self.communicate(n_string)
        return response if response else None

    def read_range(self, function):
        return self.comm(f'{function}:range?')

    def write_range(self, function, range):
        return self.comm(f'{function}:range {range}')

    def write_autorange(self, function):
        cmd = f'{function}:range:auto {"on" if self.autorange == 0 else "off"}'
        self.comm(cmd)
        return self.comm(f'{function}:range:auto?')

    def read_resolution(self, function):
        return self.comm(f'{function}:resolution?')

    def write_resolution(self, function, resolution):
        self.comm(f'{function}:resolution {resolution}')
        return self.comm(f'{function}:resolution?')

    def read_status(self):
        stb = int(self.comm('*STB?'))
        esr = int(self.comm('*ESR?'))

        if esr & (1 << 3):
            return ERROR, 'self-test/calibration/reading failed'
        if esr & (1 << 4):
            return ERROR, 'execution error'
        if esr & (1 << 5):
            return ERROR, 'syntax error'
        if esr & (1 << 2):
            return ERROR, 'query error'
        if stb & (1 << 3):
            return WARN, 'questionable data'
        if stb & (1 << 5):
            return WARN, 'standard event register is not empty'
        if stb & (1 << 6):
            return WARN, 'requested service'

        if any(stb & (1 << i) for i in range(3) or stb & (1 << 7)):
            return IDLE, ''
        if esr & (1 << 6):
            return IDLE, ''
        if esr & (1 << 7):
            return IDLE, ''
        if stb & (1 << 4):
            return IDLE, 'message available'
        if esr & (1 << 0):
            return IDLE, 'operation complete'
        if esr & (1 << 1):
            return IDLE, 'not used'


class Voltage(HP34401A, Readable):
    value = Parameter('voltage', datatype=FloatRange(0.1, 1000), unit='V')
    range = Parameter('voltage sensitivity value', FloatRange(), unit='V', default=1, readonly=False)
    resolution = Parameter('resolution')
    mode = Parameter('measurement mode: ac/dc', readonly=False)

    ioClass = HP_IO

    MODE_NAMES = {0: 'dc', 1: 'ac'}
    VOLT_RANGE = ['100mV', '1V', '10V', '100V', '1000V']
    v_range = Parameter('voltage range', EnumType('voltage index range',
                                                  {name: idx for idx, name in enumerate(VOLT_RANGE)}), readonly=False)

    acdc = None

    def write_mode(self, mode):
        """
        Set the mode - AC or DC
        :param mode: AC/DC
        :return:
        """
        if mode == 1:
            self.comm(f'configure:voltage:AC {self.range}, {self.resolution}')
        else:
            self.comm(f'configure:voltage:DC {self.range}, {self.resolution}')
        self.acdc = self.MODE_NAMES[mode]
        return self.comm(f'function?')

    def read_value(self):
        """
        Makes a AC/DC voltage measurement.
        :return: AC/DC value
        """
        return self.comm(f'measure:voltage:{self.acdc}?')

    def write_autorange_acdc(self, function):
        full_function = f'{function}:{self.acdc}'
        return self.write_autorange(full_function)

    def read_range_voltage(self):
        return self.read_range(f'voltage:{self.acdc}')

    def write_range_voltage(self, range):
        return self.write_range(f'voltage:{self.acdc}', range)

    def write_autorange_voltage(self):
        return self.write_autorange_acdc('voltage')

    def read_resolution_voltage(self):
        return self.read_resolution(f'voltage:{self.acdc}')

    def write_resolution_voltage(self, resolution):
        return self.write_resolution(f'voltage:{self.acdc}', resolution)


class Current(HP34401A, Readable, Voltage):
    value = Parameter('current', FloatRange, unit='A')
    range = Parameter('current range', FloatRange)
    CURR_RANGE_AC = ['10mA', '100mA', '1A', '3A']
    CURR_RANGE_DC = ['1A', '3A']

    def read_range_current(self):
        return self.read_range(f'current:{self.acdc}')

    def write_autorange_current(self):
        return self.write_autorange_acdc('current')

    def write_range_current(self, range):
        return self.write_range(f'current:{self.acdc}', range)

    def read_resolution_current(self):
        return self.read_resolution(f'current:{self.acdc}')

    def write_resolution_current(self, resolution):
        return self.write_resolution(f'current:{self.acdc}', resolution)


class Resistance(HP34401A, Readable):
    value = Parameter('resistance')
    mode = Parameter('measurement mode: 2-/4-wire ohms', EnumType(two_wire=2, four_wire=4), readonly=False)
    resolution = Parameter('resistance measurement resolution')
    range = Parameter('resistance measurement range')
    RESIST_RANGE = ['100Om', '1kOm', '10kOm', '100kOm', '1MOm', '10MOm', '100MOm']
    FUNCTION_MAP = {2: 'resistance', 4: 'fresistance'}

    def write_range_resistance(self, range):
        return self.write_range(f'{self.FUNCTION_MAP[self.mode]}', range)

    def read_range_resistance(self):
        return self.read_range(f'{self.FUNCTION_MAP[self.mode]}')

    def write_mode(self, mode):
        if mode == 2:
            self.comm(f'configure:resistance {self.range},{self.resolution}')
        elif mode == 4:
            self.comm(f'configure:fresistance {self.range}, {self.resolution}')
        return self.comm('configure?')

    def write_autorange_resistance(self):
        return self.write_autorange(self.FUNCTION_MAP[self.mode])

    def read_resolution_resistance(self):
        return self.read_resolution(f'{self.FUNCTION_MAP[self.mode]}')

    def write_resolution_resistance(self, resolution):
        return self.write_resolution(f'{self.FUNCTION_MAP[self.mode]}', resolution)


class Frequency(HP34401A, Readable):
    value = Parameter('frequency', FloatRange(3, 300e3), unit='Hz')

    def write_autorange_frequency(self):
        return self.write_autorange('frequency')

    def read_resolution_frequency(self):
        return self.read_resolution('frequency')

    def write_resolution_frequency(self, resolution):
        return self.write_resolution('frequency', resolution)
