from scipy.sparse import csr_matrix
import scipy.sparse as ssp
import networkx as nx
from .base import BaseGenerator
import numpy as np
from .. import register_feature


class Eigen(object):
    def __init__(self):
        pass

    def normalize_adj(self, adj):
        rowsum = np.array(adj.sum(1))
        d_inv_sqrt = np.power(rowsum, -0.5).flatten()
        d_inv_sqrt[np.isinf(d_inv_sqrt)] = 0.0
        d_inv_sqrt = ssp.diags(d_inv_sqrt)
        return adj.dot(d_inv_sqrt).transpose().dot(d_inv_sqrt)

    def forward(self, adj, d, use_eigenvalues=0, adj_norm=1):
        G = nx.from_scipy_sparse_matrix(adj)
        comp = list(nx.connected_components(G))
        results = np.zeros((adj.shape[0], d))
        for i in range(len(comp)):
            node_index = np.array(list(comp[i]))
            d_temp = min(len(node_index) - 2, d)
            if d_temp <= 0:
                continue
            temp_adj = adj[node_index, :][:, node_index].asfptype()
            if adj_norm == 1:
                temp_adj = self.normalize_adj(temp_adj)
            lamb, X = ssp.linalg.eigs(temp_adj, d_temp)
            lamb, X = lamb.real, X.real
            temp_order = np.argsort(lamb)
            lamb, X = lamb[temp_order], X[:, temp_order]
            for i in range(X.shape[1]):
                if np.sum(X[:, i]) < 0:
                    X[:, i] = -X[:, i]
            if use_eigenvalues == 1:
                X = X.dot(np.diag(np.sqrt(np.absolute(lamb))))
            elif use_eigenvalues == 2:
                X = X.dot(np.diag(lamb))
            results[node_index, :d_temp] = X
        return results


@register_feature("eigen")
class GeEigen(BaseGenerator):
    r"""concat Eigen features

    Notes
    -----
    An implementation of [1]_

    References
    ----------
    .. [1] Ziwei Zhang, Peng Cui, Jian Pei, Xin Wang, Wenwu Zhu:
        Eigen-GNN: A Graph Structure Preserving Plug-in for GNNs. CoRR abs/2006.04330 (2020)
        https://arxiv.org/abs/2006.04330


    Parameters
    ----------
    size : int
        EigenGNN hidden size
    """

    def __init__(self, size=32):
        super(GeEigen, self).__init__()
        self.size = size

    def _transform(self, data):

        adj = csr_matrix(
            (data.edge_weight, (data.edge_index[0, :], data.edge_index[1, :])),
            shape=(data.x.shape[0], data.x.shape[0]),
        )
        if np.max(adj - adj.T) > 1e-5:
            adj = adj + adj.T
        mf = Eigen()
        data.x = np.concatenate([data.x, mf.forward(adj, self.size)], axis=1)

        return data

    def _preprocess(self, data):
        if not hasattr(data, "edge_weight"):
            data.edge_weight = np.ones(data.edge_index.shape[1])

    def _postprocess(self, data):
        del data.edge_weight
