# Test methods with long descriptive names can omit docstrings
# pylint: disable=missing-docstring,unsubscriptable-object,protected-access
import unittest
from unittest.mock import Mock

from AnyQt.QtCore import Qt, QPoint
from AnyQt.QtWidgets import QWidget, QApplication, QStyleOptionViewItem

from orangewidget.widget import StateInfo

from Orange.data import Table, DiscreteVariable
from Orange.widgets.data.owdiscretize import OWDiscretize, Default, EqualFreq, \
    Remove, Leave, Custom, IncreasingNumbersListValidator, DiscDelegate, MDL, \
    EqualWidth, DState, show_tip
from Orange.widgets.tests.base import WidgetTest
from Orange.widgets.tests.base import GuiTest
from Orange.widgets.utils.state_summary import format_summary_details
from Orange.widgets.utils.itemmodels import select_row, VariableListModel


class TestOWDiscretize(WidgetTest):
    def setUp(self):
        super().setUp()
        self.widget = self.create_widget(OWDiscretize)

    def test_empty_data(self):
        data = Table("iris")
        widget = self.widget
        self.send_signal(self.widget.Inputs.data,
                         Table.from_domain(data.domain))
        for m in (OWDiscretize.Leave, OWDiscretize.MDL, OWDiscretize.EqualFreq,
                  OWDiscretize.EqualWidth, OWDiscretize.Remove,
                  OWDiscretize.Custom):
            widget.default_method = m
            widget.unconditional_commit()
            self.assertIsNotNone(self.get_output(widget.Outputs.data))

    def test_summary(self):
        """Check if status bar is updated when data is received"""
        input_sum = self.widget.info.set_input_summary = Mock()
        output_sum = self.widget.info.set_output_summary = Mock()

        data = Table("iris")
        self.send_signal(self.widget.Inputs.data, data)
        input_sum.assert_called_with(len(data), format_summary_details(data))
        output = self.get_output(self.widget.Outputs.data)
        output_sum.assert_called_with(len(output),
                                      format_summary_details(output))

        input_sum.reset_mock()
        output_sum.reset_mock()
        self.send_signal(self.widget.Inputs.data, None)
        input_sum.assert_called_once()
        self.assertIsInstance(input_sum.call_args[0][0], StateInfo.Empty)
        output_sum.assert_called_once()
        self.assertIsInstance(output_sum.call_args[0][0], StateInfo.Empty)

    def test_select_method(self):
        widget = self.widget
        data = Table("iris")[::5]
        self.send_signal(self.widget.Inputs.data, data)

        model = widget.varmodel
        view = widget.varview
        defbg = widget.default_button_group
        varbg = widget.variable_button_group
        self.assertSequenceEqual(list(model), data.domain.attributes)
        defbg.button(OWDiscretize.EqualFreq).click()
        self.assertEqual(widget.default_method, OWDiscretize.EqualFreq)
        self.assertTrue(
            all(isinstance(m, Default) and isinstance(m.method, EqualFreq)
                for m in map(widget.method_for_index,
                             range(len(data.domain.attributes)))))

        # change method for first variable
        select_row(view, 0)
        varbg.button(OWDiscretize.Remove).click()
        met = widget.method_for_index(0)
        self.assertIsInstance(met, Remove)

        # select a second var
        selmodel = view.selectionModel()
        selmodel.select(model.index(2), selmodel.Select)
        # the current checked button must unset
        self.assertEqual(varbg.checkedId(), -1)

        varbg.button(OWDiscretize.Leave).click()
        self.assertIsInstance(widget.method_for_index(0), Leave)
        self.assertIsInstance(widget.method_for_index(2), Leave)
        # reset both back to default
        varbg.button(OWDiscretize.Default).click()
        self.assertIsInstance(widget.method_for_index(0), Default)
        self.assertIsInstance(widget.method_for_index(2), Default)

    def test_migration(self):
        w = self.create_widget(OWDiscretize, stored_settings={
            "default_method": 0
        })
        self.assertEqual(w.default_method, OWDiscretize.Leave)

    def test_manual_cuts_edit(self):
        widget = self.widget
        data = Table("iris")[::5]
        self.send_signal(self.widget.Inputs.data, data)
        view = widget.varview
        varbg = widget.variable_button_group
        widget.set_default_method(OWDiscretize.Custom)
        widget.default_cutpoints = (0, 2, 4)
        ledit = widget.manual_cuts_edit
        self.assertEqual(ledit.text(), "0, 2, 4")
        ledit.setText("3, 4, 5")
        ledit.editingFinished.emit()
        self.assertEqual(widget.default_cutpoints, (3, 4, 5))
        self.assertEqual(widget._current_default_method(), Custom((3, 4, 5)))
        self.assertTrue(
            all(widget.method_for_index(i) == Default(Custom((3, 4, 5)))
                for i in range(len(data.domain.attributes)))
        )
        select_row(view, 0)
        varbg.button(OWDiscretize.Custom).click()
        ledit = widget.manual_cuts_specific
        ledit.setText("1, 2, 3")
        ledit.editingFinished.emit()
        self.assertEqual(widget.method_for_index(0), Custom((1, 2, 3)))
        ledit.setText("")
        ledit.editingFinished.emit()
        self.assertEqual(widget.method_for_index(0), Custom(()))

    def test_manual_cuts_copy(self):
        widget = self.widget
        data = Table("iris")[::5]
        self.send_signal(self.widget.Inputs.data, data)
        view = widget.varview
        select_row(view, 0)
        varbg = widget.variable_button_group
        varbg.button(OWDiscretize.EqualWidth).click()
        v = widget.discretized_var(0)
        points = tuple(v.compute_value.points)
        cc_button = widget.copy_current_to_manual_button
        cc_button.click()
        self.assertEqual(widget.method_for_index(0), Custom(points))
        self.assertEqual(varbg.checkedId(), OWDiscretize.Custom)

    def test_report(self):
        widget = self.widget
        data = Table("iris")[::5]
        self.send_signal(widget.Inputs.data, data)
        widget.send_report()


class TestValidator(unittest.TestCase):
    def test_validate(self):
        v = IncreasingNumbersListValidator()
        self.assertEqual(v.validate("", 0), (v.Acceptable, '', 0))
        self.assertEqual(v.validate("1", 1), (v.Acceptable, '1', 1))
        self.assertEqual(v.validate(",", 0), (v.Acceptable, ',', 0))
        self.assertEqual(v.validate("-", 0), (v.Intermediate, '-', 0))
        self.assertEqual(v.validate("1,,", 1), (v.Acceptable, '1,,', 1))
        self.assertEqual(v.validate("1,a,", 1), (v.Invalid, '1,a,', 1))
        self.assertEqual(v.validate("a", 1), (v.Invalid, 'a', 1))
        self.assertEqual(v.validate("1,1", 0), (v.Intermediate, '1,1', 0))
        self.assertEqual(v.validate("1,12", 0), (v.Acceptable, '1,12', 0))

    def test_fixup(self):
        v = IncreasingNumbersListValidator()
        self.assertEqual(v.fixup(""), "")
        self.assertEqual(v.fixup("1,,2"), "1, 2")
        self.assertEqual(v.fixup("1,,"), "1")
        self.assertEqual(v.fixup("1,"), "1")
        self.assertEqual(v.fixup(",1"), "1")
        self.assertEqual(v.fixup(","), "")


class TestDelegate(GuiTest):
    def test_delegate(self):
        cases = (
            (DState(Default(Leave()), None, None), ""),
            (DState(Leave(), None, None), "(leave)"),
            (DState(MDL(), [1], None), "(entropy)"),
            (DState(MDL(), [], None), "<removed>"),
            (DState(EqualFreq(2), [1], None), "(equal frequency k=2)"),
            (DState(EqualWidth(2), [1], None), "(equal width k=2)"),
            (DState(Remove(), None, None), "(removed)"),
            (DState(Custom([1]), None, None), "(custom)"),
        )
        delegate = DiscDelegate()
        var = DiscreteVariable("C", ("a", "b"))
        model = VariableListModel()
        model.append(var)
        for state, text in cases:
            model.setData(model.index(0), state, Qt.UserRole)
            option = QStyleOptionViewItem()
            delegate.initStyleOption(option, model.index(0))
            self.assertIn(text, option.text)


class TestShowTip(GuiTest):
    def test_show_tip(self):
        w = QWidget()
        show_tip(w, QPoint(100, 100), "Ha Ha")
        app = QApplication.instance()
        windows = app.topLevelWidgets()
        label = [tl for tl in windows
                 if tl.parent() is w and tl.objectName() == "tip-label"][0]
        self.assertTrue(label.isVisible())
        self.assertTrue(label.text() == "Ha Ha")

        show_tip(w, QPoint(100, 100), "Ha")
        self.assertTrue(label.text() == "Ha")
        show_tip(w, QPoint(100, 100), "")
        self.assertFalse(label.isVisible())
