#!/usr/bin/env python3

import numpy as np
import matplotlib.pyplot as plt


def load_image(path):
    """Load the specified image as uint8 Numpy array.

    Args:
        path (str or file-like):
            The image file to read: a filename, or a file-like object opened in
            read-binary mode. (Copied from plt.imread's documentation)

    Returns:
        np.ndarray: A NumPy-Array of type ``uint8`` that contains the specified
            image. The returned array has shape (M, N, 3) for RGB images, and
            (M, N) for grayscale images.
    """
    return np.uint8(plt.imread(path) * 255)


def image2pixels(image):
    """Create a list of ``(R, G, B, X, Y)`` tuples from a given 3d image array.

    This function is the inverse to the function ``pixels2image``, i.e.
    ```pixels2image(image2pixels(image)) = image.```

    Args:
        image (np.uint8-array):
            A NumPy-Array of type ``uint8`` and shape (M, N, 3).

    Returns:
        List[Tuple[np.uint8, np.uint8, np.uint8, int, int]]:
            A List containing all ``(R, G, B)``-values of the ``image`` together
            with the coordinates ``(X, Y)`` of the respective pixel.
    """
    # TODO
    m, n, k = np.shape(image)
    result = []
    for i in range(m):
        for j in range(n):
            result.append((image[i][j][0], image[i][j][1], image[i][j][2], j, i))
    return result


def pixels2image(pixels):
    """Create a 3d image array from a list of (R, G, B, X, Y) pixels.

    This function is the inverse to the function ``image2pixels``, i.e.
    ```image2pixels(pixels2image(pixels))) = pixels.```

    Args:
        pixels (List[Tuple[np.uint8, np.uint8, np.uint8, int, int]]):
            A List containing the ``(R, G, B)``-values of pixels at the
            position ``(X, Y)``.

    Returns:
        np.uint8-array:
            A NumPy-Array of type ``uint8`` and shape (M, N, 3) representing
            the image given by ``pixels``.
    """
    # TODO
    m, n = (0, 0)
    for t in pixels:
        if m < t[4]:
            m = t[4]
        if n < t[3]:
            n = t[3]
    image = np.zeros((m+1, n+1, 3))
    for t in pixels:
        image[t[4]][t[3]][0] = t[0]
        image[t[4]][t[3]][1] = t[1]
        image[t[4]][t[3]][2] = t[2]
    return image


def bounding_box(pixels):
    """Return a tuple ``((Rmin, Rmax), (Gmin, Gmax), (Bmin, Bmax))`` with the
    respective minimum and maximum values of each color.

    Args:
        pixels (List[Tuple[np.uint8, np.uint8, np.uint8, int, int]]):
            A List containing the ``(R, G, B)``-values of pixels at the
            position ``(X, Y)``.

    Returns:
        Tuple[Tuple[np.uint8, np.uint8], ...]:
            A tuple containing the respective minimum and maximum values of
            each color in the image represented by ``pixels``.
    """
    # TODO
    Rmin = pixels[0][0]
    Rmax = pixels[0][0]
    Gmin = pixels[0][1]
    Gmax = pixels[0][1]
    Bmin = pixels[0][2]
    Bmax = pixels[0][2]

    for p in pixels:
        if Rmin > p[0]:
            Rmin = p[0]
        if Rmax < p[0]:
            Rmax = p[0]
        if Gmin > p[1]:
            Gmin = p[1]
        if Gmax < p[1]:
            Gmax = p[1]
        if Bmin > p[2]:
            Bmin = p[2]
        if Bmax < p[2]:
            Bmax = p[2]
    
    return ((Rmin, Rmax), (Gmin, Gmax), (Bmin, Bmax))



def color_average(pixels):
    """Return list of tuples (Ravg, Gavg, Bavg) with averaged color values.

    Args:
        pixels (List[Tuple[np.uint8, np.uint8, np.uint8, int, int]]):
            A List containing the ``(R, G, B)``-values of pixels at the
            position ``(X, Y)``.

    Returns:
        Tuple[int, int, int]:
            A tuple containing the average value of red, green, and blue
            color values in the image represented by ``pixels``.
    """
    # TODO

    Ravg, Gavg, Bavg = (0, 0, 0)
    for p in pixels:
        Ravg += p[0]
        Gavg += p[1]
        Bavg += p[2]
    listLen = len(pixels)
    Ravg /=  listLen
    Gavg /=  listLen
    Bavg /=  listLen
    return (np.uint8(round(Ravg, 0)), np.uint8(round(Gavg, 0)), np.uint8(round(Bavg, 0)))



def main():
    """ Main-Function. Is called if this module is executed at top level.

    Loads the image ``shibuya.png``, prints its bounding box as well as the
    color average, and then shows the image using matplotlib.
    """
    shibuya = load_image('shibuya.png')
    pixels = image2pixels(shibuya)
    print(bounding_box(pixels))
    print(color_average(pixels))
    plt.imshow(pixels2image(pixels))
    plt.show()


if __name__ == "__main__": main()