import array
import os
import tempfile

from PyQt5.QtCore import QDir, QPoint, Qt
from PyQt5.QtTest import QTest

from tests.QtTestCase import QtTestCase
from urh.controller.GeneratorTabController import GeneratorTabController
from urh.controller.MainController import MainController


class TestGenerator(QtTestCase):
    def test_generation(self):
        """
        Complex test including much functionality
        1) Load a Signal
        2) Set Decoding in Compareframe
        3) Move with encoding to Generator
        4) Generate datafile
        5) Read datafile and compare with original signal

        """
        # Load a Signal
        self.add_signal_to_form("ask.complex")
        signal_frame = self.form.signal_tab_controller.signal_frames[0]
        signal_frame.ui.cbModulationType.setCurrentIndex(0)  # ASK
        signal_frame.ui.spinBoxSamplesPerSymbol.setValue(300)
        signal_frame.ui.spinBoxSamplesPerSymbol.editingFinished.emit()
        signal_frame.ui.spinBoxCenterOffset.setValue(0.032)
        signal_frame.ui.spinBoxCenterOffset.editingFinished.emit()
        signal_frame.ui.cbProtoView.setCurrentIndex(0)

        proto = "1011001001011011011011011011011011001000000"
        self.assertTrue(signal_frame.ui.txtEdProto.toPlainText().startswith(proto))

        # Set Decoding
        self.form.ui.tabWidget.setCurrentIndex(1)
        cfc = self.form.compare_frame_controller
        cfc.ui.cbDecoding.setCurrentIndex(1)  # NRZ-I
        proto_inv = cfc.proto_analyzer.decoded_proto_bits_str[0]
        self.assertTrue(self.__is_inv_proto(proto, proto_inv))

        # Move with encoding to generator
        gframe = self.form.generator_tab_controller  # type: GeneratorTabController
        gframe.ui.cbViewType.setCurrentIndex(0)
        self.add_signal_to_generator(signal_index=0)
        self.assertEqual(
            array.array("B", list(map(int, proto_inv))),
            gframe.table_model.display_data[0],
        )
        self.assertNotEqual(
            array.array("B", list(map(int, proto))), gframe.table_model.display_data[0]
        )

        gframe.table_model.protocol.messages[0].pause = 0

        # Generate Datafile
        modulator = gframe.modulators[0]
        modulator.modulation_type = "ASK"
        modulator.samples_per_symbol = 300
        buffer = gframe.prepare_modulation_buffer(
            gframe.total_modulated_samples, show_error=False
        )
        modulated_data = gframe.modulate_data(buffer)
        filename = os.path.join(QDir.tempPath(), "test_generator.complex")
        modulated_data.tofile(filename)

        # Reload datafile and see if bits match
        self.form.add_signalfile(filename)
        self.assertEqual(len(self.form.signal_tab_controller.signal_frames), 2)
        signal_frame = self.form.signal_tab_controller.signal_frames[1]

        self.assertEqual(signal_frame.signal.num_samples, 300 * len(proto))
        signal_frame.ui.cbProtoView.setCurrentIndex(0)
        self.assertEqual(signal_frame.ui.lineEditSignalName.text(), "test_generator")
        signal_frame.ui.cbModulationType.setCurrentIndex(0)  # ASK
        signal_frame.ui.spinBoxNoiseTreshold.setValue(0)
        signal_frame.ui.spinBoxNoiseTreshold.editingFinished.emit()

        signal_frame.ui.spinBoxSamplesPerSymbol.setValue(295)
        signal_frame.ui.spinBoxSamplesPerSymbol.editingFinished.emit()

        signal_frame.ui.spinBoxCenterOffset.setValue(0.1)
        signal_frame.ui.spinBoxCenterOffset.editingFinished.emit()

        signal_frame.ui.spinBoxTolerance.setValue(6)
        signal_frame.ui.spinBoxTolerance.editingFinished.emit()

        self.assertEqual(len(signal_frame.proto_analyzer.messages), 1)
        gen_proto = signal_frame.ui.txtEdProto.toPlainText()
        gen_proto = gen_proto[: gen_proto.index(" ")]
        self.assertTrue(proto.startswith(gen_proto))

    def test_close_signal(self):
        self.add_signal_to_form("ask.complex")
        sframe = self.form.signal_tab_controller.signal_frames[0]
        sframe.ui.cbModulationType.setCurrentIndex(0)  # ASK
        sframe.ui.spinBoxSamplesPerSymbol.setValue(295)
        sframe.ui.spinBoxCenterOffset.setValue(-0.1667)
        sframe.refresh()

        # Move with encoding to generator
        gframe = self.form.generator_tab_controller
        gframe.ui.cbViewType.setCurrentIndex(0)
        item = gframe.tree_model.rootItem.children[0].children[0]
        index = gframe.tree_model.createIndex(0, 0, item)
        rect = gframe.ui.treeProtocols.visualRect(index)
        QTest.mousePress(
            gframe.ui.treeProtocols.viewport(), Qt.LeftButton, pos=rect.center()
        )
        self.assertEqual(gframe.ui.treeProtocols.selectedIndexes()[0], index)
        mimedata = gframe.tree_model.mimeData(gframe.ui.treeProtocols.selectedIndexes())
        gframe.table_model.dropMimeData(
            mimedata, 1, -1, -1, gframe.table_model.createIndex(0, 0)
        )
        self.assertEqual(
            gframe.table_model.row_count,
            self.form.compare_frame_controller.protocol_model.row_count,
        )
        self.form.ui.tabWidget.setCurrentIndex(0)
        self.form.on_selected_tab_changed(0)
        sframe.ui.btnCloseSignal.click()
        self.form.ui.tabWidget.setCurrentIndex(1)
        self.form.on_selected_tab_changed(1)
        self.form.ui.tabWidget.setCurrentIndex(2)
        self.form.on_selected_tab_changed(2)
        self.assertEqual(1, 1)

    def test_create_table_context_menu(self):
        # Context menu should only contain one item (add new message)
        self.assertEqual(self.form.generator_tab_controller.table_model.rowCount(), 0)
        self.form.generator_tab_controller.ui.tableMessages.context_menu_pos = QPoint(
            0, 0
        )
        menu = self.form.generator_tab_controller.ui.tableMessages.create_context_menu()
        self.assertEqual(len(menu.actions()), 1)

        # Add data to test entries in context menu
        self.add_signal_to_form("ask.complex")
        gframe = self.form.generator_tab_controller
        index = gframe.tree_model.createIndex(
            0, 0, gframe.tree_model.rootItem.children[0].children[0]
        )
        mimedata = gframe.tree_model.mimeData([index])
        gframe.table_model.dropMimeData(
            mimedata, 1, -1, -1, gframe.table_model.createIndex(0, 0)
        )

        self.assertGreater(self.form.generator_tab_controller.table_model.rowCount(), 0)
        menu = self.form.generator_tab_controller.ui.tableMessages.create_context_menu()
        n_items = len(menu.actions())
        self.assertGreater(n_items, 1)

        # If there is a selection, additional items should be present in context menu
        gframe.ui.tableMessages.selectRow(0)
        menu = self.form.generator_tab_controller.ui.tableMessages.create_context_menu()
        self.assertGreater(len(menu.actions()), n_items)

    def test_add_empty_row_behind(self):
        self.assertEqual(self.form.generator_tab_controller.table_model.rowCount(), 0)
        gframe = self.form.generator_tab_controller
        gframe.ui.cbViewType.setCurrentIndex(0)
        gframe.table_model.add_empty_row_behind(-1, 30)
        self.assertEqual(self.form.generator_tab_controller.table_model.rowCount(), 1)

        # Add data to test
        self.add_signal_to_form("ask.complex")

        index = gframe.tree_model.createIndex(
            0, 0, gframe.tree_model.rootItem.children[0].children[0]
        )
        mimedata = gframe.tree_model.mimeData([index])
        gframe.table_model.dropMimeData(
            mimedata, 1, -1, -1, gframe.table_model.createIndex(0, 0)
        )
        self.assertEqual(self.form.generator_tab_controller.table_model.rowCount(), 2)
        self.assertNotEqual(
            len(self.form.generator_tab_controller.table_model.display_data[1]), 30
        )
        gframe.table_model.add_empty_row_behind(0, 30)
        self.assertEqual(self.form.generator_tab_controller.table_model.rowCount(), 3)
        self.assertEqual(
            len(self.form.generator_tab_controller.table_model.display_data[1]), 30
        )
        self.assertNotEqual(
            len(self.form.generator_tab_controller.table_model.display_data[2]), 30
        )

    def test_create_fuzzing_list_view_context_menu(self):
        self.form.generator_tab_controller.ui.tabWidget.setCurrentIndex(2)
        # Context menu should be empty if table is empty
        self.assertEqual(self.form.generator_tab_controller.table_model.rowCount(), 0)
        self.form.generator_tab_controller.ui.tableMessages.context_menu_pos = QPoint(
            0, 0
        )
        menu = (
            self.form.generator_tab_controller.ui.listViewProtoLabels.create_context_menu()
        )
        self.assertEqual(len(menu.actions()), 0)

        # Add data to test entries in context menu
        self.add_signal_to_form("fsk.complex")
        self.form.compare_frame_controller.add_protocol_label(0, 10, 0, 0, False)
        self.assertEqual(
            1, len(self.form.compare_frame_controller.proto_analyzer.protocol_labels)
        )
        gframe = self.form.generator_tab_controller
        index = gframe.tree_model.createIndex(
            0, 0, gframe.tree_model.rootItem.children[0].children[0]
        )
        mimedata = gframe.tree_model.mimeData([index])
        gframe.table_model.dropMimeData(
            mimedata, 1, -1, -1, gframe.table_model.createIndex(0, 0)
        )

        self.assertGreater(self.form.generator_tab_controller.table_model.rowCount(), 0)
        # Select a row so there is a message for that fuzzing labels can be shown
        self.form.generator_tab_controller.ui.tableMessages.selectRow(0)
        menu = (
            self.form.generator_tab_controller.ui.listViewProtoLabels.create_context_menu()
        )
        n_items = len(menu.actions())
        self.assertGreater(n_items, 0)

    def test_pauses_widget(self):
        assert isinstance(self.form, MainController)
        self.form.generator_tab_controller.ui.tabWidget.setCurrentIndex(1)

        menu = self.form.generator_tab_controller.ui.lWPauses.create_context_menu()
        self.assertEqual(len(menu.actions()), 1)

        self.form.generator_tab_controller.ui.lWPauses.addItem("10")
        menu = self.form.generator_tab_controller.ui.lWPauses.create_context_menu()
        self.assertEqual(len(menu.actions()), 2)

    def test_add_column(self):
        # Add data to test
        self.add_signal_to_form("ask.complex")
        gframe = self.form.generator_tab_controller
        gframe.ui.cbViewType.setCurrentText("Bit")

        index = gframe.tree_model.createIndex(
            0, 0, gframe.tree_model.rootItem.children[0].children[0]
        )
        mimedata = gframe.tree_model.mimeData([index])
        gframe.table_model.dropMimeData(
            mimedata, 1, -1, -1, gframe.table_model.createIndex(0, 0)
        )
        self.assertEqual(self.form.generator_tab_controller.table_model.rowCount(), 1)

        l1 = len(self.form.generator_tab_controller.table_model.protocol.messages[0])
        self.form.generator_tab_controller.table_model.insert_column(0, [0])
        self.assertEqual(
            l1 + 1,
            len(self.form.generator_tab_controller.table_model.protocol.messages[0]),
        )

        self.form.generator_tab_controller.generator_undo_stack.undo()
        self.assertEqual(
            l1, len(self.form.generator_tab_controller.table_model.protocol.messages[0])
        )

        self.form.generator_tab_controller.generator_undo_stack.redo()
        self.assertEqual(
            l1 + 1,
            len(self.form.generator_tab_controller.table_model.protocol.messages[0]),
        )

    def test_clear(self):
        self.add_signal_to_form("ask.complex")
        self.add_signal_to_generator(0)

        gframe = self.form.generator_tab_controller  # type: GeneratorTabController
        rows = gframe.table_model.rowCount()
        self.assertGreater(rows, 0)
        gframe.ui.tableMessages.on_clear_action_triggered()
        self.assertEqual(gframe.table_model.rowCount(), 0)
        gframe.generator_undo_stack.undo()
        self.assertEqual(gframe.table_model.rowCount(), rows)

    def test_edit_data(self):
        # load some bits from txt
        filename = os.path.join(tempfile.gettempdir(), "testdata.txt")
        data = ["101010101111", "1010101011110000", "10101010000111111"]

        with open(filename, "w") as f:
            f.writelines("\n".join(data))

        self.form.add_files([filename])
        self.add_signal_to_generator(signal_index=0)
        self.form.generator_tab_controller.ui.cbViewType.setCurrentText("Bit")

        table_model = self.form.generator_tab_controller.table_model
        self.assertEqual(table_model.rowCount(), 3)

        self.assertEqual(table_model.display_data[1][7], 0)
        self.__set_model_data(table_model, row=1, column=7, value="1")
        self.assertEqual(table_model.display_data[1][7], 1)
        self.__set_model_data(table_model, row=1, column=7, value="0")
        self.assertEqual(table_model.display_data[1][7], 0)

        self.form.generator_tab_controller.ui.cbViewType.setCurrentText("Hex")
        self.assertEqual(table_model.display_data[2][1], 10)
        self.__set_model_data(table_model, row=2, column=1, value="e")
        self.assertEqual(table_model.display_data[2][1], 14)

        self.assertLess(len(table_model.display_data[1]), 5)
        self.__set_model_data(table_model, row=1, column=4, value="3")
        self.assertEqual(table_model.display_data[1][4], 3)

        self.assertEqual(table_model.protocol.plain_hex_str[0], "aaf")
        self.__set_model_data(table_model, row=0, column=4, value="b")
        self.assertEqual(table_model.protocol.plain_hex_str[0], "aaf0b")

    def test_fuzzing_label_list_view(self):
        self.add_signal_to_form("ask.complex")
        gframe = self.form.generator_tab_controller  # type: GeneratorTabController
        gframe.ui.cbViewType.setCurrentText("Bit")

        self.add_signal_to_generator(0)
        gframe.ui.tabWidget.setCurrentWidget(gframe.ui.tab_fuzzing)

        gframe.ui.tableMessages.selectRow(0)
        self.assertEqual(gframe.label_list_model.rowCount(), 0)
        gframe.create_fuzzing_label(0, 10, 20)
        self.assertEqual(gframe.label_list_model.rowCount(), 1)

        model = gframe.label_list_model
        lbl = model.labels[0]
        self.assertTrue(bool(lbl.fuzz_me))
        self.assertEqual(len(lbl.fuzz_values), 1)

        self.assertTrue(
            bool(model.data(model.index(0, 0), role=Qt.CheckStateRole)), True
        )
        model.setData(model.index(0, 0), Qt.Unchecked, role=Qt.CheckStateRole)
        self.assertFalse(lbl.fuzz_me)

        model.setData(model.index(0, 0), "test", role=Qt.EditRole)
        self.assertEqual(
            "test (empty)", model.data(model.index(0, 0), role=Qt.DisplayRole)
        )

        lbl.fuzz_values.append("101010")
        model.update()
        self.assertEqual("test (1)", model.data(model.index(0, 0), role=Qt.DisplayRole))

    def __set_model_data(self, model, row, column, value):
        model.setData(model.createIndex(row, column), value, role=Qt.EditRole)

    def __is_inv_proto(self, proto1: str, proto2: str):
        if len(proto1) != len(proto2):
            return False

        for c1, c2 in zip(proto1, proto2):
            if not self.__is_inv_bits(c1, c2):
                return False

        return True

    def __is_inv_bits(self, a: str, b: str):
        # We only check bits here
        if a not in ("0", "1") or b not in ("0", "1"):
            return True

        if a == "0" and b == "1":
            return True
        if a == "1" and b == "0":
            return True
        return False
