import pylab as plt
import numpy as np
from axycolor import rgb2lab
from itertools import izip, product


def middle_divisors(n):
    for i in range(int(n ** (0.5)), 2, -1):
        if n % i == 0:
            return i, n // i

    return middle_divisors(n+1)  # If prime number take next one


def resolution(N, numerator=16, divisor=9):
    width = int(np.ceil(np.sqrt(16./9. * N)))
    height = int(np.ceil(float(N)/width))
    return width, height


def show_image(pixels, width, height, cmap=plt.cm.gray, scale=1, blocking=False):
    pixels = np.asarray(pixels)
    plt.imshow(scale*pixels.reshape((height, width)), interpolation="nearest", cmap=cmap)
    plt.show(blocking)


def show_images2d(list_of_pixels, shape, cmap=plt.cm.gray, scale=1, blocking=False):
    height, width = shape
    ncols, nrows = resolution(len(list_of_pixels))

    image = scale/2. * np.ones((nrows * (height+1) - 1, ncols * (width+1) - 1), dtype=float)
    for pixels, (row, col) in izip(list_of_pixels, product(range(nrows), range(ncols))):
        pixels = np.asarray(pixels).reshape(height, width)
        image[row*(height+1):(row+1)*(height+1)-1, col*(width+1):(col+1)*(width+1)-1] = pixels

    plt.imshow(scale*image, interpolation="nearest", cmap=cmap)
    plt.subplots_adjust(left=0., right=1., top=0.95, bottom=0.05)
    plt.show(blocking)


def show_images3d(list_of_pixels, shape, cmap=plt.cm.gray, scale=1, blocking=False):
    height, width, depth = shape
    ncols, nrows = resolution(len(list_of_pixels))

    for d in range(depth):
        plt.figure(d)
        image = scale/2. * np.ones((nrows * (height+1) - 1, ncols * (width+1) - 1), dtype=float)
        for pixels, (row, col) in izip(list_of_pixels, product(range(nrows), range(ncols))):
            pixels = np.asarray(pixels).reshape(height, width, depth)[:, :, d]
            image[row*(height+1):(row+1)*(height+1)-1, col*(width+1):(col+1)*(width+1)-1] = pixels

        plt.imshow(scale*image, interpolation="nearest", cmap=cmap)

    plt.subplots_adjust(left=0., right=1., top=0.95, bottom=0.05)
    plt.show(blocking)


def cmap(bg=(0, 0, 0), exclude=[], n_colors=30):
    # Generate a sizable number of RGB triples. This represents our space of
    # possible choices. By starting in RGB space, we ensure that all of the
    # colors can be generated by the monitor.
    bg = np.array([bg] + exclude)
    x = np.linspace(0, 1, n_colors)  # divisions along each axis in RGB space
    R, G, B = np.meshgrid(x, x, x)
    rgb = np.c_[R.flatten(), G.flatten(), B.flatten()]

    lab = rgb2lab(rgb)
    bglab = rgb2lab(bg)

    lastlab = bglab[0]
    mindist2 = np.ones(len(rgb)) * np.inf
    for bglab_i in bglab[1:]:
        dist2 = np.sum((lab-bglab_i)**2, axis=1)
        mindist2 = np.minimum(dist2, mindist2)  # dist2 to closest previously-chosen color

    while True:
        dX = lab - lastlab  # displacement of last from all colors on list
        dist2 = np.sum(dX**2, axis=1)  # square distance

        mindist2 = np.minimum(dist2, mindist2)  # dist2 to closest previously-chosen color
        idx = np.argmax(mindist2)  # find the entry farthest from all previously-chosen colors
        yield rgb[idx]

        lastlab = lab[idx]
