/** @file  psError.c
 *
 *  @brief Contains the definitions for the error reporting functions
 *
 *  Error reporting functions shall be used to create log entries in the
 *  event errors are detected.  The messages shall give enough information
 *  to allow the user to know where the error has occurred and the type
 *  of error detected.
 *
 *  @author Eric Van Alst, MHPCC
 *
 *  @version $Revision: 1.30 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2006-06-02 21:33:34 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
 */

#include <stdarg.h>
#include <pthread.h>
#include <string.h>

#include "psLogMsg.h"
#include "psError.h"
#include "psMemory.h"

#define MAX_ERROR_STACK_SIZE 64
static psErr* errorStack[MAX_ERROR_STACK_SIZE];
static psU32 errorStackSize = 0;
pthread_mutex_t lockErrorStack = PTHREAD_MUTEX_INITIALIZER;

static void pushErrorStack(psErr* err);

static void pushErrorStack(psErr* err)
{

    pthread_mutex_lock(&lockErrorStack);

    if (errorStackSize < MAX_ERROR_STACK_SIZE) {
        errorStack[errorStackSize] = psMemIncrRefCounter(err);
        errorStackSize++;
        p_psMemSetPersistent(err,true);
        p_psMemSetPersistent(err->msg,true);
        p_psMemSetPersistent(err->name,true);
    }

    pthread_mutex_unlock(&lockErrorStack);
}

static void errFree(psErr* err)
{
    if (err != NULL) {
        psFree(err->msg);
        psFree(err->name);
    }
}

psErr* psErrAlloc(const char* name, psErrorCode code, const char* msg)
{
    psErr* err = psAlloc(sizeof(psErr));
    err->msg = strcpy(psAlloc(strlen(msg) + 1), msg);
    err->name = strcpy(psAlloc(strlen(name) + 1), name);
    err->code = code;

    psMemSetDeallocator(err,(psFreeFunc)errFree);

    return err;
}

psErrorCode p_psError(const char* filename,
                      unsigned int lineno,
                      const char* func,
                      psErrorCode code,
                      bool new,
                      const char* format,
                      ...)
{
    char errMsg[2048];
    psErr* err;
    char msgName[1024];

    snprintf(msgName,1024,"%s (%s:%d)",func,filename,lineno);

    va_list argPtr;             // variable list arguement pointer

    if (new) {
        psErrorClear();
    }

    // Get the variable list parameters to pass to logging function
    va_start(argPtr, format);

    vsnprintf(errMsg,2048,format,argPtr);
    // Remove a single trailing \n from message -- it interferes with psErrorStackPrint
    int len = strlen(errMsg);
    if (len > 0 && errMsg[len - 1] == '\n') {
        errMsg[len - 1] = '\0';
        len--;
    }

    err = psErrAlloc(msgName,code,errMsg);
    pushErrorStack(err);

    // Call logging function with PS_LOG_ERROR level
    psLogMsg(msgName, PS_LOG_ERROR, errMsg);

    // Clean up stack after variable argument has been used
    va_end(argPtr);

    psFree(err);

    return code;
}

void p_psWarning(const char* file,
                 int lineno,
                 const char* func,
                 const char* fmt,
                 ...)
{
    char msgName[1024];

    snprintf(msgName,1024,"%s (%s:%d)",func,file,lineno);

    va_list argPtr;             // variable list argument pointer

    // Get the variable list parameters to pass to logging function
    va_start(argPtr, fmt);

    psLogMsgV(msgName, PS_LOG_WARN, fmt, argPtr);

    // Clean up stack after variable argument has been used
    va_end(argPtr);

    return;
}

psErr* psErrorGet(psS32 which)
{
    psErr* result;

    pthread_mutex_lock(&lockErrorStack);

    // Check for negative reference and if found return PS_ERR_NONE
    if (which < 0 ) {
        result = psErrAlloc("", PS_ERR_NONE, "");
    } else {

        which = errorStackSize-1-which;     // the which input is from the end of errorStack
        if (which < 0 || which >= errorStackSize) {
            result = psErrAlloc("",PS_ERR_NONE,"");    // no error at the given location
        } else {
            result = psMemIncrRefCounter(errorStack[which]); // a new reference passed back
        }
    }

    pthread_mutex_unlock(&lockErrorStack);

    return result;
}

unsigned int psErrorGetStackSize()
{
    return errorStackSize;
}

psErr* psErrorLast(void)
{
    return psErrorGet(0);
}

psErrorCode psErrorCodeLast(void)
{
    const psErr *err = psErrorGet(0);
    psErrorCode code = err->code;
    psFree(err);

    return code;
}

void psErrorClear(void)
{
    pthread_mutex_lock(&lockErrorStack);

    for (int lcv=0;lcv < errorStackSize; lcv++) {
        p_psMemSetPersistent(errorStack[lcv],false);
        p_psMemSetPersistent(errorStack[lcv]->msg,false);
        p_psMemSetPersistent(errorStack[lcv]->name,false);
        psFree(errorStack[lcv]);
    }
    errorStackSize = 0;

    pthread_mutex_unlock(&lockErrorStack);

}
void psErrorStackPrint(FILE *fd, const char *format, ...)
{
    va_list argPtr;             // variable list arguement pointer

    // Get the variable list parameters to pass to logging function
    va_start(argPtr, format);

    psErrorStackPrintV(fd,format,argPtr);

    va_end(argPtr);
}

void psErrorStackPrintV(FILE *fd, const char *format, va_list va)
{

    pthread_mutex_lock(&lockErrorStack);

    if (errorStackSize > 0) {
        vfprintf(fd,format,va);

        for (psS32 lcv=0;lcv<errorStackSize;lcv++) {
            if(errorStack[lcv]->code >= PS_ERR_BASE) {
                fprintf(fd," -> %s: %s\n     %s\n",
                        errorStack[lcv]->name,
                        psErrorCodeString(errorStack[lcv]->code),
                        errorStack[lcv]->msg);
            } else {
                fprintf(fd," -> %s: %s\n     %s\n",
                        errorStack[lcv]->name,
                        strerror(errorStack[lcv]->code),
                        errorStack[lcv]->msg);
            }
        }
    }

    pthread_mutex_unlock(&lockErrorStack);
}

