import networkx as nx
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import mpl_toolkits.mplot3d as mplot3d

import os, math, operator
import settings
def draw_network(network, output_dir,params = None):

        node_positions = nx.graphviz_layout(network, prog='fdp', args='-Goverlap=false')

        #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='gray',#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
            )



        #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 = 300)

        #clear canvas
        plt.clf()

        return image_file

def plot_clustering(clustering, 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 = utils.convert_clustering_to_dict_format(clustering)

        #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.keys():
            objects = [str(o) for o in clustering[c]]
            nodes = None
            object_captions = objects
            if ground_truth_clustering is not None:
                    nodes = [(x,{'ground_truth_cluster': ground_truth_clustering[x],'description': object_names[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': 'gray',
                                      'node_size': 300,
                                      'font_size': 8,
                                      #'node_labels': dict(zip(range(0,g.order()), object_names))#dict node name => node label
                                  }
        )

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, rotation = 30, ha = 'right')

    #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 = settings.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, format = 'png', 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',settings.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('prop_title',''), 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
    output_file = output_dir + output_filename + '.' + format
    plt.savefig(output_file, format = format, dpi = settings.CHART_DPI)

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

def draw_scatter_plot(object_list, output_dir, output_filename, format = 'png', params = None):
    #property values for x and y
    """

:type object_list: list
:type property_x: __builtin__.instancemethod
:type property_y: __builtin__.instancemethod
:type output_dir: str
:type output_filename: str
:type params: dict
:return: :raise:
"""

    if params is None:
        params = dict()

    is_3d = params.get('3d',False)

    data_by_classes = {}


    #calculate property x and y values for each object in the object list
    for object in object_list:
        class_value = 0
        if len(object)>2:
            class_value = object[-1]
            if not data_by_classes.has_key(class_value):
                data_by_classes[class_value] = {'x': [], 'y': [], 'z': []}

            data_by_classes[class_value]['x'].append(float(object[0]))
            data_by_classes[class_value]['y'].append(float(object[1]))
            if is_3d:
                data_by_classes[class_value]['z'].append(float(object[2]))


    colors = plt.cm.spectral(np.linspace(0, 1, len(data_by_classes.keys())))
    markers = ['o','s','^','>','v','d','p']

    different_markers = params.get('different_markers',False)

    plt.clf()
    fig = plt.figure()
    if not is_3d:
        ax = fig.add_subplot(111)
    else:
        ax = fig.add_subplot(111, projection='3d')

    #draw scatter chart
    i=0
    for class_name in data_by_classes:
        if not is_3d:
          plt.scatter(data_by_classes[class_name]['x'],
                   data_by_classes[class_name]['y'],
                   marker = markers[i%len(markers)] if different_markers else params.get('marker','o'),
                   label=class_name,
                   alpha=params.get('alpha',0.6),
                   edgecolor='black',
                   color=colors[i]
                   )
          #ax.set_yscale('log')
          #ax.set_xscale('log')
        else:
            ax.plot(data_by_classes[class_name]['x'],
                       data_by_classes[class_name]['y'],
                       data_by_classes[class_name]['z'],
                       markers[i%len(markers)] if different_markers else params.get('marker','o'),
                       label=class_name,
                       alpha=params.get('alpha',0.6),
                       color=colors[i]
                       )
        i+=1


    #set title for the chart
    plt.title(params.get('title',''))

    #set labels for x and y axes
    if not is_3d:
        plt.xlabel(params.get('prop_x_title',''), fontsize=20)
        plt.ylabel(params.get('prop_y_title',''), fontsize=20)
    else:
        ax.set_xlabel(params.get('prop_x_title',''), fontsize = 20)
        ax.set_ylabel(params.get('prop_y_title',''), fontsize = 20)
        ax.set_zlabel(params.get('prop_z_title',''), fontsize = 20)

    #sort legend
    handles, labels = ax.get_legend_handles_labels()
    hl = sorted(zip(handles, labels),
                key=operator.itemgetter(1))
    handles2, labels2 = zip(*hl)

    ax.legend(handles2, labels2, loc='upper right',framealpha=0.7,
              fancybox=True, fontsize = 14)

    if params.get('hide_ticks',False):
        ax.set_xticks([])
        ax.set_yticks([])
        if is_3d:
            ax.set_zticks([])

    #set min and max limits for x and y axes
    #plt.xlim(params['prop_x_min_value'],params['prop_x_max_value'])
    #plt.ylim(params['prop_y_min_value'],params['prop_y_max_value'])

    # #set ticks for x and y axes
    # xticks = [params['prop_x_group_by'] * i for i in range(0,int(math.ceil(params['prop_x_max_value']/params['prop_x_group_by']))+1)]
    # plt.xticks(xticks)
    #
    # yticks = [params['prop_y_group_by'] * i for i in range(0,int(math.ceil(params['prop_y_max_value']/params['prop_y_group_by']))+1)]
    # plt.yticks(yticks)


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

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

    #save chart to a file
    output_file = output_dir + output_filename + '.' + format
    plt.savefig(output_file, format = format, dpi = settings.CHART_DPI)

    #clear plot for the next chart
    plt.clf()
    plt.close(fig)

    return output_file

def draw_percentile_curve(self, values, output_dir, output_filename, xlabel=None):

    plot_percentiles = np.linspace(0, 100, num=500, endpoint=True).tolist()
    x = np.percentile(values, plot_percentiles)

    plt.plot(x, plot_percentiles,
            color='gray',
    )

    plt.xlabel(xlabel, fontsize = 16)
    plt.ylabel('Percentile rank', fontsize = 16)

    ax = plt.subplot(111)
    #plt.xlim(0.95,1)
    #ax.xaxis.set_minor_locator(mticker.MaxNLocator(26))
    plt.yticks(range(0,110,10))
    ax.yaxis.set_minor_locator(mticker.MaxNLocator(40))
    plt.tick_params(which='major', labelsize=9)

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

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

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

    return output_file

def draw_heatmap(data, output_dir, output_filename, x_labels = None, y_labels = None, format = 'png', params = None):

    """
    Draw heatmap from input data
    :param data:
    :param output_dir:
    :param output_filename:
    :param params:
    :return:
    """

    if params is None:
        params = dict()

    fig, ax = plt.subplots()
    colormap = plt.cm.Blues #nlcmap(plt.cm.Blues, [0, 20, 35, 50])
    heatmap = ax.pcolor(data, cmap=colormap)

    # put the major ticks at the middle of each cell
    ax.set_xticks(np.arange(data.shape[0])+0.5, minor=False)
    ax.set_yticks(np.arange(data.shape[1])+0.5, minor=False)

    # want a more natural, table-like display
    ax.invert_yaxis()
    ax.xaxis.tick_top()

    if x_labels is not None:
        ax.set_xticklabels(x_labels, minor=False)
    else:
        ax.set_xticklabels([], minor=False)
    if y_labels is not None:
        ax.set_yticklabels(y_labels, minor=False)
    else:
        ax.set_yticklabels([], minor=False)

    # if len(data)>50:
    #     plt.rcParams['font.size'] = 5
    # else:
    #     plt.rcParams['font.size'] = 8


    if params.get('show_color_bar',True):
        cbar = plt.colorbar(heatmap)


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

    #save chart to a file
    output_file = output_dir + output_filename + '.' + format
    plt.savefig(output_file, dpi = settings.CHART_DPI)

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

    return output_file

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


from matplotlib.colors import LinearSegmentedColormap

class nlcmap(LinearSegmentedColormap):
    """A nonlinear colormap"""

    name = 'nlcmap'

    def __init__(self, cmap, levels):
        self.cmap = cmap
        # @MRR: Need to add N for backend
        self.N = cmap.N
        self.monochrome = self.cmap.monochrome
        self.levels = np.asarray(levels, dtype='float64')
        self._x = self.levels / self.levels.max()
        self._y = np.linspace(0.0, 1.0, len(self.levels))

    #@MRR Need to add **kw for 'bytes'
    def __call__(self, xi, alpha=1.0, **kw):
        """docstring for fname"""
        # @MRR: Appears broken?
        # It appears something's wrong with the
        # dimensionality of a calculation intermediate
        #yi = stineman_interp(xi, self._x, self._y)
        yi = np.interp(xi, self._x, self._y)
        return self.cmap(yi, alpha)