/* Copyright 1995, 1996 by Abacus Research and
 * Development, Inc.  All rights reserved.
 */

#if !defined (OMIT_RCSID_STRINGS)
char ROMlib_rcsid_error[] =
	    "$Id: error.c 76 2005-01-04 23:45:03Z ctm $";
#endif

#include "rsys/common.h"

#include <stdarg.h>
#include "SysErr.h"
#include "SegmentLdr.h"
#include "CQuickDraw.h"

#include "rsys/vdriver.h"
#include "rsys/option.h"
#include "rsys/system_error.h"
#include "rsys/soundopts.h"
#include "rsys/version.h"
#include "rsys/mman.h"
#include "rsys/flags.h"

/* This is a bit mask containing the bitwise OR of the various
 * ERROR_..._BIT flags in rsys/error.h.
 */
uint32_t ROMlib_debug_level;


static const struct
{
  const char *name;
  uint32_t bit_mask;
}
error_options[] =
{
  { "all",           ~0				   		    },
  { "fast",	  (  ERROR_BIT_MASK (ERROR_FILESYSTEM_LOG)
		   | ERROR_BIT_MASK (ERROR_TRACE_INFO)
		   | ERROR_BIT_MASK (ERROR_TRAP_FAILURE)
		   | ERROR_BIT_MASK (ERROR_ERRNO)
		   | ERROR_BIT_MASK (ERROR_UNEXPECTED)
	           | ERROR_BIT_MASK (ERROR_UNIMPLEMENTED))          },
  { "fslog",         ERROR_BIT_MASK (ERROR_FILESYSTEM_LOG)	    },
  { "memcheck",      ERROR_BIT_MASK (ERROR_MEMORY_MANAGER_SLAM)     },
  { "textcheck",     ERROR_BIT_MASK (ERROR_TEXT_EDIT_SLAM)          },
  { "trace",         ERROR_BIT_MASK (ERROR_TRACE_INFO)              },
  { "trapfailure",   ERROR_BIT_MASK (ERROR_TRAP_FAILURE)            },
  { "errno",         ERROR_BIT_MASK (ERROR_ERRNO)                   },
  { "unexpected",    ERROR_BIT_MASK (ERROR_UNEXPECTED)              },
  { "unimplemented", ERROR_BIT_MASK (ERROR_UNIMPLEMENTED)           },
  { "sound",	     ERROR_BIT_MASK (ERROR_SOUND_LOG)               },
  { "float",	     ERROR_BIT_MASK (ERROR_FLOATING_POINT)          },
};
  
/* An option string is a sequence of comma-separated identifiers that
 * specify which debugging options should be enabled.  An example
 * would be something like "trace,unimp,memcheck".  Unambiguous prefixes
 * for the canonical flag names may be used.  This function turns
 * on the corresponding bit(s) in ROMlib_debug_level for each specified
 * option, or turns them off if the option is specified with a leading
 * "-".  For example, the string "all,-mem" will turn on all debugging,
 * and then turn off memory checking.
 *
 * Returns TRUE iff successful, FALSE if there was an error.
 */
bool
error_parse_option_string (const char *options)
{
  const char *p, *options_end;
  char *option_name;
  int opt_len;
  bool success_p;

  success_p = TRUE;
  option_name = alloca (strlen (options) + 1);  /* big enough for any option.*/
  options_end = options + strlen (options);

  /* Munch through the option string, extracting out the comma-separated
   * fields and looking them up in our table.
   */
  for (p = options; p < options_end; p += opt_len + 1 /* skip comma */)
    {
      const char *id_end;

      /* Find the end of this string. */
      id_end = strchr (p, ',');
      if (id_end == NULL)
	id_end = options_end;
      opt_len = id_end - p;
      
      if (opt_len == 0)
	{
	  fprintf (stderr, "Empty debug option specified.\n");
	  success_p = FALSE;
	}
      else
	{
	  int i, match = -1;
	  bool on_p;

	  /* You can specify a leading "-" to turn an option off. */
	  if (p[0] == '-')
	    {
	      ++p;
	      --opt_len;
	      option_name[opt_len - 1] = '\0';
	      on_p = FALSE;
	    }
	  else
	    {
	      on_p = TRUE;
	    }

	  strncpy (option_name, p, opt_len);
	  option_name[opt_len] = '\0';

	  /* Look for any matching option.  Unique prefixes are OK,
	   * so "unimp" matches "unimplemented", etc.
	   */
	  for (i = 0; i < (int) NELEM (error_options); i++)
	    if (!strncmp (error_options[i].name, option_name, opt_len))
	      {
		if (match >= 0)
		  fprintf (stderr, "Ambiguous debug option `%s' specified; "
			   "could be %s or %s.\n",
			   option_name, error_options[match].name,
			   error_options[i].name);
		else
		  {
		    match = i;
		    if (on_p)
		      ROMlib_debug_level |= error_options[i].bit_mask;
		    else
		      ROMlib_debug_level &= ~error_options[i].bit_mask;
		  }
	      }

	  if (match == -1)
	    {
	      fprintf (stderr, "Unknown debugging option `%s'.\n",
		       option_name);
	      success_p = FALSE;
	    }
	}
    }

  return success_p;
}


/* Enables or disables the specified error and returns its old enabledness. */
bool
error_set_enabled (int err, bool enabled_p)
{
  bool old_enabled_p = ERROR_ENABLED_P (err);

  if (enabled_p)
    ROMlib_debug_level |= ERROR_BIT_MASK (err);
  else
    ROMlib_debug_level &= ~ERROR_BIT_MASK (err);

  return old_enabled_p;
}


static const char *
notdir (const char *file)
{
  const char *retval;
  
  retval = strrchr (file, '/');
  return retval ? &retval[1] : file;
}

#define ERR_LOG_FILE	"/tmp/err.txt"

#if defined (MSDOS) || defined (VDRIVER_SVGALIB)
static FILE *err_log_fp;
#endif

static FILE *
get_stream (void)
{
#if defined (MSDOS) || defined (VDRIVER_SVGALIB)
  if (opt_val (common_db, "logerr", NULL))
    {
      if (err_log_fp == NULL)
	{
	  /* if err_log_fp is NULL, then we have called get_stream for
	     the first time, so we want to open the log file truncated */
	  err_log_fp = fopen (ERR_LOG_FILE, "w"); /* NOT Ufopen */
	  if (err_log_fp)
	    return err_log_fp;
	}
      else
	return err_log_fp;
    }
  return stdout;
#else
  return stderr;
#endif
}

static void
flush_stream (void)
{
#if defined (MSDOS) || defined (VDRIVER_SVGALIB)
  FILE *fp = get_stream ();
  if (fp == err_log_fp)
    {
      /* this effectively does an `fflush ()', but mat says that
         closing and reopening the file under dos maybe be more robust
         in the case of a catostrophic dos crash */
      fclose (err_log_fp);
      err_log_fp = fopen (ERR_LOG_FILE, "a"); /* NOT Ufopen */
    }
#endif
}

static void err_printf (const char *fmt, ...);

#if defined (SUPPORT_LOG_ERR_TO_RAM)

/* TRUE iff we should try to log errors to RAM when possible. */
bool log_err_to_ram_p;

/* Current number of characters in the err buf, not counting the "\0". */
static uint32_t ram_err_buf_size;

/* Maximum # of bytes that can be stored in RAM buffer. */
static uint32_t ram_err_buf_max_size;

/* Actual current text of RAM err buf. */
static char *ram_err_buf;

/* Dumps the current RAM error buf and clears it. */
void
error_dump_ram_err_buf (const char *separator_message)
{
  if (ram_err_buf_size > 0)
    {
      bool old_log_p;
      
      old_log_p = log_err_to_ram_p;
      log_err_to_ram_p = FALSE;	/* print it, don't log it! */
      err_printf ("%s%s", ram_err_buf, separator_message);
      log_err_to_ram_p = old_log_p;

      /* Clear the RAM buffer and start over. */
      ram_err_buf_size = 0;
      ram_err_buf_max_size = 0;
      ram_err_buf = NULL;
      free (ram_err_buf);
    }
}

static void
error_dump_at_exit (void)
{
  error_dump_ram_err_buf ("\n*** ATEXIT ***\n");
}

#endif /* SUPPORT_LOG_ERR_TO_RAM */

static void
err_vprintf (const char *fmt, va_list ap)
{
  static bool beenhere_p = FALSE;
  FILE *fp;

  fp = get_stream ();

#define MB (1024 * 1024U)

  /* Start any log with the current Executor version. */
  if (!beenhere_p)
    {
      fprintf (fp,
	       "This is %s, compiled %s.\n"
	       "Using %u.%02u MB for applzone, "
	       "%u.%02u MB for syszone, %u.%02u MB for stack\n"
	       "Approximate command line: %s\n",
	       ROMlib_executor_full_name, ROMlib_executor_build_time,
	       ROMlib_applzone_size / MB,
	       (ROMlib_applzone_size % MB) * 100 / MB,
	       ROMlib_syszone_size / MB,
	       (ROMlib_syszone_size % MB) * 100 / MB,
	       ROMlib_stack_size / MB,
	       (ROMlib_stack_size % MB) * 100 / MB,
	       ROMlib_command_line);
      beenhere_p = TRUE;
    }

  if (fmt == NULL)
    fmt = "";
  
#if !defined (SUPPORT_LOG_ERR_TO_RAM)
  vfprintf (fp, fmt, ap);
  fflush (fp);
#else /* defined (SUPPORT_LOG_ERR_TO_RAM) */
  if (!log_err_to_ram_p)
    {
      vfprintf (fp, fmt, ap);
      fflush (fp);
    }
  else
    {
      static bool atexit_set_up = FALSE;

      /* Set ourselves up to dump everything on exit. */
      if (!atexit_set_up)
	{
	  atexit (error_dump_at_exit);
	  atexit_set_up = TRUE;
	}

      /* Make sure we've got at least 64K free for the upcoming sprintf.
       * There's no easy way to know how much memory we're going to need.
       */
      if (ram_err_buf_size + 65536 >= ram_err_buf_max_size)
	{
	  void *new_buf;

	  if (ram_err_buf_max_size < 65536)
	    ram_err_buf_max_size = 128 * 1024 - 32;
	  else
	    ram_err_buf_max_size *= 2;
	  
	  /* Try to allocate more room for the RAM buffer. */
	  new_buf = realloc (ram_err_buf, ram_err_buf_max_size);
	  if (new_buf == NULL)
	    {
	      /* Out of memory!  Dump what we've got and start over. */
	      error_dump_ram_err_buf ("\n *** Out of RAM, flushing log ***\n");
	      vfprintf (fp, fmt, ap);
	      goto done;
	    }
	  else
	    {
	      ram_err_buf = new_buf;
	    }
	}

      vsprintf (ram_err_buf + ram_err_buf_size, fmt, ap);
      ram_err_buf_size += strlen (ram_err_buf + ram_err_buf_size);
    }
 done:;
#endif /* SUPPORT_LOG_ERR_TO_RAM */
}

static void
err_printf (const char *fmt, ...)
{
  va_list ap;
  va_start (ap, fmt);
  err_vprintf (fmt, ap);
  va_end (ap);
}

void
_gui_fatal (const char *file, int line, const char *fn,
	    const char *fmt, ...)
{
  va_list ap;
  char errbuf[10240];
  
  if (fmt == NULL)
    fmt = "";
  
  va_start (ap, fmt);
  vsprintf (errbuf, fmt, ap);
  va_end (ap);
  
  if (WWExist == EXIST_YES)
    {
      char buf[10240];
      
      /* Make sure the screen is sane. */
      SetGDevice (MR (MainDevice));
      RestoreClutDevice (MR (MainDevice));
      
      sprintf (buf,
	       "Fatal error.\r"
	       "%s:%d; in `%s'\r"
	       "%s", notdir (file), line, fn, errbuf);
      
      system_error (buf, 0,
		    "Restart", NULL, NULL,
		    NULL, NULL, NULL);
    }
  
  /* also sent output to the debug stream */
  err_printf ("%s:%d; fatal error in `%s': %s\n",
	      notdir (file), line, fn, errbuf);
  flush_stream ();
  
  ExitToShell ();

  exit (-1);  /* Convince gcc of __attribute__ ((noreturn)) status. */
}

void
_warning (int error_type, const char *type, const char *file, int line,
	  const char *fn, const char *fmt, ...)
{
  va_list ap;

  if (ERROR_ENABLED_P (error_type))
    {
      if (fmt == NULL)
	fmt = "";
  
      err_printf ("%s:%d; %s in `%s': ",
		  notdir (file), line, type, fn);
      
      va_start (ap, fmt);
      err_vprintf (fmt, ap);
      va_end (ap);
  
      err_printf ("\n");
      flush_stream ();
    }
}


#if ERROR_SUPPORTED_P (ERROR_SOUND_LOG)
void
_sound_warning (const char *file, int line,
		const char *fn, const char *fmt, ...)
{
  va_list ap;

  if (ERROR_ENABLED_P (ERROR_SOUND_LOG))
    {
      const char *snd_state;

      if (fmt == NULL)
	fmt = "";
  
      err_printf ("%s:%d; sound log in `%s': ",
		  notdir (file), line, fn);

      switch (ROMlib_PretendSound)
	{
	case soundoff:
	  snd_state = "off";
	  break;
	case soundon:
	  snd_state = "on";
	  break;
	case soundpretend:
	  snd_state = "pretend";
	  break;
	default:
	  snd_state = "???";
	  break;
	}

      err_printf ("[sound=%s] ", snd_state);
      
      va_start (ap, fmt);
      err_vprintf (fmt, ap);
      va_end (ap);
  
      err_printf ("\n");
      flush_stream ();
    }
}
#endif /* ERROR_SUPPORTED_P (ERROR_SOUND_LOG) */

void
_errno_fatal (const char *file, int line, const char *fn,
	      const char *fmt, ...)
{
  va_list ap;
  int save_errno = errno;
  
  if (fmt == NULL)
    fmt = "";
  
  vdriver_shutdown ();
  
  err_printf ("%s:%d; fatal error in `%s': ",
	      notdir (file), line, fn);
  
  va_start (ap, fmt);
  err_vprintf (fmt, ap);
  va_end (ap);
  
  err_printf (": %s\n", strerror (save_errno));

  flush_stream ();
  
  exit (-1);
}

void
_errno_warning (const char *file, int line, const char *fn,
		const char *fmt, ...)
{
  va_list ap;
  int save_errno = errno;
  
  if (fmt == NULL)
    fmt = "";
  
  err_printf ("%s:%d; warning in `%s': ",
	      notdir (file), line, fn);
  
  va_start (ap, fmt);
  err_vprintf (fmt, ap);
  va_end (ap);
  
  err_printf (": %s\n", strerror (save_errno));

  flush_stream ();
}

const char NULL_STRING[] = ""; /* used to avoid bogus gcc warnings */
