# -*- coding: utf-8 -*-
"""Test attribute descriptors.

"""
import enum

import pytest

from pyvisa import constants
from pyvisa.attributes import (
    Attribute,
    AttrVI_ATTR_ASRL_BAUD,
    AttrVI_ATTR_INTF_INST_NAME,
    BooleanAttribute,
    CharAttribute,
    EnumAttribute,
    IntAttribute,
    RangeAttribute,
    ValuesAttribute,
)

from . import BaseTestCase


class FakeResource:
    """Fake resource to test attributes."""

    def __init__(self, attr_id, attr_value):
        self.attr_id = attr_id
        self.attr_value = attr_value

    def get_visa_attribute(self, attr_id):
        if attr_id == self.attr_id:
            return self.attr_value
        else:
            raise ValueError()

    def set_visa_attribute(self, attr_id, value):
        if attr_id == self.attr_id:
            self.attr_value = value
        else:
            raise ValueError()


def create_resource_cls(
    attribute_name, attribute_type, read=True, write=True, attrs={}
):
    """Create a new attribute class and a resource using it."""
    attrs.update({"attribute_id": attribute_name, "read": read, "write": write})
    attr_cls = type("CA", (attribute_type,), attrs)

    return type("FakeR", (FakeResource,), {"attr": attr_cls()})


class TestAttributeClasses(BaseTestCase):
    """Test the descriptors used to handle VISA attributes."""

    def test_in_resource_method(self):
        """Test the in_resource class method."""
        assert AttrVI_ATTR_INTF_INST_NAME.in_resource(object())
        assert AttrVI_ATTR_ASRL_BAUD.in_resource(
            (constants.InterfaceType.asrl, "INSTR")
        )
        assert not AttrVI_ATTR_ASRL_BAUD.in_resource(object())

    def test_Attribute(self):
        """Test the base class Attribute."""
        rc = create_resource_cls("attr_id", Attribute)
        r = rc("attr_id", 1)
        assert r.attr == 1
        r.attr = 2
        assert r.attr == 2

        # Check we do pass the write ID
        r.attr_id = "dummy"
        with pytest.raises(ValueError):
            r.attr
        with pytest.raises(ValueError):
            r.attr = 2

        # Un-readable attribute
        rc = create_resource_cls("attr_id", Attribute, read=False)
        r = rc("attr_id", 1)
        with pytest.raises(AttributeError):
            r.attr

        # Un-writable attribute
        rc = create_resource_cls("attr_id", Attribute, write=False)
        r = rc("attr_id", 1)
        with pytest.raises(AttributeError):
            r.attr = 1

    def test_BooleanAttribute(self):
        """Test BooleanAttribute."""
        rc = create_resource_cls("attr_id", BooleanAttribute)
        r = rc("attr_id", constants.VI_TRUE)
        assert r.attr is True
        r.attr = False
        assert r.attr is False
        assert r.attr_value == constants.VI_FALSE

    def test_CharAttribute(self):
        """Test CharAttribute."""
        rc = create_resource_cls("attr_id", CharAttribute)
        r = rc("attr_id", ord("\n"))
        assert r.attr == "\n"
        r.attr = "\r"
        assert r.attr == "\r"
        assert r.attr_value == 13

    def test_EnumAttribute(self):
        """Test EnumAttribute"""

        @enum.unique
        class E(enum.IntEnum):
            a = 1
            b = 2

        rc = create_resource_cls("attr_id", EnumAttribute, attrs={"enum_type": E})
        r = rc("attr_id", 1)
        assert r.attr == E.a
        r.attr = E.b
        assert r.attr == E.b
        assert r.attr_value == 2

        with pytest.raises(ValueError):
            r.attr = 3

        with pytest.raises(ValueError):
            r.attr = ""

    def test_IntAttribute(self):
        """Test IntAttribute."""
        rc = create_resource_cls("attr_id", IntAttribute)
        r = rc("attr_id", "1")
        assert r.attr == 1

    def test_RangeAttribute(self):
        """Test RangeAttribute"""
        rc = create_resource_cls(
            "attr_id", RangeAttribute, attrs={"min_value": 0, "max_value": 2}
        )
        r = rc("attr_id", 1)
        r.attr = 0
        assert r.attr_value == 0
        r.attr = 2
        assert r.attr_value == 2
        r.attr = 1
        assert r.attr_value == 1

        with pytest.raises(ValueError) as cm:
            r.attr = -1

        assert "invalid value" in str(cm.exconly())
        assert " or " not in str(cm.exconly())

        with pytest.raises(ValueError) as cm:
            r.attr = 3

        assert "invalid value" in str(cm.exconly())
        assert " or " not in str(cm.exconly())

        rc = create_resource_cls(
            "attr_id",
            RangeAttribute,
            attrs={"min_value": 0, "max_value": 2, "values": [10]},
        )
        r = rc("attr_id", 1)
        r.attr = 10
        assert r.attr_value == 10

        with pytest.raises(ValueError) as cm:
            r.attr = 3

        assert "invalid value" in str(cm.exconly())
        assert " or " in str(cm.exconly())

    def test_ValuesAttribute(self):
        """Test ValuesAttribute"""
        rc = create_resource_cls("attr_id", ValuesAttribute, attrs={"values": [10, 20]})
        r = rc("attr_id", 1)
        r.attr = 10
        assert r.attr_value == 10

        with pytest.raises(ValueError) as cm:
            r.attr = 3
        assert "invalid value" in str(cm.exconly())
