/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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/>.
 *
 */

/* Library error handling definitions
 * All of the functions and macros in here are named ERRxxx because
 * this file was based on the TADS err.h, which used the ERRxxx naming
 * convention, and it would be a lot of trouble to change.
 *
 * This package defines a set of macros that allows code to raise and
 * handle exceptions.  A macro is provided which signals an error, which
 * does a non-local goto to the innermost enclosing exception handler.
 * A set of macros sets up exception handling code.
 *
 * To catch exceptions that occur inside a block of code (i.e., in the
 * code or in any subroutines called by the code), begin the block with
 * ERRBEGIN.  At the end of the protected code, place the exception
 * handler, which starts with ERRCATCH.  At the end of the exception
 * handler, place ERREND.  If no exception occurs, execution goes
 * through the protected code, then resumes at the code following
 * the ERREND.
 *
 * The exception handler can signal another error, which will cause
 * the next enclosing frame to catch the error.  Alternatively, if
 * the exception handler doesn't signal an error or return, execution
 * continues at the code following the ERREND.  Exceptions that are
 * signalled during exception handling will be caught by the next
 * enclosing frame, unless the exception handler code is itself
 * protected by another ERRBEGIN-ERREND block.
 *
 * To signal an error, use errsig().
 *
 * To use a string argument in a signalled error, cover the string
 * with errstr(ctx, str, len); for example:
 *
 * errsig1(ctx, ERR_XYZ, ERRTSTR, errstr(ctx, buf, strlen(buf)));
 *
 * This copies the string into a buffer that is unaffected by
 * stack resetting during error signalling.
 */

#ifndef GLK_TADS_TADS2_ERROR_HANDLING
#define GLK_TADS_TADS2_ERROR_HANDLING

#include "glk/tads/tads2/lib.h"
#include "glk/tads/os_frob_tads.h"

namespace Glk {
namespace TADS {
namespace TADS2 {

/**
 * Maximum length of a facility identifier
 */
#define ERRFACMAX    6

union erradef {
	int   erraint;                                      /* integer argument */
	const char *errastr;                            /* text string argument */
};

struct errdef {
	struct errdef *errprv;                          /* previous error frame */
	int            errcode;        /* error code of exception being handled */
	char           errfac[ERRFACMAX+1];        /* facility of current error */
	erradef        erraav[10];                      /* parameters for error */
	int            erraac;                   /* count of parameters in argc */
};

#define ERRBUFSIZ 512

/**
 * Seek location record for an error message by number
 */
struct errmfdef {
	uint  errmfnum;                                         /* error number */
	ulong errmfseek;                       /* seek location of this message */
};
typedef struct errmfdef errmfdef;

struct errcxdef {
	errdef   *errcxptr;                              /* current error frame */
	void    (*errcxlog)(void *, const char *fac, int err, int argc, erradef *);
										 /* error logging callback function */
	void     *errcxlgc;               /* context for error logging callback */
	int       errcxofs;                        /* offset in argument buffer */
	char      errcxbuf[ERRBUFSIZ];            /* space for argument strings */
	osfildef *errcxfp;                /* message file, if one is being used */
	errmfdef *errcxseek;              /* seek locations of messages in file */
	uint      errcxsksz;                         /* size of errcxseek array */
	ulong     errcxbase;   /* offset in physical file of logical error file */
	struct appctxdef *errcxappctx;              /* host application context */
};

/**
 * Begin protected code
 */
#define ERRBEGIN(ctx) \
  { \
	errdef fr_; \
	if (1 /*(fr_.errcode = setjmp(fr_.errbuf)) == 0 */) \
	{ \
	  fr_.errprv = (ctx)->errcxptr; \
	  (ctx)->errcxptr = &fr_;

/**
 * End protected code, begin error handler
 */
#define ERRCATCH(ctx, e) \
	  assert(1==1 && (ctx)->errcxptr != fr_.errprv); \
	  (ctx)->errcxptr = fr_.errprv; \
	} \
	else \
	{ \
	  assert(2==2 && (ctx)->errcxptr != fr_.errprv); \
	  (e) = fr_.errcode; \
	  (ctx)->errcxptr = fr_.errprv;

#define ERRCATCH_ERRCODE_UNUSED(ctx) \
	  assert(1==1 && (ctx)->errcxptr != fr_.errprv); \
	  (ctx)->errcxptr = fr_.errprv; \
	} \
	else \
	{ \
	  assert(2==2 && (ctx)->errcxptr != fr_.errprv); \
	  (ctx)->errcxptr = fr_.errprv;

/* retrieve argument (int, string) in current error frame */
#define errargint(argnum) (fr_.erraav[argnum].erraint)
#define errargstr(argnum) (fr_.erraav[argnum].errastr)


#define ERREND(ctx) \
	} \
  }

/* end protected code, begin cleanup (no handling; just cleaning up) */
#define ERRCLEAN(ctx) \
	  assert((ctx)->errcxptr != fr_.errprv); \
	  (ctx)->errcxptr = fr_.errprv; \
	} \
	else \
	{ \
	  assert((ctx)->errcxptr != fr_.errprv); \
	  (ctx)->errcxptr = fr_.errprv;

#define ERRENDCLN(ctx) \
	  errrse(ctx); \
	} \
  }



/* argument types for errors with arguments */
#define ERRTINT  erraint
#define ERRTSTR  errastr

/* set argument count in error frame */
#define errargc(ctx,cnt) ((ctx)->errcxptr->erraac=(cnt))

/* enter string argument; returns pointer to argument used in errargv */
#ifdef ERR_NO_MACRO
char *errstr(errcxdef *ctx, const char *str, int len);
#else /* ERR_NO_MACRO */

#define errstr(ctx,str,len) \
  ((memcpy(&(ctx)->errcxbuf[(ctx)->errcxofs],str,(size_t)len), \
   (ctx)->errcxofs += (len), \
   (ctx)->errcxbuf[(ctx)->errcxofs++] = '\0'), \
   &(ctx)->errcxbuf[(ctx)->errcxofs-(len)-1])

#endif /* ERR_NO_MACRO */

/* set argument in error frame argument vector */
#define errargv(ctx,index,typ,arg) \
  ((ctx)->errcxptr->erraav[index].typ=(arg))

/* signal an error with argument count already set */
#ifdef ERR_NO_MACRO
void errsign(errcxdef *ctx, int e, const char *facility);
#else /* ERR_NO_MACRO */
# ifdef DEBUG
void errjmp(jump_buf buf, int e);
#  define errsign(ctx, e, fac) \
   (strncpy((ctx)->errcxptr->errfac, fac, ERRFACMAX),\
	(ctx)->errcxptr->errfac[ERRFACMAX]='\0',\
	(ctx)->errcxofs=0, errjmp((ctx)->errcxptr->errbuf, e))
# else /* DEBUG */
#  define errsign(ctx, e, fac) \
   (strncpy((ctx)->errcxptr->errfac, fac, ERRFACMAX),\
	(ctx)->errcxptr->errfac[ERRFACMAX]='\0',\
	(ctx)->errcxofs=0, longjmp((ctx)->errcxptr->errbuf, e))
# endif /* DEBUG */
#endif /* ERR_NO_MACRO */


/* signal an error with no arguments */
#ifdef ERR_NO_MACRO
void errsigf(errcxdef *ctx, const char *facility, int err);
#else /* ERR_NO_MACRO */
#define errsigf(ctx, fac, e) (errargc(ctx,0),errsign(ctx,e,fac))
#endif /* ERR_NO_MACRO */

/* signal an error with one argument */
#define errsigf1(ctx, fac, e, typ1, arg1) \
  (errargv(ctx,0,typ1,arg1),errargc(ctx,1),errsign(ctx,e,fac))

/* signal an error with two arguments */
#define errsigf2(ctx, fac, e, typ1, arg1, typ2, arg2) \
  (errargv(ctx,0,typ1,arg1), errargv(ctx,1,typ2,arg2), \
   errargc(ctx,2), errsign(ctx,e,fac))

/* resignal the current error - only usable within exception handlers */
#ifdef ERR_NO_MACRO
void errrse1(errcxdef *ctx, errdef *fr);
# define errrse(ctx) errrse1(ctx, &fr_)
#else /* ERR_NO_MACRO */

/* void errrse(errcxdef *ctx); */
# define errrse(ctx) \
  (errargc(ctx, fr_.erraac),\
   memcpy((ctx)->errcxptr->erraav, fr_.erraav, \
	(size_t)(fr_.erraac*sizeof(erradef))),\
   errsign(ctx, fr_.errcode, fr_.errfac))

#endif /* ERR_NO_MACRO */

/*
 *   For use in an error handler (ERRCATCH..ERREND) only: Copy the
 *   parameters from the error currently being handled to the enclosing
 *   frame.  This is useful when "keeping" an error being handled - i.e.,
 *   the arguments will continue to be used outside of the
 *   ERRCATCH..ERREND code.
 */
/* void errkeepargs(errcxdef *ctx); */
#define errkeepargs(ctx) errcopyargs(ctx, &fr_)

/*
 *   copy the parameters for an error from another frame into the current
 *   frame - this can be used when we want to be able to display an error
 *   that occurred in an inner frame within code that is protected by a
 *   new enclosing error frame
 */
/* void errcopyargs(errcxdef *ctx, errdef *fr); */
#define errcopyargs(ctx, fr) \
   (errargc((ctx), (fr)->erraac), \
	memcpy((ctx)->errcxptr->erraav, (fr)->erraav, \
		   (size_t)((fr)->erraac*sizeof(erradef))))

/* log error that's been caught, using arguments already caught */
#define errclog(ctx) \
 ((*(ctx)->errcxlog)((ctx)->errcxlgc,fr_.errfac,fr_.errcode,\
  fr_.erraac,fr_.erraav))

/* log an error that's been set up but not signalled yet */
#define errprelog(ctx, err) \
 ((*(ctx)->errcxlog)((ctx)->errcxlgc,(ctx)->errcxptr->errfac,\
   err,(ctx)->errcxptr->erraac,\
   (ctx)->errcxptr->erraav))

/* log an error (no signalling, just reporting) */
#ifdef ERR_NO_MACRO
void errlogn(errcxdef *ctx, int err, const char *facility);
#else /* ERR_NO_MACRO */

#define errlogn(ctx,err,fac) \
 ((ctx)->errcxofs=0,\
  (*(ctx)->errcxlog)((ctx)->errcxlgc,fac,err,(ctx)->errcxptr->erraac,\
  (ctx)->errcxptr->erraav))

#endif /* ERR_NO_MACRO */

/* log an error with no arguments */
#ifdef ERR_NO_MACRO
void errlogf(errcxdef *ctx, const char *facility, int err);
#else /* ERR_NO_MACRO */

/* void errlogf(errcxdef *ctx, char *facility, int err); */
#define errlogf(ctx,fac,err) (errargc(ctx,0),errlogn(ctx,err,fac))

#endif /* ERR_NO_MACRO */

/* log an error with one argument */
#define errlogf1(ctx, fac, e, typ1, arg1) \
 (errargv(ctx,0,typ1,arg1),errargc(ctx,1),errlogn(ctx,e,fac))

/* log an error with two arguments */
#define errlogf2(ctx, fac, e, typ1, arg1, typ2, arg2) \
 (errargv(ctx,0,typ1,arg1),errargv(ctx,1,typ2,arg2),\
  errargc(ctx,2),errlogn(ctx,e,fac))


/*
 *   Format an error message, sprintf-style, using arguments in an
 *   erradef array (which is passed to the error-logging callback).
 *   Returns the length of the output string, even if the actual
 *   output string was truncated because the outbuf was too short.
 *   (If called with outbufl == 0, nothing will be written out, but
 *   the size of the buffer needed, minus the terminating null byte,
 *   will be computed and returned.)
 */
int errfmt(char *outbuf, int outbufl, char *fmt, int argc,
		   erradef *argv);

/* get the text of an error */
void errmsg(errcxdef *ctx, char *outbuf, uint outbufl, uint err);

/* initialize error subsystem, opening error message file if necessary */
void errini(errcxdef *ctx, osfildef *fp);

/* allocate and initialize error context, free error context */
errcxdef *lerini();
void      lerfre(errcxdef *ctx);

/* error message structure - number + text */
struct errmdef {
	uint   errmerr;                                         /* error number */
	char  *errmtxt;                                /* text of error message */
};

} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk

#endif
