/*
 * Copyright 2001-2004 Brandon Long
 * All Rights Reserved.
 *
 * ClearSilver Templating System
 *
 * This code is made available under the terms of the ClearSilver License.
 * http://www.clearsilver.net/license.hdf
 *
 */

#ifndef __NEO_ERR_H_
#define __NEO_ERR_H_ 1

#include "util/neo_misc.h"

/* For compilers (well, cpp actually) which don't define __PRETTY_FUNCTION__ */
#ifndef __GNUC__
#define __PRETTY_FUNCTION__ "unknown_function"
#endif

__BEGIN_DECLS

/* For 64 bit systems which don't like mixing ints and pointers, we have the
 * _INT version for doing that comparison */
#define STATUS_OK ((NEOERR *)0)
#define STATUS_OK_INT 0
#define INTERNAL_ERR ((NEOERR *)1)
#define INTERNAL_ERR_INT 1

/* NEOERR flags */
#define NE_IN_USE (1<<0)

typedef int NERR_TYPE;

/* Predefined Error Types - These are all registered in nerr_init */
extern NERR_TYPE NERR_PASS;
extern NERR_TYPE NERR_ASSERT;
extern NERR_TYPE NERR_NOT_FOUND;
extern NERR_TYPE NERR_DUPLICATE;
extern NERR_TYPE NERR_NOMEM;
extern NERR_TYPE NERR_PARSE;
extern NERR_TYPE NERR_OUTOFRANGE;
extern NERR_TYPE NERR_SYSTEM;
extern NERR_TYPE NERR_IO;
extern NERR_TYPE NERR_LOCK;
extern NERR_TYPE NERR_DB;
extern NERR_TYPE NERR_EXISTS;

typedef struct _neo_err 
{
  int error;
  int err_stack;
  int flags;
  char desc[256];
  const char *file;
  const char *func;
  int lineno;
  /* internal use only */
  struct _neo_err *next;
} NEOERR;

/* Technically, we could do this in configure and detect what their compiler
 * can handle, but for now... */
#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
#define USE_C99_VARARG_MACROS 1
#elif __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 4) || defined (S_SPLINT_S)
#define USE_GNUC_VARARG_MACROS 1
#else
#error The compiler is missing support for variable-argument macros.
#endif


/*
 * function: nerr_raise
 * description: Use this method to create an error "exception" for
 *              return up the call chain
 * arguments: using the macro, the function name, file, and lineno are
 *            automagically recorded for you.  You just provide the
 *            error (from those listed above) and the printf-style
 *            reason.  THIS IS A PRINTF STYLE FUNCTION, DO NOT PASS
 *            UNKNOWN STRING DATA AS THE FORMAT STRING.
 * returns: a pointer to a NEOERR, or INTERNAL_ERR if allocation of
 *          NEOERR fails
 */
#if defined(USE_C99_VARARG_MACROS)
#define nerr_raise(e,f,a...) \
   nerr_raisef(__PRETTY_FUNCTION__,__FILE__,__LINE__,e,f,##a)
#endif

NEOERR *nerr_raisef (const char *func, const char *file, int lineno,
                     NERR_TYPE error, const char *fmt, ...)
                     ATTRIBUTE_PRINTF(5,6);
  


#if defined(USE_C99_VARARG_MACROS)
#define nerr_raise_errno(e,f,a...) \
   nerr_raise_errnof(__PRETTY_FUNCTION__,__FILE__,__LINE__,e,f,##a)
#endif

NEOERR *nerr_raise_errnof (const char *func, const char *file, int lineno,
                           int error, const char *fmt, ...)
                           ATTRIBUTE_PRINTF(5,6);

/* function: nerr_pass
 * description: this function is used to pass an error up a level in the
 *              call chain (ie, if the error isn't handled at the
 *              current level).  This allows us to track the traceback
 *              of the error.
 * arguments: with the macro, the function name, file and lineno are
 *            automagically recorded.  Just pass the error.
 * returns: a pointer to an error
 */
#define nerr_pass(e) \
   nerr_passf(__PRETTY_FUNCTION__,__FILE__,__LINE__,e)

NEOERR *nerr_passf (const char *func, const char *file, int lineno,
                    NEOERR *err);

/* function: nerr_pass_ctx
 * description: this function is used to pass an error up a level in the
 *              call chain (ie, if the error isn't handled at the
 *              current level).  This allows us to track the traceback
 *              of the error.
 *              This version includes context information about lower
 *              errors
 * arguments: with the macro, the function name, file and lineno are
 *            automagically recorded.  Just pass the error and
 *            a printf format string giving more information about where
 *            the error is occuring.
 * returns: a pointer to an error
 */
#if defined(USE_C99_VARARG_MACROS)
#define nerr_pass_ctx(e,f,...) \
   nerr_pass_ctxf(__PRETTY_FUNCTION__,__FILE__,__LINE__,e,f,__VA_ARGS__)
#elif defined(USE_GNUC_VARARG_MACROS)
#define nerr_pass_ctx(e,f,a...) \
   nerr_pass_ctxf(__PRETTY_FUNCTION__,__FILE__,__LINE__,e,f,##a)
#endif

NEOERR *nerr_pass_ctxf (const char *func, const char *file, int lineno,
                        NEOERR *err, const char *fmt, ...)
                        ATTRIBUTE_PRINTF(5,6);

/* function: nerr_log_error
 * description: currently, this prints out the error to stderr, and
 *             free's the error chain
 */
void nerr_log_error (NEOERR *err);

#include "util/neo_str.h"
/* function: nerr_error_string
 * description: returns the string associated with an error (the bottom
 *              level of the error chain)
 * arguments: err - error
 *            str - string to which the data is appended
 * returns: None - errors appending to the string are ignored
 */
void nerr_error_string (NEOERR *err, STRING *str);

/* function: nerr_error_traceback
 * description: returns the full traceback of the error chain
 * arguments: err - error
 *            str - string to which the data is appended
 * returns: None - errors appending to the string are ignored
 */
void nerr_error_traceback (NEOERR *err, STRING *str);

/* function: nerr_ignore
 * description: you should only call this if you actually handle the
 *              error (should I rename it?).  Free's the error chain.
 */
void nerr_ignore (NEOERR **err);

/* function: nerr_register
 * description: register an error type.  This will assign a numeric value
 *              to the type, and keep track of the "pretty name" for it.
 * arguments: err - pointer to a NERR_TYPE
 *            name - pretty name for the error type
 * returns: NERR_NOMEM on no memory
 */
NEOERR *nerr_register (NERR_TYPE *err, const char *name);

/* function: nerr_init
 * description: initialize the NEOERR system.  Can be called more than once.
 *              Is not thread safe.  This registers all of the built in
 *              error types as defined at the top of this file.  If you don't
 *              call this, all exceptions will be returned as UnknownError.
 * arguments: None
 * returns: possibly NERR_NOMEM, but somewhat unlikely.  Possibly an
 *          UnknownError if NERR_NOMEM hasn't been registered yet.
 */
NEOERR *nerr_init (void);

/* function: nerr_match
 * description: nerr_match is used to walk the NEOERR chain and match
 *              the error against a specific error type.  In exception
 *              parlance, this would be the equivalent of "catch".
 *              Typically, you can just compare a NEOERR against STATUS_OK
 *              or just test for true if you are checking for any error.
 * arguments: err - the NEOERR that has an error.
 *            type - the NEOERR type, as registered with nerr_register
 * returns: true on match
 */
int nerr_match (NEOERR *err, NERR_TYPE type);

/* function: nerr_handle
 * description: nerr_handle is a convenience function.  It is the equivalent
 *              of nerr_match, but it will also deallocate the error chain
 *              on a match.
 * arguments: err - pointer to a pointer NEOERR
 *            type - the NEOERR type, as registered with nerr_register
 * returns: true on match
 */
int nerr_handle (NEOERR **err, NERR_TYPE type);

__END_DECLS

#endif /* __NEO_ERR_H_ */
