from numpy import linalg as LA
from igraph import *
import xlwt

class MyGraph:
    def __init__(self, name):
        g = Graph(directed=True)
        g.degree(mode="in")
        self.name = name
        self.g = g
        self.vertices = self.g.vs
        self.edges = self.g.es

    def load_gml(self, path):
        self.g = load(path)
        self.vertices = self.g.vs
        self.edges = self.g.es
        print(summary(self.g))

    def invert_edges(self):
        temp = list()
        for e in self.edges:
            temp.append((e.target, e.source))
        self.edges.delete()
        count = 1
        for e in temp:
            self.g.add_edges([(e[0], e[1])])
            if count % 10000 == 0:
                print(count, 'out of', len(temp))
            count += 1
        print(summary(self.g))

    def len_vertices(self):
        return len(self.vertices)

    def len_edges(self):
        return len(self.edges)

    def vertex_id(self, vertex):
        return vertex.index

    def vertices_degrees(self):
        out = {}
        degrees = self.g.degree()
        for i in range(len(self.vertices)):
            v = self.vertices[i]
            try:
                out[v['full_name']] = degrees[i]
            except KeyError:
                out[v['fullname']] = degrees[i]

        out = sorted(out.items(), key=lambda x: x[1])
        return dict(out)

    def vertices_in_degrees(self):
        out = {}
        degrees = self.g.indegree()
        for i in range(len(self.vertices)):
            v = self.vertices[i]
            try:
                out[v['full_name']] = degrees[i]
            except KeyError:
                out[v['fullname']] = degrees[i]

        out = sorted(out.items(), key=lambda x: x[1])
        return dict(out)

    def vertices_out_degrees(self):
        out = {}
        degrees = self.g.outdegree()
        for i in range(len(self.vertices)):
            v = self.vertices[i]
            try:
                out[v['full_name']] = degrees[i]
            except KeyError:
                out[v['fullname']] = degrees[i]

        out = sorted(out.items(), key=lambda x: x[1])
        return dict(out)

    def vertices_page_ranks(self, sort=True):
        out = {}
        vertices = self.vertices
        ranks = self.g.pagerank(weights='frequency')
        for i in range(len(vertices)):
            try:
                key = vertices[i]['full_name']
            except KeyError:
                key = vertices[i]['fullname']
            value = ranks[i]

            out[key] = value

        if sort:
            out = sorted(out.items(), key=lambda x: x[1])
        return dict(out)

    def vertices_hub_scores(self):
        out = {}
        vertices = self.vertices
        ranks = self.g.hub_score(weights='frequency')
        for i in range(len(vertices)):
            try:
                key = vertices[i]['full_name']
            except KeyError:
                key = vertices[i]['fullname']
            value = ranks[i]

            out[key] = value

        out = sorted(out.items(), key=lambda x: x[1])
        return dict(out)

    def vertices_authority_scores(self):
        out = {}
        vertices = self.vertices
        ranks = self.g.authority_score(weights='frequency')
        for i in range(len(vertices)):
            try:
                key = vertices[i]['full_name']
            except KeyError:
                key = vertices[i]['fullname']
            value = ranks[i]

            out[key] = value

        out = sorted(out.items(), key=lambda x: x[1])
        return dict(out)

    def vertices_frequency(self):
        out = {}
        vertices = self.vertices
        for v in vertices:
            try:
                key = v['full_name']
            except KeyError:
                key = v['fullname']
            value = v['frequency']

            out[key] = value

        out = sorted(out.items(), key=lambda x: x[1])
        return dict(out)

    def cluster_coeficient(self):
        return self.g.transitivity_avglocal_undirected(mode='zero')

    def find_vertex(self, full_name):
        try:
            return self.vertices.find(full_name=full_name)
        except(ValueError, KeyError):
            return None

    def find_edge(self, from_vertex_id, to_vertex_id):
        try:
            return self.g.get_eid(from_vertex_id, to_vertex_id)
        except(InternalError):
            return None

    def add_edge(self, from_vertex_id, to_vertex_id):
        edge_id = self.find_edge(from_vertex_id, to_vertex_id)
        if edge_id != None:
            edge = self.edges[edge_id]
            edge["frequency"] += 1
            edge["weight"] += 1
            return edge
        else:
            self.g.add_edges([(from_vertex_id, to_vertex_id)])
            new_edge_id = self.find_edge(from_vertex_id, to_vertex_id)
            new_edge = self.edges[new_edge_id]
            new_edge["frequency"] = 1
            new_edge["weight"] = 1
            return new_edge

    def add_vertex(self, piece, move_from, move_to):
        equivalent_rows = {'1':'8', '2':'7', '3':'6', '4':'5', '5':'4', '6':'3', '7':'2', '8':'1'}
        if piece == piece.lower(): # if piece is black
            move_from_column, move_from_row = move_from[0], equivalent_rows[move_from[1]]
            move_from = move_from_column + move_from_row
            move_to_column, move_to_row = move_to[0], equivalent_rows[move_to[1]]
            move_to = move_to_column + move_to_row
            piece = piece.upper()

        full_name = piece + move_from + '-' + piece + move_to

        if full_name == 'Ke1-Kg1':
            full_name = 'O-O'
        elif full_name == 'Ke1-Kc1':
            full_name = 'O-O-O'

        vertex = self.find_vertex(full_name)
        if vertex != None:
            vertex['frequency'] += 1
            return vertex
        else:
            self.g.add_vertices(1)
            new_vertex = self.vertices[self.len_vertices() - 1]
            new_vertex["piece"] = piece
            new_vertex["move_from"] = move_from
            new_vertex["move_to"] = move_to # if piece == piece.lower() else "white"
            new_vertex["frequency"] = 1
            new_vertex["full_name"] = full_name
            return new_vertex

    def create_matrix(self, normalized=False):

        def normalize(array):
            s = sum(array)
            return [float(i)/s for i in array]

        matrix = []
        for v1 in self.vertices:
            v1_id = self.vertex_id(v1)
            collumn = []
            for v2 in self.vertices:
                v2_id = self.vertex_id(v2)
                e_id = self.find_edge(v1_id, v2_id)
                if e_id != None:
                    collumn.append(int(self.edges[e_id]['frequency']))
                else:
                    collumn.append(0)
                if sum(collumn) == 0:
                    collumn = [1 for i in collumn]
            if normalized:
                matrix.append(normalize(collumn))
            else:
                matrix.append(collumn)
        return matrix

    def eigenvalue(self, norlamized_matrix=False):
        eig, _ = LA.eig(self.create_matrix(norlamized_matrix))
        return eig

    def visualize(self, layout_type, save=False):
        visual_style = {}
        visual_style["vertex_size"] = 30
        visual_style["vertex_label"] = self.vertices["full_name"]
        visual_style["vertex_color"] = [v["color"] for v in self.vertices]
        visual_style["edge_width"] = [1 for i in self.edges]
        visual_style["layout"] = self.g.layout(layout_type)
        visual_style["margin"] = 20
        out = plot(self.g, **visual_style)
        if save:
            out.save("exported_images/" + self.name + ".png")

    def export_to_gml(self, name=None):
        if name == None:
            self.g.write_gml("exported_graphs/" + self.name + ".gml")
        else:
            self.g.write_gml("exported_graphs/" + name + ".gml")

    def export_to_xlsx(self, name=None):
        style0 = xlwt.easyxf('font: name Times New Roman, color-index black, bold off', num_format_str='#,##0')
        wb = xlwt.Workbook()

        def export(sheet_name, style, dictionary):
            w = wb.add_sheet(sheet_name)
            row_index = 0
            for key, value in dictionary.items():
                w.write(row_index, 0, key, style)
                if sheet_name in ['PageRank', 'HubScore', 'AuthorityScore']:
                    w.write(row_index, 1, str(value), style)
                else:
                    w.write(row_index, 1, value, style)
                row_index += 1

        export('Degrees', style0, self.vertices_degrees())
        export('InDegrees', style0, self.vertices_in_degrees())
        export('OutDegrees', style0, self.vertices_out_degrees())
        export('Frequencies', style0, self.vertices_frequency())
        export('PageRank', style0, self.vertices_page_ranks())
        export('HubScore', style0, self.vertices_hub_scores())
        export('AuthorityScore', style0, self.vertices_authority_scores())

        if name == None:
            wb.save("exported_excels/" + self.name + ".xls")
        else:
            wb.save("exported_excels/" + name + ".xls")
