import os
import sys
import warnings

import tensorflow as tf
import numpy as np
import networkx as nx
from skimage.io import imsave
from skimage.segmentation import mark_boundaries
from skimage.measure import regionprops
from skimage import draw


from data import datasets, iterator
from segmentation.algorithm import generators as segmentations
from segmentation import adjacencies
from patchy import neighborhood_assemblies as neighborhoods


FLAGS = tf.app.flags.FLAGS

tf.app.flags.DEFINE_string('dataset', 'pascal_voc',
                           """The dataset. See dataset/__init__.py for a list
                           of all available datasets.""")
tf.app.flags.DEFINE_string('data_dir', None,
                           """Path to the data directory.""")
tf.app.flags.DEFINE_string('segmentation_algorithm', 'slic',
                           """The segmentation algorithm. See
                           segmentation/algorithm/__init__.py for a list of all
                           available segmentation algorithms.""")
tf.app.flags.DEFINE_string('adjacency_algorithm', 'euclidean_distance',
                           """The adjacency algorithm. See
                           segmentation/__init__.py for a list of all
                           available adjacency algorithms.""")
tf.app.flags.DEFINE_string('neighborhood_assembly', 'grid_spiral',
                           """The neighborhood assembly algorithm. See
                           patchy/helper/neighborhood_assembly.py for a list of
                           all available neighborhood assembly algorithms.""")


def draw_image(image, segmentation, adjacency, neighborhood):
    neighborhood = list(neighborhood)

    image = mark_boundaries(image, segmentation, (0, 0, 0))

    graph = nx.from_numpy_matrix(adjacency)

    segmentation += np.ones_like(segmentation)
    segments = regionprops(segmentation)

    # Save the centroids in the node properties.
    for (n, data), segment in zip(graph.nodes_iter(data=True), segments):
        data['centroid'] = segment['centroid']

    # Iterate over all edges and draw them.
    for n1, n2, data in graph.edges_iter(data=True):
        y1, x1 = map(int, graph.node[n1]['centroid'])
        y2, x2 = map(int, graph.node[n2]['centroid'])
        line = draw.line(y1, x1, y2, x2)

        n1_idx = neighborhood.index(n1) if n1 in neighborhood else -1
        n2_idx = neighborhood.index(n2) if n2 in neighborhood else -1
        if abs(n1_idx - n2_idx) == 1 and n1_idx != -1 and n2_idx != -1:
            image[line] = [1, 0, 0]
        else:
            image[line] = [0, 1, 0]

    # Draw a circle at the root node.
    for i in range(0, len(neighborhood)):
        if neighborhood[i] < 0:
            continue

        y1, x1 = graph.node[neighborhood[i]]['centroid']
        circle = draw.circle(y1, x1, 2)

        if i == 0:
            image[circle] = [1, 1, 0]
        else:
            j = (i-1)/(len(neighborhood) - 2)
            image[circle] = [j, j, j]

    return image


def iterate(dataset, segmentation_algorithm, adjacency_algorithm, eval_data,
            neighborhood_assembly, node, size):
    """Saves images with computed segment boundaries for either training or
    evaluation to an images directory into the datasets data directory.

    Args:
        dataset: The dataset.
        algorithm: The segmentation algorithm.
        eval_data: Boolean indicating if one should use the train or eval data
          set.
    """

    dirname = 'eval' if eval_data else 'train'
    images_dir = os.path.join(dataset.data_dir, FLAGS.segmentation_algorithm,
                              dirname)

    # Abort if directory already exists.
    if tf.gfile.Exists(images_dir):
        return

    # Create a subdirectory for every label.
    for label in dataset.labels:
        tf.gfile.MakeDirs(os.path.join(images_dir, label))

    image_names = {label: 0 for label in dataset.labels}

    _iterate = iterator(dataset, eval_data)

    def _before(image, label):
        segmentation = segmentation_algorithm(image)
        adjacency = adjacency_algorithm(segmentation)

        # Collect one neighborhood from one node.
        neighborhoods = neighborhood_assembly(adjacency, [node], size)
        neighborhood = tf.reshape(neighborhoods, [-1])

        return [tf.cast(image, tf.uint8), segmentation, adjacency,
                neighborhood, label]

    def _each(output, index, last_index):
        # Get the image and the label name from the output of the session.
        image = output[0]
        segmentation = output[1]
        adjacency = output[2]
        neighborhood = output[3]
        label_name = dataset.label_name(output[4])

        output_image = draw_image(image, segmentation, adjacency, neighborhood)

        # Save the image in the label named subdirectory and name it
        # incrementally.
        image_names[label_name] += 1
        image_name = '{}.png'.format(image_names[label_name])
        image_path = os.path.join(images_dir, label_name, image_name)

        # Disable precision loss warning when saving.
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            imsave(image_path, output_image)

        sys.stdout.write(
            '\r>> Saving images to {} {:.1f}%'
            .format(images_dir, 100.0 * index / last_index))
        sys.stdout.flush()

    def _done(index, last_index):
        print('')
        print('Successfully saved {} images to {}.'.format(index, images_dir))

    # Run through each single batch.
    _iterate(_each, _before, _done)


def main(argv=None):
    """Runs the script."""

    if FLAGS.data_dir is None:
        dataset = datasets[FLAGS.dataset]()
    else:
        dataset = datasets[FLAGS.dataset](FLAGS.data_dir)

    segmentation_algorithm = segmentations[FLAGS.segmentation_algorithm]()
    adjacency_algorithm = adjacencies[FLAGS.adjacency_algorithm]
    neighborhood_assembly = neighborhoods[FLAGS.neighborhood_assembly]

    # Save images for training and evaluation.
    iterate(dataset, segmentation_algorithm, adjacency_algorithm, False,
            neighborhood_assembly, node=100, size=18)
    iterate(dataset, segmentation_algorithm, adjacency_algorithm, True,
            neighborhood_assembly, node=100, size=18)


if __name__ == '__main__':
    tf.app.run()
