"""
Copyright © 2023 Laboratoire Adhesion et Inflammation, Authored by Remy Torro.
"""

from typing import Optional
from PyQt5.QtWidgets import (
    QRadioButton,
    QButtonGroup,
    QMessageBox,
    QComboBox,
    QFrame,
    QCheckBox,
    QFileDialog,
    QGridLayout,
    QTextEdit,
    QLineEdit,
    QVBoxLayout,
    QLabel,
    QHBoxLayout,
    QPushButton,
    QMainWindow,
)
from PyQt5.QtCore import Qt, QSize
from matplotlib import pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable

from celldetective.gui.gui_utils import (
    help_generic,
)
from celldetective.gui.base.figure_canvas import FigureCanvas
from celldetective.gui.base.list_widget import ListWidget
from celldetective.gui.base.feature_choice import FeatureChoice
from celldetective.gui.base.components import QHSeperationLine
from superqt import QLabeledDoubleSlider, QLabeledSlider
from superqt.fonticon import icon
from fonticon_mdi6 import MDI6
from celldetective import get_software_location
from celldetective.measure import compute_haralick_features
from celldetective.utils.experiment import extract_experiment_channels
from celldetective.utils.data_loaders import interpret_tracking_configuration
import numpy as np
import json
from shutil import copyfile
import os

from glob import glob
from celldetective.gui.settings._settings_base import CelldetectiveSettingsPanel
from celldetective.utils.image_loaders import load_frames
from celldetective import get_logger

logger = get_logger()


class SettingsTracking(CelldetectiveSettingsPanel):
    """
    UI to set tracking parameters for bTrack.

    """

    def __init__(self, parent_window: Optional[QMainWindow] = None) -> None:
        """
        Initialize the SettingsTracking widget.

        Parameters
        ----------
        parent_window : QMainWindow, optional
            The parent window.
        """
        self.parent_window = parent_window
        self.mode = self.parent_window.mode
        self.exp_dir = self.parent_window.exp_dir

        self.config_name = os.sep.join(["configs", f"btrack_config_{self.mode}.json"])
        self.track_instructions_write_path = self.parent_window.exp_dir + os.sep.join(
            ["configs", f"tracking_instructions_{self.mode}.json"]
        )

        self.config_path = self.exp_dir + self.config_name
        self.channel_names, self.channels = extract_experiment_channels(self.exp_dir)
        self.channel_names = np.array(self.channel_names)
        self.channels = np.array(self.channels)
        self.minimum_height = 300

        super().__init__(title="Configure tracking")

        self._add_to_layout()
        self._load_previous_instructions()

        self._widget.setMinimumWidth(500)
        self._adjust_size()
        self.resize(int(self.width() * 1.1), int(self._screen_height * 0.8))

    def _add_to_layout(self):
        """Add widgets to the layout."""
        tracker_hbox = QHBoxLayout()
        tracker_hbox.setContentsMargins(15, 15, 15, 15)
        tracker_hbox.addWidget(self.btrack_option, 50, alignment=Qt.AlignCenter)
        tracker_hbox.addWidget(self.trackpy_option, 50, alignment=Qt.AlignCenter)

        self._layout.addLayout(tracker_hbox)
        self._layout.addWidget(self.config_frame)
        self._layout.addWidget(self.features_frame)
        self._layout.addWidget(self.config_trackpy_frame)
        self._layout.addWidget(self.post_proc_frame)
        self._layout.addWidget(self.submit_btn)

    def _create_widgets(self):
        """
        Create the multibox design with collapsable frames.

        """

        super()._create_widgets()

        self.btrack_option = QRadioButton("bTrack")
        self.btrack_option.setChecked(True)

        self.trackpy_option = QRadioButton("trackpy")
        self.tracker_option_group = QButtonGroup()
        self.tracker_option_group.addButton(self.btrack_option)
        self.tracker_option_group.addButton(self.trackpy_option)

        self.config_frame = QFrame()
        self.config_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.populate_config_frame()

        # Second collapsable frame FEATURES
        self.features_frame = QFrame()
        self.features_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.populate_features_frame()

        self.config_trackpy_frame = QFrame()
        self.config_trackpy_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.populate_config_trackpy_frame()
        self.config_trackpy_frame.hide()

        # Third collapsable frame POST-PROCESSING
        self.post_proc_frame = QFrame()
        self.post_proc_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.populate_post_proc_frame()

        self.btrack_option.toggled.connect(self.show_tracking_options)
        self.trackpy_option.toggled.connect(self.show_tracking_options)

    def show_tracking_options(self):
        """Show options for the selected tracker."""
        if self.btrack_option.isChecked():
            self.config_frame.show()
            self.features_frame.show()
            self.config_trackpy_frame.hide()
            # self._adjustSize()
        else:
            self.config_frame.hide()
            self.features_frame.hide()
            self.config_trackpy_frame.show()
            # self._adjustSize()

    def populate_post_proc_frame(self):
        """
        Add widgets and layout in the POST-PROCESSING frame.
        """

        grid = QGridLayout(self.post_proc_frame)

        self.select_post_proc_btn = QPushButton()
        self.select_post_proc_btn.clicked.connect(self.activate_post_proc_options)
        self.select_post_proc_btn.setStyleSheet(self.button_select_all)

        self.post_proc_lbl = QLabel("POST-PROCESSING")
        self.post_proc_lbl.setStyleSheet(
            """
			font-weight: bold;
			padding: 0px;
			"""
        )
        grid.addWidget(self.post_proc_lbl, 0, 0, 1, 4, alignment=Qt.AlignCenter)

        title_hbox = QHBoxLayout()

        self.collapse_post_proc_btn = QPushButton()
        self.collapse_post_proc_btn.setIcon(icon(MDI6.chevron_down, color="black"))
        self.collapse_post_proc_btn.setIconSize(QSize(20, 20))
        self.collapse_post_proc_btn.setStyleSheet(self.button_select_all)
        # grid.addWidget(self.collapse_post_proc_btn, 0, 0, 1, 4, alignment=Qt.AlignRight)

        self.help_post_btn = QPushButton()
        self.help_post_btn.setIcon(icon(MDI6.help_circle, color=self.help_color))
        self.help_post_btn.setIconSize(QSize(20, 20))
        self.help_post_btn.clicked.connect(self.help_post)
        self.help_post_btn.setStyleSheet(self.button_select_all)
        self.help_post_btn.setToolTip("Help.")

        title_hbox.addWidget(self.select_post_proc_btn, 5)
        title_hbox.addWidget(QLabel(), 85, alignment=Qt.AlignCenter)
        title_hbox.addWidget(self.help_post_btn, 5)
        title_hbox.addWidget(self.collapse_post_proc_btn, 5)
        grid.addLayout(title_hbox, 0, 0, 1, 4)

        self.generate_post_proc_panel_contents()
        grid.addWidget(self.ContentsPostProc, 1, 0, 1, 4, alignment=Qt.AlignTop)
        self.collapse_post_proc_btn.clicked.connect(
            lambda: self.ContentsPostProc.setHidden(
                not self.ContentsPostProc.isHidden()
            )
        )
        self.collapse_post_proc_btn.clicked.connect(self.collapse_post_advanced)
        self.ContentsPostProc.hide()
        self.uncheck_post_proc()

    def collapse_post_advanced(self):
        """Collapse or expand the advanced post-processing settings."""
        features_open = not self.ContentsFeatures.isHidden()
        config_open = not self.ContentsConfig.isHidden()
        post_open = not self.ContentsPostProc.isHidden()
        is_open = np.array([features_open, config_open, post_open])

        if self.ContentsPostProc.isHidden():
            self.collapse_post_proc_btn.setIcon(icon(MDI6.chevron_down, color="black"))
            self.collapse_post_proc_btn.setIconSize(QSize(20, 20))
            if len(is_open[is_open]) == 0:
                pass
                # self._scroll_area.setMinimumHeight(int(self.minimum_height))
                # self._adjustSize()
        else:
            self.collapse_post_proc_btn.setIcon(icon(MDI6.chevron_up, color="black"))
            self.collapse_post_proc_btn.setIconSize(QSize(20, 20))

    def help_post(self):
        """
        Helper for track post-processing strategy.
        """

        dict_path = os.sep.join(
            [
                get_software_location(),
                "celldetective",
                "gui",
                "help",
                "track-postprocessing.json",
            ]
        )

        with open(dict_path) as f:
            d = json.load(f)

        suggestion = help_generic(d)
        if isinstance(suggestion, str):
            logger.info(f"{suggestion=}")
            msg_box = QMessageBox()
            msg_box.setIcon(QMessageBox.Information)
            msg_box.setTextFormat(Qt.RichText)
            msg_box.setText(rf"{suggestion}")
            msg_box.setWindowTitle("Info")
            msg_box.setStandardButtons(QMessageBox.Ok)
            return_value = msg_box.exec()
            if return_value == QMessageBox.Ok:
                return None

    def help_feature(self):
        """
        Helper for track post-processing strategy.
        """

        dict_path = os.sep.join(
            [
                get_software_location(),
                "celldetective",
                "gui",
                "help",
                "feature-btrack.json",
            ]
        )

        with open(dict_path) as f:
            d = json.load(f)

        suggestion = help_generic(d)
        if isinstance(suggestion, str):
            logger.info(f"{suggestion=}")
            msg_box = QMessageBox()
            msg_box.setIcon(QMessageBox.Information)
            msg_box.setTextFormat(Qt.RichText)
            msg_box.setText(rf"{suggestion}")
            msg_box.setWindowTitle("Info")
            msg_box.setStandardButtons(QMessageBox.Ok)
            return_value = msg_box.exec()
            if return_value == QMessageBox.Ok:
                return None

    def populate_features_frame(self):
        """
        Add widgets and layout in the FEATURES frame.
        """

        grid = QGridLayout(self.features_frame)
        title_hbox = QHBoxLayout()

        self.select_features_btn = QPushButton()
        self.select_features_btn.clicked.connect(self.activate_feature_options)
        self.select_features_btn.setStyleSheet(self.button_select_all)

        self.feature_lbl = QLabel("FEATURES")
        self.feature_lbl.setStyleSheet(
            """
			font-weight: bold;
			padding: 0px;
			"""
        )
        grid.addWidget(self.feature_lbl, 0, 0, 1, 4, alignment=Qt.AlignCenter)

        self.collapse_features_btn = QPushButton()
        self.collapse_features_btn.setIcon(icon(MDI6.chevron_down, color="black"))
        self.collapse_features_btn.setIconSize(QSize(20, 20))
        self.collapse_features_btn.setStyleSheet(self.button_select_all)

        self.help_feature_btn = QPushButton()
        self.help_feature_btn.setIcon(icon(MDI6.help_circle, color=self.help_color))
        self.help_feature_btn.setIconSize(QSize(20, 20))
        self.help_feature_btn.clicked.connect(self.help_feature)
        self.help_feature_btn.setStyleSheet(self.button_select_all)
        self.help_feature_btn.setToolTip("Help.")

        title_hbox.addWidget(self.select_features_btn, 5)
        title_hbox.addWidget(QLabel(), 85, alignment=Qt.AlignCenter)
        title_hbox.addWidget(self.help_feature_btn, 5)
        title_hbox.addWidget(self.collapse_features_btn, 5)
        grid.addLayout(title_hbox, 0, 0, 1, 4)

        self.generate_feature_panel_contents()
        grid.addWidget(self.ContentsFeatures, 1, 0, 1, 4, alignment=Qt.AlignTop)
        self.collapse_features_btn.clicked.connect(
            lambda: self.ContentsFeatures.setHidden(
                not self.ContentsFeatures.isHidden()
            )
        )
        self.collapse_features_btn.clicked.connect(self.collapse_features_advanced)
        # self.ContentsFeatures.hide()
        self.check_features()

    def collapse_features_advanced(self):
        """
        Switch the chevron icon and adjust the size for the FEATURES frame.
        """

        features_open = not self.ContentsFeatures.isHidden()
        config_open = not self.ContentsConfig.isHidden()
        post_open = not self.ContentsPostProc.isHidden()
        is_open = np.array([features_open, config_open, post_open])

        if self.ContentsFeatures.isHidden():
            self.collapse_features_btn.setIcon(icon(MDI6.chevron_down, color="black"))
            self.collapse_features_btn.setIconSize(QSize(20, 20))
            if len(is_open[is_open]) == 0:
                pass
                # self._scroll_area.setMinimumHeight(int(self.minimum_height))
                # self._adjustSize()
        else:
            self.collapse_features_btn.setIcon(icon(MDI6.chevron_up, color="black"))
            self.collapse_features_btn.setIconSize(QSize(20, 20))

    def generate_post_proc_panel_contents(self):
        """Generate the post-processing panel contents."""
        self.ContentsPostProc = QFrame()
        layout = QVBoxLayout(self.ContentsPostProc)
        layout.setContentsMargins(0, 0, 0, 0)

        post_proc_layout = QHBoxLayout()
        self.post_proc_lbl = QLabel("Post processing on the tracks:")
        post_proc_layout.addWidget(self.post_proc_lbl, 90)
        layout.addLayout(post_proc_layout)

        clean_traj_sublayout = QVBoxLayout()
        clean_traj_sublayout.setContentsMargins(15, 15, 15, 15)

        tracklength_layout = QHBoxLayout()
        self.min_tracklength_slider = QLabeledSlider()
        self.min_tracklength_slider.setSingleStep(1)
        self.min_tracklength_slider.setTickInterval(1)
        self.min_tracklength_slider.setSingleStep(1)
        self.min_tracklength_slider.setOrientation(Qt.Horizontal)
        self.min_tracklength_slider.setRange(
            0, self.parent_window.parent_window.len_movie
        )
        self.min_tracklength_slider.setValue(0)
        tracklength_layout.addWidget(QLabel("Min. tracklength: "), 40)
        tracklength_layout.addWidget(self.min_tracklength_slider, 60)
        clean_traj_sublayout.addLayout(tracklength_layout)

        self.remove_not_in_first_checkbox = QCheckBox(
            "Remove tracks that do not start at the beginning"
        )
        self.remove_not_in_first_checkbox.setIcon(
            icon(MDI6.arrow_expand_right, color="k")
        )
        clean_traj_sublayout.addWidget(self.remove_not_in_first_checkbox)

        self.remove_not_in_last_checkbox = QCheckBox(
            "Remove tracks that do not end at the end"
        )
        self.remove_not_in_last_checkbox.setIcon(
            icon(MDI6.arrow_expand_left, color="k")
        )
        clean_traj_sublayout.addWidget(self.remove_not_in_last_checkbox)

        self.interpolate_gaps_checkbox = QCheckBox(
            "Interpolate missed detections within tracks"
        )
        self.interpolate_gaps_checkbox.setIcon(
            icon(MDI6.chart_timeline_variant_shimmer, color="k")
        )
        clean_traj_sublayout.addWidget(self.interpolate_gaps_checkbox)

        self.extrapolate_post_checkbox = QCheckBox(
            "Sustain last position until the end of the movie"
        )
        self.extrapolate_post_checkbox.setIcon(icon(MDI6.repeat, color="k"))

        self.extrapolate_pre_checkbox = QCheckBox(
            "Sustain first position from the beginning of the movie"
        )
        self.extrapolate_pre_checkbox.setIcon(icon(MDI6.repeat, color="k"))

        clean_traj_sublayout.addWidget(self.extrapolate_post_checkbox)
        clean_traj_sublayout.addWidget(self.extrapolate_pre_checkbox)

        # self.interpolate_na_features_checkbox = QCheckBox('Interpolate features of missed detections')
        # self.interpolate_na_features_checkbox.setIcon(icon(MDI6.format_color_fill,color="k"))

        # clean_traj_sublayout.addWidget(self.interpolate_na_features_checkbox)
        clean_traj_sublayout.addStretch()

        self.post_proc_options_to_disable = [
            self.post_proc_lbl,
            self.min_tracklength_slider,
            self.remove_not_in_first_checkbox,
            self.remove_not_in_last_checkbox,
            self.interpolate_gaps_checkbox,
            self.extrapolate_post_checkbox,
            self.extrapolate_pre_checkbox,
        ]  # , self.interpolate_na_features_checkbox

        layout.addLayout(clean_traj_sublayout)

    def generate_feature_panel_contents(self):
        """Generate the feature panel contents."""
        self.ContentsFeatures = QFrame()
        layout = QVBoxLayout(self.ContentsFeatures)
        layout.setContentsMargins(0, 0, 0, 0)

        feature_layout = QHBoxLayout()
        feature_layout.setContentsMargins(0, 0, 0, 0)

        self.feature_lbl = QLabel("Add features:")
        self.del_feature_btn = QPushButton("")
        self.del_feature_btn.setStyleSheet(self.button_select_all)
        self.del_feature_btn.setIcon(icon(MDI6.trash_can, color="black"))
        self.del_feature_btn.setToolTip("Remove feature")
        self.del_feature_btn.setIconSize(QSize(20, 20))

        self.add_feature_btn = QPushButton("")
        self.add_feature_btn.setStyleSheet(self.button_select_all)
        self.add_feature_btn.setIcon(icon(MDI6.filter_plus, color="black"))
        self.add_feature_btn.setToolTip("Add feature")
        self.add_feature_btn.setIconSize(QSize(20, 20))

        self.features_list = ListWidget(
            FeatureChoice,
            initial_features=[
                "area",
                "intensity_mean",
            ],
        )

        self.del_feature_btn.clicked.connect(self.features_list.removeSel)
        self.add_feature_btn.clicked.connect(self.features_list.addItem)

        feature_layout.addWidget(self.feature_lbl, 90)
        feature_layout.addWidget(self.del_feature_btn, 5)
        feature_layout.addWidget(self.add_feature_btn, 5)
        layout.addLayout(feature_layout)
        layout.addWidget(self.features_list)

        self.feat_sep2 = QHSeperationLine()
        layout.addWidget(self.feat_sep2)

        self.use_channel_lbl = QLabel("Use channel:")
        layout.addWidget(self.use_channel_lbl)
        self.mask_channels_cb = [QCheckBox() for i in range(len(self.channels))]
        for cb, chn in zip(self.mask_channels_cb, self.channel_names):
            cb.setText(chn)
            cb.setChecked(True)
            layout.addWidget(cb)

        self.feat_sep1 = QHSeperationLine()
        layout.addWidget(self.feat_sep1)

        self.activate_haralick_btn = QCheckBox("activate Haralick texture features")
        self.activate_haralick_btn.toggled.connect(self.show_haralick_options)
        # Haralick features parameters

        self.haralick_channel_choice = QComboBox()
        self.haralick_channel_choice.addItems(self.channel_names)
        self.haralick_channel_lbl = QLabel("Target channel: ")

        self.haralick_distance_le = QLineEdit("1")
        self.haralick_distance_lbl = QLabel("Distance: ")

        self.haralick_n_gray_levels_le = QLineEdit("256")
        self.haralick_n_gray_levels_lbl = QLabel("# gray levels: ")

        # Slider to set vmin & vmax
        self.haralick_scale_slider = QLabeledDoubleSlider()
        self.haralick_scale_slider.setTickInterval(0.05)
        self.haralick_scale_slider.setSingleStep(1)
        self.haralick_scale_slider.setOrientation(Qt.Horizontal)
        self.haralick_scale_slider.setRange(0, 1)
        self.haralick_scale_slider.setValue(0.5)
        self.haralick_scale_lbl = QLabel("Scale: ")

        self.haralick_percentile_min_le = QLineEdit("0.01")
        self.haralick_percentile_max_le = QLineEdit("99.9")
        self.haralick_normalization_mode_btn = QPushButton()
        self.haralick_normalization_mode_btn.setStyleSheet(self.button_select_all)
        self.haralick_normalization_mode_btn.setIcon(
            icon(MDI6.percent_circle, color="black")
        )
        self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
        self.haralick_normalization_mode_btn.setToolTip(
            "Switch to absolute normalization values."
        )
        self.percentile_mode = True

        self.haralick_percentile_min_lbl = QLabel("Min percentile: ")
        self.haralick_percentile_max_lbl = QLabel("Max percentile: ")

        self.haralick_hist_btn = QPushButton()
        self.haralick_hist_btn.clicked.connect(
            self.control_haralick_intensity_histogram
        )
        self.haralick_hist_btn.setIcon(icon(MDI6.poll, color="k"))
        self.haralick_hist_btn.setStyleSheet(self.button_select_all)

        self.haralick_digit_btn = QPushButton()
        self.haralick_digit_btn.clicked.connect(self.control_haralick_digitalization)
        self.haralick_digit_btn.setIcon(icon(MDI6.image_check, color="k"))
        self.haralick_digit_btn.setStyleSheet(self.button_select_all)

        self.haralick_layout = QVBoxLayout()
        self.haralick_layout.setContentsMargins(20, 20, 20, 20)

        activate_layout = QHBoxLayout()
        activate_layout.addWidget(self.activate_haralick_btn, 80)
        activate_layout.addWidget(self.haralick_hist_btn, 10)
        activate_layout.addWidget(self.haralick_digit_btn, 10)
        self.haralick_layout.addLayout(activate_layout)

        channel_layout = QHBoxLayout()
        channel_layout.addWidget(self.haralick_channel_lbl, 40)
        channel_layout.addWidget(self.haralick_channel_choice, 60)
        self.haralick_layout.addLayout(channel_layout)

        distance_layout = QHBoxLayout()
        distance_layout.addWidget(self.haralick_distance_lbl, 40)
        distance_layout.addWidget(self.haralick_distance_le, 60)
        self.haralick_layout.addLayout(distance_layout)

        gl_layout = QHBoxLayout()
        gl_layout.addWidget(self.haralick_n_gray_levels_lbl, 40)
        gl_layout.addWidget(self.haralick_n_gray_levels_le, 60)
        self.haralick_layout.addLayout(gl_layout)

        slider_layout = QHBoxLayout()
        slider_layout.addWidget(self.haralick_scale_lbl, 40)
        slider_layout.addWidget(self.haralick_scale_slider, 60)
        self.haralick_layout.addLayout(slider_layout)

        slider_min_percentile_layout = QHBoxLayout()
        slider_min_percentile_layout.addWidget(self.haralick_percentile_min_lbl, 40)
        slider_min_percentile_layout.addWidget(self.haralick_percentile_min_le, 55)
        slider_min_percentile_layout.addWidget(self.haralick_normalization_mode_btn, 5)
        self.haralick_layout.addLayout(slider_min_percentile_layout)

        slider_max_percentile_layout = QHBoxLayout()
        slider_max_percentile_layout.addWidget(self.haralick_percentile_max_lbl, 40)
        slider_max_percentile_layout.addWidget(self.haralick_percentile_max_le, 60)
        self.haralick_layout.addLayout(slider_max_percentile_layout)

        self.haralick_to_hide = [
            self.haralick_hist_btn,
            self.haralick_digit_btn,
            self.haralick_channel_lbl,
            self.haralick_channel_choice,
            self.haralick_distance_le,
            self.haralick_distance_lbl,
            self.haralick_n_gray_levels_le,
            self.haralick_n_gray_levels_lbl,
            self.haralick_scale_lbl,
            self.haralick_scale_slider,
            self.haralick_percentile_min_lbl,
            self.haralick_percentile_min_le,
            self.haralick_percentile_max_lbl,
            self.haralick_percentile_max_le,
            self.haralick_normalization_mode_btn,
        ]
        for element in self.haralick_to_hide:
            element.hide()

        self.features_to_disable = [
            self.feature_lbl,
            self.del_feature_btn,
            self.add_feature_btn,
            self.features_list,
            self.use_channel_lbl,
            *self.mask_channels_cb,
            self.activate_haralick_btn,
        ]

        self.haralick_normalization_mode_btn.clicked.connect(
            self.switch_to_absolute_normalization_mode
        )
        layout.addLayout(self.haralick_layout)

    def switch_to_absolute_normalization_mode(self):
        """Switch between percentile and absolute normalization for Haralick features."""
        if self.percentile_mode:
            self.percentile_mode = False
            self.haralick_normalization_mode_btn.setIcon(
                icon(MDI6.percent_circle_outline, color="black")
            )
            self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
            self.haralick_normalization_mode_btn.setToolTip(
                "Switch to percentile normalization values."
            )
            self.haralick_percentile_min_lbl.setText("Min value: ")
            self.haralick_percentile_max_lbl.setText("Max value: ")
            self.haralick_percentile_min_le.setText("0")
            self.haralick_percentile_max_le.setText("10000")

        else:
            self.percentile_mode = True
            self.haralick_normalization_mode_btn.setIcon(
                icon(MDI6.percent_circle, color="black")
            )
            self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
            self.haralick_normalization_mode_btn.setToolTip(
                "Switch to absolute normalization values."
            )
            self.haralick_percentile_min_lbl.setText("Min percentile: ")
            self.haralick_percentile_max_lbl.setText("Max percentile: ")
            self.haralick_percentile_min_le.setText("0.01")
            self.haralick_percentile_max_le.setText("99.99")

    def populate_config_frame(self):
        """Populate the configuration frame."""
        grid = QGridLayout(self.config_frame)
        panel_title = QLabel(f"CONFIGURATION")
        panel_title.setStyleSheet(
            """
			font-weight: bold;
			padding: 0px;
			"""
        )

        grid.addWidget(panel_title, 0, 0, 1, 4, alignment=Qt.AlignCenter)

        self.collapse_config_btn = QPushButton()
        self.collapse_config_btn.setIcon(icon(MDI6.chevron_down, color="black"))
        self.collapse_config_btn.setIconSize(QSize(20, 20))
        self.collapse_config_btn.setStyleSheet(self.button_select_all)
        grid.addWidget(self.collapse_config_btn, 0, 0, 1, 4, alignment=Qt.AlignRight)

        self.generate_config_panel_contents()
        grid.addWidget(self.ContentsConfig, 1, 0, 1, 4, alignment=Qt.AlignTop)
        self.collapse_config_btn.clicked.connect(
            lambda: self.ContentsConfig.setHidden(not self.ContentsConfig.isHidden())
        )
        self.collapse_config_btn.clicked.connect(self.collapse_config_advanced)
        # self.ContentsConfig.hide()

    def populate_config_trackpy_frame(self):
        """Populate the trackpy configuration frame."""
        grid = QGridLayout(self.config_trackpy_frame)
        panel_title = QLabel(f"CONFIGURATION")
        panel_title.setStyleSheet(
            """
			font-weight: bold;
			padding: 0px;
			"""
        )

        grid.addWidget(panel_title, 0, 0, 1, 4, alignment=Qt.AlignCenter)

        self.collapse_config_trackpy_btn = QPushButton()
        self.collapse_config_trackpy_btn.setIcon(icon(MDI6.chevron_down, color="black"))
        self.collapse_config_trackpy_btn.setIconSize(QSize(20, 20))
        self.collapse_config_trackpy_btn.setStyleSheet(self.button_select_all)
        grid.addWidget(
            self.collapse_config_trackpy_btn, 0, 0, 1, 4, alignment=Qt.AlignRight
        )
        self.generate_config_trackpy_panel_contents()
        grid.addWidget(self.ContentsConfigTrackpy, 1, 0, 1, 4, alignment=Qt.AlignTop)
        self.collapse_config_trackpy_btn.clicked.connect(
            lambda: self.ContentsConfigTrackpy.setHidden(
                not self.ContentsConfigTrackpy.isHidden()
            )
        )
        self.collapse_config_trackpy_btn.clicked.connect(
            self.collapse_config_trackpy_advanced
        )
        # self.ContentsConfig.hide()

    def collapse_config_advanced(self):
        """
        Switch the chevron icon and adjust the size for the CONFIG frame.
        """

        features_open = not self.ContentsFeatures.isHidden()
        config_open = not self.ContentsConfig.isHidden()
        post_open = not self.ContentsPostProc.isHidden()
        is_open = np.array([features_open, config_open, post_open])

        if self.ContentsConfig.isHidden():
            self.collapse_config_btn.setIcon(icon(MDI6.chevron_down, color="black"))
            self.collapse_config_btn.setIconSize(QSize(20, 20))
            if len(is_open[is_open]) == 0:
                pass
                # self._scroll_area.setMinimumHeight(int(self.minimum_height))
                # self._adjustSize()
        else:
            self.collapse_config_btn.setIcon(icon(MDI6.chevron_up, color="black"))
            self.collapse_config_btn.setIconSize(QSize(20, 20))

    def collapse_config_trackpy_advanced(self):
        """
        Switch the chevron icon and adjust the size for the CONFIG frame.
        """

        post_open = not self.ContentsPostProc.isHidden()
        is_open = np.array([post_open])

        if self.ContentsConfigTrackpy.isHidden():
            self.collapse_config_trackpy_btn.setIcon(
                icon(MDI6.chevron_down, color="black")
            )
            self.collapse_config_trackpy_btn.setIconSize(QSize(20, 20))
            if len(is_open[is_open]) == 0:
                pass
                # self._scroll_area.setMinimumHeight(int(self.minimum_height))
                # self._adjustSize()
        else:
            self.collapse_config_trackpy_btn.setIcon(
                icon(MDI6.chevron_up, color="black")
            )
            self.collapse_config_trackpy_btn.setIconSize(QSize(20, 20))

    def generate_config_trackpy_panel_contents(self):
        """Generate the trackpy configuration panel contents."""
        self.ContentsConfigTrackpy = QFrame()
        layout = QVBoxLayout(self.ContentsConfigTrackpy)
        layout.setContentsMargins(0, 0, 0, 0)

        sr_layout = QHBoxLayout()
        self.search_range_lbl = QLabel("search range [px]: ")
        self.search_range_le = QLineEdit("30")
        self.search_range_le.setPlaceholderText("search distance in pixels")
        self.search_range_le.setValidator(self._floatValidator)
        sr_layout.addWidget(self.search_range_lbl, 30)
        sr_layout.addWidget(self.search_range_le, 70)
        layout.addLayout(sr_layout)

        memory_layout = QHBoxLayout()
        self.memory_lbl = QLabel("memory [# frames]: ")
        self.memory_slider = QLabeledSlider()
        self.memory_slider.setSingleStep(1)
        self.memory_slider.setTickInterval(1)
        self.memory_slider.setSingleStep(1)
        self.memory_slider.setOrientation(Qt.Horizontal)
        self.memory_slider.setRange(0, self.parent_window.parent_window.len_movie)
        self.memory_slider.setValue(0)
        memory_layout.addWidget(self.memory_lbl, 30)
        memory_layout.addWidget(self.memory_slider, 70)
        layout.addLayout(memory_layout)

    def generate_config_panel_contents(self):
        """Generate the configuration panel contents."""
        self.ContentsConfig = QFrame()
        layout = QVBoxLayout(self.ContentsConfig)
        layout.setContentsMargins(0, 0, 0, 0)

        btrack_config_layout = QHBoxLayout()
        self.config_lbl = QLabel("bTrack configuration: ")
        btrack_config_layout.addWidget(self.config_lbl, 90)

        self.upload_btrack_config_btn = QPushButton()
        self.upload_btrack_config_btn.setIcon(icon(MDI6.plus, color="black"))
        self.upload_btrack_config_btn.setIconSize(QSize(20, 20))
        self.upload_btrack_config_btn.setToolTip("Upload a new bTrack configuration.")
        self.upload_btrack_config_btn.setStyleSheet(self.button_select_all)
        self.upload_btrack_config_btn.clicked.connect(self.upload_btrack_config)
        btrack_config_layout.addWidget(
            self.upload_btrack_config_btn, 5
        )  # 4,3,1,1, alignment=Qt.AlignLeft

        self.reset_config_btn = QPushButton()
        self.reset_config_btn.setIcon(icon(MDI6.arrow_u_right_top, color="black"))
        self.reset_config_btn.setIconSize(QSize(20, 20))
        self.reset_config_btn.setToolTip(
            "Reset the configuration to the default bTrack config."
        )
        self.reset_config_btn.setStyleSheet(self.button_select_all)
        self.reset_config_btn.clicked.connect(self.reset_btrack_config)
        btrack_config_layout.addWidget(
            self.reset_config_btn, 5
        )  # 4,3,1,1, alignment=Qt.AlignLeft

        layout.addLayout(btrack_config_layout)

        self.config_le = QTextEdit()
        self.config_le.setMinimumHeight(150)
        # self.config_le.setStyleSheet("""
        # 							background: #EEEDEB;
        # 							border: 2px solid black;
        # 							""")
        layout.addWidget(self.config_le)
        self.load_cell_config()

    def show_haralick_options(self):
        """
        Show the Haralick texture options.
        """

        if self.activate_haralick_btn.isChecked():
            for element in self.haralick_to_hide:
                element.show()
        else:
            for element in self.haralick_to_hide:
                element.hide()

    def upload_btrack_config(self):
        """
        Upload a specific bTrack config to the experiment folder for the cell population.
        """

        self.file_dialog = QFileDialog()
        try:
            modelpath = (
                os.sep.join(
                    [self._software_path, "celldetective", "models", "tracking_configs"]
                )
                + os.sep
            )
            print("Track config path: ", modelpath)
            self.filename = self.file_dialog.getOpenFileName(
                None, "Load config", modelpath, "json files (*.json)"
            )[0]
            if self.filename != self.config_path:
                copyfile(self.filename, self.config_path)
            self.load_cell_config()
        except Exception as e:
            print(e)
            return None

    def reset_btrack_config(self):
        """
        Set the bTrack config to the default bTrack config.
        """

        msgBox = QMessageBox()
        msgBox.setIcon(QMessageBox.Question)
        msgBox.setText(
            "You are about to revert to the default bTrack configuration? Do you want to proceed?"
        )
        msgBox.setWindowTitle("Confirm")
        msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        returnValue = msgBox.exec()
        if returnValue == QMessageBox.Yes:
            config = interpret_tracking_configuration(None)
            if config != self.config_path:
                copyfile(config, self.config_path)
            self.load_cell_config()
        else:
            return None

    def activate_feature_options(self):
        """
        Tick the features option.
        """

        self.switch_feature_option()
        if self.features_ticked:
            for element in self.features_to_disable:
                element.setEnabled(True)
            self.select_features_btn.setIcon(icon(MDI6.checkbox_outline, color="black"))
            self.select_features_btn.setIconSize(QSize(20, 20))
        else:
            for element in self.features_to_disable:
                element.setEnabled(False)
            self.select_features_btn.setIcon(
                icon(MDI6.checkbox_blank_outline, color="black")
            )
            self.select_features_btn.setIconSize(QSize(20, 20))
            self.features_list.list_widget.clearSelection()
            self.activate_haralick_btn.setChecked(False)

    def activate_post_proc_options(self):
        """
        Tick the features option.
        """

        self.switch_post_proc_option()
        if self.post_proc_ticked:
            for element in self.post_proc_options_to_disable:
                element.setEnabled(True)
            self.select_post_proc_btn.setIcon(
                icon(MDI6.checkbox_outline, color="black")
            )
            self.select_post_proc_btn.setIconSize(QSize(20, 20))
        else:
            for element in self.post_proc_options_to_disable:
                element.setEnabled(False)
            self.select_post_proc_btn.setIcon(
                icon(MDI6.checkbox_blank_outline, color="black")
            )
            self.select_post_proc_btn.setIconSize(QSize(20, 20))

    def switch_feature_option(self):
        """
        Switch the feature option.
        """

        if self.features_ticked:
            self.features_ticked = False
        else:
            self.features_ticked = True

    def switch_post_proc_option(self):
        """
        Switch the feature option.
        """

        if self.post_proc_ticked:
            self.post_proc_ticked = False
        else:
            self.post_proc_ticked = True

    # def adjustScrollArea(self):
    #
    # 	"""
    # 	Auto-adjust scroll area to fill space
    # 	(from https://stackoverflow.com/questions/66417576/make-qscrollarea-use-all-available-space-of-qmainwindow-height-axis)
    # 	"""
    #
    # 	step = 5
    # 	while self._scroll_area.verticalScrollBar().isVisible() and self.height() < self.maximumHeight():
    # 		self.resize(self.width(), self.height() + step)

    def load_cell_config(self):
        """
        Load the cell configuration and write in the QLineEdit.
        """

        file_name = interpret_tracking_configuration(self.config_path)
        with open(file_name, "r") as f:
            json_data = json.load(f)
            self.config_le.setText(json.dumps(json_data, indent=4))

    def _write_instructions(self):
        """
        Write the selected options in a json file for later reading by the software.
        """

        print("Writing instructions...")

        if self.btrack_option.isChecked():
            btrack_option = True
        else:
            btrack_option = False

        # Fetch trackpky params
        if not btrack_option:
            search_range = float(self.search_range_le.text().replace(",", "."))
            memory = self.memory_slider.value()
        else:
            search_range = None
            memory = None

        tracking_options = {"btrack_config_path": self.config_path}
        tracking_options.update(
            {
                "btrack_option": btrack_option,
                "search_range": search_range,
                "memory": memory,
            }
        )

        if not self.features_ticked:
            features = None
            masked_channels = None
        else:
            features = self.features_list.getItems()
            masked_channels = self.channel_names[
                np.array([not cb.isChecked() for cb in self.mask_channels_cb])
            ]
            if len(masked_channels) == 0:
                masked_channels = None
            else:
                masked_channels = list(masked_channels)

        tracking_options.update(
            {"features": features, "mask_channels": masked_channels}
        )

        if not hasattr(self, "haralick_options"):
            self.extract_haralick_options()
        tracking_options.update({"haralick_options": self.haralick_options})

        if self.post_proc_ticked:
            post_processing_options = {
                "minimum_tracklength": int(self.min_tracklength_slider.value()),
                "remove_not_in_first": self.remove_not_in_first_checkbox.isChecked(),
                "remove_not_in_last": self.remove_not_in_last_checkbox.isChecked(),
                "interpolate_position_gaps": self.interpolate_gaps_checkbox.isChecked(),
                "extrapolate_tracks_pre": self.extrapolate_pre_checkbox.isChecked(),
                "extrapolate_tracks_post": self.extrapolate_post_checkbox.isChecked(),
                "interpolate_na": False,  # self.interpolate_na_features_checkbox.isChecked()
            }
        else:

            post_processing_options = None

        tracking_options.update({"post_processing_options": post_processing_options})
        file_name = self.track_instructions_write_path
        with open(file_name, "w") as f:
            json.dump(tracking_options, f, indent=4)

        # Save the JSON data to the file
        file_name = self.config_path
        with open(file_name, "w") as f:
            f.write(self.config_le.toPlainText())
        print("Done.")
        self.close()

    def uncheck_post_proc(self):
        """Uncheck the post-processing option."""
        self.select_post_proc_btn.setIcon(
            icon(MDI6.checkbox_blank_outline, color="black")
        )
        self.select_post_proc_btn.setIconSize(QSize(20, 20))
        self.post_proc_ticked = False
        for element in self.post_proc_options_to_disable:
            element.setEnabled(False)

    def check_post_proc(self):
        """Check the post-processing option."""
        self.select_post_proc_btn.setIcon(icon(MDI6.checkbox_outline, color="black"))
        self.select_post_proc_btn.setIconSize(QSize(20, 20))
        self.post_proc_ticked = True
        for element in self.post_proc_options_to_disable:
            element.setEnabled(True)

    def uncheck_features(self):
        """Uncheck the features option."""
        self.select_features_btn.setIcon(
            icon(MDI6.checkbox_blank_outline, color="black")
        )
        self.select_features_btn.setIconSize(QSize(20, 20))
        self.features_ticked = False
        for element in self.features_to_disable:
            element.setEnabled(False)

    def check_features(self):
        """Check the features option."""
        self.select_features_btn.setIcon(icon(MDI6.checkbox_outline, color="black"))
        self.select_features_btn.setIconSize(QSize(20, 20))
        self.features_ticked = True
        for element in self.features_to_disable:
            element.setEnabled(True)

    def extract_haralick_options(self):
        """Extract Haralick features options from the GUI."""
        if self.activate_haralick_btn.isChecked():
            self.haralick_options = {
                "target_channel": self.haralick_channel_choice.currentIndex(),
                "scale_factor": float(self.haralick_scale_slider.value()),
                "n_intensity_bins": int(self.haralick_n_gray_levels_le.text()),
                "distance": int(self.haralick_distance_le.text()),
            }
            if self.percentile_mode:
                self.haralick_options.update(
                    {
                        "percentiles": (
                            float(self.haralick_percentile_min_le.text()),
                            float(self.haralick_percentile_max_le.text()),
                        ),
                        "clip_values": None,
                    }
                )
            else:
                self.haralick_options.update(
                    {
                        "percentiles": None,
                        "clip_values": (
                            float(self.haralick_percentile_min_le.text()),
                            float(self.haralick_percentile_max_le.text()),
                        ),
                    }
                )

        else:
            self.haralick_options = None

    def _load_previous_instructions(self):
        """
        Read the tracking options from a previously written json file.
        """

        print("Reading instructions..")
        if os.path.exists(self.track_instructions_write_path):
            with open(self.track_instructions_write_path, "r") as f:
                tracking_instructions = json.load(f)
                print(tracking_instructions)

                # Features
                features = tracking_instructions["features"]
                if (features is not None) and len(features) > 0:
                    self.check_features()
                    self.ContentsFeatures.show()
                    self.features_list.list_widget.clear()
                    self.features_list.list_widget.addItems(features)
                else:
                    self.ContentsFeatures.hide()
                    self.uncheck_features()

                btrack_option = True
                if "btrack_option" in tracking_instructions:
                    btrack_option = tracking_instructions["btrack_option"]
                if btrack_option:
                    self.btrack_option.click()
                else:
                    self.trackpy_option.click()

                if "search_range" in tracking_instructions:
                    search_range = tracking_instructions["search_range"]
                    if search_range is not None:
                        self.search_range_le.setText(
                            str(search_range).replace(".", ",")
                        )
                if "memory" in tracking_instructions:
                    memory = tracking_instructions["memory"]
                    if memory is not None:
                        self.memory_slider.setValue(memory)

                # Uncheck channels that are masked
                mask_channels = tracking_instructions["mask_channels"]
                if (mask_channels is not None) and len(mask_channels) > 0:
                    for ch in mask_channels:
                        for cb in self.mask_channels_cb:
                            if cb.text() == ch:
                                cb.setChecked(False)

                haralick_options = tracking_instructions["haralick_options"]
                if haralick_options is None:
                    self.activate_haralick_btn.setChecked(False)
                    self.show_haralick_options()
                else:
                    self.activate_haralick_btn.setChecked(True)
                    self.show_haralick_options()
                    if "target_channel" in haralick_options:
                        idx = haralick_options["target_channel"]
                        # idx = self.haralick_channel_choice.findText(text_to_find)
                        self.haralick_channel_choice.setCurrentIndex(idx)
                    if "scale_factor" in haralick_options:
                        self.haralick_scale_slider.setValue(
                            float(haralick_options["scale_factor"])
                        )
                    if ("percentiles" in haralick_options) and (
                        haralick_options["percentiles"] is not None
                    ):
                        perc = list(haralick_options["percentiles"])
                        self.haralick_percentile_min_le.setText(str(perc[0]))
                        self.haralick_percentile_max_le.setText(str(perc[1]))
                    if ("clip_values" in haralick_options) and (
                        haralick_options["clip_values"] is not None
                    ):
                        values = list(haralick_options["clip_values"])
                        self.haralick_percentile_min_le.setText(str(values[0]))
                        self.haralick_percentile_max_le.setText(str(values[1]))
                        self.percentile_mode = True
                        self.switch_to_absolute_normalization_mode()
                    if "n_intensity_bins" in haralick_options:
                        self.haralick_n_gray_levels_le.setText(
                            str(haralick_options["n_intensity_bins"])
                        )
                    if "distance" in haralick_options:
                        self.haralick_distance_le.setText(
                            str(haralick_options["distance"])
                        )

                # Post processing options
                post_processing_options = tracking_instructions[
                    "post_processing_options"
                ]
                if post_processing_options is None:
                    self.uncheck_post_proc()
                    self.ContentsPostProc.hide()
                    for element in [
                        self.remove_not_in_last_checkbox,
                        self.remove_not_in_first_checkbox,
                        self.interpolate_gaps_checkbox,
                        self.extrapolate_post_checkbox,
                        self.extrapolate_pre_checkbox,
                    ]:  # self.interpolate_na_features_checkbox
                        element.setChecked(False)
                    self.min_tracklength_slider.setValue(0)

                else:
                    self.check_post_proc()
                    self.ContentsPostProc.show()
                    if "minimum_tracklength" in post_processing_options:
                        self.min_tracklength_slider.setValue(
                            int(post_processing_options["minimum_tracklength"])
                        )
                    if "remove_not_in_first" in post_processing_options:
                        self.remove_not_in_first_checkbox.setChecked(
                            post_processing_options["remove_not_in_first"]
                        )
                    if "remove_not_in_last" in post_processing_options:
                        self.remove_not_in_last_checkbox.setChecked(
                            post_processing_options["remove_not_in_last"]
                        )
                    if "interpolate_position_gaps" in post_processing_options:
                        self.interpolate_gaps_checkbox.setChecked(
                            post_processing_options["interpolate_position_gaps"]
                        )
                    if "extrapolate_tracks_pre" in post_processing_options:
                        self.extrapolate_pre_checkbox.setChecked(
                            post_processing_options["extrapolate_tracks_pre"]
                        )
                    if "extrapolate_tracks_post" in post_processing_options:
                        self.extrapolate_post_checkbox.setChecked(
                            post_processing_options["extrapolate_tracks_post"]
                        )
                    # if "interpolate_na" in post_processing_options:
                    # 	self.interpolate_na_features_checkbox.setChecked(post_processing_options["interpolate_na"])

    def locate_image(self):
        """
        Load the first frame of the first movie found in the experiment folder as a sample.
        """

        movies = glob(
            self.parent_window.parent_window.exp_dir
            + os.sep.join(
                [
                    "*",
                    "*",
                    "movie",
                    self.parent_window.parent_window.movie_prefix + "*.tif",
                ]
            )
        )
        if len(movies) == 0:
            msgBox = QMessageBox()
            msgBox.setIcon(QMessageBox.Warning)
            msgBox.setText(
                "No movies are detected in the experiment folder. Cannot load an image to test Haralick."
            )
            msgBox.setWindowTitle("Warning")
            msgBox.setStandardButtons(QMessageBox.Ok)
            returnValue = msgBox.exec()
            if returnValue == QMessageBox.Yes:
                self.test_frame = None
                return None
        else:
            stack0 = movies[0]
            n_channels = len(self.channels)
            self.test_frame = load_frames(
                np.arange(n_channels), stack0, scale=None, normalize_input=False
            )

    def control_haralick_digitalization(self):
        """
        Load an image for the first experiment movie found.
        Apply the Haralick parameters and check the result of the digitization (normalization + binning of intensities).

        """

        self.locate_image()
        self.extract_haralick_options()
        if self.test_frame is not None:
            digitized_img = compute_haralick_features(
                self.test_frame,
                np.zeros(self.test_frame.shape[:2]),
                channels=list(self.channel_names),
                return_digit_image_only=True,
                **self.haralick_options,
            )

            self.fig, self.ax = plt.subplots()
            divider = make_axes_locatable(self.ax)
            cax = divider.append_axes("right", size="5%", pad=0.05)

            self.imshow_digit_window = FigureCanvas(
                self.fig, title="Haralick: control digitization"
            )
            self.ax.clear()
            im = self.ax.imshow(digitized_img, cmap="gray")
            self.fig.colorbar(im, cax=cax, orientation="vertical")
            self.ax.set_xticks([])
            self.ax.set_yticks([])
            self.fig.set_facecolor("none")  # or 'None'
            self.fig.canvas.setStyleSheet("background-color: transparent;")
            self.imshow_digit_window.canvas.draw()
            self.imshow_digit_window.show()

    def control_haralick_intensity_histogram(self):
        """
        Load an image for the first experiment movie found.
        Apply the Haralick normalization parameters and check the normalized intensity histogram.

        """

        self.locate_image()
        self.extract_haralick_options()
        if self.test_frame is not None:
            norm_img = compute_haralick_features(
                self.test_frame,
                np.zeros(self.test_frame.shape[:2]),
                channels=list(self.channel_names),
                return_norm_image_only=True,
                **self.haralick_options,
            )
            self.fig, self.ax = plt.subplots(1, 1, figsize=(4, 3))
            self.hist_window = FigureCanvas(
                self.fig, title="Haralick: control digitized histogram"
            )
            self.ax.clear()
            flat = norm_img.flatten()
            self.ax.hist(
                flat[flat == flat], bins=self.haralick_options["n_intensity_bins"]
            )
            self.ax.set_xlabel("gray level value")
            self.ax.set_ylabel("#")
            plt.tight_layout()
            self.fig.set_facecolor("none")  # or 'None'
            self.fig.canvas.setStyleSheet("background-color: transparent;")
            self.hist_window.canvas.draw()
            self.hist_window.show()
