/*
   igraph library.
   Copyright (C) 2006-2012  Gabor Csardi <csardi.gabor@gmail.com>
   334 Harvard st, Cambridge MA, 02139 USA

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc.,  51 Franklin Street, Fifth Floor, Boston, MA
   02110-1301 USA

*/

#include <igraph.h>

#include "test_utilities.h"

typedef struct {
    int dim;
    int m;
    int nei;
    igraph_bool_t directed, mutual, periodic;
    igraph_int_t *dimedges;
} lat_test_t;

#define LAT_TEST(id, d, m, ne, di, mu, ci, ...) \
    igraph_int_t lat_ ## id ## _edges[] = { __VA_ARGS__ } ; \
    lat_test_t lat_ ## id = { d, m, ne, di, mu, ci, lat_ ## id ## _edges }

/*----------------d--m--ne-di-mu-ci-dimedges------------------------*/
LAT_TEST(u_0,     0, 0, 1, 0, 0, 0,  -1 );
LAT_TEST(u_01,    1, 0, 1, 0, 0, 0,  0 );
LAT_TEST(u_02,    2, 0, 1, 0, 0, 0,  0, 1 );
LAT_TEST(u_03,    2, 0, 1, 0, 0, 0,  1, 0 );

LAT_TEST(d_0,     0, 0, 1, 1, 0, 0,  -1 );
LAT_TEST(d_01,    1, 0, 1, 1, 0, 0,  0 );
LAT_TEST(d_02,    2, 0, 1, 1, 0, 0,  0, 1 );
LAT_TEST(d_03,    2, 0, 1, 1, 0, 0,  1, 0 );

LAT_TEST(u_1,     1, 0, 1, 0, 0, 0,  1 );
LAT_TEST(u_1x1,   2, 0, 1, 0, 0, 0,  1, 1 );
LAT_TEST(u_2,     1, 1, 1, 0, 0, 0,  2, 0, 1 );
LAT_TEST(u_2x1,   2, 1, 1, 0, 0, 0,  2, 1, 0, 1 );
LAT_TEST(u_2x2,   2, 4, 1, 0, 0, 0,  2, 2, 0, 1, 0, 2, 1, 3, 2, 3 );

LAT_TEST(uc_1,    1, 0, 1, 0, 0, 1,  1 );
LAT_TEST(uc_1x1,  2, 0, 1, 0, 0, 1,  1, 1 );
LAT_TEST(uc_2,    1, 1, 1, 0, 0, 1,  2, 0, 1 );
LAT_TEST(uc_2x1,  2, 1, 1, 0, 0, 1,  2, 1, 0, 1 );
LAT_TEST(uc_2x2,  2, 4, 1, 0, 0, 1,  2, 2, 0, 1, 0, 2, 1, 3, 2, 3 );

LAT_TEST(dc_1,    1, 0, 1, 1, 0, 1,  1 );
LAT_TEST(dc_1x1,  2, 0, 1, 1, 0, 1,  1, 1 );
LAT_TEST(dc_2,    1, 2, 1, 1, 0, 1,  2, 0, 1, 1, 0 );
LAT_TEST(dc_2x1,  2, 2, 1, 1, 0, 1,  2, 1, 0, 1, 1, 0 );
LAT_TEST(dc_2x2,  2, 8, 1, 1, 0, 1,  2, 2, 0, 1, 0, 2, 1, 3, 2, 3,
         1, 0, 2, 0, 3, 1, 3, 2, );

LAT_TEST(d_1,     1, 0, 1, 1, 0, 0,  1 );
LAT_TEST(d_1x1,   2, 0, 1, 1, 0, 0,  1, 1 );
LAT_TEST(d_2,     1, 1, 1, 1, 0, 0,  2, 0, 1 );
LAT_TEST(d_2x1,   2, 1, 1, 1, 0, 0,  2, 1, 0, 1 );
LAT_TEST(d_2x2,   2, 4, 1, 1, 0, 0,  2, 2, 0, 1, 0, 2, 1, 3, 2, 3 );

LAT_TEST(dmc_1,   1, 0, 1, 1, 0, 1,  1 );
LAT_TEST(dmc_1x1, 2, 0, 1, 1, 0, 1,  1, 1 );
LAT_TEST(dmc_2,   1, 2, 1, 1, 0, 1,  2, 0, 1, 1, 0 );
LAT_TEST(dmc_2x1, 2, 2, 1, 1, 0, 1,  2, 1, 0, 1, 1, 0 );
LAT_TEST(dmc_2x2, 2, 4, 1, 1, 0, 1,  2, 2, 0, 1, 0, 2, 1, 3, 2, 3,
         1, 0, 3, 2, );
/*----------------d--m--ne-di-mu-ci-dimedges------------------------*/

/* TODO: add more */

lat_test_t *all_checks[] = { /*  1 */ &lat_u_0,   /*  2 */ &lat_u_01,
                                      /*  3 */ &lat_u_02,  /*  4 */ &lat_u_03,
                                      /*  5 */ &lat_d_0,   /*  6 */ &lat_d_01,
                                      /*  7 */ &lat_d_02,  /*  8 */ &lat_d_03,
                                      /*  9 */ &lat_u_1,   /* 10 */ &lat_u_1x1,
                                      /* 11 */ &lat_u_2,   /* 12 */ &lat_u_2x1,
                                      /* 13 */ &lat_u_2x2, /* 14 */ &lat_u_1,
                                      /* 15 */ &lat_u_1x1, /* 16 */ &lat_u_2,
                                      /* 17 */ &lat_u_2x1, /* 18 */ &lat_uc_2x2,
                                      /* 19 */ &lat_dc_1,  /* 20 */ &lat_dc_1x1,
                                      /* 21 */ &lat_dc_2,  /* 22 */ &lat_dc_2x1,
                                      /* 23 */ &lat_dc_2x2,/* 24 */ &lat_d_1,
                                      /* 25 */ &lat_d_1x1, /* 26 */ &lat_d_2,
                                      /* 27 */ &lat_d_2x1, /* 28 */ &lat_d_2x2,
                                      /* 29 */ &lat_dc_2x2,/* 30 */ &lat_d_1,
                                      /* 31 */ &lat_d_1x1, /* 32 */ &lat_d_2,
                                      /* 33 */ &lat_d_2x1, /* 34 */ &lat_d_2x2,
                                      0
                           };

int check_lattice_properties(const igraph_t *lattice) {
    igraph_bool_t res;

    /* Connected */
    igraph_is_connected(lattice, &res, IGRAPH_WEAK);
    if (!res && igraph_vcount(lattice) > 0) {
        printf("Not connected\n");
        return 1;
    }

    /* Simple */
    igraph_is_simple(lattice, &res, IGRAPH_DIRECTED);
    if (!res) {
        printf("Not simple\n");
        return 2;
    }

    return 0;
}

int check_lattice(const lat_test_t *test) {
    igraph_t graph, othergraph;
    igraph_vector_int_t otheredges;
    igraph_vector_int_t dimvector;
    igraph_vector_bool_t periodic;
    igraph_bool_t iso;
    int ret;

    /* Create lattice */
    dimvector = igraph_vector_int_view(test->dimedges, test->dim);
    igraph_vector_bool_init(&periodic, test->dim);
    igraph_vector_bool_fill(&periodic, test->periodic);
    igraph_square_lattice(
        &graph, &dimvector, test->nei, test->directed, test->mutual, &periodic
    );
    igraph_vector_bool_destroy(&periodic);

    /* Check its properties */
    if ((ret = check_lattice_properties(&graph))) {
        igraph_destroy(&graph);
        printf("Lattice properties are not satisfied\n");
        return ret;
    }

    /* Check that it is isomorphic to the stored graph */
    otheredges = igraph_vector_int_view(test->dimedges + test->dim, test->m * 2);
    igraph_create(&othergraph, &otheredges, igraph_vector_int_prod(&dimvector),
                  test->directed);
    igraph_isomorphic(&graph, &othergraph, &iso);
    if (!iso) {
        printf("--\n");
        print_graph(&graph);
        printf("--\n");
        print_graph(&othergraph);
        igraph_destroy(&graph);
        igraph_destroy(&othergraph);
        return 50;
    }

    igraph_destroy(&graph);
    igraph_destroy(&othergraph);
    return 0;
}

int main(void) {
    int i, ret;

    i = 0;
    while (all_checks[i]) {
        if ((ret = check_lattice(all_checks[i]))) {
            printf("Check no #%d failed.\n", (int) (i + 1));
            return ret;
        }
        i++;
    }

    VERIFY_FINALLY_STACK();

    /* Compare to the hypercube graph. */
    {
        igraph_t g1, g2;
        igraph_vector_int_t dims;
        igraph_bool_t iso;

        /* Q_1 is the singleton graph */

        igraph_hypercube(&g1, 0, IGRAPH_UNDIRECTED);
        IGRAPH_ASSERT(igraph_vcount(&g1) == 1);
        IGRAPH_ASSERT(igraph_ecount(&g1) == 0);
        IGRAPH_ASSERT(!igraph_is_directed(&g1));
        igraph_destroy(&g1);

        /* Q_4 undirected */

        igraph_vector_int_init(&dims, 4);
        igraph_vector_int_fill(&dims, 2);

        igraph_square_lattice(&g2, &dims, 1, IGRAPH_UNDIRECTED, false, NULL);
        igraph_hypercube(&g1, 4, IGRAPH_UNDIRECTED);
        igraph_isomorphic(&g1, &g2, &iso);
        IGRAPH_ASSERT(iso);
        igraph_destroy(&g1);
        igraph_destroy(&g2);

        igraph_vector_int_destroy(&dims);

        /* Q_5 directed */

        igraph_vector_int_init(&dims, 5);
        igraph_vector_int_fill(&dims, 2);

        igraph_square_lattice(&g2, &dims, 1, IGRAPH_DIRECTED, false, NULL);
        igraph_hypercube(&g1, 5, IGRAPH_DIRECTED);
        igraph_isomorphic(&g1, &g2, &iso);
        IGRAPH_ASSERT(iso);
        igraph_destroy(&g1);
        igraph_destroy(&g2);

        igraph_vector_int_destroy(&dims);

        CHECK_ERROR(igraph_hypercube(&g1, 128, false), IGRAPH_EINVAL);
    }

    VERIFY_FINALLY_STACK();

    return 0;
}
