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

import math
from tkinter import *
from typing import List, Optional, Tuple

import cv2
import numpy as np
import pandas as pd
from PIL import Image, ImageTk
from shapely.geometry import Polygon

from simba.mixins.plotting_mixin import PlottingMixin
from simba.roi_tools.roi_utils import (get_circle_df_headers,
                                       get_polygon_df_headers,
                                       get_rectangle_df_headers,
                                       insert_gridlines_on_roi_img)
from simba.utils.checks import check_valid_polygon
from simba.utils.enums import ROI_SETTINGS, Keys

DRAW_FRAME_NAME = "DEFINE SHAPE"

BR_TAG = 'Bottom right tag'
B_TAG = 'Bottom tag'
T_TAG = 'Top tag'
C_TAG = 'Center tag'
BL_TAG = 'Bottom left tag'
TR_TAG = 'Top right tag'
TL_TAG = 'Top left tag'
R_TAG = 'Right tag'
L_TAG = 'Left tag'
BR_X = "Bottom_right_X"
BR_Y = "Bottom_right_Y"
TL_X = 'topLeftX'
TL_Y = "topLeftY"
CENTER_X, CENTER_Y = "Center_X", "Center_Y"
HEIGHT, WIDTH = 'height', 'width'
BORDER_TAG = 'Border tag'
CIRCLE_C_X = 'centerX'
CIRCLE_C_Y = 'centerY'
RADIUS = 'radius'
VERTICES = 'vertices'
OVERLAY_GRID_COLOR = 'OVERLAY_GRID_COLOR'

def _plot_roi(roi_dict: dict, 
              img: np.ndarray,
              show_tags: bool = False,
              omitted_roi: Optional[str] = None):
    
    rectangles_df, circles_df, polygon_df = pd.DataFrame(columns=get_rectangle_df_headers()), pd.DataFrame(columns=get_circle_df_headers()), pd.DataFrame(columns=get_polygon_df_headers())
    for roi_name, roi_data in roi_dict.items():
        if (roi_data['Shape_type'].lower() == ROI_SETTINGS.RECTANGLE.value) and (roi_name != omitted_roi):
            rectangles_df = pd.concat([rectangles_df, pd.DataFrame([roi_data])], ignore_index=True)
        elif roi_data['Shape_type'].lower() == ROI_SETTINGS.CIRCLE.value and (roi_name != omitted_roi):
            circles_df = pd.concat([circles_df, pd.DataFrame([roi_data])], ignore_index=True)
        elif roi_data['Shape_type'].lower() == ROI_SETTINGS.POLYGON.value and (roi_name != omitted_roi):
            polygon_df = pd.concat([polygon_df, pd.DataFrame([roi_data])], ignore_index=True)
    roi_dict = {Keys.ROI_RECTANGLES.value: rectangles_df, Keys.ROI_CIRCLES.value: circles_df, Keys.ROI_POLYGONS.value: polygon_df}
    img = PlottingMixin.roi_dict_onto_img(img=img, roi_dict=roi_dict, circle_size=None, show_tags=show_tags)


    return img


class InteractiveROIModifier():
    """
    Interactive GUI tool for modifying ROI (Region of Interest) shapes by clicking and dragging.

    This class provides an interactive interface for modifying existing ROI shapes (rectangles, circles, and polygons)
    by clicking and dragging control points (tags/vertices). Users can resize, reposition, and reshape ROIs through
    mouse interactions. The tool supports both OpenCV windows and Tkinter windows, and can display optional grid overlays
    for alignment assistance.

    .. note::
       - For rectangles: Click and drag corner tags, edge tags (top, bottom, left, right), or center tag to modify the shape.
       - For circles: Click and drag the center tag to move the circle, or the border tag to resize the radius.
       - For polygons: Click and drag individual vertex tags to reshape the polygon, or the center tag to move the entire polygon.
       - All modifications are constrained to stay within the image boundaries.
       - The modified ROI dictionary is updated in-place during interactions.

    .. seealso::
       For creating new ROIs from scratch, see :func:`simba.roi_tools.roi_selector_polygon_tkinter.ROISelectorPolygonTkinter`
       and :func:`simba.roi_tools.roi_selector_circle_tkinter.ROISelectorCircleTkinter`.

    :param str window_name: Name of the OpenCV window to display the interactive ROI editor.
    :param dict roi_dict: Dictionary containing ROI definitions. Each key is an ROI name, and each value is a dictionary
                         with shape-specific keys (e.g., 'Shape_type', 'Name', 'Tags', 'Color BGR', 'Thickness', etc.).
                         Must include 'Tags' dictionary with control point coordinates for each ROI.
    :param np.ndarray img: NumPy array representing the image with ROIs already drawn. Shape should be (height, width, channels).
    :param np.ndarray orginal_img: NumPy array representing the original, unmodified image without ROIs. Used as base for redrawing.
                                   Shape should match `img`.
    :param Optional[dict] settings: Optional dictionary of ROI display settings. If None, uses default settings from `ROI_SETTINGS`.
    :param bool tkinter_window: If True, displays the interface in a Tkinter window. If False, uses OpenCV window. Default: True.
    :param Optional[List[Polygon]] hex_grid: Optional list of Shapely Polygon objects representing a hexagonal grid overlay.
                                             Gridlines will be drawn on the image for alignment assistance.
    :param Optional[List[Polygon]] rectangle_grid: Optional list of Shapely Polygon objects representing a rectangular grid overlay.
                                                    Gridlines will be drawn on the image for alignment assistance.

    :example:
    >>> import cv2
    >>> import numpy as np
    >>> from simba.roi_tools.interactive_modifier_ui import InteractiveROIModifier
    >>> from simba.utils.enums import ROI_SETTINGS
    >>> # Load an image
    >>> img = cv2.imread('path/to/image.jpg')
    >>> original_img = img.copy()
    >>> # Define a rectangle ROI
    >>> rectangle_roi = {
    ...     'Shape_type': ROI_SETTINGS.RECTANGLE.value,
    ...     'Name': 'My_rectangle',
    ...     'Color BGR': (0, 0, 255),
    ...     'Thickness': 7,
    ...     'topLeftX': 100,
    ...     'topLeftY': 100,
    ...     'Bottom_right_X': 250,
    ...     'Bottom_right_Y': 250,
    ...     'Tags': {
    ...         'Top left tag': (100, 100),
    ...         'Bottom right tag': (250, 250),
    ...         'Center tag': (175, 175)
    ...     },
    ...     'Ear_tag_size': 15
    ... }
    >>> roi_dict = {'My_rectangle': rectangle_roi}
    >>> # Draw ROIs on image
    >>> # ... (drawing code here) ...
    >>> # Create and run the interactive modifier
    >>> modifier = InteractiveROIModifier(
    ...     window_name='DEFINE SHAPE',
    ...     roi_dict=roi_dict,
    ...     img=img,
    ...     orginal_img=original_img,
    ...     tkinter_window=False
    ... )
    >>> modifier.run()
    >>> # After closing the window, roi_dict will contain the modified ROI coordinates
    """

    def __init__(self,
                 window_name: DRAW_FRAME_NAME,
                 roi_dict: dict,
                 img: np.ndarray,
                 orginal_img: np.ndarray,
                 settings: Optional[dict] = None,
                 tkinter_window: bool = True,
                 hex_grid: Optional[List[Polygon]] = None,
                 rectangle_grid: Optional[List[Polygon]] = None):

        self.window_name = window_name
        self.roi_dict = roi_dict
        self.img = img
        self.img_h, self.img_w = (img.shape[0], img.shape[1])
        self.circle_size = PlottingMixin().get_optimal_circle_size(frame_size=(self.img_w, self.img_h), circle_frame_ratio=100)

        self.original = orginal_img
        self.dragging = False
        self.temp_img = None
        self.tkinter_window = tkinter_window
        self.terminate = False
        self.rectangle_grid, self.hex_grid = rectangle_grid, hex_grid
        if settings is None:
            self.settings = {item.name: item.value for item in ROI_SETTINGS}
        else:
            self.settings = settings
        if self.tkinter_window:
            self.root = Tk()
            self.root.title(DRAW_FRAME_NAME)
            self.root.geometry(f"{self.img_w}x{self.img_h}")
            self.img_tk_frame = Frame(self.root, width=self.img_w, height=self.img_h, bg="gray")
            self.img_tk_frame.pack(pady=20)
            self.root.mainloop()


    def get_gridline_copy(self):
        gridline_img = self.original.copy()
        if self.rectangle_grid is not None:
            gridline_img = insert_gridlines_on_roi_img(img=gridline_img, grid=self.rectangle_grid, color=self.settings[OVERLAY_GRID_COLOR], thickness=max(1, int(self.circle_size/5)))
        if self.hex_grid is not None:
            gridline_img = insert_gridlines_on_roi_img(img=gridline_img, grid=self.rectangle_grid, color=self.settings[OVERLAY_GRID_COLOR], thickness=max(1, int(self.circle_size / 5)))
        return gridline_img


    def find_closest_tag(self, roi_dict: dict, click_coordinate: Tuple[int, int]):
        clicked_roi, clicked_tag = None, None
        for roi_name, roi_data in roi_dict.items():
            ear_tag_size = roi_data['Ear_tag_size']
            for roi_tag_name, roi_tag_coordinate in roi_data['Tags'].items():
                distance = math.sqrt((roi_tag_coordinate[0] - click_coordinate[0]) ** 2 + (roi_tag_coordinate[1] - click_coordinate[1]) ** 2)
                if distance <= ear_tag_size:
                    clicked_roi, clicked_tag = roi_data, roi_tag_name
        return clicked_roi, clicked_tag

    def _select_rectangle_bottom_right(self):
        self.clicked_roi[BR_X], self.clicked_roi[BR_Y] = self.x, self.y
        self.clicked_roi[HEIGHT], self.clicked_roi[WIDTH] = self.h, self.w
        self.clicked_roi[CENTER_X], self.clicked_roi[CENTER_Y] = int(self.tl_x + self.w / 2), int(self.tl_y + self.h / 2)
        self.clicked_roi['Tags'][TR_TAG] = (self.x, self.y - self.h)
        self.clicked_roi['Tags'][BL_TAG] = (self.x - self.w, self.y)
        self.clicked_roi['Tags'][BR_TAG] = (self.x, self.y)
        self.clicked_roi['Tags'][R_TAG] = (self.x, int(self.tl_y + (self.h / 2)))
        self.clicked_roi['Tags'][L_TAG] = (self.tl_x, int(self.tl_y + (self.h / 2)))
        self.clicked_roi['Tags'][B_TAG] = (int(self.tl_x + self.w / 2), self.y)
        self.clicked_roi['Tags'][T_TAG] = (int(self.tl_x + self.w / 2), self.y - self.h)
        self.clicked_roi['Tags'][C_TAG] = (self.clicked_roi[CENTER_X], self.clicked_roi[CENTER_Y])
        cv2.rectangle(self.temp_img, self.clicked_roi['Tags'][TL_TAG], (self.x, self.y), self.clicked_roi['Color BGR'],self.clicked_roi['Thickness'])
        cv2.line(self.temp_img, (self.x, self.y), self.clicked_roi['Tags'][BL_TAG], self.settings['ROI_SELECT_CLR'], self.clicked_roi['Thickness'], lineType=self.settings['LINE_TYPE'])
        cv2.line(self.temp_img, (self.x, self.y), self.clicked_roi['Tags'][TR_TAG], self.settings['ROI_SELECT_CLR'], self.clicked_roi['Thickness'], lineType=self.settings['LINE_TYPE'])

    def _select_rectangle_right(self):
        self.clicked_roi[WIDTH] = self.w
        self.clicked_roi[CENTER_X], self.clicked_roi[CENTER_Y] = int(self.tl_x + self.w / 2), int(self.tl_y + self.h / 2)
        self.clicked_roi['Tags'][TR_TAG] = (self.x, self.tl_y)
        self.clicked_roi['Tags'][BR_TAG] = (self.x, self.tl_y + self.h)
        self.clicked_roi['Tags'][R_TAG] = (self.tl_x + self.w, self.tl_y + int(self.h / 2))
        self.clicked_roi['Tags'][T_TAG] = (self.tl_x + int(self.w / 2), self.tl_y)
        self.clicked_roi['Tags'][B_TAG] = (self.tl_x + int(self.w / 2), self.tl_y + self.h)
        self.clicked_roi['Tags'][C_TAG] = (self.tl_x + int(self.w / 2), self.tl_y + int(self.h / 2))
        self.clicked_roi[BR_X] = self.x
        self.clicked_roi[BR_Y] = self.tl_y + self.h
        cv2.rectangle(self.temp_img, self.clicked_roi['Tags'][TL_TAG], self.clicked_roi['Tags'][BR_TAG], self.clicked_roi['Color BGR'], self.clicked_roi['Thickness'])
        cv2.line(self.temp_img, self.clicked_roi['Tags'][BR_TAG], self.clicked_roi['Tags'][TR_TAG], self.settings['ROI_SELECT_CLR'], self.clicked_roi['Thickness'], lineType=self.settings['LINE_TYPE'])

    def _select_rectangle_bottom(self):
        self.clicked_roi['Tags'][BR_TAG] = (self.tl_x + self.w, self.tl_y + self.h)
        self.clicked_roi['Tags'][BL_TAG] = (self.tl_x, self.tl_y + self.h)
        self.clicked_roi['Tags'][L_TAG] = (self.tl_x, self.tl_y + int(self.h / 2))
        self.clicked_roi['Tags'][R_TAG] = (self.tl_x + self.w, self.tl_y + int(self.h / 2))
        self.clicked_roi['Tags'][B_TAG] = (self.tl_x + int(self.w / 2), self.tl_y + self.h)
        self.clicked_roi['Tags'][C_TAG] = (self.tl_x + int(self.w / 2), self.tl_y + int(self.h / 2))
        self.clicked_roi[BR_X] = self.tl_x + self.w
        self.clicked_roi[BR_Y] = self.tl_y + self.h
        self.clicked_roi[HEIGHT] = self.h
        self.clicked_roi[CENTER_X], self.clicked_roi[CENTER_Y] = self.tl_x + int(self.w / 2), self.tl_y + int(self.h / 2)
        cv2.rectangle(self.temp_img, self.clicked_roi['Tags'][TL_TAG], self.clicked_roi['Tags'][BR_TAG], self.clicked_roi['Color BGR'], self.clicked_roi['Thickness'])
        cv2.line(self.temp_img, self.clicked_roi['Tags'][BL_TAG], self.clicked_roi['Tags'][BR_TAG], self.settings['ROI_SELECT_CLR'], self.clicked_roi['Thickness'], lineType=self.settings['LINE_TYPE'])


    def _select_rectangle_bottom_left(self):
        self.clicked_roi['Tags'][TL_TAG] = (self.x, self.tr_y)
        self.clicked_roi['Tags'][L_TAG] = (self.x, self.tr_y + int(self.h / 2))
        self.clicked_roi['Tags'][BL_TAG] = (self.x, self.y)
        self.clicked_roi['Tags'][B_TAG] = (self.x + int(self.w / 2), self.y)
        self.clicked_roi['Tags'][BR_TAG] = (self.x + self.w, self.tr_y + self.h)
        self.clicked_roi['Tags'][R_TAG] = (self.x + self.w, self.tr_y + int(self.h / 2))
        self.clicked_roi['Tags'][T_TAG] = (self.x + int(self.w / 2), self.tr_y)
        self.clicked_roi['Tags'][C_TAG] = (self.x + int(self.w / 2), self.tr_y + int(self.h / 2))
        self.clicked_roi[TL_X] = self.x
        self.clicked_roi[BR_X] = self.x + self.w
        self.clicked_roi[BR_Y] = self.tr_y + self.h
        self.clicked_roi[HEIGHT] = self.h
        self.clicked_roi[WIDTH] = self.w
        self.clicked_roi[CENTER_X], self.clicked_roi[CENTER_Y] = self.x + int(self.w / 2), self.tr_y + int(self.h / 2)
        cv2.rectangle(self.temp_img, self.clicked_roi['Tags'][TL_TAG], self.clicked_roi['Tags'][BR_TAG], self.clicked_roi['Color BGR'], self.clicked_roi['Thickness'])
        cv2.line(self.temp_img, self.clicked_roi['Tags'][BL_TAG], self.clicked_roi['Tags'][TL_TAG], self.settings['ROI_SELECT_CLR'], self.clicked_roi['Thickness'], lineType=self.settings['LINE_TYPE'])
        cv2.line(self.temp_img, self.clicked_roi['Tags'][BL_TAG], self.clicked_roi['Tags'][BR_TAG], self.settings['ROI_SELECT_CLR'], self.clicked_roi['Thickness'], lineType=self.settings['LINE_TYPE'])

    def _select_rectangle_left_tag(self):
        self.clicked_roi[WIDTH] = self.w
        self.clicked_roi[CENTER_X], self.clicked_roi[CENTER_Y] = int(self.tr_x - (self.w / 2)), int(self.tr_y + self.h / 2)
        self.clicked_roi['Tags'][BL_TAG] = (self.tr_x - self.w, self.tr_y + self.h)
        self.clicked_roi['Tags'][TL_TAG] = (self.tr_x - self.w, self.tr_y)
        self.clicked_roi['Tags'][L_TAG] = (self.x, int(self.tr_y + self.h / 2))
        self.clicked_roi['Tags'][B_TAG] = (self.tr_x - int(self.w / 2), self.tr_y + self.h)
        self.clicked_roi['Tags'][T_TAG] = (self.tr_x - int(self.w / 2), self.tr_y)
        self.clicked_roi['Tags'][C_TAG] = (int(self.tr_x - (self.w / 2)), int(self.tr_y + self.h / 2))
        self.clicked_roi[TL_X] = self.tr_x - self.w
        cv2.rectangle(self.temp_img, self.clicked_roi['Tags'][TL_TAG], self.clicked_roi['Tags'][BR_TAG], self.clicked_roi['Color BGR'], self.clicked_roi['Thickness'])
        cv2.line(self.temp_img, self.clicked_roi['Tags'][BL_TAG], self.clicked_roi['Tags'][TL_TAG], self.settings['ROI_SELECT_CLR'], self.clicked_roi['Thickness'], lineType=self.settings['LINE_TYPE'])

    def _select_rectangle_top_tag(self):
        self.clicked_roi['Tags'][TR_TAG] = (self.br_x, self.br_y - self.h)
        self.clicked_roi['Tags'][TL_TAG] = (self.br_x - self.w, self.br_y - self.h)
        self.clicked_roi['Tags'][T_TAG] = (self.br_x - int(self.w / 2), self.br_y - self.h)
        self.clicked_roi['Tags'][L_TAG] = (self.br_x - self.w, self.br_y - int(self.h / 2))
        self.clicked_roi['Tags'][R_TAG] = (self.br_x, self.br_y - int(self.h / 2))
        self.clicked_roi['Tags'][C_TAG] = (self.br_x - int(self.w / 2), self.br_y - int(self.h / 2))
        self.clicked_roi[TL_X] = self.br_x - self.w
        self.clicked_roi[TL_Y] = self.br_y - self.h
        self.clicked_roi[HEIGHT] = self.h
        self.clicked_roi[CENTER_X], self.clicked_roi[CENTER_Y] = self.br_x - int(self.w / 2), self.br_y - int(self.h / 2)
        cv2.rectangle(self.temp_img, self.clicked_roi['Tags'][TL_TAG], self.clicked_roi['Tags'][BR_TAG], self.clicked_roi['Color BGR'], self.clicked_roi['Thickness'])
        cv2.line(self.temp_img, self.clicked_roi['Tags'][TL_TAG], self.clicked_roi['Tags'][TR_TAG], self.settings['ROI_SELECT_CLR'], self.clicked_roi['Thickness'], lineType=self.settings['LINE_TYPE'])


    def _select_rectangle_top_left_tag(self):
        self.clicked_roi['Tags'][TR_TAG] = (self.br_x, self.br_y - self.h)
        self.clicked_roi['Tags'][T_TAG] = (self.br_x - int(self.w / 2), self.br_y - self.h)
        self.clicked_roi['Tags'][L_TAG] = (self.br_x - self.w, self.br_y - int(self.h / 2))
        self.clicked_roi['Tags'][R_TAG] = (self.br_x, self.br_y - int(self.h / 2))
        self.clicked_roi['Tags'][BL_TAG] = (self.x, self.br_y)
        self.clicked_roi['Tags'][B_TAG] = (self.x + int(self.w/2), self.br_y)
        self.clicked_roi['Tags'][TL_TAG] = (self.x, self.y)
        self.clicked_roi['Tags'][C_TAG] = (self.x + int(self.w / 2), self.y + int(self.h / 2))
        self.clicked_roi[TL_X] = self.x
        self.clicked_roi[TL_Y] = self.y
        self.clicked_roi[HEIGHT] = self.h
        self.clicked_roi[WIDTH] = self.w
        self.clicked_roi[CENTER_X], self.clicked_roi[CENTER_Y] = self.x + int(self.w / 2), self.y + int(self.h / 2)
        cv2.rectangle(self.temp_img, self.clicked_roi['Tags'][TL_TAG], self.clicked_roi['Tags'][BR_TAG], self.clicked_roi['Color BGR'], self.clicked_roi['Thickness'])
        cv2.line(self.temp_img, self.clicked_roi['Tags'][TL_TAG], self.clicked_roi['Tags'][TR_TAG], self.settings['ROI_SELECT_CLR'], self.clicked_roi['Thickness'], lineType=self.settings['LINE_TYPE'])
        cv2.line(self.temp_img, self.clicked_roi['Tags'][TL_TAG], self.clicked_roi['Tags'][BL_TAG], self.settings['ROI_SELECT_CLR'], self.clicked_roi['Thickness'], lineType=self.settings['LINE_TYPE'])

    def _select_rectangle_top_right_tag(self):
        self.clicked_roi['Tags'][TL_TAG] = (self.bl_x, self.y)
        self.clicked_roi['Tags'][L_TAG] = (self.bl_x, self.bl_y - int(self.h / 2))
        self.clicked_roi['Tags'][T_TAG] = (self.bl_x + int(self.w / 2), self.y)
        self.clicked_roi['Tags'][TR_TAG] = (self.x, self.y)
        self.clicked_roi['Tags'][R_TAG] = (self.x, self.bl_y - int(self.h / 2))
        self.clicked_roi['Tags'][BR_TAG] = (self.x, self.y + self.h)
        self.clicked_roi['Tags'][B_TAG] = (self.x - int(self.w / 2), self.bl_y)
        self.clicked_roi['Tags'][C_TAG] = (self.bl_x + int(self.w / 2), self.bl_y - int(self.h / 2))
        self.clicked_roi[TL_X] = self.bl_x
        self.clicked_roi[TL_Y] = self.y
        self.clicked_roi[BR_X] = self.x
        self.clicked_roi[BR_Y] = self.y + self.h
        self.clicked_roi[HEIGHT] = self.h
        self.clicked_roi[WIDTH] = self.w
        self.clicked_roi[CENTER_X], self.clicked_roi[CENTER_Y] = self.bl_x + int(self.w / 2), self.bl_y - int(self.h / 2)
        cv2.rectangle(self.temp_img, self.clicked_roi['Tags'][TL_TAG], self.clicked_roi['Tags'][BR_TAG], self.clicked_roi['Color BGR'], self.clicked_roi['Thickness'])
        cv2.line(self.temp_img, self.clicked_roi['Tags'][TL_TAG], self.clicked_roi['Tags'][TR_TAG], self.settings['ROI_SELECT_CLR'], self.clicked_roi['Thickness'], lineType=self.settings['LINE_TYPE'])
        cv2.line(self.temp_img, self.clicked_roi['Tags'][TR_TAG], self.clicked_roi['Tags'][BR_TAG], self.settings['ROI_SELECT_CLR'], self.clicked_roi['Thickness'], lineType=self.settings['LINE_TYPE'])

    def _select_rectangle_center_tag(self):
        self.clicked_roi['Tags'][TL_TAG] = (self.tl_x, self.tl_y)
        self.clicked_roi['Tags'][L_TAG] = (self.tl_x, self.tl_y + int(self.h / 2))
        self.clicked_roi['Tags'][BL_TAG] = (self.tl_x, self.tl_y + self.h)
        self.clicked_roi['Tags'][B_TAG] = (self.tl_x + int(self.w / 2), self.tl_y + self.h)
        self.clicked_roi['Tags'][BR_TAG] = (self.br_x, self.br_y)
        self.clicked_roi['Tags'][R_TAG] = (self.tl_x + self.w, self.tl_y + int(self.h / 2))
        self.clicked_roi['Tags'][TR_TAG] = (self.tl_x + self.w, self.tl_y)
        self.clicked_roi['Tags'][T_TAG] = (self.tl_x + int(self.w / 2), self.tl_y)
        self.clicked_roi['Tags'][C_TAG] = (self.x, self.y)
        self.clicked_roi[TL_X] = self.tl_x
        self.clicked_roi[TL_Y] = self.tl_y
        self.clicked_roi[BR_X] = self.br_x
        self.clicked_roi[BR_Y] = self.br_y
        self.clicked_roi[HEIGHT] = self.h
        self.clicked_roi[WIDTH] = self.w
        self.clicked_roi[CENTER_X], self.clicked_roi[CENTER_Y] = self.x, self.y
        cv2.rectangle(self.temp_img, self.clicked_roi['Tags'][TL_TAG], self.clicked_roi['Tags'][BR_TAG], self.settings['ROI_SELECT_CLR'], self.clicked_roi['Thickness'], lineType=self.settings['LINE_TYPE'])

    def _select_circle_border(self):
        self.clicked_roi[RADIUS] = self.radius
        self.clicked_roi["Tags"][BORDER_TAG] = self.l_edge
        cv2.circle(self.temp_img, center=self.clicked_roi["Tags"][C_TAG], radius=self.radius, color=self.settings['ROI_SELECT_CLR'], thickness=self.clicked_roi['Thickness'], lineType=self.settings['LINE_TYPE'])

    def _select_circle_center(self):
        self.clicked_roi[CIRCLE_C_X], self.clicked_roi[CIRCLE_C_Y] = self.x, self.y
        self.clicked_roi["Tags"][C_TAG] = (self.x, self.y)
        self.clicked_roi["Tags"][BORDER_TAG] = self.l_edge
        cv2.circle(self.temp_img, center=self.clicked_roi["Tags"][C_TAG], radius=self.radius, color=self.settings['ROI_SELECT_CLR'], thickness=self.clicked_roi['Thickness'], lineType=self.settings['LINE_TYPE'])

    def _select_polygon_center(self):
        self.new_vertices = np.array([np.array(v) for k, v in self.new_tags.items() if k != 'Center_tag']).astype(np.int32)
        self.clicked_roi['Tags'] = self.new_tags
        self.clicked_roi[VERTICES] = self.new_vertices
        self.clicked_roi[CENTER_X], self.clicked_roi[CENTER_Y] = self.clicked_roi['Tags']["Center_tag"]
        self.clicked_roi['center'] = self.clicked_roi['Tags']['Center_tag']
        cv2.polylines(self.temp_img, [self.clicked_roi[VERTICES]], True, color=self.settings['ROI_SELECT_CLR'], thickness=self.clicked_roi['Thickness'],lineType=self.settings['LINE_TYPE'])

    def _select_polygon_vertice(self):
        self.clicked_roi['Tags'][self.clicked_tag] = self.new_poly_tag_loc
        self.clicked_roi[VERTICES][self.clicked_tag_id] = np.array([self.new_poly_tag_loc])
        self.clicked_roi['center'] = self.polygon_centroid
        self.clicked_roi['area'] = self.polygon_area
        self.clicked_roi['Tags']['Center_tag'] = tuple(self.polygon_centroid)
        cv2.polylines(self.temp_img, [self.clicked_roi[VERTICES]], True, color=self.clicked_roi['Color BGR'], thickness=self.clicked_roi['Thickness'])
        cv2.line(self.temp_img, self.clicked_roi['Tags'][self.clicked_tag], self.clicked_roi['Tags'][self.n_tag_1_id], self.settings['ROI_SELECT_CLR'], self.clicked_roi['Thickness'], lineType=self.settings['LINE_TYPE'])
        cv2.line(self.temp_img, self.clicked_roi['Tags'][self.clicked_tag], self.clicked_roi['Tags'][self.n_tag_2_id], self.settings['ROI_SELECT_CLR'], self.clicked_roi['Thickness'], lineType=self.settings['LINE_TYPE'])


    def mouse_callback(self, event, x, y, flags, param):
        if event == cv2.EVENT_LBUTTONDOWN:
            self.clicked_roi, self.clicked_tag = self.find_closest_tag(roi_dict=self.roi_dict, click_coordinate=(x, y))
            if self.clicked_roi is not None:
                self.dragging = True

        elif event == cv2.EVENT_LBUTTONUP:
            if self.clicked_roi is not None:
                self.roi_dict[self.clicked_roi['Name']] = self.clicked_roi
                draw_img = self.get_gridline_copy() if self.rectangle_grid is not None or self.hex_grid is not None else self.original.copy()
                self.temp_img = _plot_roi(roi_dict=self.roi_dict, img=draw_img, show_tags=True)
            self.dragging = False

        elif event == cv2.EVENT_MOUSEMOVE and self.dragging:
            self.x, self.y = x, y
            draw_img = self.get_gridline_copy() if self.rectangle_grid is not None or self.hex_grid is not None else self.original.copy()
            self.temp_img = _plot_roi(roi_dict=self.roi_dict, omitted_roi=self.clicked_roi['Name'], img=draw_img)
            if self.clicked_roi['Shape_type'].lower() == ROI_SETTINGS.RECTANGLE.value:
                self.tl_x, self.tl_y = self.clicked_roi['Tags'][TL_TAG]
                self.tr_x, self.tr_y = self.clicked_roi['Tags'][TR_TAG]
                self.br_x, self.br_y = self.clicked_roi['Tags'][BR_TAG]
                self.bl_x, self.bl_y = self.clicked_roi['Tags'][BL_TAG]
                if self.clicked_tag == BR_TAG:
                    self.w, self.h = x - self.tl_x, y - self.tl_y
                    if (self.w > 1 < self.h) and (0 <= self.x <= self.img_w) and (0 <= self.y <= self.img_h):
                        self._select_rectangle_bottom_right()
                elif self.clicked_tag == R_TAG:
                    self.w, self.h = x - self.tl_x, self.clicked_roi[HEIGHT]
                    if (self.w > 1 < self.h) and (0 <= self.x <= self.img_w) and (0 <= self.y <= self.img_h):
                        self._select_rectangle_right()
                elif self.clicked_tag == B_TAG:
                    self.w, self.h = self.clicked_roi[WIDTH], y - self.tl_y
                    if (self.w > 1 < self.h) and (0 <= self.x <= self.img_w) and (0 <= self.y <= self.img_h):
                        self._select_rectangle_bottom()
                elif self.clicked_tag == BL_TAG:
                    self.w, self.h = self.tr_x - x, y - self.tr_y
                    if (self.w > 1 < self.h) and (0 <= self.x <= self.img_w) and (0 <= self.y <= self.img_h):
                        self._select_rectangle_bottom_left()
                elif self.clicked_tag == L_TAG:
                    self.w, self.h = self.tr_x - x, self.clicked_roi[HEIGHT]
                    if (self.w > 1 < self.h) and (0 <= self.x <= self.img_w) and (0 <= self.y <= self.img_h):
                        self._select_rectangle_left_tag()
                elif self.clicked_tag == T_TAG:
                    self.w, self.h = self.clicked_roi[WIDTH], self.br_y - self.y
                    if (self.w > 1 < self.h) and (0 <= self.x <= self.img_w) and (0 <= self.y <= self.img_h):
                        self._select_rectangle_top_tag()
                elif self.clicked_tag == TL_TAG:
                    self.w, self.h = self.br_x - self.x, self.br_y - self.y
                    if (self.w > 1 < self.h) and (0 <= self.x <= self.img_w) and (0 <= self.y <= self.img_h):
                        self._select_rectangle_top_left_tag()
                elif self.clicked_tag == TR_TAG:
                    self.w, self.h = self.x - self.bl_x, self.bl_y - self.y
                    if (self.w > 1 < self.h) and (0 <= self.x <= self.img_w) and (0 <= self.y <= self.img_h):
                        self._select_rectangle_top_right_tag()
                elif self.clicked_tag == C_TAG:
                    self.w, self.h = self.clicked_roi[WIDTH], self.clicked_roi[HEIGHT]
                    self.tl_x, self.tl_y = self.x - int(self.w/2), self.y - int(self.h/2)
                    self.tr_x, self.tr_y = self.x + int(self.w/2), self.y - int(self.h/2)
                    self.br_x, self.br_y = self.x + int(self.w/2), self.y + int(self.h/2)
                    if (0 <= self.tl_x) and (0 <= self.tl_y) and (self.tr_x <= self.img_w) and (self.br_y <= self.img_h):
                        self._select_rectangle_center_tag()

            elif self.clicked_roi['Shape_type'].lower() == ROI_SETTINGS.CIRCLE.value:
                if self.clicked_tag == BORDER_TAG:
                    c_x, c_y = self.clicked_roi["Tags"][C_TAG]
                    self.radius = self.clicked_roi["Tags"][C_TAG][0] - self.x
                    self.l_edge, t_edge =  (c_x - self.radius, c_y), (c_x, c_y - self.radius)
                    r_edge, b_edge = (c_x +self. radius, c_y), (c_x, c_y + self.radius)
                    if (0 <= self.l_edge[0]) and (0 <= t_edge[1]) and (r_edge[0] <= self.img_w) and (b_edge[1] <= self.img_h) and (self.radius >= 1):
                        self._select_circle_border()
                if self.clicked_tag == C_TAG:
                    self.radius = self.clicked_roi[RADIUS]
                    self.l_edge, t_edge = (self.x - self.radius, self.y), (self.x, self.y - self.radius)
                    r_edge, b_edge = (self.x + self.radius, self.y), (self.x, self.y + self.radius)
                    if (0 <= self.l_edge[0]) and (0 <= t_edge[1]) and (r_edge[0] <= self.img_w) and (b_edge[1] <= self.img_h):
                        self._select_circle_center()

            elif self.clicked_roi['Shape_type'].lower() == ROI_SETTINGS.POLYGON.value:
                if self.clicked_tag in ['Center_tag', 'Center tag']:
                    x_diff, y_diff = self.x - self.clicked_roi[CENTER_X], self.y - self.clicked_roi[CENTER_Y]
                    self.new_tags, outside_img_flag = {}, False
                    for tag_name, tag in self.clicked_roi['Tags'].items():
                        t  = (tag[0] + x_diff, tag[1] + y_diff)
                        self.new_tags[tag_name] = t
                        # if (0 > t[0]) or (t[0] > self.img_w) or (0 > t[1]) or (t[1] > self.img_h):
                        #     outside_img_flag = True
                    if not outside_img_flag:
                        self._select_polygon_center()
                else:
                    print()
                    self.clicked_tag_id = int(self.clicked_tag[-1])
                    click_tag = self.clicked_roi['Tags'][self.clicked_tag]
                    max_tag_id = len(self.clicked_roi["Tags"].keys()) - 2
                    self.n_tag_1_id = f'Tag_{0 if self.clicked_tag_id == max_tag_id else max_tag_id if self.clicked_tag_id == 0 else self.clicked_tag_id - 1}'
                    self.n_tag_2_id = f'Tag_{self.clicked_tag_id - 1 if self.clicked_tag_id == max_tag_id else self.clicked_tag_id + 1}'
                    x_diff, y_diff = self.x - click_tag[0], self.y - click_tag[1]
                    self.new_poly_tag_loc = (click_tag[0] + x_diff, click_tag[1] + y_diff)
                    self.new_vertices = np.copy(self.clicked_roi[VERTICES]).astype(np.int32)
                    self.new_vertices[self.clicked_tag_id] = np.array([self.new_poly_tag_loc])
                    polygon = Polygon(self.new_vertices)
                    self.polygon_area = polygon.area
                    valid_polygon = check_valid_polygon(polygon=polygon, raise_error=False)
                    self.polygon_centroid = np.array(polygon.centroid).astype(int)
                    if valid_polygon:
                        self._select_polygon_vertice()

            else:
                pass

    def run(self):
        cv2.namedWindow(self.window_name, cv2.WINDOW_NORMAL)
        cv2.setMouseCallback(self.window_name, self.mouse_callback)
        while not self.terminate:
            if not self.dragging and self.temp_img is None:
                self.temp_img = np.copy(self.img)
            if not self.tkinter_window:
                cv2.imshow(self.window_name, self.temp_img)
                cv2.waitKey(1)
            else:
                pil_image = Image.fromarray(self.temp_img)
                tk_image = ImageTk.PhotoImage(pil_image)
                label = Label(self.img_tk_frame, image=tk_image)
                label.image = tk_image
                label.pack()

        if self.terminate:
            cv2.waitKey(1)
            cv2.imshow(self.window_name, self.temp_img)






#
#
# rectangle_example =  {'Video':             'My_video',
#                       'Shape_type':        ROI_SETTINGS.RECTANGLE.value,
#                       'Name':              'My_rectangle_shape',
#                       'Color name':        'red',
#                       'Color BGR':         (0, 0, 255),
#                       'Thickness':         7,
#                       'Center_X':          175,
#                       'Center_Y':          175,
#                       'topLeftX':          100,
#                       'topLeftY':          100,
#                       'Bottom_right_X':    250,
#                       'Bottom_right_Y':    250,
#                       'width':             150,
#                       'height':            150,
#                       "Tags":             {"Center tag": (175, 175),
#                                            "Top left tag": (100, 100),
#                                            "Bottom right tag": (250, 250),
#                                            "Top right tag": (250, 100),
#                                            "Bottom left tag": (100, 250),
#                                            "Top tag": (175, 100),
#                                            "Right tag": (250, 175),
#                                            "Left tag": (100, 175),
#                                            "Bottom tag": (175, 250)},
#                       'Ear_tag_size':      15}
#
#
# circle_example =      {'Video':            'My_video',
#                        'Shape_type':        ROI_SETTINGS.CIRCLE.value,
#                        'Name':              'My_circle_shape',
#                        'Color name':        'blue',
#                        'Color BGR':          (255, 0, 255),
#                        'Thickness':          7,
#                        'centerX':           400,
#                        'centerY':           400,
#                        'radius':            50,
#                        "Tags":             {"Center tag": (400, 400),
#                                             "Border tag": (350, 400)},
#                        'Ear_tag_size':      15}
#
#
#
#
# polygon  = Polygon(np.array([[300, 450], [100, 500], [400, 300], [600, 300], [650, 400]]))
# polygon_vertices  = np.array(polygon.convex_hull.exterior.coords)[1:]
# polygon_center = np.array(polygon.centroid).astype(np.int32)
# polygon_tags = {}
# for cnt, poly in enumerate(polygon_vertices):
#     polygon_tags[f'Tag_{cnt}'] = tuple(poly.astype(np.int32))
# polygon_tags['Center tag'] = tuple(polygon_center)
#
# polygon_example =      {'Video':                    'My_video',
#                        'Shape_type':                ROI_SETTINGS.POLYGON.value,
#                        'Name':                      'My_polygon_shape',
#                        'Color name':                'yellow',
#                        'Color BGR':                 (0, 255, 255),
#                        'Thickness':                 7,
#                        'Center_X':                  polygon_center[0],
#                        'Center_Y':                  polygon_center[1],
#                        'vertices':                  polygon_vertices,
#                        'center':                    (polygon_center[0], polygon_center[1]),
#                        'area':                      None,
#                        'max_vertice_distance':      None,
#                        "area_cm":                   None,
#                        'Tags':                      polygon_tags,
#                        'Ear_tag_size':              15}
#
#
#
#
#
#
#
#
# roi_dict = {'My_rectangle_shape': rectangle_example, 'My_circle_shape': circle_example, 'My_polygon_shape': polygon_example}
#
# frm = read_frm_of_video(video_path=r"C:\troubleshooting\mitra\project_folder\videos\501_MA142_Gi_CNO_0514.mp4")
# original_img = np.copy(frm)
# for shape_name, shape in roi_dict.items():
#     if shape['Shape_type'] == ROI_SETTINGS.RECTANGLE.value:
#         cv2.rectangle(frm, shape['Tags']['Top left tag'], shape['Tags']['Bottom right tag'], shape['Color BGR'], shape['Thickness'])
#         for tag_name, tag in shape['Tags'].items():
#             cv2.circle(frm, tag, shape['Ear_tag_size'], shape['Color BGR'], -1)
#     if shape['Shape_type'] == ROI_SETTINGS.CIRCLE.value:
#         cv2.circle(frm, center=shape['Tags']['Center tag'], radius=shape['radius'], color=shape['Color BGR'], thickness=shape['Thickness'])
#         for tag_name, tag in shape['Tags'].items():
#             cv2.circle(frm, tag, shape['Ear_tag_size'], shape['Color BGR'], -1)
#     if shape['Shape_type'] == ROI_SETTINGS.POLYGON.value:
#         cv2.polylines(frm, [shape["vertices"].astype(int)], True, color=shape['Color BGR'], thickness=shape['Thickness'])
#         for tag_name, tag in shape['Tags'].items():
#             cv2.circle(frm, tag, shape['Ear_tag_size'], shape['Color BGR'], -1)
#
#
# cv2.namedWindow(DRAW_FRAME_NAME, cv2.WINDOW_NORMAL)
# cv2.imshow(DRAW_FRAME_NAME, frm)
#
#
# inter = InteractiveROIModifier(window_name=DRAW_FRAME_NAME, roi_dict=roi_dict, img=frm, orginal_img=original_img)
# inter.run()
# print('s')









