from collections import defaultdict
import json
import matplotlib.pyplot as plt
from matplotlib.ticker import PercentFormatter
import mplleaflet
import networkx as nx
import numpy as np
from operator import itemgetter
import pandas as pd
from scipy.spatial import distance
from scipy import stats
from shapely.geometry import Polygon
import sys


def create_network(routes, df):
    shapes = {}
    for i in range(len(routes['features'])):
        movement_id = int(routes['features'][i]['properties']['MOVEMENT_ID'])
        coordinates = routes['features'][i]['geometry']['coordinates'][0]
    if(len(coordinates) <= 3):
        coordinates = routes['features'][i]['geometry']['coordinates'][0][0]
        shapes[movement_id] = Polygon(coordinates)

    fixed = defaultdict(list)
    for k, v in shapes.items():
        for point in shapes[k].exterior.coords:
            if point not in fixed[k]:
                fixed[k].append(point)

    fixed_shapes = {}
    for k, v in fixed.items():
        fixed_shapes[k] = Polygon(fixed[k])
    shapes = fixed_shapes
    adj_matrix = pd.DataFrame(
        data=0, columns=shapes.keys(), index=shapes.keys())

    for i in range(2, len(shapes)+1):
        for j in range(i+1, len(shapes)+1):
            shape1 = shapes[i]
            shape2 = shapes[j]
            if(shape1.touches(shape2)):
                adj_matrix.iloc[i-1, j-1] = 1
                adj_matrix.iloc[j-1, i-1] = 1

    adj_area = {}
    for i in range(1, len(shapes)+1):
        adj_area[i] = []
        for j in range(1, len(shapes)+1):
            if(i != j):
                if(shapes[i].touches(shapes[j])):
                    adj_area[i].append(j)

    adj_1 = pd.DataFrame(data=0, index=adj_area.keys(),
                         columns=adj_area.keys())
    adj_2 = pd.DataFrame(data=0, index=adj_area.keys(),
                         columns=adj_area.keys())
    adj_3 = pd.DataFrame(data=0, index=adj_area.keys(),
                         columns=adj_area.keys())
    dfs = [adj_1, adj_2, adj_3]

    for i, j in adj_area.items():
        loc_df1 = i-1
        for k in j:
            loc_df2 = k-1
            temp = df[(df['sourceid'] == i) & (df['dstid'] == k)]
            hourRem = temp['mean_travel_time'].min()
            for myTime in range(len(dfs)):
                try:  # +1 for january
                    val = temp[temp['month'] == (
                        myTime)+1]['mean_travel_time'].values[0]
                    val = val - hourRem + 1
                except:
                    val = 0
                dfs[myTime].iloc[loc_df1, loc_df2] = val

    G = nx.from_pandas_adjacency(dfs[0])

    return G


def update_network_metrics(G):
    betweenness = nx.betweenness_centrality(G, weight='weight')
    nx.set_node_attributes(G, betweenness, 'betweenness')
    closeness = nx.closeness_centrality(G, distance='weight')
    nx.set_node_attributes(G, closeness, 'closeness')
    clustering = nx.clustering(G, weight='weight')
    nx.set_node_attributes(G, clustering, 'clustering')
    return G


def relative_lcc(G, n):
    components = [len(C) for C in nx.connected_components(G)]
    return max(components)/n


def local_efficiency(G, n):
    return nx.local_efficiency(G)


def calculate_metric_assimetry(G, sort, metric):
    d = 20
    n = len(G.nodes)
    p = int(n/d)-5
    metric_list = []
    for x in range(0, d+1):
        metric_list.append(metric(G, n))
        sample = sort[-p:]
        G.remove_nodes_from(sample)
        sort = sort[:-p]
    return metric_list


def visualize_assimetry(metric_list, marker, color, label):
    plt.xticks(ticks=np.arange(0, 22, 2), labels=np.arange(0, 110, 10))
    plt.plot(metric_list, marker=marker, color=color, label=label)
    return


def visualize_top_network(G, sorted_metric):
    pos = nx.get_node_attributes(G, 'pos')
    p = int(len(G.nodes())*0.01)
    sorted_pos = [[pos[k] for k in sorted_metric[0][-p:]],
                  [pos[k] for k in sorted_metric[1][-p:]],
                  [pos[k] for k in sorted_metric[2][-p:]]]
    coor = [list(map(list, zip(*sorted_pos[0]))),
            list(map(list, zip(*sorted_pos[1]))),
            list(map(list, zip(*sorted_pos[2])))]
    plt.figure()
    plt.scatter(coor[0][0], coor[0][1], s=100,
                color='b', marker='o', alpha=0.5)
    plt.scatter(coor[1][0], coor[1][1], s=100,
                color='g', marker='s', alpha=0.5)
    plt.scatter(coor[2][0], coor[2][1], s=200,
                color='r', marker='^', alpha=0.5)
    mplleaflet.show()
    return


def visualize_metrics(metric_list):
    plt.figure()
    scaled_metric = []
    weights = []
    bins = np.arange(0, 1.1, 0.1)
    for i in range(0, len(metric_list)):
        scaled_metric.append([j/max(metric_list[i]) for j in metric_list[i]])
    weights = [np.ones_like(scaled_metric[0])/float(len(scaled_metric[0])),
               np.ones_like(scaled_metric[1])/float(len(scaled_metric[1])),
               np.ones_like(scaled_metric[2])/float(len(scaled_metric[2]))]
    plt.hist([scaled_metric[0], scaled_metric[1], scaled_metric[2]],
             bins=bins, weights=weights, rwidth=0.9, color=['b', 'g', 'r'])
    plt.gca().yaxis.set_major_formatter(PercentFormatter(1))
    plt.gca().xaxis.set_major_formatter(PercentFormatter(1))
    plt.xticks(bins)
    plt.yticks(bins)
    plt.savefig('histogram.png')
    return


def skewness(metric_list):
    plt.figure()
    legend = [round(stats.skew(metric_list[0], bias=False), 4), round(stats.skew(
        metric_list[1], bias=False), 4), round(stats.skew(metric_list[2], bias=False), 4)]
    visualize_assimetry(metric_list[0], color='b',
                        marker='o', label=f"Between.={legend[0]}")
    visualize_assimetry(metric_list[1], color='g',
                        marker='s', label=f"Close.={legend[1]}")
    visualize_assimetry(metric_list[2], color='r',
                        marker='^', label=f"Cluster.={legend[2]}")
    plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc='lower left',
               ncol=3, mode="expand", borderaxespad=0.)
    return


def main():
    name = sys.argv[1]

    # Network creation and metrics calculation
    with open('data/' + name + '.geojson', encoding='latin1') as f:
        routes = json.load(f)
    df = pd.read_csv('data/' + name + '.csv')
    print('File read')
    G = create_network(routes, df)
    print('Network created')
    G1 = update_network_metrics(G)
    print('Network metrics updated')
    nx.write_gml(G1, name+'.gml')

    G = nx.read_gml(name+'.gml')

    # Visualize metrics
    betweenness = nx.get_node_attributes(G, 'betweenness')
    closeness = nx.get_node_attributes(G, 'closeness')
    clustering = nx.get_node_attributes(G, 'clustering')
    sorted_list = [list(dict(sorted(betweenness.items(), key=itemgetter(1))).keys()),
                   list(dict(sorted(closeness.items(), key=itemgetter(1))).keys()),
                   list(dict(sorted(clustering.items(), key=itemgetter(1))).keys())]
    visualize_top_network(G, sorted_list)
    visualize_metrics([list(betweenness.values()),
                       list(closeness.values()),
                       list(clustering.values())])
    print('Metrics visualized')

    # Skewness visualized
    metric = local_efficiency
    efficiency_list = []
    for i in range(0, len(sorted_list)):
        efficiency_list.append(calculate_metric_assimetry(
            G.copy(), sorted_list[i], metric))
    skewness(efficiency_list)
    plt.xlabel("Percentage of Removed Nodes(%)")
    plt.ylabel("Efficiency")
    plt.savefig('efficiency.png')

    metric = relative_lcc
    lcc_list = []
    for i in range(0, len(sorted_list)):
        lcc_list.append(calculate_metric_assimetry(
            G.copy(), sorted_list[i], metric))
    skewness(lcc_list)
    plt.xlabel("Percentage of Removed Nodes(%)")
    plt.ylabel("Relative Largest Connected Component")
    plt.savefig('lcc.png')
    print('Skewness visualized')

    return


if __name__ == "__main__":
    main()
