__author__ = "Simon Nilsson; sronilsson@gmail.com"

import os
from copy import deepcopy
from subprocess import PIPE, Popen
from tkinter import *
from typing import Any, Dict, List, Optional, Tuple, Union

import matplotlib

matplotlib.use('agg')
import cv2
import pandas as pd
from PIL import Image, ImageTk
from tabulate import tabulate

try:
    from typing import Literal
except ImportError:
    from typing_extensions import Literal

import simba
from simba.mixins.config_reader import ConfigReader
from simba.ui.tkinter_functions import (CreateLabelFrameWithIcon, Entry_Box,
                                        SimbaButton, SimbaCheckbox, SimBALabel)
from simba.utils.checks import (check_file_exist_and_readable, check_int,
                                check_that_column_exist, check_valid_boolean,
                                check_valid_dataframe, check_valid_dict,
                                check_video_and_data_frm_count_align)
from simba.utils.enums import Formats, TagNames
from simba.utils.errors import FrameRangeError, NoDataError, NoFilesFoundError
from simba.utils.lookups import (get_current_time, get_display_resolution,
                                 get_img_resize_info,
                                 get_labelling_img_kbd_bindings,
                                 get_labelling_video_kbd_bindings)
from simba.utils.printing import log_event, stdout_success
from simba.utils.read_write import (find_closest_readable_frame,
                                    get_video_meta_data, read_config_entry,
                                    read_df, read_frm_of_video, write_df)
from simba.utils.warnings import DataHeaderWarning, FrameRangeWarning

PLAY_VIDEO_SCRIPT_PATH = os.path.join(os.path.dirname(simba.__file__), "labelling/play_annotation_video.py")
PADDING = 5

DISPLAY_RESOLUTION = get_display_resolution()
MAX_FRAME_RATI0 = (0.33, 0.33) # W * H RATIO OF IMAGE FRAME ONLY


class LabellingInterface(ConfigReader):
    """
    Launch ``standard`` or ``pseudo``-labelling (annotation) GUI interface in SimBA.

    .. note::
       See the `tutorial <https://github.com/sgoldenlab/simba/blob/master/docs/label_behavior.md>`__.

    .. image:: _static/img/annotator.png
       :width: 500
       :align: center


    :param Union[str, os.PathLike] config_path: path to SimBA project config file in Configparser format
    :param Union[str, os.PathLike] file_path: Path to video that is to be annotated.
    :param Optional[Dict[str, float]] thresholds: Probability thresholds applied to classifier predictions when running pseudo-labelling. Keys must match classifier names and values must be between 0.0 and 1.0. Defaults to None.
    :param bool continuing: Set True to resume annotations from an existing targets file. Defaults to False.

    :example:
    >>> _ = LabellingInterface(config_path=r"C:/troubleshooting/mitra/project_folder/project_config.ini", file_path=r"C:/troubleshooting/mitra/project_folder/videos/501_MA142_Gi_CNO_0521.mp4", thresholds=None, continuing=False)
    """

    def __init__(self,
                 config_path: Union[str, os.PathLike],
                 file_path: Union[str, os.PathLike],
                 thresholds: Optional[Dict[str, float]] = None,
                 continuing: bool = False):

        ConfigReader.__init__(self, config_path=config_path)
        log_event(logger_name=str(self.__class__.__name__), log_type=TagNames.CLASS_INIT.value, msg=self.create_log_msg_from_init_args(locals=locals()))
        check_file_exist_and_readable(file_path=file_path)
        self.video_meta_data = get_video_meta_data(video_path=file_path)
        check_valid_boolean(value=continuing, source=f'{self.__class__.__name__} continuing')
        if thresholds is not None:
            check_valid_dict(x=thresholds, valid_key_dtypes=(str,), valid_values_dtypes=(float,), min_value=0.0, max_value=1.0, valid_keys=self.clf_names)
        self.img_idx, self.thresholds, self.video_path = 0, thresholds, file_path
        self.video_name = self.video_meta_data['video_name']
        self.features_extracted_file_path = os.path.join(self.features_dir, f"{self.video_name}.{self.file_type}")
        self.targets_inserted_file_path = os.path.join(self.targets_folder, f"{self.video_name}.{self.file_type}")
        self.machine_results_file_path = os.path.join(self.machine_results_dir, f"{self.video_name}.{self.file_type}")
        self.cap = cv2.VideoCapture(self.video_path)
        self.img_kbd_bindings = get_labelling_img_kbd_bindings()
        self.video_kbd_bindings = get_labelling_video_kbd_bindings()
        self.max_frm_id = self.video_meta_data["frame_count"] - 1
        self.temp_file_path = os.path.join(os.path.dirname(self.config_path), "subprocess.txt")
        if len(self.clf_names) == 0:
            raise NoDataError(msg='To annotate behaviors, your SimBA project needs at least one defined classifier. Found 0 classifiers defined in SimBA project', source=self.__class__.__name__)
        self.p_cols = [f"Probability_{x}" for x in self.clf_names]
        self.video_last_played_frm_path = os.path.join(os.path.dirname(self.config_path), "labelling_info.txt")
        self.main_window = Toplevel()
        if thresholds is not None:
            if not os.path.isfile(self.machine_results_file_path):
                raise NoFilesFoundError(msg=f'When doing pseudo-annotations, SimBA expects a file at {self.machine_results_file_path} representing video {self.video_name}. SimBA could not find this file.', source=self.__class__.__name__)
            self.data_df = read_df(self.machine_results_file_path, self.file_type)
            check_valid_dataframe(df=self.data_df, source=self.machine_results_file_path, required_fields=self.clf_names + self.p_cols)
            for clf in self.clf_names:
                self.data_df.loc[self.data_df[f"Probability_{clf}"] > self.thresholds[clf], clf] = 1
                self.data_df.loc[self.data_df[f"Probability_{clf}"] <= self.thresholds[clf], clf] = 0
                self.main_window.title(f"SIMBA ANNOTATION INTERFACE (PSEUDO-LABELLING) - {self.video_name}")
        elif continuing:
            if not os.path.isfile(self.targets_inserted_file_path):
                raise NoFilesFoundError( msg=f'When continuing annotations, SimBA expects a file at {self.targets_inserted_file_path}. SimBA could not find this file.', source=self.__class__.__name__)
            self.data_df = read_df(self.targets_inserted_file_path, self.file_type)
            for clf in self.clf_names:
                if not check_that_column_exist(df=self.data_df, column_name=clf, file_name=self.targets_inserted_file_path, raise_error=False):
                    DataHeaderWarning(msg=f'No column named {clf} in file {self.targets_inserted_file_path} - setting all annotations to absent for behavior {clf}.', source=self.__class__.__name__)
                    self.data_df[clf] = 0
            if os.path.isfile(self.features_extracted_file_path):
                features_df = read_df(self.features_extracted_file_path, self.file_type)
                new_x = [x for x in features_df.columns if x not in self.data_df.columns and x not in self.bp_col_names]
                if len(new_x) > 0:
                    if len(features_df) == len(self.data_df):
                        x_df = self.data_df.drop(self.clf_names, axis=1)
                        self.data_df = pd.concat([x_df, features_df[new_x], self.data_df[self.clf_names]], axis=1).reset_index(drop=True).sort_index()
                    else:
                        DataHeaderWarning(msg=f'Cannot append {len(new_x)} additional feature(s) to your annotated data set. The CSV file at {self.features_extracted_file_path} and {self.targets_inserted_file_path} contain different numbers of rows ({len(features_df)} vs {len(self.data_df)}).')
            self.main_window.title(f"SIMBA ANNOTATION INTERFACE (CONTINUING ANNOTATIONS) - {self.video_name}")
            self.img_idx = max(0, read_config_entry(self.config, "Last saved frames", self.video_name, data_type="int", default_value=0))
            if self.video_meta_data['frame_count'] != len(self.data_df):
                self.max_frm_id = min(len(self.data_df), self.video_meta_data['frame_count']) - 1
                msg = f'The data file for video {self.video_name} contains data for {len(self.data_df)} frames, while the video contains data for {self.video_meta_data["frame_count"]} frames. Setting maximal annotated frame index to {self.img_idx}'
                FrameRangeWarning(msg=msg)
            if (self.img_idx > 0) and (self.img_idx > self.max_frm_id):
                FrameRangeWarning(msg=f'The last annotated frame of video {self.video_name} as detected in in the [Last saved frames] section of the project_config.ini was frame {self.img_idx}. However the video {self.video_name} contains {self.max_frm_id} frames. \n SimBA will display frame {self.max_frm_id}.')
                self.img_idx = deepcopy(self.max_frm_id)


        else:
            if not os.path.isfile(self.features_extracted_file_path):
                raise NoFilesFoundError(msg=f'When annotating data from scratch, SimBA expects a data file representing video {self.video_name} at {self.features_extracted_file_path}. SimBA could not find this file. Extract features for video {self.video_name} before annotating data.', source=self.__class__.__name__)
            self.data_df = read_df(self.features_extracted_file_path, self.file_type)
            self.main_window.title(f"SIMBA ANNOTATION INTERFACE (ANNOTATING FROM SCRATCH) - {self.video_name}")
            for clf in self.clf_names: self.data_df[clf] = 0

        aligns = check_video_and_data_frm_count_align(video=self.video_path, data=self.data_df, name=self.video_name, raise_error=False)
        if not aligns:
            self.max_frm_id = min(len(self.data_df), self.video_meta_data['frame_count']) - 1
            msg = f'The data file for video {self.video_name} contains data for {len(self.data_df)} frames, while the video contains data for {self.video_meta_data["frame_count"]} frames. Setting maximal annotated frame index to {self.img_idx}'
            FrameRangeWarning(msg=msg)
        check_valid_dataframe(df=self.data_df, source=f'{self.__class__.__name__} file_path', valid_dtypes=Formats.NUMERIC_DTYPES.value, required_fields=self.clf_names)

        self.data_df_targets = self.data_df[self.clf_names]
        self.data_df = self.data_df.drop(self.clf_names, axis=1)


        scaled_w, scaled_h, _, _  = get_img_resize_info(img_size=(self.video_meta_data['width'], self.video_meta_data['height']), display_resolution=None, max_width_ratio=MAX_FRAME_RATI0[0], max_height_ratio=MAX_FRAME_RATI0[1])
        self.img_display_size = (scaled_w, scaled_h)
        self.img_frm = CreateLabelFrameWithIcon(parent=self.main_window, header='VIDEO FRAME', icon_name='video', relief='solid')
        self.img_lbl = Label(self.img_frm, name='img_lbl')
        self.img_frm.grid(row=0, column=0, sticky=NW, padx=10, pady=10, columnspan=3, rowspan=2)
        self.img_lbl.grid(row=0, column=0, sticky=NW)


        self.navigation_frm = CreateLabelFrameWithIcon(parent=self.main_window, header='FRAME NAVIGATION',  icon_name='navigation', relief='solid')
        self.current_frm_eb = Entry_Box(parent=self.navigation_frm, fileDescription='CURRENT FRAME: ', labelwidth=20, validation='numeric', entry_box_width=10, value=self.img_idx, justify='center', label_font=Formats.FONT_HEADER.value, entry_font=Formats.FONT_HEADER.value)
        self.got_to_frm_btn = SimbaButton(parent=self.navigation_frm, txt="GO TO FRAME", img='walk', font=Formats.FONT_HEADER.value, cmd=self.__advance_frame, cmd_kwargs={'frm_id': lambda: int(self.current_frm_eb.entry_get)}, hover_font=Formats.FONT_HEADER.value)
        self.first_frm_btn = SimbaButton(parent=self.navigation_frm, txt="", tooltip_txt='GO TO FIRST FRAME', img='skip_previous_large', cmd=self.__advance_frame, cmd_kwargs={'frm_id': 0})
        self.reverse_btn = SimbaButton(parent=self.navigation_frm, txt="", img='reverse_large_red', tooltip_txt="-1 FRAME", cmd=self.__advance_frame, cmd_kwargs={'frm_id': lambda: self.img_idx - 1})
        self.forward_btn = SimbaButton(parent=self.navigation_frm, txt="", img='play_large_green', tooltip_txt="+1 FRAME", cmd=self.__advance_frame, cmd_kwargs={'frm_id': lambda: self.img_idx + 1})
        self.last_frm_btn = SimbaButton(parent=self.navigation_frm, txt="", img='skip_forward_large', tooltip_txt="GO TO LAST FRAME", cmd=self.__advance_frame, cmd_kwargs={'frm_id': self.max_frm_id})

        self.set_img(img_lbl=self.img_lbl, video_path=self.video_path, img_idx=self.img_idx, display_size=self.img_display_size)

        self.navigation_frm.grid(row=2, column=0, sticky=NSEW, pady=10, padx=10, columnspan=1)
        self.current_frm_eb.grid(row=0, column=0, sticky=EW, columnspan=4)
        self.got_to_frm_btn.grid(row=0, column=5, sticky=EW, columnspan=2, pady=32)
        self.first_frm_btn.grid(row=1, column=0, sticky=E, padx=5, pady=5)
        self.reverse_btn.grid(row=1, column=1, sticky=E, padx=5, pady=5)
        self.forward_btn.grid(row=1, column=2, sticky=E, padx=5, pady=5)
        self.last_frm_btn.grid(row=1, column=3, sticky=E, padx=5, pady=5)


        self.jump_frm = CreateLabelFrameWithIcon(parent=self.main_window, header='JUMP TO FRAME',  icon_name='jump', relief='solid')
        self.jump_scale = Scale(self.jump_frm, from_=0, to=100, orient=HORIZONTAL, length=200, label="FRAME JUMP SIZE", tickinterval=25)
        self.jump_scale.set(0)
        self.jump_back = SimbaButton(parent=self.jump_frm, txt="", tooltip_txt="JUMP BACKWARDS", img='skip_previous_large', cmd=self.__advance_frame, cmd_kwargs={'frm_id': lambda: self.img_idx - int(self.jump_scale.get())})
        self.jump_forwards = SimbaButton(parent=self.jump_frm, txt="", img='skip_forward_large', tooltip_txt='JUMP FORWARDS', cmd=self.__advance_frame, cmd_kwargs={'frm_id': lambda: self.img_idx + int(self.jump_scale.get())})

        self.jump_frm.grid(row=2, column=1, sticky=NSEW, pady=10, padx=10, columnspan=2)
        self.jump_scale.grid(row=0, column=0, sticky=W, padx=5, pady=5, columnspan=2)
        self.jump_back.grid(row=1, column=0, sticky=E, padx=5, pady=5, columnspan=1)
        self.jump_forwards.grid(row=1, column=1, sticky=W, padx=5, pady=5, columnspan=1)

        self.range_frm = CreateLabelFrameWithIcon(parent=self.main_window, header='ANNOTATE FRAME RANGE', icon_name='range_large', relief='solid')
        self.range_start_eb = Entry_Box(parent=self.range_frm, fileDescription='START FRAME: ', labelwidth=30, validation='numeric', entry_box_width=20, justify='center', label_font=Formats.FONT_HEADER.value, entry_font=Formats.FONT_HEADER.value)
        self.range_end_eb = Entry_Box(parent=self.range_frm, fileDescription='RANGE END FRAME: ', labelwidth=30, validation='numeric', entry_box_width=20, justify='center', label_font=Formats.FONT_HEADER.value, entry_font=Formats.FONT_HEADER.value)
        self.save_range_btn = SimbaButton(parent=self.range_frm, txt="SAVE RANGE", img='save_small', cmd=self.__save_behavior_in_range, cmd_kwargs={'start_frm': lambda: self.range_start_eb.entry_get, 'end_frm': lambda: self.range_end_eb.entry_get})
        self.range_frm.grid(row=3, column=0, sticky=NSEW, padx=10, pady=10, columnspan=1)
        self.range_start_eb.grid(row=0, column=0, sticky=NW)
        self.range_end_eb.grid(row=1, column=0, sticky=NW)
        self.save_range_btn.grid(row=2, column=0, sticky=NW)

        self.save_frm = CreateLabelFrameWithIcon(parent=self.main_window, header='SAVE ANNOTATIONS', icon_name='save_large', relief='solid')
        self.save_btn = SimbaButton(parent=self.save_frm, txt="ANNOTATIONS SAVE", tooltip_txt="SAVE ANNOTATIONS TO DISK", img='rocket', cmd=self.__save_results, font=Formats.FONT_HEADER.value)
        self.save_frm.grid(row=3, column=1, sticky=NSEW, padx=10, pady=10, columnspan=2)
        self.save_btn.grid(row=0, column=0, sticky=NW, padx=5,  pady=20)


        self.clf_cb_frm = CreateLabelFrameWithIcon(parent=self.main_window, header='CHECK BEHAVIOR', icon_name='forest', relief='solid')
        self.clf_cbs = self.get_clf_cbs(parent=self.clf_cb_frm, clfs=self.clf_names)
        self.clf_cb_frm.grid(row=0, column=4, sticky=NSEW, padx=10, pady=10)

        self.selected_shortcuts = 'frame'
        self.shortcuts_frm = CreateLabelFrameWithIcon(parent=self.main_window, header='KEYBOARD SHORTCUTS', icon_name='documentation', relief='solid')
        self.frame_navigation_shortcuts_btn = SimbaButton(parent=self.shortcuts_frm, txt="FRAME NAVIGATION SHORTCUTS", cmd=self.get_keyboard_shortcuts_lbl, cmd_kwargs={'type': lambda: 'frame'}, hover_font=None, hover_bg_clr=None)
        self.video_navigation_shortcuts_btn = SimbaButton(parent=self.shortcuts_frm, txt="VIDEO NAVIGATION SHORTCUTS", cmd=self.get_keyboard_shortcuts_lbl, cmd_kwargs={'type': lambda: 'video'}, hover_font=None, hover_bg_clr=None)
        self.shortcuts_frm.grid(row=1, column=4, sticky=NSEW, padx=10, pady=10, columnspan=2)
        self.frame_navigation_shortcuts_btn.grid(row=0, column=0, sticky=NSEW)
        self.video_navigation_shortcuts_btn.grid(row=0, column=1, sticky=NSEW)
        self.shortcuts_container = Frame(self.shortcuts_frm)
        self.shortcuts_container.grid(row=1, column=0, sticky=NSEW, columnspan=4)
        self.get_keyboard_shortcuts_lbl(type=self.selected_shortcuts)
        self.__bind_frm_shortcut_keys()

        self.video_player_frm = CreateLabelFrameWithIcon(parent=self.main_window, header='PLAY VIDEO', icon_name='tv_large', relief='solid')
        self.play_video_btn = SimbaButton(parent=self.video_player_frm, txt="OPEN VIDEO", img='video_file_large', cmd=self.__play_video)
        self.update_img_from_video = SimbaButton(parent=self.video_player_frm, txt="SHOW CURRENT VIDEO FRAME", img='display_large', cmd=self.__update_frame_from_video)
        self.video_player_frm.grid(row=2, column=4, sticky=NSEW, padx=10, pady=10)
        self.play_video_btn.grid(row=0, column=0, sticky=NSEW, padx=3, pady=3)
        self.update_img_from_video.grid(row=0, column=1, sticky=NSEW,  padx=3, pady=3)


        self.main_window.mainloop()

    def get_keyboard_shortcuts_lbl(self,
                                   type: str):

            if hasattr(self, 'shortcuts_container'):
                self.shortcuts_container.destroy()
                self.shortcuts_container = Frame(self.shortcuts_frm)
                self.shortcuts_container.grid(row=1, column=0, sticky=NSEW, columnspan=2)

            if type == 'frame':
                kbd_shortcuts = self.img_kbd_bindings
                self.frame_navigation_shortcuts_btn.config(bg="black", fg="white")
                self.video_navigation_shortcuts_btn.config(bg="white", fg="black")
            else:
                kbd_shortcuts = self.video_kbd_bindings
                self.frame_navigation_shortcuts_btn.config(bg="white", fg="black")
                self.video_navigation_shortcuts_btn.config(bg="black", fg="white")
            for row_cnt, (k, v) in enumerate(kbd_shortcuts.items()):
                label, shortcut = v['label'].split(sep='=', maxsplit=2)
                label = SimBALabel(parent=self.shortcuts_container, txt=label, font=Formats.FONT_REGULAR_BOLD.value, justify='left')
                shortcut = SimBALabel(parent=self.shortcuts_container, txt=shortcut, font=Formats.FONT_REGULAR.value, justify='right')
                label.grid(row=row_cnt+1, column=0, sticky=W, padx=(5, 0))
                shortcut.grid(row=row_cnt+1, column=1, sticky=E, padx=(5, 0))


    def __bind_frm_shortcut_keys(self):
        self.main_window.bind(self.img_kbd_bindings['save']['kbd'], lambda x: self.__save_results())
        self.main_window.bind(self.img_kbd_bindings['frame+1_keep_choices']['kbd'], lambda x: self.__advance_frame(frm_id=self.img_idx+1, keep_checks=True))
        self.main_window.bind(self.img_kbd_bindings['frame-1_keep_choices']['kbd'], lambda x: self.__advance_frame(frm_id=self.img_idx-1, keep_checks=True))
        self.main_window.bind(self.img_kbd_bindings['print_annotation_statistic']['kbd'], lambda x: self.print_annotation_statistics(video_meta_data=self.video_meta_data, clf_names=self.clf_names, data_df=self.data_df_targets))
        self.main_window.bind(self.img_kbd_bindings['frame+1']['kbd'], lambda x: self.__advance_frame(frm_id=self.img_idx+1))
        self.main_window.bind(self.img_kbd_bindings['frame-1']['kbd'], lambda x: self.__advance_frame(frm_id=self.img_idx-1))
        self.main_window.bind(self.img_kbd_bindings['last_frame']['kbd'], lambda x: self.__advance_frame(frm_id=self.max_frm_id))
        self.main_window.bind(self.img_kbd_bindings['first_frame']['kbd'], lambda x: self.__advance_frame(0))

    def __play_video(self):
        p = Popen(f"python {PLAY_VIDEO_SCRIPT_PATH}",stdin=PIPE, stdout=PIPE, shell=True)
        p.stdin.write(bytes(self.video_path, "utf-8")); p.stdin.close()
        with open(self.temp_file_path, "w") as text_file: text_file.write(str(p.pid))

    def __update_frame_from_video(self):
        if not os.path.isfile(self.video_last_played_frm_path):
            raise FrameRangeError(msg=f'Cannot show the most recent played video image frame, the video has not been played.', source=self.__class__.__name__)
        f = open(os.path.join(os.path.dirname(self.config_path), "labelling_info.txt"), "r+")
        os.fsync(f.fileno())
        vid_frame_no = int(f.readline())
        if 0 > vid_frame_no > self.max_frm_id:
            raise FrameRangeError(msg=f'Cannot show the most recent played video image frame, the video has not been played (got frame: {vid_frame_no})', source=self.__class__.__name__)
        self.__advance_frame(frm_id=vid_frame_no)
        f.close()

    def set_img(self,
                img_lbl: Label,
                video_path: Union[str, os.PathLike],
                img_idx: int,
                display_size: Optional[Tuple[int, int]] = None):

        self.img = read_frm_of_video(video_path=video_path, frame_index=img_idx, size=display_size, raise_error=False)
        if self.img is None:
            FrameRangeWarning(msg=f'Frame {img_idx} could not be read in video {video_path}. Attempting to find closest readable frame...')
            img, img_idx = find_closest_readable_frame(video_path=video_path, target_frame=img_idx, max_search_range=50)
            if img_idx is not None:
                print(f'Showing frame {img_idx} in video {self.video_name}...')
                self.img = read_frm_of_video(video_path=video_path, frame_index=img_idx, size=display_size, raise_error=True)
                self.img_idx = deepcopy(img_idx)
                self.current_frm_eb.entry_set(val=str(img_idx))
            else:
                raise FrameRangeError(msg=f'Could not find a readable frame (± frames of {img_idx}) in video {video_path}. Cannot proceed with annotations in this video.', source=self.__class__.__name__)
        img_rgb = cv2.cvtColor(self.img, cv2.COLOR_BGR2RGB)
        self.pil_image = Image.fromarray(img_rgb)
        self.tk_image = ImageTk.PhotoImage(self.pil_image)
        img_lbl.configure(image=self.tk_image)
        img_lbl.image = self.tk_image

    def get_clf_cbs(self,
                    parent: Toplevel,
                    clfs: List[str]) -> Dict[str, BooleanVar]:

        clf_cb_vars = {}
        for clf_cnt, clf_name in enumerate(clfs):
            cb, clf_cb_vars[clf_name] = SimbaCheckbox(parent=parent, txt=clf_name, font=Formats.FONT_HEADER.value)
            cb.grid(row=clf_cnt, column=0, sticky=NW, padx=5, pady=5)
        return clf_cb_vars


    def print_annotation_statistics(self,
                                    video_meta_data: Dict[str, Any],
                                    clf_names: List[str],
                                    data_df: pd.DataFrame):

        table_view = [["Video name", video_meta_data['video_name']],
                      ["Video frames", video_meta_data["frame_count"]],
                      ['Current time:', get_current_time()]]
        for clf in clf_names:
            present = len(data_df[data_df[clf] == 1])
            absent = len(data_df[data_df[clf] == 0])
            table_view.append([clf + " present labels", present])
            table_view.append([clf + " absent labels", absent])
            table_view.append([clf + " % present", present / video_meta_data["frame_count"]])
            table_view.append([clf + " % absent", absent / video_meta_data["frame_count"]])
        headers = ["VARIABLE", "VALUE"]
        print(tabulate(table_view, headers, tablefmt="github"))

    def __advance_frame(self,
                        frm_id: int,
                        keep_checks: bool = False):

        if frm_id > self.max_frm_id:
            raise FrameRangeError(msg=f"TRYING TO SHOW FRAME {frm_id} WHICH CANNOT BE SHOWN - THE VIDEO {self.video_name} HAS {self.video_meta_data['frame_count']} FRAMES")
        elif frm_id < 0:
            raise FrameRangeError(msg=f"TRYING TO SHOW FRAME {frm_id} WHICH CANNOT BE SHOWN. FRAME INDEX HAS TO BE 0 OR ABOVE")
        else:
            prior_frm_idx = int(self.current_frm_eb.entry_get)
            self.__create_print_statements(frame=prior_frm_idx)
            self.current_frm_eb.entry_set(val=frm_id)
            for clf in self.clf_names:
                self.save_behavior_in_frm(clf_name=clf, frame_idx=prior_frm_idx)
                if keep_checks:
                    self.clf_cbs[clf].set(bool(self.data_df_targets[clf].loc[prior_frm_idx]))
                else:
                    self.clf_cbs[clf].set(bool(self.data_df_targets[clf].loc[frm_id]))
            self.img_idx = deepcopy(frm_id)
            self.set_img(img_lbl=self.img_lbl, video_path=self.video_path, img_idx=frm_id, display_size=self.img_display_size)

    def __save_behavior_in_range(self,
                                 start_frm: int,
                                 end_frm: int):

        check_int(name="RANGE START FRAME", value=start_frm, min_value=0, max_value=self.max_frm_id)
        check_int(name="RANGE END FRAME", value=end_frm, min_value=0, max_value=self.max_frm_id)
        start_frm, end_frm = int(start_frm), int(end_frm)
        if start_frm >= end_frm:
            raise FrameRangeError(msg=f'The RANGE START FRAME ({start_frm}) has to be smaller than the RANGE END FRAME ({end_frm})', source=self.__class__.__name__)
        for frm_no in range(int(start_frm), int(end_frm) + 1):
            for target in self.clf_names:
                self.data_df_targets[target].loc[frm_no] = int(self.clf_cbs[target].get())
        self.set_img(img_lbl=self.img_lbl, video_path=self.video_path, img_idx=end_frm, display_size=self.img_display_size)
        self.current_frm_eb.entry_set(end_frm)
        self.img_idx = deepcopy(end_frm)
        self.__create_print_statements(frame=(start_frm, end_frm))

    def save_behavior_in_frm(self,
                             frame_idx: int,
                             clf_name: str):

        self.data_df_targets[clf_name].loc[frame_idx] = int(self.clf_cbs[clf_name].get())

    def __save_results(self):
        self.save_df = pd.concat([self.data_df, self.data_df_targets], axis=1)
        try:
            write_df(self.save_df, self.file_type, self.targets_inserted_file_path)
        except Exception as e:
            print(e, f"SIMBA ERROR: File for video {self.video_name} could not be saved.")
            raise FileExistsError
        stdout_success(msg=f"SAVED: Annotation file for video {self.video_name} saved within the {self.targets_folder} directory at file path: {self.targets_inserted_file_path}.", source=self.__class__.__name__)
        if not self.config.has_section("Last saved frames"):
            self.config.add_section("Last saved frames")
        self.config.set("Last saved frames", str(self.video_name), str(self.img_idx-1))
        with open(self.config_path, "w") as configfile: self.config.write(configfile)

    def __create_print_statements(self,
                                  frame: Union[int, Tuple[int, int]]):

        for target in self.clf_names:
            target_present_choice = self.clf_cbs[target].get()
            if target_present_choice:
                if isinstance(frame, int):
                    print(f"{target} PRESENT in frame {frame}")
                else:
                    print(f"{target} PRESENT in frames {frame[0]} to {frame[1]}")
            else:
                if isinstance(frame, int):
                    print(f"{target} ABSENT in frame {frame}")
                else:
                    print(f"{target} ABSENT in frames {frame[0]} to {frame[1]}")


# test = LabellingInterface(config_path=r"C:\troubleshooting\mitra\project_folder\project_config.ini",
#                               threshold_dict=None, #threshold_dict={'Attack': 0.4}
#                               setting='from_scratch',
#                               continuing=False)

# _ = LabellingInterface(config_path=r"C:\troubleshooting\mitra\project_folder\project_config.ini",
#                        file_path=r"C:\troubleshooting\mitra\project_folder\videos\501_MA142_Gi_CNO_0521.mp4",
#                        thresholds=None,
#                        continuing=False)


# _ = LabellingInterface(config_path=r"C:\troubleshooting\mitra\project_folder\project_config.ini",
#                        file_path=r"C:\troubleshooting\mitra\project_folder\videos\video_test_vaishnavi.mp4",
#                        thresholds=None,
#                        continuing=False)



# _ = LabellingInterface(config_path=r"C:\troubleshooting\mitra\project_folder\project_config.ini",
#                        file_path=r"C:\troubleshooting\mitra\project_folder\videos\video_test_vaishnavi.mp4",
#                        thresholds=None,
#                        continuing=False)



# _ = LabellingInterface(config_path=r"D:\troubleshooting\lbl_frm_rng\project_folder\project_config.ini",
#                        file_path=r"D:\troubleshooting\lbl_frm_rng\project_folder\videos\M5_Prb3dTom_SNP_EmptyCupHab.mp4",
#                        thresholds=None,
#                        continuing=True)
#



#
# import threading
# import subprocess
#
# def _start_subprocess():
#     p = subprocess.Popen("some cli cmd")
#     p.wait()
#
# thread = threading.Thread(target=_start_subprocess, daemon=True)
# thread.start()


# #
# test = select_labelling_video(config_path=r"/Users/simon/Desktop/envs/simba/troubleshooting/mitra/project_folder/project_config.ini",
#                               threshold_dict={'Attack': 0.4},
#                               setting='from_scratch',
#                               continuing=False)
