class DetectionConfigScreen(QWidget):
    BRIGHTNESS_STEP = 0.1
    CONTRAST_STEP = 5
    THRESHOLD1_STEP = 5
    THRESHOLD2_STEP = 5
    __last_selected_row = -1
    backscreen: Signal
    nextscreen: Signal
    captured = Signal()
    camera_changed = Signal(object)

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.__cam_array = helpers.get_all_camera_index()
        self.__current_cfg = None
        self.ui = Ui_DetectionConfigScreen()
        self.ui.setupUi(self)
        self.build()
        self.binding()

    def build(self):
        self.image1 = ImageWidget()
        self.image2 = ImageWidget()
        self.image3 = ImageWidget()
        self.imageLayout = self.ui.screen1.parentWidget().layout()
        self.imageLayout.replaceWidget(self.ui.screen1, self.image1)
        self.imageLayout.replaceWidget(self.ui.screen2, self.image2)
        self.imageLayout.replaceWidget(self.ui.screen3, self.image3)
        self.ui.screen1.deleteLater()
        self.ui.screen2.deleteLater()
        self.ui.screen3.deleteLater()

        self.ui.cbbWidth.setPlaceholderText("Width")
        self.ui.cbbHeight.setPlaceholderText("Height")
        self.ui.cbbCamera.setPlaceholderText("Choose Cam")

        self.ui.cbbCamera.clear()
        for camera in self.__cam_array:
            self.ui.cbbCamera.addItem("Camera " + str(camera), userData=camera)

        frame_resize_values = [
            "160", "240", "320", "400", "480", "560", "640", "720", "800",
            "880", "960", "1040", "1120", "1200", "1280"
        ]

        self.ui.cbbHeight.clear()
        for value in frame_resize_values:
            self.ui.cbbHeight.addItem(value, userData=int(value))

        self.ui.cbbWidth.clear()
        for value in frame_resize_values:
            self.ui.cbbWidth.addItem(value, userData=int(value))

        self.ui.cbbMethod.clear()
        self.ui.cbbMethod.addItem("Edge", userData="edge")
        self.ui.cbbMethod.addItem("Threshold", userData="thresh")
        self.ui.cbbMethod.addItem("Range", userData="range")

        table = self.ui.tblCameraConfig
        table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
        table.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.ResizeToContents)
        table.setEditTriggers(QAbstractItemView.NoEditTriggers)
        table.setContextMenuPolicy(Qt.CustomContextMenu)
        self.__reload_table()

        # create context menu
        self.tblPopMenu = QMenu(self)
        self.tblPopActionEditName = QAction('Edit name', self)
        self.tblPopActionRemove = QAction('Remove', self)
        self.tblPopMenu.addAction(self.tblPopActionEditName)
        self.tblPopMenu.addAction(self.tblPopActionRemove)

    def showEvent(self, event):
        _, self.__current_cfg = DetectorConfig.instance().get_current_cfg()
        self.__set_btn_capture_text()
        if self.__current_cfg is None:
            self.__show_config_section(False)
            return
        self.__load_config()

    def __reload_table(self):
        table = self.ui.tblCameraConfig
        table.clearContents()
        table.setRowCount(0)
        manager = DetectorConfig.instance().get_manager()
        cfgs = manager.get_configs()
        for cfg in cfgs:
            camera_name = cfg["name"]
            is_main = cfg["is_main"]
            if (is_main):
                self.__add_new_row(table, camera_name, "Main camera")
            else:
                self.__add_new_row(table, camera_name, "Side camera")
        table.clearSelection()
        table.itemSelectionChanged.emit()
        self.__last_selected_row = -1
        self.image1.imshow(None)
        self.image2.imshow(None)
        self.image3.imshow(None)

    def __reload_roles(self):
        table = self.ui.tblCameraConfig
        manager = DetectorConfig.instance().get_manager()
        cfgs = manager.get_configs()
        count = 0
        for cfg in cfgs:
            camera_name = cfg["name"]
            is_main = cfg["is_main"]
            item = table.item(count, 1)
            if (is_main):
                item.setData(Qt.ItemDataRole.DisplayRole, "Main camera")
            else:
                item.setData(Qt.ItemDataRole.DisplayRole, "Side camera")
            count += 1

    #BINDING
    def binding(self):
        self.backscreen = self.ui.btnBack.clicked
        self.nextscreen = self.ui.btnNext.clicked
        self.ui.btnCapture.clicked.connect(self.btn_capture_clicked)
        DetectorConfig.instance().manager_changed.connect(self.manager_changed)
        self.ui.sldBrightness.valueChanged.connect(
            self.sld_brightness_value_change)
        self.ui.sldContrast.valueChanged.connect(
            self.sld_contrast_value_change)
        self.ui.sldThreshold1.valueChanged.connect(
            self.sld_threshold1_value_change)
        self.ui.sldThreshold2.valueChanged.connect(
            self.sld_threshold2_value_change)
        self.ui.sldBlur.valueChanged.connect(self.sld_blur_value_change)
        self.ui.sldDilate.valueChanged.connect(self.dilate_value_change)
        self.ui.sldErode.valueChanged.connect(self.sld_erode_value_change)
        self.ui.sldBkgThresh.valueChanged.connect(self.sld_bkg_value_change)
        self.ui.sldLightAdj.valueChanged.connect(
            self.sld_light_adj_value_change)
        self.ui.sldLightAdjRange.valueChanged.connect(
            self.sld_light_adj_range_value_change)
        self.ui.cbbCamera.currentIndexChanged.connect(self.cbbCamera_changed)
        self.ui.btnColorFrom.clicked.connect(self.btn_color_from_clicked)
        self.ui.btnColorTo.clicked.connect(self.btn_color_to_clicked)
        self.ui.cbbHeight.currentIndexChanged.connect(self.cbbHeight_changed)
        self.ui.cbbWidth.currentIndexChanged.connect(self.cbbWidth_changed)
        self.ui.cbbMethod.currentIndexChanged.connect(self.cbbMethod_changed)
        self.ui.ckbInvertThresh.stateChanged.connect(
            self.chk_thresh_invert_state_change)
        self.ui.ckbInvertRange.stateChanged.connect(
            self.chk_range_invert_state_change)
        self.ui.tblCameraConfig.itemSelectionChanged.connect(
            self.tbl_camera_item_selection_changed)
        self.ui.tblCameraConfig.customContextMenuRequested.connect(
            self.table_context_menu)
        self.tblPopActionEditName.triggered.connect(
            self.table_action_edit_name_triggered)
        self.tblPopActionRemove.triggered.connect(
            self.table_action_remove_triggered)
        self.ui.btnAdd.clicked.connect(self.btn_add_clicked)

    #HANDLERS
    def btn_capture_clicked(self):
        self.captured.emit()
        self.__set_btn_capture_text()

    def __set_btn_capture_text(self):
        timer_active = DetectorConfig.instance().get_timer().isActive()
        self.ui.btnCapture.setText("CAPTURE" if not timer_active else "STOP")

    def manager_changed(self):
        self.__reload_table()

    def table_context_menu(self, point):
        self.tblPopMenu.exec_(self.ui.tblCameraConfig.mapToGlobal(point))

    def table_action_edit_name_triggered(self):
        table = self.ui.tblCameraConfig
        chosen_row = table.currentRow()
        detector_cfg = DetectorConfig.instance()
        camera_name = table.item(chosen_row, 0).text()
        dialog = DialogEditName(camera_name, parent=self)
        choice = dialog.exec_()
        if choice != QDialog.Accepted:
            return
        new_name = dialog.get_inp_edit_name().strip()
        err_text = detector_cfg.validate_config_name(new_name)
        if err_text is not None:
            helpers.show_message(err_text)
            return
        _, cfg = detector_cfg.get_current_cfg()
        cfg["name"] = new_name
        self.__reload_table()

    def table_action_remove_triggered(self, point):
        table = self.ui.tblCameraConfig
        chosen_row = table.currentRow()
        detector_cfg = DetectorConfig.instance()
        camera_name = table.item(chosen_row, 0).text()
        _, cfg = detector_cfg.get_manager().get_config_by_name(camera_name)
        detector_cfg.remove_config(cfg)
        self.__reload_table()

    #edge detection method
    def chk_thresh_invert_state_change(self):
        if self.ui.ckbInvertThresh.isChecked():
            self.__current_cfg["d_cfg"]["thresh_inv"] = True
        else:
            self.__current_cfg["d_cfg"]["thresh_inv"] = False

    def chk_range_invert_state_change(self):
        if self.ui.ckbInvertRange.isChecked():
            self.__current_cfg["d_cfg"]["color_inv"] = True
        else:
            self.__current_cfg["d_cfg"]["color_inv"] = False

    def sld_brightness_value_change(self):
        value = round(self.ui.sldBrightness.value() * self.BRIGHTNESS_STEP, 1)
        self.__current_cfg["d_cfg"]["alpha"] = value
        self.ui.grpboxSldBrightness.setTitle("Brightness: " + str(value))

    def sld_contrast_value_change(self):
        value = self.ui.sldContrast.value() * self.CONTRAST_STEP
        self.__current_cfg["d_cfg"]["beta"] = value
        self.ui.grbboxSldContrast.setTitle("Contrast: " + str(value))

    def sld_threshold1_value_change(self):
        value = self.ui.sldThreshold1.value() * self.THRESHOLD1_STEP
        self.__current_cfg["d_cfg"]["threshold1"] = value
        self.ui.grbboxSldThreshold.setTitle("Threshold 1: " + str(value))

    def sld_threshold2_value_change(self):
        value = self.ui.sldThreshold2.value() * self.THRESHOLD2_STEP
        self.__current_cfg["d_cfg"]["threshold2"] = value
        self.ui.grbboxSldThreshold2.setTitle("Threshold 2: " + str(value))

    def sld_blur_value_change(self):
        value = self.ui.sldBlur.value()
        self.__current_cfg["d_cfg"]["kernel"] = (2 * value + 1, 2 * value + 1)
        self.ui.grpboxSldBlur.setTitle("Blur: " + str(value))

    def dilate_value_change(self):
        value = self.ui.sldDilate.value()
        self.__current_cfg["d_cfg"]["d_kernel"] = np.ones((value, value))
        self.ui.grbboxSldDilate.setTitle("Dilate: " + str(value))

    def sld_erode_value_change(self):
        value = self.ui.sldErode.value()
        self.__current_cfg["d_cfg"]["e_kernel"] = np.ones((value, value))
        self.ui.grbboxSldErode.setTitle("Erode: " + str(value))

    #threshold detection method
    def sld_bkg_value_change(self):
        value = self.ui.sldBkgThresh.value()
        self.__current_cfg["d_cfg"]["bg_thresh"] = value
        self.ui.grpboxBkgThreshold.setTitle("Background Threshold: " +
                                            str(value))

    def sld_light_adj_value_change(self):
        value = self.ui.sldLightAdj.value()
        self.__current_cfg["d_cfg"]["light_adj_thresh"] = value
        self.ui.grpboxLightAdj.setTitle("Light Adjustment: " + str(value))

    #range detection method
    def sld_light_adj_range_value_change(self):
        value = self.ui.sldLightAdjRange.value()
        self.__current_cfg["d_cfg"]["light_adj_thresh"] = value
        self.ui.grpboxLightAdjRange.setTitle(f"Light Adjustment: {value}")

    def btn_color_from_clicked(self):
        # get initial color
        hsv = self.__current_cfg["d_cfg"]["cr_from"]
        h = 359 if (int(hsv[0] * 2) > 359) else int(hsv[0] * 2)
        s = int(hsv[1])
        v = int(hsv[2])
        init_hsv = QColor.fromHsv(h, s, v, a=255)

        color = QColorDialog.getColor(parent=self, initial=init_hsv)
        if color.isValid():
            hsv = color.getHsv()
            hsv = (hsv[0] / 2, hsv[1], hsv[2])
            self.__current_cfg["d_cfg"]["cr_from"] = hsv
            color_hex = color.name()
            self.ui.btnColorFrom.setStyleSheet("background-color: " +
                                               color_hex)

    def btn_color_to_clicked(self):
        #get initial color
        hsv = self.__current_cfg["d_cfg"]["cr_to"]
        h = 359 if (int(hsv[0] * 2) > 359) else int(hsv[0] * 2)
        s = int(hsv[1])
        v = int(hsv[2])
        init_hsv = QColor.fromHsv(h, s, v, a=255)

        color = QColorDialog.getColor(parent=self, initial=init_hsv)
        if color.isValid():
            hsv = color.getHsv()
            hsv = (hsv[0] / 2, hsv[1], hsv[2])
            self.__current_cfg["d_cfg"]["cr_to"] = hsv
            color_hex = color.name()
            self.ui.btnColorTo.setStyleSheet("background-color: " + color_hex)

    def btn_add_clicked(self):
        detector_cfg = DetectorConfig.instance()
        manager = detector_cfg.get_manager()
        table = self.ui.tblCameraConfig
        camera_name = self.ui.txtNewCamera.text().strip()
        err_text = detector_cfg.validate_config_name(camera_name)
        if err_text is not None:
            helpers.show_message(err_text)
            return
        new_cfg = detector.default_detector_config()
        new_cfg["name"] = camera_name
        detector_cfg.add_config(new_cfg)
        self.__add_new_row(table, camera_name, "Side camera")

    def tbl_camera_item_selection_changed(self):
        table = self.ui.tblCameraConfig
        chosen_row = table.currentRow()
        if chosen_row == self.__last_selected_row: return
        self.__last_selected_row = chosen_row
        detector_cfg = DetectorConfig.instance()
        if chosen_row != -1:
            camera_name = table.item(chosen_row, 0).text()
            detector_cfg.set_current_cfg_name(camera_name)
            _, self.__current_cfg = detector_cfg.get_current_cfg()
            self.__show_config_section(True)
            self.__load_config()
        else:
            detector_cfg.set_current_cfg_name(None)
            self.__show_config_section(False)
            self.camera_changed.emit(-1)

    def cbbCamera_changed(self):
        index = self.ui.cbbCamera.currentData()
        self.__current_cfg["camera_uri"] = index
        self.camera_changed.emit(index)

    def cbbMethod_changed(self, index: int):
        method = self.ui.cbbMethod.currentData()
        self.__current_cfg["detect_method"] = method
        self.ui.stackContainerMid.setCurrentIndex(index)

    def cbbHeight_changed(self):
        value = self.ui.cbbHeight.currentData()
        self.__current_cfg["frame_height"] = value

    def cbbWidth_changed(self):
        value = self.ui.cbbWidth.currentData()
        self.__current_cfg["frame_width"] = value

    # view camera
    def view_cam(self, image):
        # read image in BGR format
        label_w = self.image1.width()
        label_h = self.image1.height()
        dim = (label_w, label_h)
        if image is None:
            self.image1.imshow(image)
            self.image2.imshow(image)
            self.image3.imshow(image)
            return
        contour, proc = self.__process_contours(image.copy())
        img_resized = cv2.resize(image, dim)
        contour_resized = cv2.resize(contour, dim)
        proc_resized = cv2.resize(proc, dim)
        self.image1.imshow(img_resized)
        self.image2.imshow(contour_resized)
        self.image3.imshow(proc_resized)

    def __process_contours(self, image):
        manager = DetectorConfig.instance().get_manager()
        boxes, proc = manager.extract_boxes(self.__current_cfg, image)
        for b in boxes:
            c, rect, dimA, dimB, box, tl, tr, br, bl, minx, maxx, cenx = b
            helper.draw_boxes(image, box)
        return image, proc

    def __show_config_section(self, shown):
        if shown:
            helpers.show_all_children(self.ui.containerMidRange)
            helpers.show_all_children(self.ui.containerMidEdge)
            helpers.show_all_children(self.ui.containerMidThresh)
            helpers.show_all_children(self.ui.containerLeft)
            self.ui.btnCapture.show()
            self.ui.btnNext.show()
        else:
            helpers.hide_all_children(self.ui.containerMidRange)
            helpers.hide_all_children(self.ui.containerMidEdge)
            helpers.hide_all_children(self.ui.containerMidThresh)
            helpers.hide_all_children(self.ui.containerLeft)
            self.ui.btnCapture.hide()
            self.ui.btnNext.hide()

    #load init configs
    def __load_config(self):
        #edge
        brightness = self.__current_cfg["d_cfg"]["alpha"]
        contrast = self.__current_cfg["d_cfg"]["beta"]
        thresh1 = self.__current_cfg["d_cfg"]["threshold1"]
        thresh2 = self.__current_cfg["d_cfg"]["threshold2"]
        blur = self.__current_cfg["d_cfg"]["kernel"][0]
        dilate = self.__current_cfg["d_cfg"]["d_kernel"].shape[1]
        erode = self.__current_cfg["d_cfg"]["e_kernel"].shape[
            1] if self.__current_cfg["d_cfg"]["e_kernel"] is not None else 0
        #threshold
        bkg = self.__current_cfg["d_cfg"]["bg_thresh"]
        light_thresh = self.__current_cfg["d_cfg"]["light_adj_thresh"]
        #range
        light_range = self.__current_cfg["d_cfg"]["light_adj_thresh"]
        color_to = self.__current_cfg["d_cfg"]["cr_to"]
        color_from = self.__current_cfg["d_cfg"]["cr_from"]
        #main controls
        method_index = self.ui.cbbMethod.findData(
            self.__current_cfg["detect_method"])
        height_index = self.ui.cbbHeight.findData(
            self.__current_cfg["frame_height"])
        width_index = self.ui.cbbWidth.findData(
            self.__current_cfg["frame_width"])

        self.ui.sldBrightness.setValue(brightness / self.BRIGHTNESS_STEP)
        self.ui.sldContrast.setValue(contrast / self.CONTRAST_STEP)
        self.ui.sldThreshold1.setValue(thresh1 / self.THRESHOLD1_STEP)
        self.ui.sldThreshold2.setValue(thresh2 / self.THRESHOLD2_STEP)
        self.ui.sldBlur.setValue((blur - 1) / 2)
        self.ui.sldDilate.setValue(dilate)
        self.ui.sldErode.setValue(erode)

        self.ui.sldBkgThresh.setValue(bkg)
        self.ui.sldLightAdj.setValue(light_thresh)

        self.ui.sldLightAdjRange.setValue(light_range)
        hsv_from = self.__current_cfg["d_cfg"]["cr_from"]
        init_hsv_from = QColor.fromHsv(hsv_from[0] * 2, hsv_from[1],
                                       hsv_from[2], 255)
        self.ui.btnColorFrom.setStyleSheet("background-color: " +
                                           init_hsv_from.name())
        hsv_to = self.__current_cfg["d_cfg"]["cr_to"]
        init_hsv_to = QColor.fromHsv(hsv_from[0] * 2, hsv_from[1], hsv_from[2],
                                     255)
        self.ui.btnColorFrom.setStyleSheet("background-color: " +
                                           init_hsv_to.name())

        self.ui.cbbMethod.setCurrentIndex(method_index)
        self.ui.cbbHeight.setCurrentIndex(height_index)
        self.ui.cbbWidth.setCurrentIndex(width_index)

        camera_uri = self.__current_cfg["camera_uri"]
        if camera_uri is not None and camera_uri < self.ui.cbbCamera.count():
            self.ui.cbbCamera.setCurrentIndex(camera_uri)
            self.camera_changed.emit(camera_uri)
        else:
            self.ui.cbbCamera.setCurrentIndex(-1)
        self.__reload_roles()

    def __add_new_row(self, table, camera_name, is_main):
        current_row = table.rowCount()
        table.insertRow(current_row)
        name_item = QTableWidgetItem(camera_name)
        is_main_item = QTableWidgetItem(is_main)
        table.setItem(current_row, 0, name_item)
        table.setItem(current_row, 1, is_main_item)