/*
 * Support a Pan-STARRS error stack
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include "psLogMsg.h"
#include "psMemory.h"
#include "psMisc.h"
#include "psError.h"
#include "psLib.h"

/************************************************************************************************************/
/*
 * Description strings for errors
 */
#include "psErrorCodes.h"
#include "psErrorCodes.c"

static int nerrorString = 0;		// number of known error messages
static char **errorStringList = NULL;	// error strings indexed by errorCode

/************************************************************************************************************/
/*
 * Here's the error stack
 */
typedef struct errStack {
    psErr error;
    struct errStack *next;		// pointer to next message on the stack
} errStack;

static errStack *errorStack = NULL;	// the error stack itself, last message pushed is FIRST
int nerror = 0;				// number of errors

/************************************************************************************************************/
/*
 * Push an element onto the error stack
 */
void errStackPush(const char *name,		// name of error
		  psErrorCode code,		// class of error
		  const char *fmt,		// format for error message
		  va_list ap)			// optional arguments for error
{
    errStack *err = psAlloc(sizeof(errStack));

    err->next = errorStack;
    errorStack = err;
    nerror++;

    err->error.name = psStringCopy(name);
    err->error.code = code;

    char buff[256];
    int n = vsnprintf(buff, sizeof(buff), fmt, ap);
    err->error.msg = strcpy(psAlloc(n + 1), buff);
}
/*
 * Free an errStack element; don't worry about fixing the ->next pointer (that's why it's Del not Free)
 */
void errStackDel(errStack *err)
{
    if (err == NULL) {
	return;
    }

    psFree(err->error.name);
    psFree(err->error.msg);
    psFree(err);
}

/************************************************************************************************************/
/*
 * Push an error onto the errorstack and print an error message
 */
int psError(const char *name,		// category of code that caused the error
	    psErrorCode code,		// code of error (equivalent to errno)
	    int new,			// is this a new error?
	    const char *fmt, ...)	// format and possible extra arguments
{
    if (code == PS_ERR_NONE || new) { // free old error stack
	errStack *eptr = errorStack;

	while (eptr != NULL) {
	    errStack *next = eptr->next;
	    errStackDel(eptr);
	    eptr = next;
	}

	errorStack = NULL;
	nerror = 0;
    }

    if (code != PS_ERR_NONE) {
	va_list ap;
	va_start(ap, fmt);
	errStackPush(name, code, fmt, ap);
	va_end(ap);
	
	va_start(ap, fmt);
	psLogMsgV(name, PS_LOG_ERROR, fmt, ap);
	va_end(ap);
    }

    return code;
}

/************************************************************************************************************/
/*
 * Clear the error stack
 */
void psErrorClear(void)
{
    psError("", PS_ERR_NONE, 1, "");
}

/************************************************************************************************************/
/*
 * Return the last error; if there is none, return one with code "PS_ERR_NONE"
 */
const psErr *psErrorLast(void)
{
    return psErrorGet(0);
}

/*
 * Return specified error (0: last error, 1: previous error, and so on)
 */
const psErr *psErrorGet(int which)
{
    static psErr noError = { "", PS_ERR_NONE, "" };

    errStack *eptr = errorStack;
    for (int i = 0; i < which && eptr != NULL; i++) {
	eptr = eptr->next;
    }

    return (eptr == NULL) ? &noError : &eptr->error;
}

/************************************************************************************************************/
/*
 * Register a set of psErrorDescription
 */
void p_psErrorRegister(const psErrorDescription *errors,       // errors to register
		       int nerror)			       // number of errors
{
    if (nerror <= 0) {			// no new errors
	return;
    }

    int codeMax = errors[nerror - 1].code; // largest new error code

    if (codeMax > nerrorString) {
	errorStringList = psRealloc(errorStringList, (codeMax + 1)*sizeof(char *));

	for (int i = nerrorString; i <= codeMax; i++) {
	    errorStringList[i] = NULL;
	}
	nerrorString = codeMax + 1;
    }

    for (int i = 0; i < nerror; i++) {
	if (errorStringList[errors[i].code] != NULL && // that value is already registered differently
	    strcmp(errorStringList[errors[i].code], errors[i].descrip) != 0) {
	    psLogMsg("utils.error", PS_LOG_WARN,
		     "Attempt to register error code %d as \"%s\" that is already registered as \"%s\"",
		     errors[i].code, errors[i].descrip, errorStringList[errors[i].code]);
	}

	errorStringList[errors[i].code] = (char *)errors[i].descrip;
    }
}

/************************************************************************************************************/
/*
 * Return the string associated with an error code
 */
const char *psErrorCodeString(psErrorCode code)
{
    if (errorStringList == NULL) {	// need to build the errorStringList
	psErrorRegister();
    }

    if (code < PS_ERR_BASE) {
	return strerror(code);
    } else if (code >= nerrorString) {
	static char buff[30];
	sprintf(buff, "(Unknown code %d)", code);
	return buff;
    } else {
	return errorStringList[code];
    }
}

/************************************************************************************************************/
/*
 * Print the header then error stack to the provided file descriptor
 */
void psErrorStackPrint(FILE *fd,	// write to this file descriptor
		       const char *fmt,	// format for any header information; may be NULL
		       ...)		// arguments for format
{
    va_list ap;
    /*
     * Print header?
     */
    if (fmt != NULL) {
	va_start(ap, fmt);
    }

    psErrorStackPrintV(fd, fmt, ap);

    if (fmt != NULL) {
	va_end(ap);
    }
}

void psErrorStackPrintV(FILE *fd,	// write to this file descriptor
			const char *fmt,// format for any header information; may be NULL
			va_list ap)	// arguments for format
{
    if (errorStringList == NULL) {	// need to build the errorStringList
	psErrorRegister();
    }

    if (errorStack == NULL) {
	return;
    }
    /*
     * Print header?
     */
    if (fmt != NULL) {
	vfprintf(fd, fmt, ap);
	if (strchr(fmt, '\n') == NULL) {
	    fprintf(fd, "\n");
	}
    }
    /*
     * Print error stack
     */
    const errStack *stack[nerror];	// we need to reverse the list
    errStack *eptr = errorStack;
    for (int i = 0; i < nerror; i++) {
	stack[nerror - i - 1] = eptr;
	eptr = eptr->next;
    }
    
    for (int i = 0; i < nerror; i++) {
	int code = stack[i]->error.code;
	fprintf(fd, "%*s%-*s %-30s %s\n",
		i, "", 30 - i, stack[i]->error.name, psErrorCodeString(code), stack[i]->error.msg);
    }
}
