
/*
 * This file contains functions that are useful when writing tests.
 * Add #include "test_utilities.h" to the test program to use them.
 */

#include "test_utilities.h"
#include <stdio.h>
#include <string.h>

/* Print an igraph_real_t value. Be consistent in printing NaN/Inf across platforms. */
void print_real(FILE *f, igraph_real_t x, const char *format) {
    igraph_bool_t g8 = !strcmp(format, "%8g");
    if (isfinite(x)) {
        if (x == 0 && signbit(x)) {
            /* print negative zeros as positive zeros for sake of consistency */
            x = +0.0;
        }
        fprintf(f, format, x);
    } else if (isnan(x)) {
        fprintf(f, g8 ? "     NaN" : "NaN");
    } else if (isinf(x) && x > 0) {
        fprintf(f, g8 ? "     Inf" : "Inf");
    } else if (isinf(x) && x < 0) {
        fprintf(f, g8 ? "    -Inf" : "-Inf");
    }
}

void print_vector_format(const igraph_vector_t *v, FILE *f, const char *format) {
    igraph_integer_t i, n = igraph_vector_size(v);
    fprintf(f, "(");
    for (i=0; i < n; i++) {
        fprintf(f, " ");
        print_real(f, VECTOR(*v)[i], format);
    }
    fprintf(f, " )\n");
}

/* Print elements of a vector. Use parentheses to make it clear when a vector has size zero. */
void print_vector(const igraph_vector_t *v) {
    print_vector_format(v, stdout, "%g");
}

/* Round elements of a vector to integers and print them. */
/* This is meant to be used when the elements of a vector are integer values. */
void print_vector_round(const igraph_vector_t *v) {
    print_vector_format(v, stdout, "%.f");
}


/* Print elements of an integer vector */
void print_vector_int(const igraph_vector_int_t *v) {
    igraph_integer_t i, n = igraph_vector_int_size(v);
    printf("(");
    for (i=0; i < n; i++) {
        printf(" %" IGRAPH_PRId, VECTOR(*v)[i]);
    }
    printf(" )\n");
}

/* Print all vectors in an integer vector list. Use brackets around each vector
 * and also use brackets around the entire list to make it clear when the list
 * is empty */
void print_vector_int_list(const igraph_vector_int_list_t *v) {
    igraph_integer_t i, n = igraph_vector_int_list_size(v);
    printf("{\n");
    for (i = 0; i < n; ++i) {
        printf("  %" IGRAPH_PRId ": ", i);
        print_vector_int(igraph_vector_int_list_get_ptr(v, i));
    }
    printf("}\n");
}

/* Print elements of a matrix. Use brackets to make it clear when a vector has size zero. */
void print_matrix_format(const igraph_matrix_t *m, FILE *f, const char *format) {
    igraph_integer_t i, j, nrow = igraph_matrix_nrow(m), ncol = igraph_matrix_ncol(m);
    if (nrow == 0 || ncol == 0) {
        /* When the matrix is empty, output the dimensions */
        fprintf(f, "[ %" IGRAPH_PRId "-by-%" IGRAPH_PRId" ]\n", nrow, ncol);
        return;
    }
    for (i = 0; i < nrow; i++) {
        fprintf(f, i == 0 ? "[" : " ");
        for (j = 0; j < ncol; j++) {
            fprintf(f, " ");
            print_real(f, MATRIX(*m, i, j), format);
        }
        fprintf(f, i == nrow-1 ? " ]\n" : "\n");
    }
}

void print_matrix(const igraph_matrix_t *m) {
    print_matrix_format(m, stdout, "%8g");
}

void print_matrix_int(const igraph_matrix_int_t *m) {
    igraph_integer_t i, j, nrow = igraph_matrix_int_nrow(m), ncol = igraph_matrix_int_ncol(m);
    if (nrow == 0 || ncol == 0) {
        /* When the matrix is empty, output the dimensions */
        printf("[ %" IGRAPH_PRId "-by-%" IGRAPH_PRId" ]\n", nrow, ncol);
        return;
    }
    for (i = 0; i < nrow; i++) {
        printf(i == 0 ? "[" : " ");
        for (j = 0; j < ncol; j++) {
            printf(" ");
            printf("%8" IGRAPH_PRId, MATRIX(*m, i, j));
        }
        printf(i == nrow-1 ? " ]\n" : "\n");
    }
}

/* Round elements of a matrix to integers and print them. */
/* This is meant to be used when the elements of a matrix are integer values. */
void print_matrix_round(const igraph_matrix_t *m) {
    print_matrix_format(m, stdout, "%4.f");
}

void print_matrix_complex_round(const igraph_matrix_complex_t *m) {

    igraph_integer_t nr = igraph_matrix_complex_nrow(m);
    igraph_integer_t nc = igraph_matrix_complex_ncol(m);
    igraph_integer_t i, j;
    for (i = 0; i < nr; i++) {
        for (j = 0; j < nc; j++) {
            igraph_complex_t z = MATRIX(*m, i, j);
            if (j != 0) {
                putchar(' ');
            }
            printf("%.f%+.fi", IGRAPH_REAL(z), IGRAPH_IMAG(z));
        }
        printf("\n");
    }
}

/* Print an adjacency list. Use brackets around each vector and also use
 * brackets around the entire adjacency list to make it clear when the list
 * is empty.
 */
void print_adjlist(const igraph_adjlist_t *adjlist) {
    igraph_integer_t vcount = igraph_adjlist_size(adjlist);
    igraph_integer_t i;

    printf("{\n");
    for (i = 0; i < vcount; ++i) {
        printf("  %" IGRAPH_PRId ": ", i);
        print_vector_int(igraph_adjlist_get(adjlist, i));
    }
    printf("}\n");
}

/* Print a graph. Use brackets to make it obvious when the edge list is empty. */
void print_graph(const igraph_t *graph) {
    print_weighted_graph(graph, NULL);
}

/* Print a graph with edge weights from a vector. Use brackets to make it
 * obvious when the edge list is empty. */
void print_weighted_graph(const igraph_t *graph, const igraph_vector_t* weights) {
    igraph_integer_t ecount = igraph_ecount(graph);
    igraph_integer_t vcount = igraph_vcount(graph);
    igraph_integer_t i;

    printf("directed: %s\n", igraph_is_directed(graph) ? "true" : "false");
    printf("vcount: %" IGRAPH_PRId "\n", vcount);
    printf("edges: {\n");
    for (i=0; i < ecount; ++i) {
        if (weights) {
            printf(
                "%" IGRAPH_PRId " %" IGRAPH_PRId ": %g\n",
                IGRAPH_FROM(graph, i), IGRAPH_TO(graph, i),
                VECTOR(*weights)[i]
            );
        } else {
            printf(
                "%" IGRAPH_PRId " %" IGRAPH_PRId "\n",
                IGRAPH_FROM(graph, i), IGRAPH_TO(graph, i)
            );
        }
    }
    printf("}\n");
}

/* Print a graph with edge weights from an edge attribute. Use brackets to make
 * it obvious when the edge list is empty. */
void print_weighted_graph_attr(const igraph_t *graph, const char* attr) {
    igraph_integer_t ecount = igraph_ecount(graph);
    igraph_integer_t vcount = igraph_vcount(graph);
    igraph_integer_t i;

    printf("directed: %s\n", igraph_is_directed(graph) ? "true" : "false");
    printf("vcount: %" IGRAPH_PRId "\n", vcount);
    printf("edges: {\n");
    for (i=0; i < ecount; ++i)
        printf
            ("%" IGRAPH_PRId " %" IGRAPH_PRId ": %g\n",
            IGRAPH_FROM(graph, i), IGRAPH_TO(graph, i),
            EAN(graph, attr, i)
        );
    printf("}\n");
}

/* Print an incidence list. Use brackets around each vector and also use
 * brackets around the entire incidence list to make it clear when the list
 * is empty.
 */
void print_inclist(const igraph_inclist_t *inclist) {
    igraph_integer_t vcount = igraph_inclist_size(inclist);
    igraph_integer_t i;

    printf("{\n");
    for (i = 0; i < vcount; ++i) {
        printf("  %" IGRAPH_PRId ": ", i);
        print_vector_int(igraph_inclist_get(inclist, i));
    }
    printf("}\n");
}

/* Print a lazy adjacency list. Use brackets around each vector and also use
 * brackets around the entire lazy adjacency list to make it clear when the list
 * is empty.
 */
void print_lazy_adjlist(igraph_lazy_adjlist_t *adjlist) {
    igraph_integer_t vcount = igraph_lazy_adjlist_size(adjlist);
    igraph_integer_t i;

    printf("{\n");
    for (i = 0; i < vcount; ++i) {
        printf("  %" IGRAPH_PRId ": ", i);
        print_vector_int(igraph_lazy_adjlist_get(adjlist, i));
    }
    printf("}\n");
}

/* Print a lazy incidence list. Use brackets around each vector and also use
 * brackets around the entire incidence list to make it clear when the list
 * is empty.
 */
void print_lazy_inclist(igraph_lazy_inclist_t *inclist) {
    igraph_integer_t vcount = igraph_lazy_inclist_size(inclist);
    igraph_integer_t i;

    printf("{\n");
    for (i = 0; i < vcount; ++i) {
        printf("  %" IGRAPH_PRId ": ", i);
        print_vector_int(igraph_lazy_inclist_get(inclist, i));
    }
    printf("}\n");
}

/* Edge comparison function used for sorting in print_graph_canon(). */
int edge_compare(const void *e1, const void *e2) {
    const igraph_integer_t *edge1 = (const igraph_integer_t *) e1;
    const igraph_integer_t *edge2 = (const igraph_integer_t *) e2;
    if (edge1[0] < edge2[0]) {
        return -1;
    } else if (edge1[0] > edge2[0]) {
        return 1;
    } else if (edge1[1] < edge2[1]) {
        return -1;
    } else if (edge1[1] > edge2[1]) {
        return 1;
    } else {
        return 0;
    }
}

/* Print a graph using a sorted edge list. Other than sorting (i.e. canonicalizing) the edge list,
 * this function is identical to print_graph(). */
void print_graph_canon(const igraph_t *graph) {
    igraph_integer_t ecount = igraph_ecount(graph);
    igraph_integer_t vcount = igraph_vcount(graph);
    igraph_integer_t i;
    igraph_vector_int_t edges;

    printf("directed: %s\n", igraph_is_directed(graph) ? "true" : "false");
    printf("vcount: %" IGRAPH_PRId "\n", vcount);
    printf("edges: {\n");

    igraph_vector_int_init(&edges, 0);
    igraph_get_edgelist(graph, &edges, false);

    /* If the graph is undirected, we make sure that the first vertex of undirected edges
     * is always the one with the lower ID. */
    if (! igraph_is_directed(graph)) {
        for (i=0; i < ecount; ++i) {
            if (VECTOR(edges)[2*i] > VECTOR(edges)[2*i+1]) {
                igraph_integer_t tmp = VECTOR(edges)[2*i];
                VECTOR(edges)[2*i] = VECTOR(edges)[2*i+1];
                VECTOR(edges)[2*i+1] = tmp;
            }
        }
    }

    /* Sort the edge list */
    igraph_qsort(&VECTOR(edges)[0], ecount, 2*sizeof(igraph_integer_t), &edge_compare);

    for (i=0; i < ecount; ++i) {
        printf("%" IGRAPH_PRId " %" IGRAPH_PRId "\n", VECTOR(edges)[2*i], VECTOR(edges)[2*i+1]);
    }

    igraph_vector_int_destroy(&edges);

    printf("}\n");
}

/* Print a vector, ensuring that the first nonzero element is positive. */
void print_vector_first_nonzero_element_positive(const igraph_vector_t *vector, const char* format) {
    igraph_vector_t copy;
    igraph_integer_t i, n;

    igraph_vector_init_copy(&copy, vector);

    n = igraph_vector_size(&copy);

    for (i = 0; i < n; i++) {
        if (VECTOR(copy)[i] < 0) {
            for (; i < n; i++) {
                if (VECTOR(copy)[i] != 0) {
                    VECTOR(copy)[i] *= -1;
                }
            }
            break;
        } else if (VECTOR(copy)[i] > 0) {
            break;
        }
    }

    igraph_vector_printf(&copy, format);
    igraph_vector_destroy(&copy);
}

/* Print a complex vector, ensuring that the first element with nonzero real
 * part has a positive real part. */
void print_vector_complex_first_nonzero_real_part_positive(const igraph_vector_complex_t *vector) {
    igraph_vector_complex_t copy;
    igraph_integer_t i, n;

    igraph_vector_complex_init_copy(&copy, vector);

    n = igraph_vector_complex_size(&copy);

    for (i = 0; i < n; i++) {
        if (IGRAPH_REAL(VECTOR(copy)[i]) < 0) {
            for (; i < n; i++) {
                if (IGRAPH_REAL(VECTOR(copy)[i]) != 0) {
                    IGRAPH_REAL(VECTOR(copy)[i]) *= -1;
                }
                if (IGRAPH_IMAG(VECTOR(copy)[i]) != 0) {
                    IGRAPH_IMAG(VECTOR(copy)[i]) *= -1;
                }
            }
            break;
        } else if (IGRAPH_REAL(VECTOR(copy)[i]) > 0) {
            break;
        }
    }

    igraph_vector_complex_print(&copy);
    igraph_vector_complex_destroy(&copy);
}

/* Print a matrix, ensuring that the first nonzero element in each column is
 * positive. */
void print_matrix_first_row_positive(const igraph_matrix_t *matrix, const char* format) {
    igraph_matrix_t copy;
    igraph_integer_t i, j, nrow, ncol;

    igraph_matrix_init_copy(&copy, matrix);

    nrow = igraph_matrix_nrow(&copy);
    ncol = igraph_matrix_ncol(&copy);

    for (i = 0; i < ncol; i++) {
        for (j = 0; j < nrow; j++) {
            if (MATRIX(copy, j, i) < 0) {
                for (; j < nrow; j++) {
                    if (MATRIX(copy, j, i) != 0) {
                        MATRIX(copy, j, i) *= -1;
                    }
                }
                break;
            } else if (MATRIX(copy, j, i) > 0) {
                break;
            }
        }
    }

    igraph_matrix_printf(&copy, format);
    igraph_matrix_destroy(&copy);
}

/* Print a complex matrix, ensuring that the first element with nonzero real
 * part in each column has a positive real part. */
void print_matrix_complex_first_row_positive(const igraph_matrix_complex_t *matrix) {
    igraph_matrix_complex_t copy;
    igraph_integer_t i, j, nrow, ncol;
    igraph_complex_t z;
    char buf[256];
    size_t len;

    igraph_matrix_complex_init_copy(&copy, matrix);

    nrow = igraph_matrix_complex_nrow(&copy);
    ncol = igraph_matrix_complex_ncol(&copy);

    for (i = 0; i < ncol; i++) {
        for (j = 0; j < nrow; j++) {
            if (IGRAPH_REAL(MATRIX(copy, j, i)) < 0) {
                for (; j < nrow; j++) {
                    if (IGRAPH_REAL(MATRIX(copy, j, i)) != 0) {
                        IGRAPH_REAL(MATRIX(copy, j, i)) *= -1;
                    }
                    if (IGRAPH_IMAG(MATRIX(copy, j, i)) != 0) {
                        IGRAPH_IMAG(MATRIX(copy, j, i)) *= -1;
                    }
                }
                break;
            } else if (IGRAPH_REAL(MATRIX(copy, j, i)) > 0) {
                break;
            }
        }
    }

    for (i = 0; i < nrow; i++) {
        for (j = 0; j < ncol; j++) {
            z = MATRIX(copy, i, j);
            if (j != 0) {
                putchar(' ');
            }

            snprintf(buf, sizeof(buf), "%g%+gi", IGRAPH_REAL(z), IGRAPH_IMAG(z));
            len = strlen(buf);

            /* ensure that we don't print -0 in the imaginary part */
            if (len > 3 && buf[len-3] == '-' && buf[len-2] == '0' && buf[len-1] == 'i') {
              buf[len-3] = '+';
            }

            /* ensure that we don't print -0 in the real part either */
            if (buf[0] == '-' && buf[1] == '0' && (buf[2] == '+' || buf[2] == '-')) {
                printf("%s", buf + 1);
            } else {
                printf("%s", buf);
            }
        }
        printf("\n");
    }

    igraph_matrix_complex_destroy(&copy);
}

void matrix_init_int_row_major(igraph_matrix_t *mat, igraph_integer_t nrow, igraph_integer_t ncol, const int *elem) {
    igraph_integer_t c, r;
    size_t i_elem = 0;
    igraph_matrix_init(mat, nrow, ncol);
    for (r = 0; r < nrow; r++) {
        for (c = 0; c < ncol; c++) {
            MATRIX(*mat, r, c) = elem[i_elem];
            i_elem++;
        }
    }
}

void matrix_int_init_int_row_major(igraph_matrix_int_t *mat, igraph_integer_t nrow, igraph_integer_t ncol, const int *elem) {
    igraph_integer_t c, r;
    size_t i_elem = 0;
    igraph_matrix_int_init(mat, nrow, ncol);
    for (r = 0; r < nrow; r++) {
        for (c = 0; c < ncol; c++) {
            MATRIX(*mat, r, c) = elem[i_elem];
            i_elem++;
        }
    }
}

void matrix_init_real_row_major(igraph_matrix_t *mat, igraph_integer_t nrow, igraph_integer_t ncol, const igraph_real_t *elem) {
    igraph_integer_t c, r;
    size_t i_elem = 0;
    igraph_matrix_init(mat, nrow, ncol);
    for (r = 0; r < nrow; r++) {
        for (c = 0; c < ncol; c++) {
            MATRIX(*mat, r, c) = elem[i_elem];
            i_elem++;
        }
    }
}

void matrix_chop(igraph_matrix_t *mat, igraph_real_t cutoff) {
    igraph_integer_t nelems = igraph_matrix_nrow(mat) * igraph_matrix_ncol(mat);
    for (igraph_integer_t i = 0; i < nelems; i++) {
        if (fabs(VECTOR(mat->data)[i]) < cutoff) {
            VECTOR(mat->data)[i] = 0;
        }
    }
}

void vector_chop(igraph_vector_t *vec, igraph_real_t cutoff) {
    igraph_integer_t nelems = igraph_vector_size(vec);
    for (igraph_integer_t i = 0; i < nelems; i++) {
        if (fabs(VECTOR(*vec)[i]) < cutoff) {
            VECTOR(*vec)[i] = 0;
        }
    }
}

/* print all graph, edge and vertex attributes of a graph */
void print_attributes(const igraph_t *g) {
    igraph_vector_int_t gtypes, vtypes, etypes;
    igraph_strvector_t gnames, vnames, enames;
    igraph_integer_t i;

    igraph_integer_t j;

    igraph_vector_int_init(&gtypes, 0);
    igraph_vector_int_init(&vtypes, 0);
    igraph_vector_int_init(&etypes, 0);
    igraph_strvector_init(&gnames, 0);
    igraph_strvector_init(&vnames, 0);
    igraph_strvector_init(&enames, 0);

    igraph_cattribute_list(g, &gnames, &gtypes, &vnames, &vtypes,
                           &enames, &etypes);

    /* Graph attributes */
    for (i = 0; i < igraph_strvector_size(&gnames); i++) {
        if (i != 0)
            putchar(' ');
        printf("%s=", igraph_strvector_get(&gnames, i));
        if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) {
            igraph_real_printf(GAN(g, igraph_strvector_get(&gnames, i)));
        } else if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_BOOLEAN) {
            printf("%d", GAB(g, igraph_strvector_get(&gnames, i)));
        } else {
            printf("\"%s\"", GAS(g, igraph_strvector_get(&gnames, i)));
        }
    }
    if (igraph_strvector_size(&gnames))
        printf("\n");

    for (i = 0; i < igraph_vcount(g); i++) {
        printf("Vertex %" IGRAPH_PRId ":", i);
        for (j = 0; j < igraph_strvector_size(&vnames); j++) {
            putchar(' ');
            printf("%s=", igraph_strvector_get(&vnames, j));
            if (VECTOR(vtypes)[j] == IGRAPH_ATTRIBUTE_NUMERIC) {
                igraph_real_printf(VAN(g, igraph_strvector_get(&vnames, j), i));
            } else if (VECTOR(vtypes)[j] == IGRAPH_ATTRIBUTE_BOOLEAN) {
                printf("%d", VAB(g, igraph_strvector_get(&vnames, j), i));
            } else {
                printf("\"%s\"", VAS(g, igraph_strvector_get(&vnames, j), i));
            }
        }
        printf("\n");
    }

    for (i = 0; i < igraph_ecount(g); i++) {
        printf("Edge %" IGRAPH_PRId " (%" IGRAPH_PRId "-%" IGRAPH_PRId "):", i, IGRAPH_FROM(g, i), IGRAPH_TO(g, i));
        for (j = 0; j < igraph_strvector_size(&enames); j++) {
            putchar(' ');
            printf("%s=", igraph_strvector_get(&enames, j));
            if (VECTOR(etypes)[j] == IGRAPH_ATTRIBUTE_NUMERIC) {
                igraph_real_printf(EAN(g, igraph_strvector_get(&enames, j), i));
            } else if (VECTOR(etypes)[j] == IGRAPH_ATTRIBUTE_BOOLEAN) {
                printf("%d", EAB(g, igraph_strvector_get(&enames, j), i));
            } else {
                printf("\"%s\"", EAS(g, igraph_strvector_get(&enames, j), i));
            }
        }
        printf("\n");
    }
    printf("\n");

    igraph_strvector_destroy(&enames);
    igraph_strvector_destroy(&vnames);
    igraph_strvector_destroy(&gnames);
    igraph_vector_int_destroy(&etypes);
    igraph_vector_int_destroy(&vtypes);
    igraph_vector_int_destroy(&gtypes);
}

expect_warning_context_t expect_warning_ctx;

void record_last_warning(const char *reason, const char *file, int line) {
    IGRAPH_UNUSED(file); IGRAPH_UNUSED(line);

    if (expect_warning_ctx.observed) {
        igraph_free(expect_warning_ctx.observed);
    }

    expect_warning_ctx.observed = strdup(reason);
}
