/* -*- mode: C -*-  */
/*
   IGraph library.
   Copyright (C) 2005-2012  Gabor Csardi <csardi.gabor@gmail.com>
   334 Harvard street, 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 "config.h"
#include "igraph_error.h"
#include "igraph_types.h"

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdarg.h>

static IGRAPH_THREAD_LOCAL igraph_error_handler_t *igraph_i_error_handler = 0;
static IGRAPH_THREAD_LOCAL char igraph_i_errormsg_buffer[500];
static IGRAPH_THREAD_LOCAL char igraph_i_warningmsg_buffer[500];

/* Error strings corresponding to each igraph_error_type_t enum value. */
static const char *igraph_i_error_strings[] = {
    /*  0 */ "No error",
    /*  1 */ "Failed",
    /*  2 */ "Out of memory",
    /*  3 */ "Parse error",
    /*  4 */ "Invalid value",
    /*  5 */ "Already exists",
    /*  6 */ "Invalid edge vector",
    /*  7 */ "Invalid vertex id",
    /*  8 */ "Non-square matrix",
    /*  9 */ "Invalid mode",
    /* 10 */ "File operation error",
    /* 11 */ "Unfold infinite iterator",
    /* 12 */ "Unimplemented function call",
    /* 13 */ "Interrupted",
    /* 14 */ "Numeric procedure did not converge",
    /* 15 */ "Matrix-vector product failed",
    /* 16 */ "N must be positive",
    /* 17 */ "NEV must be positive",
    /* 18 */ "NCV must be greater than NEV and less than or equal to N "
    "(and for the non-symmetric solver NCV-NEV >=2 must also hold)",
    /* 19 */ "Maximum number of iterations should be positive",
    /* 20 */ "Invalid WHICH parameter",
    /* 21 */ "Invalid BMAT parameter",
    /* 22 */ "WORKL is too small",
    /* 23 */ "LAPACK error in tridiagonal eigenvalue calculation",
    /* 24 */ "Starting vector is zero",
    /* 25 */ "MODE is invalid",
    /* 26 */ "MODE and BMAT are not compatible",
    /* 27 */ "ISHIFT must be 0 or 1",
    /* 28 */ "NEV and WHICH='BE' are incompatible",
    /* 29 */ "Could not build an Arnoldi factorization",
    /* 30 */ "No eigenvalues to sufficient accuracy",
    /* 31 */ "HOWMNY is invalid",
    /* 32 */ "HOWMNY='S' is not implemented",
    /* 33 */ "Different number of converged Ritz values",
    /* 34 */ "Error from calculation of a real Schur form",
    /* 35 */ "LAPACK (dtrevc) error for calculating eigenvectors",
    /* 36 */ "Unknown ARPACK error",
    /* 37 */ "Negative loop detected while calculating shortest paths",
    /* 38 */ "Internal error, likely a bug in igraph",
    /* 39 */ "Maximum number of iterations reached",
    /* 40 */ "No shifts could be applied during a cycle of the "
    "Implicitly restarted Arnoldi iteration. One possibility "
    "is to increase the size of NCV relative to NEV",
    /* 41 */ "The Schur form computed by LAPACK routine dlahqr "
    "could not be reordered by LAPACK routine dtrsen.",
    /* 42 */ "Big integer division by zero",
    /* 43 */ "GLPK Error, GLP_EBOUND",
    /* 44 */ "GLPK Error, GLP_EROOT",
    /* 45 */ "GLPK Error, GLP_ENOPFS",
    /* 46 */ "GLPK Error, GLP_ENODFS",
    /* 47 */ "GLPK Error, GLP_EFAIL",
    /* 48 */ "GLPK Error, GLP_EMIPGAP",
    /* 49 */ "GLPK Error, GLP_ETMLIM",
    /* 50 */ "GLPK Error, GLP_STOP",
    /* 51 */ "Internal attribute handler error",
    /* 52 */ "Unimplemented attribute combination for this type",
    /* 53 */ "LAPACK call resulted an error",
    /* 54 */ "Internal DrL error",
    /* 55 */ "Integer or double overflow",
    /* 56 */ "Internal GPLK error",
    /* 57 */ "CPU time exceeded",
    /* 58 */ "Integer or double underflow",
    /* 59 */ "Random walk got stuck",
    /* 60 */ "Search stopped; this error should never be visible to the user, "
    "please report this error along with the steps to reproduce it."
};

const char* igraph_strerror(const int igraph_errno) {
    if (igraph_errno < 0 || igraph_errno >= sizeof(igraph_i_error_strings) / sizeof(char *)) {
        return "Invalid error code; no error string available.";
    }
    return igraph_i_error_strings[igraph_errno];
}

int igraph_error(const char *reason, const char *file, int line,
                 int igraph_errno) {

    if (igraph_i_error_handler) {
        igraph_i_error_handler(reason, file, line, igraph_errno);
#ifndef USING_R
    }  else {
        igraph_error_handler_abort(reason, file, line, igraph_errno);
#endif
    }
    return igraph_errno;
}

int igraph_errorf(const char *reason, const char *file, int line,
                  int igraph_errno, ...) {
    va_list ap;
    va_start(ap, igraph_errno);
    vsnprintf(igraph_i_errormsg_buffer,
              sizeof(igraph_i_errormsg_buffer) / sizeof(char), reason, ap);
    return igraph_error(igraph_i_errormsg_buffer, file, line, igraph_errno);
}

int igraph_errorvf(const char *reason, const char *file, int line,
                   int igraph_errno, va_list ap) {
    vsnprintf(igraph_i_errormsg_buffer,
              sizeof(igraph_i_errormsg_buffer) / sizeof(char), reason, ap);
    return igraph_error(igraph_i_errormsg_buffer, file, line, igraph_errno);
}

#ifndef USING_R
void igraph_error_handler_abort (const char *reason, const char *file,
                                 int line, int igraph_errno) {
    fprintf(stderr, "Error at %s:%i :%s, %s\n", file, line, reason,
            igraph_strerror(igraph_errno));
    abort();
}
#endif

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

    IGRAPH_FINALLY_FREE();
}

#ifndef USING_R
void igraph_error_handler_printignore (const char *reason, const char *file,
                                       int line, int igraph_errno) {
    IGRAPH_FINALLY_FREE();
    fprintf(stderr, "Error at %s:%i :%s, %s\n", file, line, reason,
            igraph_strerror(igraph_errno));
}
#endif

igraph_error_handler_t *
igraph_set_error_handler (igraph_error_handler_t * new_handler) {
    igraph_error_handler_t * previous_handler = igraph_i_error_handler;
    igraph_i_error_handler = new_handler;
    return previous_handler;
}

IGRAPH_THREAD_LOCAL struct igraph_i_protectedPtr igraph_i_finally_stack[100];

/*
 * Adds another element to the free list
 */

void IGRAPH_FINALLY_REAL(void (*func)(void*), void* ptr) {
    int no = igraph_i_finally_stack[0].all;
    assert (no < 100);
    assert (no >= 0);
    igraph_i_finally_stack[no].ptr = ptr;
    igraph_i_finally_stack[no].func = func;
    igraph_i_finally_stack[0].all ++;
    /* printf("--> Finally stack contains now %d elements\n", igraph_i_finally_stack[0].all); */
}

void IGRAPH_FINALLY_CLEAN(int minus) {
    igraph_i_finally_stack[0].all -= minus;
    if (igraph_i_finally_stack[0].all < 0) {
        /* fprintf(stderr, "corrupt finally stack, popping %d elements when only %d left\n", minus, igraph_i_finally_stack[0].all+minus); */
        igraph_i_finally_stack[0].all = 0;
    }
    /* printf("<-- Finally stack contains now %d elements\n", igraph_i_finally_stack[0].all); */
}

void IGRAPH_FINALLY_FREE(void) {
    int p;
    /*   printf("[X] Finally stack will be cleaned (contained %d elements)\n", igraph_i_finally_stack[0].all);  */
    for (p = igraph_i_finally_stack[0].all - 1; p >= 0; p--) {
        igraph_i_finally_stack[p].func(igraph_i_finally_stack[p].ptr);
    }
    igraph_i_finally_stack[0].all = 0;
}

int IGRAPH_FINALLY_STACK_SIZE(void) {
    return igraph_i_finally_stack[0].all;
}

static IGRAPH_THREAD_LOCAL igraph_warning_handler_t *igraph_i_warning_handler = 0;

/**
 * \function igraph_warning_handler_ignore
 * Ignore all warnings
 *
 * This warning handler function simply ignores all warnings.
 * \param reason Textual description of the warning.
 * \param file The source file in which the warning was noticed.
 * \param line The number of line in the source file which triggered the
 *         warning..
 * \param igraph_errno Warnings could have potentially error codes as well,
 *        but this is currently not used in igraph.
 */

void igraph_warning_handler_ignore (const char *reason, const char *file,
                                    int line, int igraph_errno) {
    IGRAPH_UNUSED(reason);
    IGRAPH_UNUSED(file);
    IGRAPH_UNUSED(line);
    IGRAPH_UNUSED(igraph_errno);
}

#ifndef USING_R

/**
 * \function igraph_warning_handler_print
 * Print all warning to the standard error
 *
 * This warning handler function simply prints all warnings to the
 * standard error.
 * \param reason Textual description of the warning.
 * \param file The source file in which the warning was noticed.
 * \param line The number of line in the source file which triggered the
 *         warning..
 * \param igraph_errno Warnings could have potentially error codes as well,
 *        but this is currently not used in igraph.
 */

void igraph_warning_handler_print (const char *reason, const char *file,
                                   int line, int igraph_errno) {
    IGRAPH_UNUSED(igraph_errno);
    fprintf(stderr, "Warning: %s in file %s, line %i\n", reason, file, line);
}
#endif

int igraph_warning(const char *reason, const char *file, int line,
                   int igraph_errno) {

    if (igraph_i_warning_handler) {
        igraph_i_warning_handler(reason, file, line, igraph_errno);
#ifndef USING_R
    }  else {
        igraph_warning_handler_print(reason, file, line, igraph_errno);
#endif
    }
    return igraph_errno;
}

int igraph_warningf(const char *reason, const char *file, int line,
                    int igraph_errno, ...) {
    va_list ap;
    va_start(ap, igraph_errno);
    vsnprintf(igraph_i_warningmsg_buffer,
              sizeof(igraph_i_warningmsg_buffer) / sizeof(char), reason, ap);
    return igraph_warning(igraph_i_warningmsg_buffer, file, line,
                          igraph_errno);
}

igraph_warning_handler_t *
igraph_set_warning_handler (igraph_warning_handler_t * new_handler) {
    igraph_warning_handler_t * previous_handler = igraph_i_warning_handler;
    igraph_i_warning_handler = new_handler;
    return previous_handler;
}
