import colorsys

from cv2 import cv2

import numpy as np
import math
from skimage import color
from colorsys import hls_to_rgb
import matplotlib.pyplot as plt

def dist(p1, p2):
    """
        Compute euclidean distance

        :param p1: first coordinate tuple
        :param p2: second coordinate tuple
        :return: distance Float
    """

    return math.sqrt((p2[0] - p1[0]) ** 2 + (p1[1] - p2[1]) ** 2)

def dist_edge(e1, e2):
    """
        Compute the size difference between two edges

        :param e1: Matrix of coordinates of points composing the first edge
        :param e2: Matrix of coordinates of points composing the second edge
        :return: Float
    """
    e1_begin = e1.shape[0]
    e1_end = e1.shape[-1]
    e2_begin = e2.shape[0]
    e2_end = e2.shape[-1]
    dist_e1 = dist(e1_begin, e1_end)
    dist_e2 = dist(e2_begin, e2_end)
    res = math.fabs(dist_e1 - dist_e2)
    val = (dist_e1 + dist_e2) / 2
    return res, val
    
def have_edges_similar_length(e1, e2, percent):
    """
        Return a boolean to determine if the difference between two edges is > 20%

        :param e1: Matrix of coordinates of points composing the first edge
        :param e2: Matrix of coordinates of points composing the second edge
        :return: Boolean
    """
    res, val = dist_edge(e1, e2)    
    return res < (val * percent)

def normalize_vect_len(e1, e2):
    """
        Return the shortest and the longest edges.

        :param e1: Matrix of coordinates of points composing the first edge
        :param e2: Matrix of coordinates of points composing the second edge
        :return: Matrix of coordinates, Matrix of coordinates
    """

    longest = e1 if len(e1) > len(e2) else e2
    shortest = e2 if len(e1) > len(e2) else e1
    return shortest, longest


def diff_match_edges(e1, e2, reverse=True):
    """
        Return the distance between two edges.

        :param e1: Matrix of coordinates of points composing the first edge
        :param e2: Matrix of coordinates of points composing the second edge
        :param reverse: Optional parameter to reverse the second edge
        :return: distance Float
    """

    shortest, longest = normalize_vect_len(e1, e2)
    diff = 0
    for i, p in enumerate(shortest):
        ratio = i / len(shortest)
        j = int(len(longest) * ratio)
        x1 = longest[j]
        x2 = shortest[len(shortest) - i - 1] if reverse else shortest[i]
        diff += (x2 - x1) ** 2
    return diff / len(shortest)

def diff_match_edges2(e1, e2, reverse=True, thres=5, pad=False):
    """
    Return the distance between two edges by performing a simple norm on each points.

        :param e1: Matrix of coordinates of points composing the first edge
        :param e2: Matrix of coordinates of points composing the second edge
        :param reverse: Optional parameter to reverse the second edge
        :return: distance Float
    """
    if e2.shape[0] > e1.shape[0]:
        e1, e2 = e2, e1
    
    if pad:
        pad_length = (e1.shape[0] - e2.shape[0]) // 2
        pad_left, pad_right = pad_length, (pad_length if pad_length * 2 == (e1.shape[0] - e2.shape[0]) else pad_length + 1)

        # Pad the shortest with 0
        e2 = np.lib.pad(e2, ((pad_left, pad_right), (0, 0)), 'constant', constant_values=(0, 0))
    else:
        # No padding just cut longest to match shortest length
        e1 = e1[:e2.shape[0]]

    if reverse:
        e2 = np.flip(e2, 0)
    d = np.linalg.norm(e1 - e2, axis=1)
    return np.sum(d > thres) / e1.shape[0]

def euclideanDistance(e1_lab_colors, e2_lab_colors):
    sum = 0
    max = 50
    len1 = len(e1_lab_colors)
    len2 = len(e2_lab_colors)
    if len1 < len2:
        max = len1
    else:
        max = len2
    t1 = len1 / max
    t2 = len2 / max

    def dist_color(tuple1, tuple2):
        return np.sqrt((tuple1[0] - tuple2[0]) ** 2
                        + (tuple1[1] - tuple2[1]) ** 2
                        + (tuple1[2] - tuple2[2]) ** 2)

    for i in range(max):
        sum += dist_color(e1_lab_colors[int(t1 * i)], e2_lab_colors[int(t2 * i)])
    return sum

def real_edge_compute(e1, e2):
    """
        Return the distance between colors of two edges for real puzzle.

        :param e1: Edge object
        :param e2: Edge object
        :return: distance Float
    """
    
    rgbs1 = []
    rgbs2 = []
    if not have_edges_similar_length(e1, e2, 0.20):
        return float('inf')

    e1_lab_colors = []
    for col in e1.color:
        rgb = colorsys.hls_to_rgb(col[0], col[1], col[2])
        rgb = [x * 255.0 for x in rgb]
        rgbs1.append(rgb)
        e1_lab_colors.append(color.rgb2lab([[[rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0]]])[0][0])
        # Drop luminance
        e1_lab_colors[-1] = [0, e1_lab_colors[-1][1], e1_lab_colors[-1][2]]

    e2_lab_colors = []
    for col in e2.color:
        rgb = colorsys.hls_to_rgb(col[0], col[1], col[2])
        rgb = [x * 255.0 for x in rgb]
        rgbs2.append(rgb)
        e2_lab_colors.append(color.rgb2lab([[[rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0]]])[0][0])
        # Drop Luminance
        e2_lab_colors[-1] = [0, e2_lab_colors[-1][1], e2_lab_colors[-1][2]]

    return min(euclideanDistance(e1_lab_colors, e2_lab_colors), euclideanDistance(e1_lab_colors, e2_lab_colors[::-1]))


def generated_edge_compute(e1, e2):
    """
        Return the distance between colors of two edges for generated puzzle.

        :param e1: Edge object
        :param e2: Edge object
        :return: distance Float
    """
    #edge size
    shapevalue, distvalue = dist_edge(e1, e2) 

    #edges diff

    edge_shape_score = diff_match_edges2(np.array(e1.shape), np.array(e2.shape))
     # Sigmoid
    L = 10
    K = -1.05
    #edge_color_score = 1 / (1 + math.exp(-L * (edge_color_score - 0.5)))
    edge_shape_score = (K * edge_shape_score) / (K - edge_shape_score + 1)

    #colors
    rgbs1 = []
    rgbs2 = []

    e1_lab_colors = []
    for col in e1.color:
        rgb = colorsys.hls_to_rgb(col[0], col[1], col[2])
        rgb = [x * 255.0 for x in rgb]
        rgbs1.append(rgb)
        e1_lab_colors.append(color.rgb2lab([[[rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0]]])[0][0])
        # Drop luminance
        e1_lab_colors[-1] = [0, e1_lab_colors[-1][1], e1_lab_colors[-1][2]]

    e2_lab_colors = []
    for col in e2.color:
        rgb = colorsys.hls_to_rgb(col[0], col[1], col[2])
        rgb = [x * 255.0 for x in rgb]
        rgbs2.append(rgb)
        e2_lab_colors.append(color.rgb2lab([[[rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0]]])[0][0])
        # Drop Luminance
        e2_lab_colors[-1] = [0, e2_lab_colors[-1][1], e2_lab_colors[-1][2]]

    val = min(euclideanDistance(e1_lab_colors, e2_lab_colors), euclideanDistance(e1_lab_colors, e2_lab_colors[::-1]))
    return val * (1.0 + math.sqrt(shapevalue) * 0.3) * (1.0 + edge_shape_score * 0.001)