import pytest
import logging
import numpy as np
from collections import OrderedDict
from pyqtgraph import AxisItem, BarGraphItem
from unittest import mock
from ...widgets.channel import PyDMChannel
from ...widgets.scatterplot import ScatterPlotCurveItem, PyDMScatterPlot, MINIMUM_BUFFER_SIZE, DEFAULT_BUFFER_SIZE
from ...utilities import remove_protocol

logger = logging.getLogger(__name__)


@pytest.mark.parametrize(
    "y_addr, x_addr, redraw_mode, bufferSizeChannelAddress, name",
    [
        ("ca://test_value:FloatY", "ca://test_value:FloatX", None, None, "test_name"),
        ("ca://test_value:FloatY", "ca://test_value:FloatX", None, None, ""),
        ("ca://test_value:FloatY", "ca://test_value:FloatX", None, None, None),
        ("", "", None, None, None),
        (None, None, None, None, None),
        ("ca://test_value:FloatY", "ca://test_value:FloatX", ScatterPlotCurveItem.REDRAW_ON_BOTH, None, ""),
        ("ca://test_value:FloatY", "ca://test_value:FloatX", None, "ca://test_value:Int", "test_name"),
    ]
)
def test_scatterplotcurveitem_construct(qtbot, y_addr, x_addr, redraw_mode, bufferSizeChannelAddress, name):
    plot_curve_item = ScatterPlotCurveItem(y_addr,
                                           x_addr,
                                           redraw_mode=redraw_mode,
                                           bufferSizeChannelAddress=bufferSizeChannelAddress,
                                           name=name)
    assert plot_curve_item is not None
    assert isinstance(plot_curve_item, ScatterPlotCurveItem)

    assert plot_curve_item._bufferSize == DEFAULT_BUFFER_SIZE
    assert plot_curve_item.redraw_mode == redraw_mode or ScatterPlotCurveItem.REDRAW_ON_EITHER
    assert np.array_equal(
        plot_curve_item.data_buffer,
        np.zeros((2, plot_curve_item._bufferSize), order='f', dtype=float))
    for item in "x_connected y_connected bufferSizeChannel_connected".split():
        assert getattr(plot_curve_item, item) is False
    assert plot_curve_item.points_accumulated == 0
    for item in "latest_x_value latest_y_value".split():
        assert getattr(plot_curve_item, item) is None

    keys = (
        ("x_address", x_addr),
        ("y_address", y_addr),
        ("bufferSizeChannelAddress", bufferSizeChannelAddress),
    )
    for attr, kw in keys:
        obj = getattr(plot_curve_item, attr)
        expect = kw if kw else None
        assert obj == expect, f"{attr} {kw} {expect}"


@pytest.mark.parametrize(
    "y_addr, x_addr, redraw_mode, bufferSizeChannelAddress, name",
    [
        ("ca://test_value:FloatY", "ca://test_value:FloatX", None, None, "test_name"),
        ("ca://test_value:FloatY", "ca://test_value:FloatX", None, None, ""),
        ("ca://test_value:FloatY", "ca://test_value:FloatX", None, None, None),
        ("", "", None, None, None),
        (None, None, None, None, None),
        ("ca://test_value:FloatY", "ca://test_value:FloatX", ScatterPlotCurveItem.REDRAW_ON_BOTH, None, ""),
        ("ca://test_value:FloatY", "ca://test_value:FloatX", None, "ca://test_value:Int", "test_name"),
    ]
)
def test_scatterplotcurveitem_to_dict(qtbot, y_addr, x_addr, redraw_mode, bufferSizeChannelAddress, name):
    plot_curve_item = ScatterPlotCurveItem(y_addr,
                                           x_addr,
                                           redraw_mode=redraw_mode,
                                           bufferSizeChannelAddress=bufferSizeChannelAddress,
                                           name=name)

    dictionary = plot_curve_item.to_dict()
    assert isinstance(dictionary, OrderedDict)

    y_name = remove_protocol(y_addr if y_addr is not None else "")
    x_name = remove_protocol(x_addr if x_addr is not None else "")
    if name is None:
        if y_addr is None and x_addr is None:
            assert dictionary["name"] == ""
        else:
            assert dictionary["name"] == f"{y_name} vs. {x_name}"
    else:
        assert dictionary["name"] == name


@pytest.mark.parametrize("new_address", [
    "new_address",
    "",
    None
])
def test_scatterplotcurveitem_properties_and_setters(qtbot, new_address):
    plot_curve_item = ScatterPlotCurveItem(new_address,
                                           new_address,
                                           bufferSizeChannelAddress=new_address)

    assert plot_curve_item.x_address in (None, new_address)
    assert plot_curve_item.y_address in (None, new_address)
    assert plot_curve_item.bufferSizeChannelAddress in (None, new_address)

    if new_address:
        assert isinstance(plot_curve_item.x_channel, PyDMChannel)
        assert plot_curve_item.x_channel.address == new_address
        assert plot_curve_item.y_channel.address == new_address
    else:
        assert plot_curve_item.x_channel is None
        assert plot_curve_item.y_channel is None
        assert plot_curve_item.bufferSizeChannel is None


def test_scatterplotcurveitem_connection_state_changed(qtbot, signals):
    plot_curve_item = ScatterPlotCurveItem(None, None)
    assert plot_curve_item.x_connected is False
    assert plot_curve_item.y_connected is False

    signals.connection_state_signal.connect(plot_curve_item.xConnectionStateChanged)
    signals.connection_state_signal.emit(True)
    assert plot_curve_item.x_connected


@pytest.mark.parametrize(
    "redraw_mode, new_data",
    [
        (ScatterPlotCurveItem.REDRAW_ON_EITHER, [(0,0), (1,1), (2,2), (2.5,3.1)]),
        (ScatterPlotCurveItem.REDRAW_ON_BOTH, [(0,0), (1,1), (2,2), (2.5,3.1)]),
        (ScatterPlotCurveItem.REDRAW_ON_X, [(0,0), (1,1), (2,2), (2.5,3.1)]),
        (ScatterPlotCurveItem.REDRAW_ON_Y, [(0,0), (1,1), (2,2), (2.5,3.1)]),
    ]
)
def test_scatterplotcurveitem_receive_values(qtbot, signals, redraw_mode, new_data):
    # REDRAW_ON_X, REDRAW_ON_Y, REDRAW_ON_EITHER, REDRAW_ON_BOTH
    plot_curve_item = ScatterPlotCurveItem(None, None, redraw_mode=redraw_mode)

    expected_data_buffer = np.zeros((2, plot_curve_item._bufferSize), order='f', dtype=float)
    expected_data_buffer[0] = plot_curve_item.data_buffer[0]
    assert np.array_equal(expected_data_buffer, plot_curve_item.data_buffer)

    # inject the new_data, point by point
    assert plot_curve_item.points_accumulated == 0
    for i, pair in enumerate(new_data):
        new_x, new_y = pair

        plot_curve_item.receiveXValue(new_x)
        assert plot_curve_item.latest_x_value == new_x
        assert plot_curve_item.points_accumulated == 2*i

        plot_curve_item.receiveYValue(new_y)
        assert plot_curve_item.latest_y_value == new_y
        assert plot_curve_item.points_accumulated == 2*i + 1


def test_scatterplotcurve_initialize_buffer(qtbot):
    plot_curve_item = ScatterPlotCurveItem(None, None)

    plot_curve_item.initialize_buffer()

    assert plot_curve_item.points_accumulated == 0
    expected_data_buffer = np.zeros((2, plot_curve_item._bufferSize), order='f', dtype=float)
    expected_data_buffer[0] = plot_curve_item.data_buffer[0]

    assert np.array_equal(expected_data_buffer, plot_curve_item.data_buffer)


@pytest.mark.parametrize("new_buffer_size, expected_set_buffer_size", [
    (0, MINIMUM_BUFFER_SIZE),
    (-5, MINIMUM_BUFFER_SIZE),
    (100, 100),
    (MINIMUM_BUFFER_SIZE + 1, MINIMUM_BUFFER_SIZE + 1)
])
def test_scatterplotcurve_get_set_reset_buffer_size(qtbot, new_buffer_size, expected_set_buffer_size):
    plot_curve_item = ScatterPlotCurveItem(None, None)

    assert plot_curve_item.getBufferSize() == DEFAULT_BUFFER_SIZE

    plot_curve_item.setBufferSize(new_buffer_size)
    assert plot_curve_item.getBufferSize() == expected_set_buffer_size

    plot_curve_item.resetBufferSize()
    assert plot_curve_item.getBufferSize() == DEFAULT_BUFFER_SIZE


@pytest.mark.parametrize(
    "addr, new_size, expected_buffer_size",
    [
        (None, None, DEFAULT_BUFFER_SIZE),
        ("", None, DEFAULT_BUFFER_SIZE),
        ("ca://test_value:Int", None, DEFAULT_BUFFER_SIZE),
    ]
)
def test_scatterplotcurve_get_set_reset_buffer_size(qtbot, addr, new_size, expected_buffer_size):
    plot_curve_item = ScatterPlotCurveItem(None, None, bufferSizeChannelAddress=addr)

    assert plot_curve_item.getBufferSize() == DEFAULT_BUFFER_SIZE

    if new_size is not None:
        plot_curve_item.bufferSizeChannelValueReceiver(new_size)
        assert plot_curve_item.getBufferSize() == new_size
        assert plot_curve_item.data_buffer.shape == (2, new_size)
