# *****************************************************************************
#
# 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:
#   Markus Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
"""test frappy.extparams"""


from test.test_modules import LoggerStub, ServerStub
import pytest
from frappy.core import FloatRange, Module, Parameter
from frappy.extparams import StructParam, FloatEnumParam
from frappy.errors import ProgrammingError


def test_with_read_ctrlpars():
    class Mod(Module):
        ctrlpars = StructParam('ctrlpar struct', dict(
            p = Parameter('control parameter p', FloatRange()),
            i = Parameter('control parameter i', FloatRange()),
            d = Parameter('control parameter d', FloatRange()),
        ), 'pid_', readonly=False)

        def read_ctrlpars(self):
            return self._ctrlpars

        def write_ctrlpars(self, value):
            self._ctrlpars = value
            return self.read_ctrlpars()

    logger = LoggerStub()
    updates = {}
    srv = ServerStub(updates)

    ms = Mod('ms', logger, {'description':''}, srv)

    value = {'p': 1, 'i': 2, 'd': 3}
    assert ms.write_ctrlpars(value) == value
    assert ms.read_ctrlpars() == value
    assert ms.read_pid_p() == 1
    assert ms.read_pid_i() == 2
    assert ms.read_pid_d() == 3
    assert ms.write_pid_i(5) == 5
    assert ms.write_pid_d(0) == 0
    assert ms.read_ctrlpars() == {'p': 1, 'i': 5, 'd': 0}
    assert set(Mod.ctrlpars.influences) == {'pid_p', 'pid_i', 'pid_d'}
    assert Mod.pid_p.influences == ('ctrlpars',)
    assert Mod.pid_i.influences == ('ctrlpars',)
    assert Mod.pid_d.influences == ('ctrlpars',)


def test_without_read_ctrlpars():
    class Mod(Module):
        ctrlpars = StructParam('ctrlpar struct', dict(
            p = Parameter('control parameter p', FloatRange()),
            i = Parameter('control parameter i', FloatRange()),
            d = Parameter('control parameter d', FloatRange()),
        ), readonly=False)

        _pid_p = 0
        _pid_i = 0

        def read_p(self):
            return self._pid_p

        def write_p(self, value):
            self._pid_p = value
            return self.read_p()

        def read_i(self):
            return self._pid_i

        def write_i(self, value):
            self._pid_i = value
            return self.read_i()

    logger = LoggerStub()
    updates = {}
    srv = ServerStub(updates)

    ms = Mod('ms', logger, {'description': ''}, srv)

    value = {'p': 1, 'i': 2, 'd': 3}
    assert ms.write_ctrlpars(value) == value
    assert ms.read_ctrlpars() == value
    assert ms.read_p() == 1
    assert ms.read_i() == 2
    assert ms.read_d() == 3
    assert ms.write_i(5) == 5
    assert ms.write_d(0) == 0
    assert ms.read_ctrlpars() == {'p': 1, 'i': 5, 'd': 0}
    assert set(Mod.ctrlpars.influences) == {'p', 'i', 'd'}
    assert Mod.p.influences == ('ctrlpars',)
    assert Mod.i.influences == ('ctrlpars',)
    assert Mod.d.influences == ('ctrlpars',)


def test_readonly():
    class Mod(Module):
        ctrlpars = StructParam('ctrlpar struct', dict(
            p = Parameter('control parameter p', FloatRange()),
            i = Parameter('control parameter i', FloatRange()),
            d = Parameter('control parameter d', FloatRange()),
        ), readonly=True)

    assert Mod.ctrlpars.readonly is True
    assert Mod.p.readonly is True
    assert Mod.i.readonly is True
    assert Mod.d.readonly is True


def test_order_dependence1():
    test_without_read_ctrlpars()
    test_with_read_ctrlpars()


def test_order_dependence2():
    test_with_read_ctrlpars()
    test_without_read_ctrlpars()


def test_float_enum():
    class Mod(Module):
        vrange = FloatEnumParam('voltage range', [
            (1, '50uV'), '200 µV', '1mV', ('5mV', 0.006), (9, 'max', 0.024)], 'V')
        gain = FloatEnumParam('gain factor', ('1', '2', '4', '8'), idx_name='igain')
        dist = FloatEnumParam('distance', ('1m', '1mm', '1µm'), unit='m')

        _vrange_idx = None

        def write_vrange_idx(self, value):
            self._vrange_idx = value

    logger = LoggerStub()
    updates = {}
    srv = ServerStub(updates)

    m = Mod('m', logger, {'description': ''}, srv)

    assert m.write_vrange_idx(1) == 1
    assert m._vrange_idx == '50uV'
    assert m._vrange_idx == 1
    assert m.vrange == 5e-5

    assert m.write_vrange_idx(2) == 2
    assert m._vrange_idx == '200 µV'
    assert m._vrange_idx == 2
    assert m.vrange == 2e-4

    assert m.write_vrange(6e-5) == 5e-5  # round to the next value
    assert m._vrange_idx == '50uV'
    assert m._vrange_idx == 1
    assert m.write_vrange(20e-3) == 24e-3  # round to the next value
    assert m._vrange_idx == 'max'
    assert m._vrange_idx == 9

    for idx in range(4):
        value = 2 ** idx
        updates.clear()
        assert m.write_igain(idx) == idx
        assert updates == {'m': {'igain': idx, 'gain': value}}
        assert m.igain == idx
        assert m.igain == str(value)
        assert m.gain == value

    for idx in range(4):
        value = 2 ** idx
        assert m.write_gain(value) == value
        assert m.igain == idx
        assert m.igain == str(value)

    for idx in range(3):
        value = 10 ** (-3 * idx)
        assert m.write_dist(value) == value
        assert m.dist_idx == idx


@pytest.mark.parametrize('labels, unit, error', [
    (FloatRange(),      '', 'not a datatype'),     # 2nd arg must not be a datatype
    ([(1, 2, 3)],       '', 'must be strings'),    # label is not a string
    ([(1, '1V', 3, 4)], 'V', 'labels or tuples'),  # 4-tuple
    ([('1A', 3, 4)],    'A', 'labels or tuples'),  # two values after label
    (('1m', (0, '1k')), '', 'conflicts with'),     # two times index 0
    (['1mV', '10mA'],   'V', 'not the form'),      # wrong unit
    (['.mV'],           'V', 'not the form'),      # bad number
    (['mV'],            'V', 'not the form'),      # missing number
    (['1+mV'],          'V', 'not the form'),      # bad number
])
def test_bad_float_enum(labels, unit, error):
    with pytest.raises(ProgrammingError, match=error):
        class Mod(Module):  # pylint:disable=unused-variable
            param = FloatEnumParam('', labels, unit)
