import networkx as nx

import matplotlib
matplotlib.use('Agg') # to fix X Display issue on remote host
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import clustering
import numpy as np
import os,math

CHART_DPI = 400
HISTOGRAM_Y_AXIS_SHOW_PERCENTAGE = False

def get_default_node_layout(G):
    return get_optimal_graphviz_layout(G)

def get_optimal_graphviz_layout(G):
    return nx.graphviz_layout(G, prog='fdp', args='-Goverlap=false')
def draw_network(network, output_dir, layout = None,  params = None):

    if layout =='circular':
        node_positions = nx.circular_layout(network)
    elif layout == 'optimal':
        node_positions = get_optimal_graphviz_layout(network)
    else:
        #positions of the nodes
        if params.get('node_positions',None) is not None:
            node_positions = params.get('node_positions')
        else:
            node_positions =  get_default_node_layout(network)



    #Draw parameters
    node_size=300
    font_size=10
    edge_width=1
    edge_largest_conn_comp_width=6
    with_labels=True

    #If network is too large, change some parameters
    if network.order()>50:
        node_size=100
        font_size=7
        edge_width=0.5
        edge_largest_conn_comp_width=3

    node_size = params.get('node_size',node_size)
    font_size = params.get('font_size',font_size)

    #node colors
    node_colors = 'gray'
    node_attribute_for_coloring = params.get('node_color_by_attribute_value',None)
    if  node_attribute_for_coloring is not None:#transform attribute values to the values in the range [0,1]

        value_color_dict = params.get('node_attr_value_color_dict',None)
        if value_color_dict is None:
            #find all unique attr values
            value_color_dict = dict()
            for node_name in network.nodes():
                node_attr_value = network.node[node_name].get(node_attribute_for_coloring)
                if node_attr_value is not None:
                    value_color_dict[node_attr_value]=0

            #assign a value between 0 and 1 for each unique attr value
            #(evenly distribute values in the range [0,1])
            i=0
            incr = 1.0/max(1,len(value_color_dict.keys()))
            for node_attr_value in value_color_dict:
                i += incr
                value_color_dict[node_attr_value] = i

        #assign colors (values in [0,1]) to nodes according to their attribute values
        node_colors = []
        for node_name in network.nodes():
            node_attr_value = network.node[node_name].get(node_attribute_for_coloring)
            node_colors.append(value_color_dict.get(node_attr_value,0))


    #draw a network
    nx.draw(
        network,
        node_positions,
        node_color=node_colors,
        edge_color=params.get('edge_colors','gray'),
        width=edge_width,
        with_labels = with_labels and not params.has_key('node_labels'),
        node_size=node_size,
    )

    if with_labels and params.has_key('node_labels') :
        nx.draw_networkx_labels(
            network,
            node_positions,
            params.get('node_labels'),
            font_size=font_size
        )

    #get network's largest connected component
    #largest_connected_component = network.get_largest_connected_component()

    #if there are several connected components, highlight the largest
    # if params.get('highlight_largest_component',True) and largest_connected_component != network:
    #     nx.draw_networkx_edges(largest_connected_component,node_positions,
    #         edge_color='gray',
    #         alpha=0.2,
    #         width=edge_largest_conn_comp_width
    #     )

    #show textbox
    if params.get('textbox',None) is not None:
        plt.figtext(
            0.05,
            0.8,
            params['textbox'],
            bbox = dict(boxstyle='round', facecolor='wheat', alpha=0.5),
            size='small')


    #if there is no specified output filename, use network name instead
    output_filename = params.get('output_filename',None)#since Network class does not implement get_name()
    if output_filename is None:
        output_filename = network.get_name()

    #if output directory does not exists, create it
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)

    #save figure to the file
    image_file = output_dir + output_filename + '.png'
    plt.savefig(image_file, dpi = CHART_DPI)

    #clear canvas
    plt.clf()

    return image_file


def draw_timeseries(data,output_dir,output_filename, params = None):

    if params is None:
        params = dict()

    #list of markers for different time series
    marker_list = ['o','s','^','>','v','d','p']
    #list of colors for different time series
    #colors = ['#348ABD', '#7A68A6', '#A60628', '#467821', '#CF4457', '#188487', '#E24A33']
    colors = ['blue','red','green','brown','violet','aqua','orange']

    #needed to set ticks for axis x
    data_x,x = [],[]
    #counter to change markers and colors
    i=0

    error_bars_data = params.get('y_err',None)

    for series in data:

        series_data = data[series]

        #limit on max number of series in one plot
        if i >= len(marker_list):
            break

        #get data for axis x and axis y
        data_x = [d[0] for d in series_data]
        data_y = [d[1] for d in series_data]

        #axis x need to numeric - use integers
        #later set labels by using ticks for axis x
        x = range(1,len(data_x)+1)


        #draw a plot for a series
        plt.plot(x,data_y,marker_list[i] + '-', label = series, c = colors[i],mfc=colors[i],  ms=5, lw=1, alpha=0.5)

        #add error bars
        if error_bars_data is not None:
            plt.errorbar(x,data_y,yerr = error_bars_data.get(series,None), c = colors[i])

        i+=1


    #set title
    #if params.get('title','') != '':
    #    plt.title(params.get('title',''))

    #set labels for axis x and axis y
    plt.xlabel(params.get('xlabel',''), fontsize = 16)
    plt.ylabel(params.get('ylabel',''), fontsize = 16)

    leg = plt.legend(loc='upper right')
    #add transparency
    leg.get_frame().set_alpha(0.5)

    #set ticks for axis x
    plt.xticks(x,data_x)

    #set min and max values for axis y
    if params.has_key('ymin') and params.has_key('ymax'):
        plt.ylim(params.get('ymin'),params.get('ymax'))

    #draw a grid
    plt.grid()

    #create output directory if it does not exists
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)

    #save histogram to a file
    output_file = output_dir + '/' + output_filename + '.png'
    plt.savefig(output_file, dpi = CHART_DPI)

    #clear plot for the next histogram
    plt.clf()

    return output_file




def draw_histogram(object_list, property, output_dir, output_filename, params):
    draw_values_histogram(
        [property(object) for object in object_list],
        output_dir,
        output_filename,
        params
    )

def draw_values_histogram(values, output_dir, output_filename, params = None):

    if params is None:
        params = dict()

    #max and min values for x axis
    prop_min_value = params.get('prop_min_value', min(0,min(values)))
    prop_max_value = params.get('prop_max_value', max(values))

    #determine number of bins - how to group values
    #(it is specified either by the total number of bins in the histogram or by individual bin range)
    if params.has_key('number_of_bins'):
        number_of_bins =  params['number_of_bins']
    else:
        if params.has_key('prop_group_by'):
            number_of_bins = float(prop_max_value - prop_min_value) / params.get('prop_group_by', 1)
        else:
            number_of_bins = float(prop_max_value - prop_min_value) / get_range_group_by(prop_max_value-prop_min_value)

    ax = plt.subplot(111)

    #show percentages or values for axis y
    if params.get('show_percentage',HISTOGRAM_Y_AXIS_SHOW_PERCENTAGE):
        ylabel = 'Percentage'
        values_count = len(values)
        plt.yticks(
            [x*values_count/100.0 for x in range(0,110,10)],
            ['%s%%' % x for x in range(0,110,10)]
        )
        ax.yaxis.set_minor_locator(mticker.MultipleLocator(values_count/40.0))
    else:
        ylabel = 'Count'

    ##TITLES
    #set histogram title
    plt.title(params.get('title',''))

    #set axis title
    plt.xlabel(params.get('x_label',''), fontsize = 16)
    plt.ylabel(ylabel, fontsize = 16)

    #set limits for x axis
    plt.xlim(prop_min_value,prop_max_value)
    #set limits for y axis
    plt.ylim(0,params.get('count_max_value',len(values)))


    ##TICKS
    #set ticks for x axis
    number_of_major_ticks = params.get('number_of_ticks',min(10,number_of_bins))

    #set major and minor ticks for axis x
    ax.xaxis.set_minor_locator(mticker.MaxNLocator(number_of_bins+1))
    ax.xaxis.set_major_locator(mticker.FixedLocator(locs = ax.xaxis.get_ticklocs(minor=True)[::(number_of_bins/number_of_major_ticks)]))

    #set major ticks formatter
    if prop_max_value<1.0:
        ax.xaxis.set_major_formatter(mticker.FormatStrFormatter('%.3f'))

    #decrease font size if tick labels may overlap
    if prop_max_value == 1:
        max_x_label_length = 5
    else:
        max_x_label_length = len(str(int(prop_max_value)))+4
    if max_x_label_length*2 +1 >= number_of_major_ticks:
        plt.tick_params(which='major', labelsize=9)
    else:
        plt.tick_params(which='major', labelsize=7)

    #adjust axis labels when changing font size
    #plt.subplots_adjust(bottom=0.13,left=0.17)

    #create histogram
    plt.hist(
        values,
        bins = ax.xaxis.get_ticklocs(minor=True),
        histtype = params.get('hist_type','bar'),
        color = params.get('color','gray')
    )

    #create output directory if it does not exists
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)

    #save histogram to a file
    plt.savefig(output_dir + '/' + output_filename + '.png', dpi = CHART_DPI)

    #clear plot for the next histogram
    plt.clf()


def get_range_group_by(range):
    """
    Get optimal value to group by for a range of values
    """
    group_by = 10**(math.floor(math.log10(range))-1)

    if range/group_by > 50:
        return 5*group_by
    if range/group_by > 40:
        return 4*group_by
    if range/group_by > 20:
        return 2*group_by

    return group_by


def draw_clustering(clustering_dict, output_dir, output_filename = None, ground_truth_clustering = None, object_names = None,
                    ground_truth_clustering_colors = None):
    """
    Plot clustering as a graph where clusters are cliques. Use ground truth for node coloring.
    """
    g = nx.Graph()
    clustering_dict = clustering.convert_clustering_to_dict_format(clustering_dict)

    #Cannot use object_names as node names - since in two captions are the same, different nodes will be treated as one
    for c in clustering_dict:
        objects = clustering_dict[c]
        nodes = None
        object_captions = objects
        if ground_truth_clustering is not None and object_names is not None:
                nodes = [(x,{'ground_truth_cluster': ground_truth_clustering[x],'description': object_names[x]}) for x in objects]
        elif ground_truth_clustering is not None and object_names is not None:
                nodes = [(x,{'ground_truth_cluster': ground_truth_clustering[x],'description': x}) for x in objects]
        else:
                nodes = objects

        g.add_nodes_from(nodes)
        g.add_edges_from([(x,y) for x in object_captions for y in object_captions])


    #save for Cytoscape
    # nx.write_edgelist(g,output_dir + output_filename + '.txt')
    # output_csv = csv.writer(open(output_dir + output_filename + '.csv', "wb"))
    # output_csv.writerow(['#','Description', 'Ground Truth'])
    # for node in g.nodes(data=True):
    #     output_csv.writerow([node[0],node[1]['description'], node[1]['ground_truth_cluster']])

    draw_network(g,
                              output_dir,
                              params={
                                  #'node_positions': nx.spring_layout(g),
                                  'node_color_by_attribute_value': 'ground_truth_cluster',
                                  'highlight_largest_component': False,
                                  'output_filename': 'clustering' if output_filename is None else output_filename,
                                  'node_attr_value_color_dict': ground_truth_clustering_colors,
                                  'edge_colors': 'white',
                                  'node_size': 100,
                                  'font_size': 8,
                                  'node_labels': dict(zip(range(0,g.order()), object_names)) if object_names is not None else None#dict node name => node label
                              }
    )
