/*
 * Copyright (c) 2001-2003 Shiman Associates Inc. All Rights Reserved.
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
/*
 * $Id: mas_error.c,v 1.3 2003/06/26 20:52:21 rocko Exp $
 *
 * Copyright (c) 2000, 2001 by Shiman Associates Inc. and Sun
 * Microsystems, Inc. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions: The above
 * copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the names of the authors or
 * copyright holders shall not be used in advertising or otherwise to
 * promote the sale, use or other dealings in this Software without
 * prior written authorization from the authors or copyright holders,
 * as applicable.
 *
 * All trademarks and registered trademarks mentioned herein are the
 * property of their respective owners. No right, title or interest in
 * or to any trademark, service mark, logo or trade name of the
 * authors or copyright holders or their licensors is granted.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include "mas/mas_common.h"
#ifdef _POSIX_SOURCE
#include <errno.h>
#include <string.h>
#endif

static int32 _setup_statstring_module( int32 mi );

/*** MAS error strings (merror) */
static const char* masc_errstrings[] = 
{
    "none",
    "generic error",
    "generic big error",
    "file not found",
    "can't open file",
    "insufficient resources",
    "end of file",
    "I/O error",
    "invalid file type",
    "invalid argument",
    "buffer underrun/overrun",
    "POSIX scheduling error",
    "comm error",
    "NOT SUPPORTED",
    "not defined",
    "can't create file/socket",
    "null pointer",
    "no data",
    "feature not implemented",
    "exceeded boundaries",
    ""
};

/***************************************************************************
 * masc_strmerror
 *
 * arguments:
 *  1. int32 error code
 *
 *  Maps the MAS error code to a string.
 *
 * returns: const char* error description string
 *
 ***************************************************************************/
const char*
masc_strmerror(int32 error)
{
    int i;
    static int errcount = 0;
    int32 merror;

    /* hold the size of the error array in static var */
    if (errcount == 0)
    {
	i=0;
	while (*masc_errstrings[i]) i++;
	errcount = i;
    }
    
    /* get the real error code */
    merror = mas_get_merror(error);

    /* don't overrun the array */
    if (merror < errcount)
	return masc_errstrings[merror];
    else return "";
}

/***************************************************************************
 * masc_strserror
 *
 * arguments:
 *  1. int32 error code
 *
 *  Maps the system error code (serror) to a string.  For POSIX
 *  systems, this assumes the serror is an errno value.
 *
 * returns: const char* error description string
 *
 ***************************************************************************/
char*
masc_strserror(int32 error)
{
    int32 serror = mas_get_serror(error);
    
#ifdef _POSIX_SOURCE
    return strerror(serror);
#else
    return "";
#endif
}

/***************************************************************************
 * masc_strerrorlayer
 *
 * arguments:
 *  1. int32 error code
 *
 *  Maps the error layer to a descriptive string.
 *
 * returns: const char* error layer string
 *
 ***************************************************************************/
const char*
masc_strerrorlayer(int32 error)
{
    int32 errorlayer = mas_get_errorlayer(error);

    switch (errorlayer)
    {
    case MAS_ERRORLAYER_DEV: return "device"; 
    case MAS_ERRORLAYER_SCH: return "scheduler"; 
    case MAS_ERRORLAYER_ASM: return "assembler"; 
    case MAS_ERRORLAYER_PRO: return "protocol"; 
    case MAS_ERRORLAYER_LIB: return "client-side library"; 
    case MAS_ERRORLAYER_SYS: return "operating system"; 
    default: return "";
    }
}

/***************************************************************************
 * masc_logerror
 *
 * arguments:
 *  1. const char* error string
 *  2. int32 error code
 *
 *  Logs the error message, along with an extra string to the
 *  appropriate place determined by the loglevel of the error code.
 *
 * returns: void
 *
 ***************************************************************************/
void
masc_logerror(int32 errnum, const char* errstring, ... )
{
    int32   severity   = mas_get_severity(errnum);
    int32   merror     = mas_get_merror(errnum);
    int32   derror     = mas_get_derror(errnum);
    int32   serror     = mas_get_serror(errnum);
    int32   errorlayer = mas_get_errorlayer(errnum);
    int     verbosity;
    char    pr_errstring[256];
    va_list ap;

    if (errnum >= 0) return; /* not an error */
    
    switch (severity)
    {
    case MAS_ERR_CRITICAL:
        verbosity = MAS_VERBLVL_COREERR;
        masc_log_message( verbosity, "***************************************************");
        masc_log_message( verbosity, "* MAS CRITICAL ERROR: System is unusable!");
        masc_log_message( verbosity, "* Error code %d: %s", merror, masc_strmerror(errnum)); 
        break;
    case MAS_ERR_ERROR:
        verbosity = MAS_VERBLVL_ERROR;
        masc_log_message( verbosity, "***************************************************");
	masc_log_message( verbosity, "* MAS ERROR %d: %s", merror, masc_strmerror(errnum)); 
	break;
    case MAS_ERR_WARNING:
        verbosity = MAS_VERBLVL_WARNING;
	masc_log_message( verbosity, "* MAS WARNING %d: %s", merror, masc_strmerror(errnum)); 
	break;
    case MAS_ERR_ALERT:
        verbosity = MAS_VERBLVL_ALERT;
	masc_log_message( verbosity, "* MAS ALERT %d: %s", merror, masc_strmerror(errnum)); 
	break;
    case MAS_ERR_INFO:
        verbosity = MAS_VERBLVL_INFO;
	masc_log_message( verbosity, "* MAS INFO %d: %s", merror, masc_strmerror(errnum)); 
	break;
    case MAS_ERR_DEBUG:
        verbosity = MAS_VERBLVL_DEBUG;
	masc_log_message( verbosity, "* MAS DEBUG %d: %s", merror, masc_strmerror(errnum)); 
	break;
    default:
    case MAS_ERR_NONE:
        verbosity = MAS_VERBLVL_ERROR;
	masc_log_message( verbosity, "* MAS ERROR %d: %s", merror, masc_strmerror(errnum)); 
	break;
    }

    strcpy( pr_errstring, "* \"" );
    strncat( pr_errstring, errstring, 255 - strlen( "* \"" ) );
    strncat( pr_errstring, "\"", 255 - strlen( "* \"\"" ) );

    va_start(ap, errstring);
    masc_log_message( verbosity, pr_errstring, ap );
    va_end(ap);
    
    if (serror)
	masc_log_message( verbosity, "* System error %d: %s", serror, masc_strserror(errnum));
	
    if (derror)
	masc_log_message( verbosity, "* Device error %d", derror);

    if (errorlayer)
	masc_log_message( verbosity, "* Message originated in %s layer.", 
                          masc_strerrorlayer(errnum));

}

/* used by the error string handling functions to dynamically store
   error strings. The linked list is sorted numerically by offset. */
struct _statstring_node
{
    char* module;
    int32 mi; /* uniquely identifies this module -- index in hash */
    int32 cnt;    /* count of error strings in statstring */
    int32 offset; /* -1 times the error number of the first error in
                     statstring.  This is a positive number! */
    char** statstring; /* array of strings */
    struct _statstring_node *next;
};

/* status string control block */
struct _statstring_cb
{
    struct _statstring_node** hash;
    int32 entries;
    int32 slots;
    int32 last;
};

static struct _statstring_cb *_scb = NULL;

#define MAS_EPTRNULL mas_error(MERR_NULLPTR)
#define MAS_EOVERLAP mas_error(MERR_INVALID)
#define MAS_EMEMEX   mas_error(MERR_MEMORY)
#define MAS_ERANGE   mas_error(MERR_BOUNDS)
#define MAS_EMODND   mas_error(MERR_NOTDEF)
#define MAS_EUSED    mas_error(MERR_INVALID)

/*must be a power of 2*/
#define SCB_DEFAULT_SLOTS 16

/* returns module index or error */
/* does not copy strings */
int32
masc_register_statstrings_temp( char* module, int32 mi, char** statstring_list, int32 offset )
{
    int cnt = 0;
    struct _statstring_node *n = 0, *p = 0;
    int tmax, nmax;

    if ( _scb == NULL )
    {
        /* setup the statstring control block */
        _scb = MAS_NEW(_scb);
        if ( _scb == NULL )
            return MAS_EMEMEX;
        _scb->slots = SCB_DEFAULT_SLOTS;
        _scb->hash = masc_rtalloc( sizeof (void*) * _scb->slots );
        if ( _scb->hash == NULL )
            return MAS_EMEMEX;
    }
    
    if ( statstring_list == NULL )
        return MAS_EPTRNULL;

    /* Count statstring_list strings, assume null terminator. */
    while ( *statstring_list[cnt] != 0 ) cnt++;

    offset *= -1; /* flip the error offset to positive */
    tmax = offset + cnt - 1;

    if ( mi < 0 )
    {
        /* Set up new module */
        mi = _setup_statstring_module( mi );
    }
    else if ( mi >= _scb->slots )
    {
        /* User specified a module that's out of range of our hash */
        return MAS_ERANGE;
    }
    else if ( _scb->hash[mi] == NULL )
    {
        /* User specified a module that needs to be created */
        mi = _setup_statstring_module( mi );
    }

    /* check for overlaps & find our place in the list */
    for ( n = _scb->hash[mi]; n != NULL; n = n->next )
    {
        /* make sure the domains do not overlap */
        nmax = n->offset + n->cnt - 1;
        if ( ( ( offset >= n->offset ) && ( offset <= nmax ) ) || ( ( tmax >= n->offset ) && ( tmax <= nmax ) ) )
            return MAS_EOVERLAP;

        if ( ( offset < n->offset ) && ( tmax > nmax ) )
            return MAS_EOVERLAP;

        /* Our bail-out condition is when the domain of statstring_list
           lies to the left of the domain of this node. */
        if ( tmax < n->offset )
            break;

        /* otherwise, retain the previous node */
        p = n;
    }

    /* create the new list node and insert it into the list */
    n = MAS_NEW(n);
    n->cnt = cnt;
    n->offset = offset;
    n->statstring = statstring_list;
    n->module = module;
    n->mi = mi;
    
    if ( p == NULL )
    {
        /* we're first in the list */
        _scb->hash[mi] = n;
    }
    else
    {
        n->next = p->next;
        p->next = n;
    }

    return mi;
}

/* Returns a NULL string if the error wasn't defined. */
char*
masc_statstring_temp( int32 err, int32 mi )
{
    struct _statstring_node *n;

    err *= -1; /* flip error to positive to use as array index */

    if ( mi < 0 || mi >= _scb->slots || _scb->hash[mi] == NULL )
        return NULL;

    /* seek through the list */
    for ( n = _scb->hash[mi]; n != NULL; n = n->next )
    {
        if ( ( err >= n->offset ) && ( err < ( n->offset + n->cnt ) ) )
            return n->statstring[err - n->offset];
    }

    return NULL;
}

void
masc_statstring_log_temp( int32 loglevel, int32 err, int32 mi, char* message )
{
    char* statstring;

    statstring = masc_statstring_temp( err, mi );
    if ( statstring == NULL )
        statstring = "Status code not defined";

    masc_log_message( loglevel, "%s: %s", message, statstring );

    return;
}

int32
_setup_statstring_module( int32 mi )
{
    void *tptr;
    
    /* If we're out of space in the hash, realloc it using the *2
     * tactic. */
    if ( _scb->entries >= _scb->slots )
    {
        _scb->slots *= 2;
        tptr = masc_rtrealloc( _scb->hash, _scb->slots * sizeof (void*) );
        if ( tptr == NULL )
            return MAS_EMEMEX;
        _scb->hash = tptr;

        /* initialize the new portions of the array */
        memset( _scb->hash + _scb->slots/2, 0, _scb->slots/2 * sizeof (void*) );
    }

    /* Check to see if the caller specifically requested a module
       index.  Grant it if there's no entries in the hash at that
       module index and it's not out of range. */
    if ( mi >= _scb->slots )
        return MAS_ERANGE;

    if ( mi >= 0 )
    {
        if (_scb->hash[mi] == NULL )
        {
            return mi;
        }
        else
        {
            return MAS_EUSED;
        }
    }
    
    /* find the next empty slot */
    while ( _scb->hash[_scb->last] != NULL )
    {
        _scb->last = (++_scb->last) % _scb->slots;
    }

    return _scb->last;
}

int32
_strike_statstring_module(int32 mi)
{
    struct _statstring_node *n, *nextn;

    if ( mi < 0 || mi >= _scb->slots )
        return MAS_ERANGE;

    if ( _scb->hash[mi] == NULL )
        return MAS_EPTRNULL;

    /* does not free contents */
    for ( n = _scb->hash[mi]; n != 0; n = nextn )
    {
        nextn = n->next;
        masc_rtfree( n );
    }
    
    _scb->hash[mi] = NULL;

    return 0;
}
