import numpy as np
import PIL.Image, PIL.ImageDraw, PIL.ImageFilter, PIL.ImageFont, PIL.ImageOps
from .delboeuf import _delboeuf_parameters_sizeinner, _delboeuf_parameters_sizeouter
from ..image import image_circle
from ..psychopy.psychopy_circles import psychopy_circle



def ebbinghaus_psychopy(window, parameters=None, **kwargs):
    """Create a PsychoPy stimulus of the Ebbinghaus illusion.
    
    
    The Ebbinghaus illusion is an optical illusion of relative size perception,
    where circles of identical size appear as different because of their
    surrounding context.

    Parameters
    ----------
    window : object
        The window object in which the stimulus will be rendered.
    parameters : dict
        Parameters of the Ebbinghaus illusion generated by `ebbinghaus_parameters()`.
    **kwargs
        Additional arguments passed into `ebbinghaus_parameters()`.
    
    Returns
    -------
    In-place modification of the PsychoPy window (No explicit return).

    Examples
    ---------
    >>> import pyllusion as ill
    >>> from psychopy import visual, event

    >>> # Create parameters
    >>> parameters = ill.ebbinghaus_parameters(illusion_strength=1, difference=2)

    >>> # Initiate Window
    >>> window = visual.Window(size=[800, 600], winType='pygame', color="white")
    
    >>> # Display illusion
    >>> ill.ebbinghaus_psychopy(window=window, parameters=parameters)
    
    >>> # Refresh and close window    
    >>> window.flip()
    >>> event.waitKeys()  # Press any key to close
    >>> window.close()
    """

    # Create white canvas and get drawing context
    if parameters is None:
        parameters = ebbinghaus_parameters(**kwargs)

    # Loop circles
    for side in ["Left", "Right"]:
        _ebbinghaus_psychopy_draw(window,
                                  parameters,
                                  side=side,
                                  color_inner="red",
                                  color_outer="black")
    

def ebbinghaus_image(
    parameters=None, width=800, height=600, background="white", **kwargs
):
    """Create a PIL image of the Ebbinghaus illusion.


    The Ebbinghaus illusion is an optical illusion of relative size perception,
    where circles of identical size appear as different because of their
    surrounding context.

    Parameters
    ----------
    parameters : dict
        Parameters of the Ebbinghaus illusion generated by `ebbinghaus_parameters()`.
    width : int
        Width of the returned image.
    height : int
        Height of the returned image.
    background : str
        Color of the background.
    **kwargs
        Additional arguments passed into `ebbinghaus_parameters()`.

    Returns
    -------
    Image
        Image of the Ebbinghaus illusion, defaults to 800 x 600 pixels.
        Can be resized
        (`resize()`, See https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.resize)
        and saved in different file formats
        (`save()` See https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html).

    Examples
    ---------
    >>> import pyllusion as ill
    >>>
    >>> parameters = ill.ebbinghaus_parameters(illusion_strength=1, difference=2)
    >>> ill.ebbinghaus_image(parameters)
    """
    # Create white canvas and get drawing context
    if parameters is None:
        parameters = ebbinghaus_parameters(**kwargs)

    # Background
    image = PIL.Image.new("RGB", (width, height), color=background)

    # Outer circles
    for side in ["Left", "Right"]:
        image = _ebbinghaus_image_draw(image,
                                       parameters,
                                       side=side,
                                       color_inner="red",
                                       color_outer="black")

    return image


def _ebbinghaus_psychopy_draw(window, p, side="Left", color_inner="red", color_outer="black"):

    # Draw inner circle
    psychopy_circle(window, size=p["Size_Inner_" + side], x=p["Position_" + side], y=0,
                    color=color_inner, outline_color=color_inner, outline=0.5)

    # Get width/height ratio to have equidistant circles
    ratio = window.size[0] / window.size[1]

    # Adjust for non-squared screen
    x = p["Position_Outer_x_" + side] / ratio 
    x = x + (p["Position_" + side] - np.mean(x))
    
    # Plot each outer circles
    for i in range(len(p["Position_Outer_x_" + side])):
        psychopy_circle(window, size=p["Size_Outer_" + side],
                        x=x[i], y=p["Position_Outer_y_" + side][i],
                        color=color_outer, outline_color=color_outer, outline=0.5)
        
        
def _ebbinghaus_image_draw(image, p, side="Left", color_inner="red", color_outer="black"):

    # Draw inner circle
    image = image_circle(image=image, size=p["Size_Inner_" + side], x=p["Position_" + side], y=0, color=color_inner)

    # Get width/height ratio to have equidistant circles
    ratio = image.size[0] / image.size[1]

    # Adjust for non-squared screen
    x = p["Position_Outer_x_" + side] / ratio
    x = x + (p["Position_" + side] - np.mean(x))

    # Plot each outer circles
    for i in range(len(p["Position_Outer_x_" + side])):
        image = image_circle(image=image, size=p["Size_Outer_" + side], x=x[i], y=p["Position_Outer_y_" + side][i], color=color_outer)

    return image



# ------------------------------------------
# Parameters
# ------------------------------------------

def ebbinghaus_parameters(illusion_strength=0, difference=0, size_min=0.25, distance=1, distance_auto=False):
    """Compute Parameters for Ebbinghaus Illusion.

    Parameters
    ----------
    illusion_strength : float
        The strength of the surrounding context, i.e. outer circles, in biasing perception of unequally sized inner circles.
        Specifically, the size of right outer circles relative to the left (in percentage, e.g, if ``difference=1``,
        it means that the right outer circles will be 100% bigger, i.e., 2 times bigger than the left
        outer circles). A negative sign reflects the size difference of the left outer circles relative
        to the right, i.e., ``difference=-1`` means the left outer circles will be 100% bigger than the
        right outer circles.
    difference : float
        The objective size difference of the inner circles.
        Specifically, the size of left inner circle relative to the right (in percentage, e.g., if ``difference=1``,
        it means that the left inner circle will be 100% bigger, i.e., 2 times bigger than the right).
         A negative sign reflects the size difference of the right inner circle relative to the left, i.e.,
         ``difference=-1`` means the right inner circle will be 100% bigger than the left inner circle.
    size_min : float
        Size of smaller inner circle.
    distance : float
        Distance between circles.
    distance_auto : bool
        If true, distance is between edges (fixed spacing), if false, between centers (fixed location).

    Returns
    -------
    dict
        Dictionary of parameters of the Ebbinghaus illusion.
    """

    # Size inner circles
    parameters = _delboeuf_parameters_sizeinner(difference=difference, size_min=size_min)
    inner_size_left = parameters["Size_Inner_Left"]
    inner_size_right = parameters["Size_Inner_Right"]

    # Position
    position_left = -0.5
    position_right = 0.5

    # Base size outer circles
    outer_size_left = size_min
    outer_size_right = size_min

    # Actual outer size based on illusion
    outer_size_left, outer_size_right = _delboeuf_parameters_sizeouter(outer_size_left,
                                                                       outer_size_right,
                                                                       difference=difference,
                                                                       illusion_strength=illusion_strength,
                                                                       both_sizes=True)

    # Location outer circles
    l_outer_x, l_outer_y, l_distance_edges = _ebbinghaus_parameters_outercircles(x=position_left,
                                                            y=0,
                                                            size_inner=inner_size_left,
                                                            size_outer=outer_size_left,
                                                            n="auto")
    r_outer_x, r_outer_y, r_distance_edges = _ebbinghaus_parameters_outercircles(x=position_right,
                                                            y=0,
                                                            size_inner=inner_size_right,
                                                            size_outer=outer_size_right,
                                                            n="auto")

    # Get location and distances
    if distance_auto is False:
        distance_centers = distance
        position_left, position_right = -(distance_centers / 2), (distance_centers / 2)
        distance_edges_inner = distance_centers - (inner_size_left/2 + inner_size_right/2)
        distance_edges_outer = distance_centers - l_distance_edges - (outer_size_left/2) - r_distance_edges - (outer_size_right/2)

    else:
        distance_edges_outer = distance
        distance_centers = distance_edges_outer + l_distance_edges + (outer_size_left/2) + r_distance_edges + (outer_size_right/2)
        distance_edges_inner = distance_centers - (outer_size_left/2 + outer_size_right/2)
        position_left, position_right = -(distance_centers / 2), (distance_centers / 2)

    parameters.update({
        "Illusion": "Ebbinghaus",
        "Illusion_Strength": illusion_strength,
        "Illusion_Type": "Congruent" if illusion_strength > 0 else "Incongruent",

        "Size_Outer_Left": outer_size_left,
        "Size_Outer_Right": outer_size_right,

        "Distance_Centers": distance_centers,
        "Distance_Edges_Inner": distance_edges_inner,
        "Distance_Edges_Outer": distance_edges_outer,

        "Size_Inner_Smaller": np.min([inner_size_left, inner_size_right]),
        "Size_Inner_Larger": np.max([inner_size_left, inner_size_right]),
        "Size_Outer_Smaller": np.min([outer_size_left, outer_size_right]),
        "Size_Outer_Larger": np.max([outer_size_left, outer_size_right]),

        "Position_Outer_x_Left": l_outer_x,
        "Position_Outer_y_Left": l_outer_y,
        "Position_Outer_x_Right": r_outer_x,
        "Position_Outer_y_Right": r_outer_y,

        "Position_Left": position_left,
        "Position_Right": position_right
        })

    return parameters



def _ebbinghaus_parameters_outercircles(x=0, y=0, size_inner=0.25, size_outer=0.3, n="auto"):
    # Find distance between center of inner circle and centers of outer circles
    distance = (size_inner / 2) + (size_outer / 2) + 0.01

    # Find n
    if n == "auto":
        perimeter = 2 * np.pi * distance
        n = np.int(perimeter / size_outer)

    # Get position of outer circles
    angle = np.deg2rad(np.linspace(0, 360, num=n, endpoint=False))
    circle_x = x + (np.cos(angle) * distance)
    circle_y = y + (np.sin(angle) * distance)

    return circle_x, circle_y, distance
