/* 
 *  gretl -- Gnu Regression, Econometrics and Time-series Library
 *  Copyright (C) 2001 Allin Cottrell and Riccardo "Jack" Lucchetti
 * 
 *  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 3 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, see <http://www.gnu.org/licenses/>.
 * 
 */

#include "libgretl.h"
#include "gretl_func.h"
#include "libset.h"
#include "monte_carlo.h"

#include <errno.h>

#define EDEBUG 0

#define ERRLEN 2048

static char gretl_errmsg[ERRLEN];
static char gretl_warnmsg[ERRLEN];

static int gretl_errno;
static int gretl_warnnum;

static const char *gretl_error_messages[] = {
    NULL,
    NULL,
    N_("Data error"),                                            /* E_DATA = 2 */
    N_("Exact or near collinearity encountered"),                /* E_SINGULAR */
    N_("Insufficient degrees of freedom for regression"),        /* E_DF */
    N_("Dependent variable is all zeros, aborting regression"),  /* E_ZERO */
    N_("Total sum of squares was not positive"),                 /* E_TSS */
    N_("Sum of squared residuals negative!"),                    /* E_ESS */
    N_("Sorry, command not available for this estimator"),       /* E_NOTIMP */
    N_("Unspecified error -- FIXME"),                            /* E_UNSPEC */
    N_("Syntax error in genr formula"),                          /* E_SYNTAX */
    N_("This command won't work with the current periodicity"),  /* E_PDWRONG */
    N_("Error attempting to open file"),                         /* E_FOPEN */
    N_("Out of memory!"),                                        /* E_ALLOC */
    N_("No formula supplied in genr"),                           /* E_EQN */
    N_("Unknown variable name in command"),                      /* E_UNKVAR */
    N_("Command has insufficient arguments"),                    /* E_ARGS */
    N_("This command is implemented only for OLS models"),       /* E_OLSONLY */
    N_("Invalid argument for function"),                         /* E_INVARG */
    N_("Syntax error in command line"),                          /* E_PARSE */
    N_("No independent variables left after omissions"),         /* E_NOVARS */
    N_("No independent variables were omitted"),                 /* E_NOOMIT */
    N_("No new independent variables were added"),               /* E_NOADD */
    N_("One or more \"added\" vars were already present"),       /* E_ADDDUP */
    N_("Error generating logarithms"),                           /* E_LOGS */
    N_("Error generating squares"),                              /* E_SQUARES */
    N_("Error generating lagged variables"),                     /* E_LAGS */
    N_("Attempting to take square root of negative number"),     /* E_SQRT */
    N_("Excessive exponent in genr formula"),                    /* E_HIGH */
    N_("Need valid starting and ending observations"),           /* E_OBS */
    N_("You must include a constant in this sort of model"),     /* E_NOCONST */
    N_("The statistic you requested is not available"),          /* E_BADSTAT */
    N_("Missing sub-sample information; can't merge data"),      /* E_NOMERGE */
    N_("The convergence criterion was not met"),                 /* E_NOCONV */
    N_("The operation was canceled"),                            /* E_CANCEL */
    N_("Missing values encountered"),                            /* E_MISSDATA */
    N_("Not a Number in calculation"),                           /* E_NAN */
    N_("Matrices not conformable for operation"),                /* E_NONCONF */
    N_("Data types not conformable for operation"),              /* E_TYPES */
    N_("Wrong data type"),                                       /* E_DATATYPE */
    N_("Incompatible options"),                                  /* E_BADOPT */
    N_("The restrictions do not identify the parameters"),       /* E_NOIDENT */
    N_("External command failed"),                               /* E_EXTERNAL */
    N_("Maximum length of command line (16384 bytes) exceeded"),  /* E_TOOLONG */
    N_("No dataset is in place"),                                /* E_NODATA */
    N_("Matrix is not positive definite"),                       /* E_NOTPD */
    N_("Failed to calculate Jacobian"),                          /* E_JACOBIAN */
    N_("Insufficient observations for this operation"),          /* E_TOOFEW */
    N_("Error executing function"),                              /* E_FUNCERR */
    N_("Execution aborted by request"),                          /* E_STOP */
    N_("Syntax error in genr formula"),                          /* E_BADCOMMA */
    NULL,                                                        /* E_DB_DUP */
    NULL,                                                        /* E_OK */
    NULL                                                         /* E_MAX */
};

static const char *gretl_warning_messages[] = {
    NULL,
    N_("gradient is not close to zero"),                 /* W_GRADIENT */
    N_("generated missing values"),                      /* W_GENMISS */
    N_("generated non-finite values"),                   /* W_GENNAN */
    NULL                                                 /* W_MAX */
};

static const char *look_up_errmsg (int err)
{
    if (err > 0 && err < E_MAX) {
	return gretl_error_messages[err];
    } else if (err == 0) {
	return "";
    } else {
	fprintf(stderr, "look_up_errmsg: out of bounds code %d\n", err);
	return "missing error message!";
    }
}

static const char *look_up_warnmsg (int w)
{
    if (w > 0 && w < W_MAX) {
	return gretl_warning_messages[w];
    } else {
	fprintf(stderr, "look_up_warnmsg: out of bounds code %d\n", w);
	return "missing warning message!";
    }
}

static int error_printed;
static int alarm_set;

/**
 * errmsg_get_with_default: 
 * @err: gretl error code (see #gretl_error_codes).
 *
 * Returns: a specific error message if available,
 * otherwise a generic error message corresponding to the 
 * given @err.
 */

const char *errmsg_get_with_default (int err)
{
    const char *ret = "";

#if EDEBUG
    fprintf(stderr, "errmsg_get_with_default: msg='%s'\n",
	    gretl_errmsg);
#endif    

    if (*gretl_errmsg != '\0') {
	ret = gretl_errmsg;
    } else {
	const char *deflt = look_up_errmsg(err);

	if (deflt != NULL) {
	    ret = _(deflt);
	}
    }

    return ret;
}

/**
 * gretl_warnmsg_get: 
 *
 * Returns: the current gretl warning message, or %NULL if no
 * warning is currently in place.
 */

const char *gretl_warnmsg_get (void)
{
    const char *ret = NULL;

    if (gretl_warnnum) {
	if (*gretl_warnmsg != '\0') {
	    ret = gretl_warnmsg;
	    /* note; can't zero the message here! */
	} else {
	    const char *deflt = look_up_warnmsg(gretl_warnnum);

	    if (deflt != NULL) {
		ret = _(deflt);
	    }
	}
	gretl_warnnum = 0;
    }

    return ret;
}

/**
 * errmsg:
 * @err: gretl error code (see #error_codes).
 * @prn: gretl printing struct.
 *
 * Prints to @prn a specific error message if available, 
 * otherwise a generic error message corresponding to the 
 * given @err.
 */

void errmsg (int err, PRN *prn)
{
#if EDEBUG
    fprintf(stderr, "errmsg: err=%d, error_printed=%d\n",
	    err, error_printed);
#endif

    if (!error_printed && prn != NULL) {
	const char *msg = errmsg_get_with_default(err);

	pprintf(prn, "%s\n", msg);
	error_printed = 1;
    } 
}

/**
 * warnmsg:
 * @prn: gretl printing struct.
 *
 * If a gretl warning is set, prints a message to @prn
 * and zeros the warning signal.
 */

void warnmsg (PRN *prn)
{
    if (prn == NULL || gretl_warnnum == 0) {
	return;
    }

    if (!gretl_warnings_on()) {
	*gretl_warnmsg = '\0';
	gretl_warnnum = 0;
	return;
    }

    if (*gretl_warnmsg != '\0') {
	pprintf(prn, "%s: %s\n", _("Warning"), gretl_warnmsg);
	*gretl_warnmsg = '\0';
    } else {
	const char *s = look_up_warnmsg(gretl_warnnum);

	pprintf(prn, "%s: %s\n", _("Warning"), _(s));
    }

    gretl_warnnum = 0;
}

/**
 * gretl_errmsg_get:
 *
 * Returns: a specific error message if available,
 * otherwise an empty string.
 */

const char *gretl_errmsg_get (void)
{
    return gretl_errmsg;
}

/**
 * gretl_errmsg_set:
 * @str: an error message.
 *
 * If %gretl_errmsg is currently blank, copy the given string into
 * the message space; or if the error message is not blank but
 * sufficient space remains, append @str to the message.
 */

void gretl_errmsg_set (const char *str)
{
#if EDEBUG
    fprintf(stderr, "gretl_errmsg_set: '%s'\n", str);
#endif

    if (alarm_set && *gretl_errmsg != '\0') {
	/* leave the current error message in place */
	return;
    }

    if (*gretl_errmsg == '\0') {
	strncat(gretl_errmsg, str, ERRLEN - 1);
    } else if (strcmp(gretl_errmsg, str)) {
	/* should we do the following? */
	int n = strlen(gretl_errmsg);
	int m = strlen(str);

	if (n + m + 2 < ERRLEN) {
	    strcat(gretl_errmsg, "\n");
	    strcat(gretl_errmsg, str);
	}
    }

#if EDEBUG
    fprintf(stderr, "gretl_errmsg now: '%s'\n", gretl_errmsg);
#endif
}

/**
 * gretl_warnmsg_set:
 * @str: a warning message.
 *
 * If copy the given string into the warning message space.
 */

void gretl_warnmsg_set (const char *str)
{
    *gretl_warnmsg = '\0';
    strncat(gretl_warnmsg, str, ERRLEN - 1);
    gretl_warnnum = W_MAX;
}

/**
 * gretl_errmsg_sprintf:
 * @fmt: format string.
 * @...: arguments, as to sprintf.
 *
 * Append a formatted message to the current gretl
 * error message.
 */

void gretl_errmsg_sprintf (const char *fmt, ...)
{
#if EDEBUG
    fprintf(stderr, "gretl_errmsg_sprintf: fmt='%s'\n", fmt);
#endif

    if (*gretl_errmsg == '\0') {
	va_list ap;

	va_start(ap, fmt);
	vsnprintf(gretl_errmsg, ERRLEN, fmt, ap);
	va_end(ap);
    } else if (strstr(gretl_errmsg, "*** error in fun") &&
	       strstr(fmt, "*** error in fun")) {
	/* don't print more than one "error in function" 
	   message, as this gets confusing 
	*/
	;
    } else {
	/* find the number of characters left */
	int len0 = strlen(gretl_errmsg);
	int n = ERRLEN - len0 - 2;

	if (n > 31) {
	    char tmp[ERRLEN];
	    va_list ap;

	    *tmp = '\0';
	    va_start(ap, fmt);
	    vsnprintf(tmp, n, fmt, ap);
	    va_end(ap);

	    if (gretl_errmsg[len0 - 1] != '\n') {
		strncat(gretl_errmsg, "\n", 1);
	    }
	    strncat(gretl_errmsg, tmp, strlen(tmp));
	} 
    }
}

/**
 * gretl_warnmsg_sprintf:
 * @fmt: format string.
 * @...: arguments, as to sprintf.
 *
 * Write a formatted message to the current gretl
 * warning message space.
 */

void gretl_warnmsg_sprintf (const char *fmt, ...)
{
    va_list ap;

    *gretl_warnmsg = '\0';

    va_start(ap, fmt);
    vsnprintf(gretl_warnmsg, ERRLEN, fmt, ap);
    va_end(ap);

    gretl_warnnum = W_MAX;
}

/**
 * gretl_errmsg_set_from_errno:
 * @s: string to prepend to error message, or %NULL.
 *
 * If %gretl_errmsg is currently blank, copy the string 
 * returned by %strerror into the message space; or if the 
 * error message is not blank but sufficient space remains, 
 * append the new error info to the message.
 */

void gretl_errmsg_set_from_errno (const char *s)
{
    char *msg = NULL;

    if (errno) {
	msg = strerror(errno);
	errno = 0;
    }

    if (msg != NULL) {
	if (s != NULL) {
	    gretl_errmsg_sprintf("%s: %s", s, msg);
	} else {
	    gretl_errmsg_set(msg);
	}
    }
}

/**
 * gretl_error_clear:
 *
 * Blank out any previously recorded error message.
 */

void gretl_error_clear (void)
{
#if EDEBUG
    fprintf(stderr, "gretl_error_clear\n");
#endif
    if (!alarm_set) {
	*gretl_errmsg = '\0';
    }
    error_printed = 0;
    errno = 0;
    gretl_warnnum = 0;
    *gretl_warnmsg = '\0';
}

/**
 * gretl_errmsg_is_set:
 *
 * Returns: 1 if the gretl error message is currently
 * set (not blank), otherwise 0.
 */

int gretl_errmsg_is_set (void)
{
    return (*gretl_errmsg != '\0');
}

/**
 * maybe_save_gretl_errmsg:
 *
 * Returns: An allocated copy of the current gretl error
 * message, or NULL if @err is zero or the current message
 * is blank.
 */

char *maybe_save_gretl_errmsg (int err)
{
    if (err && *gretl_errmsg != '\0') {
	return gretl_strdup(gretl_errmsg);
    } else {
	return NULL;
    }
}

/* setting the "alarm" prevents gretl_errmsg from being
   overwritten
*/

void set_gretl_alarm (int val)
{
    alarm_set = val;
}

void set_gretl_errno (int err)
{
    gretl_errno = err;
}

void set_gretl_warning (int w)
{
    gretl_warnnum = w;
}

int get_gretl_errno (void)
{
    int err = gretl_errno;

    gretl_errno = 0;
    return err;
}

int check_gretl_errno (void)
{
    return gretl_errno;
}

int check_gretl_warning (void)
{
    return gretl_warnnum;
}

int gretl_error_is_fatal (void)
{
    if (gretl_compiling_function()) {
	return 1;
    } else if (gretl_compiling_loop()) {
	return 1;
    } else if (libset_get_bool(HALT_ON_ERR) == 0) {
	return 0;
    } else {
	return gretl_in_batch_mode();
    }
}

