from __future__ import absolute_import, division

import numpy as np
from PIL import ImageDraw


def _pascal_color_map(N=256, normalized=False):
    """
    Python implementation of the color map function for the PASCAL VOC data set.
    Official Matlab version can be found in the PASCAL VOC devkit
    http://host.robots.ox.ac.uk/pascal/VOC/voc2012/index.html#devkit
    """

    def bitget(byteval, idx):
        return (byteval & (1 << idx)) != 0

    dtype = 'float32' if normalized else 'uint8'
    cmap = np.zeros((N, 3), dtype=dtype)
    for i in range(N):
        r = g = b = 0
        c = i
        for j in range(8):
            r = r | (bitget(c, 0) << 7 - j)
            g = g | (bitget(c, 1) << 7 - j)
            b = b | (bitget(c, 2) << 7 - j)
            c = c >> 3

        cmap[i] = np.array([r, g, b])

    cmap = cmap / 255 if normalized else cmap
    return cmap


def plot_scribble(ax, scribble, frame, output_size=None, **kwargs):
    """ Plot scribbles into an axis.

    # Arguments
        ax: Matplotlib Axis. Axis where to plot the scribble lines.
        scribbles: Scribble. Scribble to plot.
        frame: Integer. Frame of the scribble to plot.
        output_size: Tuple. Image size to scale the scribble points `(H, W)`.
        **kwargs: Dictionary. Additional parameters to pass at the
            `ax.plot(**kwargs)` method.

    # Returns
        matplotlib.axis: Returns the given axis with the scribbles plotted on
            it.
    """
    scribbles = scribble['scribbles']
    if frame >= len(scribbles):
        raise ValueError('Frame value not valid')

    cmap = _pascal_color_map(normalized=True)

    frame_scribbles = scribbles[frame]

    for line in frame_scribbles:
        path, obj_id = line['path'], line['object_id']
        path = np.asarray(path, dtype=np.float32)
        color = cmap[obj_id]

        if output_size:
            img_size = np.asarray(output_size, dtype=np.float32)
            img_size -= 1
            path *= img_size

        ax.plot(*path.T, color=color, **kwargs)

    return ax


def draw_scribble(img, scribble, frame, output_size=None, width=5):
    """ Draw scribbles into a PIL Image.

    # Arguments
        img: PIL Image. Image where to draw the scribbles.
        scribbles: Scribble. Scribble to plot.
        frame: Integer. Frame of the scribble to plot.
        output_size: Tuple. Image size to scale the scribble points `(H, W)`.
        width: Integer. Width of the drawed lines.

    # Returns
        PIL Image: Returns the original image `img` with the scribble draw on
            it.
    """
    scribbles = scribble['scribbles']
    if frame >= len(scribbles):
        raise ValueError('Frame value not valid')

    cmap = _pascal_color_map(normalized=False)

    frame_scribbles = scribbles[frame]

    draw = ImageDraw.Draw(img)

    for line in frame_scribbles:
        path, obj_id = line['path'], line['object_id']
        path = np.asarray(path, dtype=np.float32)
        color = cmap[obj_id]

        if output_size:
            img_size = np.asarray(output_size, dtype=np.float32)
            img_size -= 1
            path *= img_size
        path = path.ravel().tolist()
        draw.line(path, fill=tuple(color), width=width)

    del draw

    return img


def overlay_mask(im, ann, alpha=0.5, colors=None, contour_thickness=None):
    """ Overlay mask over image.

    This function allows you to overlay a mask over an image with some
    transparency.

    # Arguments
        im: Numpy Array. Array with the image. The shape must be (H, W, 3) and
            the pixels must be represented as `np.uint8` data type.
        ann: Numpy Array. Array with the mask. The shape must be (H, W) and the
            values must be intergers
        alpha: Float. Proportion of alpha to apply at the overlaid mask.
        colors: Numpy Array. Optional custom colormap. It must have shape (N, 3)
            being N the maximum number of colors to represent.
        contour_thickness: Integer. Thickness of each object index contour draw
            over the overlay. This function requires to have installed the
            package `opencv-python`.

    # Returns
        Numpy Array: Image of the overlay with shape (H, W, 3) and data type
            `np.uint8`.
    """
    im, ann = np.asarray(im, dtype=np.uint8), np.asarray(ann, dtype=np.int)
    if im.shape[:-1] != ann.shape:
        raise ValueError('First two dimensions of `im` and `ann` must match')
    if im.shape[-1] != 3:
        raise ValueError('im must have three channels at the 3 dimension')

    colors = colors or _pascal_color_map()
    colors = np.asarray(colors, dtype=np.uint8)

    mask = colors[ann]
    fg = im * alpha + (1 - alpha) * mask

    img = im.copy()
    img[ann > 0] = fg[ann > 0]

    if contour_thickness:  # pragma: no cover
        import cv2
        for obj_id in np.unique(ann[ann > 0]):
            contours = cv2.findContours((ann == obj_id).astype(
                np.uint8), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[-2:]
            cv2.drawContours(img, contours[0], -1, colors[obj_id].tolist(),
                             contour_thickness)
    return img
