#!/usr/bin/env python

import array
from mock import patch, call, ANY
import pyaardvark
from pyaardvark.constants import *
import pytest


@patch('pyaardvark.aardvark.api', autospec=True)
def test_api_version(api):
    api.py_version.return_value = 83887390
    v = pyaardvark.api_version()
    api.py_version.assert_called_once_with()
    assert v == "5.30"

@patch('pyaardvark.aardvark.api', autospec=True)
def test_open_default(api):
    api.py_aa_open_ext.return_value = (42, (0,) * 6)
    a = pyaardvark.open()
    api.py_aa_open_ext.assert_called_once_with(0)
    assert a.handle == api.py_aa_open_ext.return_value[0]

@patch('pyaardvark.aardvark.api', autospec=True)
def test_find_devices_valid_devices(api):
    def f(num, devices):
        return 2

    def f_ext(num, num_ids, devices, ids):
        if num >= 2:
            devices[0] = 42 | pyaardvark.PORT_NOT_FREE
            ids[0] = 1234567890
            devices[1] = 4711
            ids[1] = 1111222222
        return 2

    api.py_aa_find_devices.side_effect = f
    api.py_aa_find_devices_ext.side_effect = f_ext

    devs = pyaardvark.find_devices()
    api.py_aa_find_devices.assert_has_calls([
        call(0, array.array('H')),
    ])
    api.py_aa_find_devices_ext.assert_has_calls([
        call(2, 2, ANY, ANY),
    ])
    assert len(devs) == 2
    assert devs[0]['port'] == 42
    assert devs[0]['serial_number'] == '1234-567890'
    assert devs[0]['in_use']
    assert devs[1]['port'] == 4711
    assert devs[1]['serial_number'] == '1111-222222'
    assert not devs[1]['in_use']

@patch('pyaardvark.aardvark.api', autospec=True)
def test_open_port(api):
    api.py_aa_open_ext.return_value = (42, (0,) * 6)
    a = pyaardvark.open(4711)
    api.py_aa_open_ext.assert_called_once_with(4711)
    assert a.handle == api.py_aa_open_ext.return_value[0]

@patch('pyaardvark.aardvark.api', autospec=True)
def test_open_serial_number(api):
    devices = {
            42: 1234567890,
            4711: 1111222222,
            5: 3333444444,
    }

    def f(_num, devs):
        return len(devices)

    def f_ext(_num, _num_ids, devs, ids):
        for i, (dev, unique) in enumerate(devices.items()):
            if _num > i:
                devs[i] = dev
            if _num_ids > i:
                ids[i] = unique
        return len(devices)

    def open(handle):
        return (handle, (0,) * 6)

    def unique_id(handle):
        return devices[handle]

    api.py_aa_find_devices.side_effect = f
    api.py_aa_find_devices_ext.side_effect = f_ext
    api.py_aa_open_ext.side_effect = open
    api.py_aa_unique_id.side_effect = unique_id

    a = pyaardvark.open(serial_number='1234-567890')
    assert a.handle == 42

    a = pyaardvark.open(serial_number='1111-222222')
    assert a.handle == 4711

    with pytest.raises(IOError):
        pyaardvark.open(serial_number='7777-888888')

@patch('pyaardvark.aardvark.api', autospec=True)
def test_open_versions(api):
    api.py_aa_open_ext.return_value = (1, (0x101, 0x202, 0x303, 0, 0, 0))
    a = pyaardvark.open()
    assert a.api_version == '1.01'
    assert a.firmware_version == '2.02'
    assert a.hardware_revision == '3.03'

@patch('pyaardvark.aardvark.api', autospec=True)
def test_open_error(api):
    with pytest.raises(IOError):
        api.py_aa_open_ext.return_value = (-1, (0,) * 6)
        pyaardvark.open()

@patch('pyaardvark.aardvark.api', autospec=True)
def test_open_version_check_firmware(api):
    with pytest.raises(IOError):
        api.py_aa_open_ext.return_value = (1, (0, 100, 0, 0, 200, 0))
        pyaardvark.open()

@patch('pyaardvark.aardvark.api', autospec=True)
def test_open_version_check_api(api):
    with pytest.raises(IOError):
        api.py_aa_open_ext.return_value = (1, (100, 0, 0, 200, 0, 0))
        pyaardvark.open()

@patch('pyaardvark.aardvark.api', autospec=True)
class TestAardvark(object):
    @patch('pyaardvark.aardvark.api', autospec=True)
    def setup_method(self, method, api):
        api.py_aa_open_ext.return_value = (1, (0,) * 6)
        self.a = pyaardvark.open()

    def test_close(self, api):
        handle = self.a.handle
        self.a.close()
        api.py_aa_close.assert_called_once_with(handle)

    def test_context_manager(self, api):
        api.py_aa_open_ext.return_value = (1, (0,) * 6)
        with pyaardvark.open() as a:
            assert a.handle == api.py_aa_open_ext.return_value[0]
            api.py_aa_open_ext.assert_called_once_with(0)
        api.py_aa_close.assert_called_once_with(
                api.py_aa_open_ext.return_value[0])

    def test_unique_id(self, api):
        api.py_aa_unique_id.return_value = 3627028473
        unique_id = self.a.unique_id()
        api.py_aa_unique_id.assert_called_once_with(self.a.handle)
        assert unique_id == api.py_aa_unique_id.return_value

    def test_unique_id_str(self, api):
        api.py_aa_unique_id.return_value = 627008473
        unique_id_str = self.a.unique_id_str()
        api.py_aa_unique_id.assert_called_once_with(self.a.handle)
        assert unique_id_str == '0627-008473'

    def test_enable_i2c_1(self, api):
        api.py_aa_configure.return_value = CONFIG_SPI_GPIO
        assert not self.a.enable_i2c
        self.a.enable_i2c = True
        api.py_aa_configure.assert_called_with(self.a.handle, CONFIG_SPI_I2C)

    def test_enable_i2c_2(self, api):
        api.py_aa_configure.return_value = CONFIG_GPIO_ONLY
        assert not self.a.enable_i2c
        self.a.enable_i2c = True
        api.py_aa_configure.assert_called_with(self.a.handle, CONFIG_GPIO_I2C)

    def test_enable_i2c_3(self, api):
        api.py_aa_configure.return_value = CONFIG_GPIO_I2C
        assert self.a.enable_i2c
        self.a.enable_i2c = False
        api.py_aa_configure.assert_called_with(self.a.handle, CONFIG_GPIO_ONLY)

    def test_enable_i2c_4(self, api):
        api.py_aa_configure.return_value = CONFIG_SPI_I2C
        assert self.a.enable_i2c
        self.a.enable_i2c = False
        api.py_aa_configure.assert_called_with(self.a.handle, CONFIG_SPI_GPIO)

    def test_enable_i2c_error(self, api):
        with pytest.raises(IOError):
            api.py_aa_configure.return_value = -1
            self.a.enable_i2c = True

    def test_enable_spi_1(self, api):
        api.py_aa_configure.return_value = CONFIG_GPIO_I2C
        assert not self.a.enable_spi
        self.a.enable_spi = True
        api.py_aa_configure.assert_called_with(self.a.handle, CONFIG_SPI_I2C)

    def test_enable_spi_2(self, api):
        api.py_aa_configure.return_value = CONFIG_GPIO_ONLY
        assert not self.a.enable_spi
        self.a.enable_spi = True
        api.py_aa_configure.assert_called_with(self.a.handle, CONFIG_SPI_GPIO)

    def test_enable_spi_3(self, api):
        api.py_aa_configure.return_value = CONFIG_SPI_GPIO
        assert self.a.enable_spi
        self.a.enable_spi = False
        api.py_aa_configure.assert_called_with(self.a.handle, CONFIG_GPIO_ONLY)

    def test_enable_spi_4(self, api):
        api.py_aa_configure.return_value = CONFIG_SPI_I2C
        assert self.a.enable_spi
        self.a.enable_spi = False
        api.py_aa_configure.assert_called_with(self.a.handle, CONFIG_GPIO_I2C)

    def test_enable_spi_error(self, api):
        with pytest.raises(IOError):
            api.py_aa_configure.return_value = -1
            self.a.enable_spi = True

    def test_i2c_bitrate(self, api):
        api.py_aa_i2c_bitrate.return_value = 100
        self.a.i2c_bitrate = 4711
        assert self.a.i2c_bitrate == 100

        api.py_aa_i2c_bitrate.assert_has_calls([
                call(self.a.handle, 4711),
                call(self.a.handle, 0),
        ])

    def test_i2c_bitrate_error(self, api):
        with pytest.raises(IOError):
            api.py_aa_i2c_bitrate.return_value = -1
            self.a.i2c_bitrate = 0

    def test_i2c_pullups(self, api):
        api.py_aa_i2c_pullup.return_value = 0
        self.a.i2c_pullups = False
        self.a.i2c_pullups = True
        pullup = self.a.i2c_pullups
        api.py_aa_i2c_pullup.assert_has_calls([
            call(self.a.handle, pyaardvark.I2C_PULLUP_NONE),
            call(self.a.handle, pyaardvark.I2C_PULLUP_BOTH),
            call(self.a.handle, pyaardvark.I2C_PULLUP_QUERY),
        ])

    def test_i2c_pullups_error(self, api):
        with pytest.raises(IOError):
            api.py_aa_i2c_pullup.return_value = -1
            _ = self.a.i2c_pullups

    def test_enable_target_power(self, api):
        api.py_aa_target_power.return_value = 0
        self.a.target_power = False
        self.a.target_power = True
        _ = self.a.target_power
        api.py_aa_target_power.assert_has_calls([
            call(self.a.handle, pyaardvark.TARGET_POWER_NONE),
            call(self.a.handle, pyaardvark.TARGET_POWER_BOTH),
            call(self.a.handle, pyaardvark.TARGET_POWER_QUERY),
        ])

    def test_enable_target_power_error(self, api):
        with pytest.raises(IOError):
            api.py_aa_target_power.return_value = -1
            self.a.target_power = 0

    def test_i2c_bus_timeout(self, api):
        api.py_aa_i2c_bus_timeout.return_value = 10
        self.a.i2c_bus_timeout = 300
        assert self.a.i2c_bus_timeout == 10

        api.py_aa_i2c_bus_timeout.assert_has_calls([
                call(self.a.handle, 300),
                call(self.a.handle, 0),
        ])

    def test_i2c_master_write(self, api):
        addr = 0x50
        data = b'\x01\x02\x03'
        flags = pyaardvark.I2C_NO_STOP | pyaardvark.I2C_SIZED_READ
        api.py_aa_i2c_write_ext.return_value = (I2C_STATUS_OK, 3)
        self.a.i2c_master_write(addr, data, flags)
        api.py_aa_i2c_write_ext.assert_called_once_with(
                self.a.handle, addr, flags, len(data), array.array('B', data))

    def test_i2c_master_write_default_flags(self, api):
        api.py_aa_i2c_write_ext.return_value = (I2C_STATUS_OK, 0)
        self.a.i2c_master_write(0, b'')
        api.py_aa_i2c_write_ext.assert_called_once_with(
                ANY, ANY, pyaardvark.I2C_NO_FLAGS, ANY, ANY)

    def test_i2c_master_write_error(self, api):
        with pytest.raises(IOError):
            api.py_aa_i2c_write_ext.return_value = (I2C_STATUS_BUS_ERROR, 0)
            self.a.i2c_master_write(0, b'')

    def test_i2c_master_read(self, api):
        def i2c_master_read(_handle, _addr, _flags, length, data):
            assert data == array.array('B', (0,) * length)
            for i in range(length):
                data[i] = i
            return (I2C_STATUS_OK, length)

        addr = 0x50
        flags = pyaardvark.I2C_NO_STOP | pyaardvark.I2C_SIZED_READ
        api.py_aa_i2c_read_ext.side_effect = i2c_master_read
        data = self.a.i2c_master_read(addr, 3, flags)
        api.py_aa_i2c_read_ext.assert_called_once_with(
                self.a.handle, addr, flags, 3, ANY)
        assert data == b'\x00\x01\x02'

    def test_i2c_master_read_default_flags(self, api):
        api.py_aa_i2c_read_ext.return_value = (I2C_STATUS_OK, 1)
        _ = self.a.i2c_master_read(0, 0)
        api.py_aa_i2c_read_ext.assert_called_once_with(
                ANY, ANY, pyaardvark.I2C_NO_FLAGS, ANY, ANY)

    def test_i2c_master_read_error(self, api):
        with pytest.raises(IOError):
            api.py_aa_i2c_read_ext.return_value = (I2C_STATUS_BUS_ERROR, -1)
            self.a.i2c_master_read(0, 0)

    def test_i2c_master_write_read(self, api):
        def i2c_master_read(_handle, _addr, _flags, length, data):
            assert data == array.array('B', (0,) * length)
            for i in range(length):
                data[i] = i
            return (I2C_STATUS_OK, length)

        api.py_aa_i2c_write_ext.return_value = (I2C_STATUS_OK, 0)
        api.py_aa_i2c_read_ext.side_effect = i2c_master_read
        addr = 0x50
        data = self.a.i2c_master_write_read(addr, b'\x00\x01\x02', 3)
        api.py_aa_i2c_write_ext.assert_called_once_with(self.a.handle, addr,
                pyaardvark.I2C_NO_STOP, len(data), array.array('B', data))
        api.py_aa_i2c_read_ext.assert_called_once_with(
                self.a.handle, addr, pyaardvark.I2C_NO_FLAGS, 3, ANY)
        assert data == b'\x00\x01\x02'

    def test_i2c_master_write_read_default_flags(self, api):
        api.py_aa_i2c_read_ext.return_value = (I2C_STATUS_OK, 1)
        data = self.a.i2c_master_read(0, 0)
        api.py_aa_i2c_read_ext.assert_called_once_with(
                ANY, ANY, pyaardvark.I2C_NO_FLAGS, ANY, ANY)

    def test_i2c_master_write_read_error_read(self, api):
        with pytest.raises(IOError):
            api.py_aa_i2c_write_ext.return_value = (I2C_STATUS_OK, 1)
            api.py_aa_i2c_read_ext.return_value = (I2C_STATUS_BUS_ERROR, -1)
            self.a.i2c_master_write_read(0, b'', 0)

    def test_i2c_master_write_read_error_write(self, api):
        with pytest.raises(IOError):
            api.py_aa_i2c_write_ext.return_value = (I2C_STATUS_BUS_ERROR, -1)
            api.py_aa_i2c_read_ext.return_value = (I2C_STATUS_OK, 1)
            self.a.i2c_master_write_read(0, b'', 0)

    def test_i2c_stop(self, api):
        api.py_aa_i2c_free_bus.return_value = 0
        self.a.i2c_stop()
        api.py_aa_i2c_free_bus.assert_called_once_with(self.a.handle)

    def test_i2c_stop_ignore_error(self, api):
        api.py_aa_i2c_free_bus.return_value = -1
        self.a.i2c_stop(ignore_errors=True)
        api.py_aa_i2c_free_bus.assert_called_once_with(self.a.handle)

    def test_i2c_stop_error(self, api):
        with pytest.raises(IOError):
            api.py_aa_i2c_free_bus.return_value = -1
            self.a.i2c_stop(ignore_errors=False)

    def test_enable_i2c_slave(self, api):
        api.py_aa_i2c_slave_enable.return_value = 0
        addr = 0x50
        self.a.enable_i2c_slave(addr)
        api.py_aa_i2c_slave_enable.assert_called_once_with(
                self.a.handle, addr, self.a.BUFFER_SIZE, self.a.BUFFER_SIZE)

    def test_enable_i2c_slave_error(self, api):
        with pytest.raises(IOError):
            api.py_aa_i2c_slave_enable.return_value = -1
            self.a.enable_i2c_slave(0)

    def test_disable_i2c_slave(self, api):
        api.py_aa_i2c_slave_disable.return_value = 0
        self.a.disable_i2c_slave()
        api.py_aa_i2c_slave_disable.assert_called_once_with(self.a.handle)

    def test_disable_i2c_slave_error(self, api):
        with pytest.raises(IOError):
            api.py_aa_i2c_slave_disable.return_value = -1
            self.a.disable_i2c_slave()

    def test_i2c_slave_read(self, api):
        addr = 0x50
        def i2c_slave_read(_handle, length, data):
            assert data == array.array('B', (0,) * length)
            length = 3
            for i in range(length):
                data[i] = i
            return (I2C_STATUS_OK, addr, length)

        api.py_aa_i2c_slave_read_ext.side_effect = i2c_slave_read
        ret = self.a.i2c_slave_read()
        api.py_aa_i2c_slave_read_ext.assert_called_once_with(
                self.a.handle, self.a.BUFFER_SIZE, ANY)
        assert ret == (addr, b'\x00\x01\x02')

    def test_i2c_slave_read_error(self, api):
        with pytest.raises(IOError):
            api.py_aa_i2c_slave_read_ext.return_value = (-1, 0, I2C_STATUS_BUS_ERROR)
            self.a.i2c_slave_read()

    def test_i2c_slave_set_response(self, api):
        api.py_aa_i2c_slave_set_response.return_value = 0
        data = range(4)
        self.a.i2c_slave_response = data
        api.py_aa_i2c_slave_set_response.assert_called_once_with(
                self.a.handle, len(data), array.array('B', data))

    def test_i2c_slave_set_response_error(self, api):
        with pytest.raises(IOError):
            api.py_aa_i2c_slave_set_response.return_value = -1
            data = range(4)
            self.a.i2c_slave_response = data

    def test_last_transmit_size(self, api):
        expected_result = 23
        api.py_aa_i2c_slave_write_stats.return_value = expected_result
        actual_result = self.a.i2c_slave_last_transmit_size
        api.py_aa_i2c_slave_write_stats.assert_called_once_with(self.a.handle)
        assert actual_result == expected_result

    def test_last_transmit_size_error(self, api):
        with pytest.raises(IOError):
            api.py_aa_i2c_slave_write_stats.return_value = -1
            _ = self.a.i2c_slave_last_transmit_size

    def test_spi_bitrate(self, api):
        api.py_aa_spi_bitrate.return_value = 1000
        self.a.spi_bitrate = 4711
        assert self.a.spi_bitrate == 1000

        api.py_aa_spi_bitrate.assert_has_calls([
                call(self.a.handle, 4711),
                call(self.a.handle, 0),
        ])

    def test_spi_bitrate_error(self, api):
        with pytest.raises(IOError):
            api.py_aa_spi_bitrate.return_value = -1
            self.a.spi_bitrate = 0

    def test_spi_configure(self, api):
        api.py_aa_spi_configure.return_value = 0
        self.a.spi_configure(1, 2, 3)
        api.py_aa_spi_configure.assert_called_once_with(self.a.handle, 1, 2, 3)

    def test_spi_configure_error(self, api):
        with pytest.raises(IOError):
            api.py_aa_spi_configure.return_value = -1
            self.a.spi_configure(0, 0, 0)

    def test_spi_configure_mode_0(self, api):
        api.py_aa_spi_configure.return_value = 0
        self.a.spi_configure_mode(pyaardvark.SPI_MODE_0)
        api.py_aa_spi_configure.assert_called_once_with(self.a.handle, 0, 0, 0)

    def test_spi_configure_mode_3(self, api):
        api.py_aa_spi_configure.return_value = 0
        self.a.spi_configure_mode(pyaardvark.SPI_MODE_3)
        api.py_aa_spi_configure.assert_called_once_with(self.a.handle, 1, 1, 0)

    def test_spi_configure_mode_error(self, api):
        with pytest.raises(IOError):
            api.py_aa_spi_configure.return_value = -1
            self.a.spi_configure_mode(0)

    def test_spi_configure_mode_unknown_mode(self, api):
        with pytest.raises(RuntimeError):
            self.a.spi_configure_mode(1)

    def test_spi_ss_polarity(self, api):
        api.py_aa_spi_master_ss_polarity.return_value = 0
        self.a.spi_ss_polarity(42)
        api.py_aa_spi_master_ss_polarity.assert_called_once_with(self.a.handle, 42)

    def test_spi_ss_polarity_error(self, api):
        with pytest.raises(IOError):
            api.py_aa_spi_master_ss_polarity.return_value = -1
            self.a.spi_ss_polarity(0)

    def test_spi_spi_write(self, api):
        def spi_write(_handle, len_tx, tx, len_rx, rx):
            assert len_tx == len_rx
            for i, v in enumerate(reversed(tx)):
                rx[i] = v
            return len_tx
        api.py_aa_spi_write.side_effect = spi_write
        data = b'\x01\x02\x03'
        rx_data = self.a.spi_write(data)
        assert rx_data == data[::-1]
        api.py_aa_spi_write.assert_called_once_with(self.a.handle, len(data),
                array.array('B', data), len(data), ANY)

    def test_spi_spi_write_error(self, api):
        with pytest.raises(IOError):
            api.py_aa_spi_write.return_value = -1
            self.a.spi_write(b'')
