# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

# pylint: skip-file
from __future__ import print_function
import numpy as np
import scipy as sp
import mxnet as mx
import random
import itertools
from numpy.testing import assert_allclose, assert_array_equal
from mxnet.test_utils import *
import unittest

def check_uniform(out, num_hops, max_num_vertices):
    sample_id = out[0]
    sub_csr = out[1]
    layer = out[2]
    # check sample_id
    assert (len(sample_id) == max_num_vertices+1)
    num_vertices = sample_id[-1].asnumpy()[0]
    # check sub_csr
    sub_csr.check_format(full_check=True)
    assert np.all((sub_csr.indptr[num_vertices:] == sub_csr.indptr[num_vertices]).asnumpy())
    # check layer
    for data in layer[:num_vertices]:
        assert(data <= num_hops)

def check_non_uniform(out, num_hops, max_num_vertices):
    sample_id = out[0]
    sub_csr = out[1]
    prob = out[2]
    layer = out[3]
    # check sample_id
    assert (len(sample_id) == max_num_vertices+1)
    num_vertices = sample_id[-1].asnumpy()[0]
    # check sub_csr
    sub_csr.check_format(full_check=True)
    assert np.all((sub_csr.indptr[num_vertices:] == sub_csr.indptr[num_vertices]).asnumpy())
    # check prob
    assert (len(prob) == max_num_vertices)
    # check layer
    for data in layer[:num_vertices]:
        assert(data <= num_hops)

def check_compact(csr, id_arr, num_nodes):
    compact = mx.nd.contrib.dgl_graph_compact(csr, id_arr, graph_sizes=num_nodes, return_mapping=False)
    assert compact.shape[0] == num_nodes
    assert compact.shape[1] == num_nodes
    assert mx.nd.sum(compact.indptr == csr.indptr[0:int(num_nodes + 1)]).asnumpy() == num_nodes + 1
    sub_indices = compact.indices.asnumpy()
    indices = csr.indices.asnumpy()
    id_arr = id_arr.asnumpy()
    for i in range(len(sub_indices)):
        sub_id = sub_indices[i]
        assert id_arr[sub_id] == indices[i]

def test_uniform_sample():
    shape = (5, 5)
    data_np = np.array([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20], dtype=np.int64)
    indices_np = np.array([1,2,3,4,0,2,3,4,0,1,3,4,0,1,2,4,0,1,2,3], dtype=np.int64)
    indptr_np = np.array([0,4,8,12,16,20], dtype=np.int64)
    a = mx.nd.sparse.csr_matrix((data_np, indices_np, indptr_np), shape=shape)

    seed = mx.nd.array([0,1,2,3,4], dtype=np.int64)
    out = mx.nd.contrib.dgl_csr_neighbor_uniform_sample(a, seed, num_args=2, num_hops=1, num_neighbor=2, max_num_vertices=5)
    assert (len(out) == 3)
    check_uniform(out, num_hops=1, max_num_vertices=5)
    num_nodes = out[0][-1].asnumpy()
    assert num_nodes > 0
    assert num_nodes < len(out[0])
    check_compact(out[1], out[0], num_nodes)

    seed = mx.nd.array([0], dtype=np.int64)
    out = mx.nd.contrib.dgl_csr_neighbor_uniform_sample(a, seed, num_args=2, num_hops=1, num_neighbor=1, max_num_vertices=4)
    assert (len(out) == 3)
    check_uniform(out, num_hops=1, max_num_vertices=4)
    num_nodes = out[0][-1].asnumpy()
    assert num_nodes > 0
    assert num_nodes < len(out[0])
    check_compact(out[1], out[0], num_nodes)

    seed = mx.nd.array([0], dtype=np.int64)
    out = mx.nd.contrib.dgl_csr_neighbor_uniform_sample(a, seed, num_args=2, num_hops=2, num_neighbor=1, max_num_vertices=3)
    assert (len(out) == 3)
    check_uniform(out, num_hops=2, max_num_vertices=3)
    num_nodes = out[0][-1].asnumpy()
    assert num_nodes > 0
    assert num_nodes < len(out[0])
    check_compact(out[1], out[0], num_nodes)

    seed = mx.nd.array([0,2,4], dtype=np.int64)
    out = mx.nd.contrib.dgl_csr_neighbor_uniform_sample(a, seed, num_args=2, num_hops=1, num_neighbor=2, max_num_vertices=5)
    assert (len(out) == 3)
    check_uniform(out, num_hops=1, max_num_vertices=5)
    num_nodes = out[0][-1].asnumpy()
    assert num_nodes > 0
    assert num_nodes < len(out[0])
    check_compact(out[1], out[0], num_nodes)

    seed = mx.nd.array([0,4], dtype=np.int64)
    out = mx.nd.contrib.dgl_csr_neighbor_uniform_sample(a, seed, num_args=2, num_hops=1, num_neighbor=2, max_num_vertices=5)
    assert (len(out) == 3)
    check_uniform(out, num_hops=1, max_num_vertices=5)
    num_nodes = out[0][-1].asnumpy()
    assert num_nodes > 0
    assert num_nodes < len(out[0])
    check_compact(out[1], out[0], num_nodes)

    seed = mx.nd.array([0,4], dtype=np.int64)
    out = mx.nd.contrib.dgl_csr_neighbor_uniform_sample(a, seed, num_args=2, num_hops=2, num_neighbor=2, max_num_vertices=5)
    assert (len(out) == 3)
    check_uniform(out, num_hops=2, max_num_vertices=5)
    num_nodes = out[0][-1].asnumpy()
    assert num_nodes > 0
    assert num_nodes < len(out[0])
    check_compact(out[1], out[0], num_nodes)

    seed = mx.nd.array([0,4], dtype=np.int64)
    out = mx.nd.contrib.dgl_csr_neighbor_uniform_sample(a, seed, num_args=2, num_hops=1, num_neighbor=2, max_num_vertices=5)
    assert (len(out) == 3)
    check_uniform(out, num_hops=1, max_num_vertices=5)
    num_nodes = out[0][-1].asnumpy()
    assert num_nodes > 0
    assert num_nodes < len(out[0])
    check_compact(out[1], out[0], num_nodes)

def test_non_uniform_sample():
    shape = (5, 5)
    prob = mx.nd.array([0.9, 0.8, 0.2, 0.4, 0.1], dtype=np.float32)
    data_np = np.array([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20], dtype=np.int64)
    indices_np = np.array([1,2,3,4,0,2,3,4,0,1,3,4,0,1,2,4,0,1,2,3], dtype=np.int64)
    indptr_np = np.array([0,4,8,12,16,20], dtype=np.int64)
    a = mx.nd.sparse.csr_matrix((data_np, indices_np, indptr_np), shape=shape)

    seed = mx.nd.array([0,1,2,3,4], dtype=np.int64)
    out = mx.nd.contrib.dgl_csr_neighbor_non_uniform_sample(a, prob, seed, num_args=3, num_hops=1, num_neighbor=2, max_num_vertices=5)
    assert (len(out) == 4)
    check_non_uniform(out, num_hops=1, max_num_vertices=5)

    seed = mx.nd.array([0], dtype=np.int64)
    out = mx.nd.contrib.dgl_csr_neighbor_non_uniform_sample(a, prob, seed, num_args=3, num_hops=1, num_neighbor=1, max_num_vertices=4)
    assert (len(out) == 4)
    check_non_uniform(out, num_hops=1, max_num_vertices=4)

    seed = mx.nd.array([0], dtype=np.int64)
    out = mx.nd.contrib.dgl_csr_neighbor_non_uniform_sample(a, prob, seed, num_args=3, num_hops=2, num_neighbor=1, max_num_vertices=4)
    assert (len(out) == 4)
    check_non_uniform(out, num_hops=2, max_num_vertices=4)

    seed = mx.nd.array([0,2,4], dtype=np.int64)
    out = mx.nd.contrib.dgl_csr_neighbor_non_uniform_sample(a, prob, seed, num_args=3, num_hops=1, num_neighbor=2, max_num_vertices=5)
    assert (len(out) == 4)
    check_non_uniform(out, num_hops=1, max_num_vertices=5)

    seed = mx.nd.array([0,4], dtype=np.int64)
    out = mx.nd.contrib.dgl_csr_neighbor_non_uniform_sample(a, prob, seed, num_args=3, num_hops=1, num_neighbor=2, max_num_vertices=5)
    assert (len(out) == 4)
    check_non_uniform(out, num_hops=1, max_num_vertices=5)

    seed = mx.nd.array([0,4], dtype=np.int64)
    out = mx.nd.contrib.dgl_csr_neighbor_non_uniform_sample(a, prob, seed, num_args=3, num_hops=2, num_neighbor=2, max_num_vertices=5)
    assert (len(out) == 4)
    check_non_uniform(out, num_hops=2, max_num_vertices=5)

    seed = mx.nd.array([0,4], dtype=np.int64)
    out = mx.nd.contrib.dgl_csr_neighbor_non_uniform_sample(a, prob, seed, num_args=3, num_hops=1, num_neighbor=2, max_num_vertices=5)
    assert (len(out) == 4)
    check_non_uniform(out, num_hops=1, max_num_vertices=5)

def test_edge_id():
    shape = rand_shape_2d()
    data = rand_ndarray(shape, stype='csr', density=0.4)
    ground_truth = np.zeros(shape, dtype=np.float32)
    ground_truth -= 1.0
    indptr_np = data.indptr.asnumpy()
    data_np = data.data.asnumpy()
    indices_np = data.indices.asnumpy()
    for i in range(shape[0]):
        for j in range(indptr_np[i], indptr_np[i+1]):
            idx = indices_np[j]
            ground_truth[i, idx] = data_np[j]

    np_u = np.random.randint(0, shape[0], size=(5, ))
    np_v = np.random.randint(0, shape[1], size=(5, ))
    mx_u = mx.nd.array(np_u)
    mx_v = mx.nd.array(np_v)
    assert_almost_equal(mx.nd.contrib.edge_id(data, mx_u, mx_v).asnumpy(),
                        ground_truth[np_u, np_v], rtol=1e-5, atol=1e-6)

def generate_graph(n):
    arr = sp.sparse.random(n, n, density=0.2, format='coo')
    arr.data = np.arange(0, len(arr.row), dtype=np.float32)
    return arr.tocsr(), mx.nd.sparse.csr_matrix(arr.tocsr()).astype(np.int64)

def test_subgraph():
    sp_g, g = generate_graph(100)
    vertices = np.unique(np.random.randint(0, 100, size=(20)))
    subgs = mx.nd.contrib.dgl_subgraph(g, mx.nd.array(vertices, dtype=np.int64),
                                       return_mapping=True)
    subgs[0].check_format()
    subgs[1].check_format()
    assert_array_equal(subgs[0].indptr, subgs[1].indptr)
    assert_array_equal(subgs[0].indices, subgs[1].indices)
    sp_subg = subgs[1].asscipy()
    for i in range(len(subgs[0].indptr) - 1):
        subv1 = i
        v1 = vertices[subv1]
        row_start = int(subgs[0].indptr[subv1].asnumpy()[0])
        row_end = int(subgs[0].indptr[subv1 + 1].asnumpy()[0])
        if row_start >= len(subgs[0].indices):
            remain = subgs[0].indptr[subv1:].asnumpy()
            assert np.sum(remain == row_start) == len(remain)
            break
        row = subgs[0].indices[row_start:row_end]
        for _, subv2 in enumerate(row.asnumpy()):
            v2 = vertices[subv2]
            assert sp_g[v1, v2] == sp_subg[subv1, subv2]

def test_adjacency():
    sp_g, g = generate_graph(100)
    adj = mx.nd.contrib.dgl_adjacency(g)
    assert adj.dtype == np.float32
    assert adj.shape == g.shape
    assert_array_equal(adj.indptr, g.indptr)
    assert_array_equal(adj.indices, g.indices)
    assert_array_equal(adj.data, mx.nd.ones(shape=g.indices.shape))

