import os
import io

import cv2
import numpy as np
import matplotlib.pyplot as plt

from improutils import *

%matplotlib inline
np.set_printoptions(formatter={'float': lambda x: "{0:0.3f}".format(x)})
def rotate_image(image, angle, image_center=None):
    """ Rotates the input image by specified angle.
    
    Parameters
    ----------
    image : np.ndarray
        Image to be rotated.
    angle : float
        Rotation angle.
    image_center : Optional[tuple(int, int)]
        Center of rotation.
    Returns
    -------
    np.ndarray
        Returns the rotated input image by specified angle.
    """
    if image_center is None:
        image_center = tuple(np.array(image.shape[1::-1]) / 2)
    rot_mat = cv2.getRotationMatrix2D(image_center, angle, 1.0)
    result = cv2.warpAffine(image, rot_mat, image.shape[1::-1], flags=cv2.INTER_LINEAR)
    return result
def draw_rotated_text(img, text, point, angle, text_scale, text_color, text_thickness):
    img_filled = np.full(img.shape, text_color, dtype=np.uint8)
    # create rotated text mask
    text_mask = np.zeros((img.shape[0], img.shape[1]), dtype=np.uint8)
    cv2.putText(text_mask, "{:.2f} cm".format(text), point, 0, text_scale, (255, 255, 255), text_thickness)
    if angle > 0:
        angle = -angle + 90
    elif angle < 0:
        angle = angle + 90
    text_mask = rotate_image(text_mask, -angle, point)
    result = copy_to(img_filled, img.copy(), text_mask)
    return result
def draw_real_sizes(img, rect, width_text, height_text, lbl_size_scale=2, lbl_color=(0, 0, 255), lbl_thickness=8):
    """
    Draws real sizes of rotated rectangle into the image.
    Parameters
    ----------
    img : ndarray
        Input image.
    rect : tuple
        Rotated rectangle.
    width_text : string
        Width of the rectangle in the form of string.
    height_text : string
        Height of the rectangle in the form of string.
    lbl_size_scale : double
        Scale of text.
    lbl_color : tuple
        Color of text.
    lbl_thickness : int
        Thickness of text.
    Returns
    -------
    Output image.
    """

    """
    For simplicity, we don't care about which side is width, which is height (as it can be ambiguous),
    we just map the sides to correspoinding dimension texts based on the lengths.
    This way it always maps correctly no matter the direction of the rotation.
    """
    
    tl, tr, br, bl = order_points(cv2.boxPoints(rect))

    side_a = tl - tr
    side_b = tr - br

    mid_pt_a = midpoint(tl, tr)
    mid_pt_b = midpoint(tr, br)
    
    shorter, longer = sorted([int(width_text), int(height_text)])

    if np.linalg.norm(side_a) <= np.linalg.norm(side_b):
        label_a, label_b = shorter, longer
    else:
        label_a, label_b = longer, shorter
        
    

    pt_label_a = (int(mid_pt_a[0] + 10), int(mid_pt_a[1] + 10))
    pt_label_b = (int(mid_pt_b[0] - 10), int(mid_pt_b[1]))
        
    result = draw_rotated_text(img, label_a, pt_label_a, rect[2], lbl_size_scale, lbl_color, lbl_thickness)
    result = draw_rotated_text(result, label_b, pt_label_b, rect[2], lbl_size_scale, lbl_color, lbl_thickness)
    return result
from ipywidgets import interact, interactive, fixed, interact_manual
import ipywidgets as widgets
import ipywidgets as widgets
from IPython.display import display

def create_slider(min, max, description):
    description = description.ljust(30, '\xa0')
    return widgets.IntRangeSlider( min=min, max=max, step=1,value=[min,max], 
                                   description=description, 
                                   continuous_update=False, 
                                   orientation='horizontal',
                                   style=dict(description_width='initial'),
                                   layout=widgets.Layout(width='auto'),
                                  )
    
def multicolor_segmentation(func,colors):
    """ Allows interactive HSV thresholding for multiple colors with saving and returning thresholds that are picked by the user.
    
    Parameters
    ----------
    func : function
        function with arguments hue = h_range (int, range: 0-360), saturation = s_range (int, range: 0-255), value = v_range (int, range: 0-255)
    colors : list
        list of colors that the user can choose from, e.g. ['red', 'green', 'blue'], these colors will be used as keys in the output dictionary
    Returns
    -------
    color_thresholds: dict
        Returns a dictionary with the chosen thresholds for each color, e.g. {'red': (0, 0, 0), 'green': (0, 0, 0), 'blue': (0, 0, 0)}, can be also empty if no thresholds were saved
    """
    color_thresholds = {}
    
    # initialize sliders, buttons etc.
    h_slider=create_slider(min=0, max=360, description='Hue:')
    s_slider=create_slider(min=0, max=255, description='Saturation:')
    v_slider=create_slider(min=0, max=255, description='Value:')
    
    color_dropdown = widgets.Dropdown(options=colors, description='Color:'.ljust(30, '\xa0'), style ={'description_width': 'initial'},layout = {'width': 'max-content'})
    
    save_button = widgets.Button(description='Save threshold for color',layout=widgets.Layout(width='auto'),button_style='success')
    finish_button = widgets.Button(description='Return saved thresholds',layout=widgets.Layout(width='auto'),button_style='danger')
    
    text_output = widgets.Output()
    interactive_output = widgets.interactive_output(func,{'h_range':h_slider,'s_range':s_slider,'v_range':v_slider})
    
    # widget layout
    input_box = widgets.VBox([h_slider,s_slider,v_slider,color_dropdown])
    button_box = widgets.HBox([save_button, finish_button])
    other_box = widgets.VBox([text_output, interactive_output])
    
    def reset_sliders():
        h_slider.value = (0,360)
        s_slider.value = (0,255)
        v_slider.value = (0,255)
    
    # button callbacks
    def on_save_clicked(b):
        with text_output:
            text_output.clear_output()
            color_thresholds[color_dropdown.value] = (h_slider.value, s_slider.value, v_slider.value)
            print(f"Saved for color '{color_dropdown.value}', threshold: {color_thresholds[color_dropdown.value]}\nResetting sliders...\nChanging to next color...")
            reset_sliders()
            # set next color in dropdown
            color_dropdown.value = colors[(colors.index(color_dropdown.value)+1)%len(colors)]
        
    
    def on_finish_clicked(b):
        with text_output:
            text_output.clear_output()
            print('Returned saved thresholds!')
            reset_sliders()
                
    
    save_button.on_click(on_save_clicked)
    finish_button.on_click(on_finish_clicked)
    # display widget
    display(input_box, button_box,other_box)

    return color_thresholds