#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2014-18 Richard Hull and contributors
# See LICENSE.rst for details.

import pytest

from luma.led_matrix.device import neopixel
from luma.core.render import canvas

from unittest.mock import Mock, call


ws = Mock(unsafe=True)
chan = "channel"
leds = "leds"


def setup_function(function):
    ws.reset_mock()
    ws.command.side_effect = None
    ws.ws2811_init = Mock(return_value=0)
    ws.ws2811_render = Mock(return_value=0)
    ws.ws2811_channel_get = Mock(return_value=chan)
    ws.ws2811_new_ws2811_t = Mock(return_value=leds)


def test_init_cascaded():
    device = neopixel(ws, cascaded=7)
    assert device.width == 7
    assert device.height == 1
    assert ws.ws2811_channel_t_count_set.called
    assert ws.ws2811_channel_t_gpionum_set.called
    assert ws.ws2811_channel_t_invert_set.called
    assert ws.ws2811_channel_t_brightness_set.called
    assert ws.ws2811_channel_t_strip_type_set.called
    assert ws.ws2811_t_freq_set.called
    assert ws.ws2811_t_dmanum_set.called
    assert ws.ws2811_init.called
    ws.ws2811_led_set.assert_has_calls([
        call(chan, i, 0x000000) for i in range(7)])
    assert ws.ws2811_render.called


def test_init_4x8():
    device = neopixel(ws)
    assert device.cascaded == 32
    assert ws.ws2811_channel_t_count_set.called
    assert ws.ws2811_channel_t_gpionum_set.called
    assert ws.ws2811_channel_t_invert_set.called
    assert ws.ws2811_channel_t_brightness_set.called
    assert ws.ws2811_channel_t_strip_type_set.called
    assert ws.ws2811_t_freq_set.called
    assert ws.ws2811_t_dmanum_set.called
    assert ws.ws2811_init.called
    ws.ws2811_led_set.assert_has_calls([
        call(chan, i, 0x000000) for i in range(32)])
    assert ws.ws2811_render.called


def test_init_fail():
    ws.reset_mock()
    ws.ws2811_init = Mock(return_value=-1)
    with pytest.raises(RuntimeError) as ex:
        neopixel(ws, cascaded=7)
    assert "ws2811_init failed with code -1" in str(ex.value)


def test_clear():
    device = neopixel(ws)
    ws.reset_mock()
    device.clear()
    ws.ws2811_led_set.assert_has_calls([
        call(chan, i, 0x000000) for i in range(32)])
    assert ws.ws2811_render.called


def test_cleanup():
    device = neopixel(ws)
    device.cleanup()
    ws.ws2811_led_set.assert_has_calls([
        call(chan, i, 0x000000) for i in range(32)])
    assert ws.ws2811_render.called
    assert ws.ws2811_fini.called
    assert ws.delete_ws2811_t.called
    assert device._leds is None
    assert device._channel is None


def test_hide():
    device = neopixel(ws, cascaded=5)
    ws.reset_mock()
    device.hide()
    ws.ws2811_led_set.assert_not_called()
    assert ws.ws2811_render.called


def test_show():
    device = neopixel(ws, cascaded=5)
    ws.reset_mock()
    device.hide()
    device.show()
    ws.ws2811_led_set.assert_not_called()
    assert ws.ws2811_render.called


def test_contrast():
    device = neopixel(ws, cascaded=6)
    ws.reset_mock()
    device.contrast(0x6B)
    ws.ws2811_channel_t_brightness_set.assert_called_once_with(chan, 0x6B)


def test_display():
    device = neopixel(ws, width=4, height=4)
    ws.reset_mock()

    with canvas(device) as draw:
        draw.rectangle(device.bounding_box, outline="red")

    ws.ws2811_led_set.assert_has_calls([
        call(chan, 0, 0xFF0000),
        call(chan, 1, 0xFF0000),
        call(chan, 2, 0xFF0000),
        call(chan, 3, 0xFF0000),
        call(chan, 4, 0xFF0000),
        call(chan, 5, 0x000000),
        call(chan, 6, 0x000000),
        call(chan, 7, 0xFF0000),
        call(chan, 8, 0xFF0000),
        call(chan, 9, 0x000000),
        call(chan, 10, 0x000000),
        call(chan, 11, 0xFF0000),
        call(chan, 12, 0xFF0000),
        call(chan, 13, 0xFF0000),
        call(chan, 14, 0xFF0000),
        call(chan, 15, 0xFF0000),
    ])
    assert ws.ws2811_render.called


def test_display_fail():
    device = neopixel(ws, cascaded=7)
    ws.reset_mock()
    ws.ws2811_render = Mock(return_value=-1)

    with pytest.raises(RuntimeError) as ex:
        with canvas(device) as draw:
            draw.rectangle(device.bounding_box, outline="red")

    assert "ws2811_render failed with code -1" in str(ex.value)


def test_mapping():
    num_pixels = 16
    device = neopixel(ws, cascaded=num_pixels, mapping=reversed(list(range(num_pixels))))
    ws.reset_mock()

    with canvas(device) as draw:
        for i in range(device.cascaded):
            draw.point((i, 0), (i, 0, 0))

    expected = [call(chan, num_pixels - i - 1, i << 16) for i in range(num_pixels)]
    ws.ws2811_led_set.assert_has_calls(expected)

    assert ws.ws2811_render.called
