/* ----------------------------------------------------------------------------- 
 * This file is part of SWIG, which is licensed as a whole under version 3 
 * (or any later version) of the GNU General Public License. Some additional
 * terms also apply to certain portions of SWIG. The full details of the SWIG
 * license and copyrights can be found in the LICENSE and COPYRIGHT files
 * included with the SWIG source code as distributed by the SWIG developers
 * and at https://www.swig.org/legal.html.
 *
 * error.c
 *
 * Error handling functions.   These are used to issue warnings and
 * error messages.
 * ----------------------------------------------------------------------------- */

#include "swig.h"
#include <stdarg.h>
#include <ctype.h>

/* -----------------------------------------------------------------------------
 * Commentary on the warning filter.
 *
 * The warning filter is a string of numbers prefaced by (-) or (+) to
 * indicate whether or not a warning message is displayed.  For example:
 *
 *      "-304-201-140+210+201"
 *
 * The filter string is scanned left to right and the first occurrence
 * of a warning number is used to determine printing behavior.
 *
 * The same number may appear more than once in the string.  For example, in the 
 * above string, "201" appears twice.  This simply means that warning 201
 * was disabled after it was previously enabled.  This may only be temporary
 * setting--the first number may be removed later in which case the warning
 * is reenabled.
 * ----------------------------------------------------------------------------- */

#if defined(_WIN32)
#  define  DEFAULT_ERROR_MSG_FORMAT EMF_MICROSOFT
#else
#  define  DEFAULT_ERROR_MSG_FORMAT EMF_STANDARD
#endif
static ErrorMessageFormat msg_format = DEFAULT_ERROR_MSG_FORMAT;
static int silence = 0;		/* Silent operation */
static String *filter = 0;	/* Warning filter */
static int warnall = 0;
static int nwarning = 0;
static int nerrors = 0;

static int init_fmt = 0;
static char wrn_wnum_fmt[64];
static char wrn_nnum_fmt[64];
static char err_line_fmt[64];
static char err_eof_fmt[64];
static char diag_line_fmt[64];
static char diag_eof_fmt[64];

static String *format_filename(const_String_or_char_ptr filename);

/* -----------------------------------------------------------------------------
 * Swig_warning()
 *
 * Issue a warning message on stderr.
 * ----------------------------------------------------------------------------- */

void Swig_warning(int wnum, const_String_or_char_ptr filename, int line, const char *fmt, ...) {
  String *out;
  char *msg;
  int wrn = 1;
  va_list ap;
  if (silence)
    return;
  if (!init_fmt)
    Swig_error_msg_format(DEFAULT_ERROR_MSG_FORMAT);

  va_start(ap, fmt);

  out = NewStringEmpty();
  vPrintf(out, fmt, ap);

  msg = Char(out);
  if (isdigit((unsigned char) *msg)) {
    unsigned long result = strtoul(msg, &msg, 10);
    if (msg != Char(out)) {
      msg++;
      wnum = result;
    }
  }

  /* Check in the warning filter */
  if (filter) {
    char temp[32];
    char *c;
    char *f = Char(filter);
    sprintf(temp, "%d", wnum);
    while (*f != '\0' && (c = strstr(f, temp))) {
      if (*(c - 1) == '-') {
	wrn = 0;		/* Warning disabled */
        break;
      }
      if (*(c - 1) == '+') {
	wrn = 1;		/* Warning enabled */
        break;
      }
      f += strlen(temp);
    }
  }
  if (warnall || wrn) {
    String *formatted_filename = format_filename(filename);
    String *full_message = NewString("");
    if (wnum) {
      Printf(full_message, wrn_wnum_fmt, formatted_filename, line, wnum);
    } else {
      Printf(full_message, wrn_nnum_fmt, formatted_filename, line);
    }
    Printf(full_message, "%s", msg);
    Printv(stderr, full_message, NIL);
    nwarning++;
    Delete(full_message);
    Delete(formatted_filename);
  }
  Delete(out);
  va_end(ap);
}

/* -----------------------------------------------------------------------------
 * Swig_error()
 *
 * Issue an error message on stderr.
 * ----------------------------------------------------------------------------- */

void Swig_error(const_String_or_char_ptr filename, int line, const char *fmt, ...) {
  va_list ap;
  String *formatted_filename = NULL;
  String *full_message = NULL;

  if (silence)
    return;
  if (!init_fmt)
    Swig_error_msg_format(DEFAULT_ERROR_MSG_FORMAT);

  va_start(ap, fmt);
  formatted_filename = format_filename(filename);
  full_message = NewString("");
  if (line > 0) {
    Printf(full_message, err_line_fmt, formatted_filename, line);
  } else {
    Printf(full_message, err_eof_fmt, formatted_filename);
  }
  vPrintf(full_message, fmt, ap);
  Printv(stderr, full_message, NIL);
  va_end(ap);
  nerrors++;
  Delete(full_message);
  Delete(formatted_filename);
}

/* -----------------------------------------------------------------------------
 * Swig_error_count()
 *
 * Returns number of errors received.
 * ----------------------------------------------------------------------------- */

int Swig_error_count(void) {
  return nerrors;
}

/* -----------------------------------------------------------------------------
 * Swig_error_silent()
 *
 * Set silent flag
 * ----------------------------------------------------------------------------- */

void Swig_error_silent(int s) {
  silence = s;
}


/* -----------------------------------------------------------------------------
 * Swig_warnfilter()
 *
 * Takes a comma separate list of warning numbers and puts in the filter.
 * ----------------------------------------------------------------------------- */

void Swig_warnfilter(const_String_or_char_ptr wlist, int add) {
  char *c;
  char *cw;
  String *s;
  if (!filter)
    filter = NewStringEmpty();

  s = NewString("");
  cw = Char(wlist);
  while (*cw != '\0') {
    if (*cw != ' ') {
      Putc(*cw, s);
    }
    ++cw;
  }
  c = Char(s);
  c = strtok(c, ", ");
  while (c) {
    if (isdigit((int) *c) || (*c == '+') || (*c == '-')) {
      /* Even if c is a digit, the rest of the string might not be, eg in the case of typemap 
       * warnings (a bit odd really), eg: %warnfilter(SWIGWARN_TYPEMAP_CHARLEAK_MSG) */
      if (add) {
	Insert(filter, 0, c);
	if (isdigit((int) *c)) {
	  Insert(filter, 0, "-");
	}
      } else {
	char *temp = (char *)Malloc(sizeof(char)*strlen(c) + 2);
	if (isdigit((int) *c)) {
	  sprintf(temp, "-%s", c);
	} else {
	  strcpy(temp, c);
	}
	Replace(filter, temp, "", DOH_REPLACE_FIRST);
	Free(temp);
      }
    }
    c = strtok(NULL, ", ");
  }
  Delete(s);
}

void Swig_warnall(void) {
  warnall = 1;
}


/* ----------------------------------------------------------------------------- 
 * Swig_warn_count()
 *
 * Return the number of warnings
 * ----------------------------------------------------------------------------- */

int Swig_warn_count(void) {
  return nwarning;
}

/* -----------------------------------------------------------------------------
 * Swig_error_msg_format()
 *
 * Set the type of error/warning message display
 * ----------------------------------------------------------------------------- */

void Swig_error_msg_format(ErrorMessageFormat format) {
  const char *error = "Error";
  const char *warning = "Warning";

  const char *fmt_eof = 0;
  const char *fmt_line = 0;

  /* here 'format' could be directly a string instead of an enum, but
     by now a switch is used to translated into one. */
  switch (format) {
  case EMF_MICROSOFT:
    fmt_line = "%s(%d) ";
    fmt_eof = "%s(999999) ";	/* Is there a special character for EOF? Just use a large number. */
    break;
  case EMF_STANDARD:
  default:
    fmt_line = "%s:%d";
    fmt_eof = "%s:EOF";
  }

  sprintf(wrn_wnum_fmt, "%s: %s %%d: ", fmt_line, warning);
  sprintf(wrn_nnum_fmt, "%s: %s: ", fmt_line, warning);
  sprintf(err_line_fmt, "%s: %s: ", fmt_line, error);
  sprintf(err_eof_fmt, "%s: %s: ", fmt_eof, error);
  sprintf(diag_line_fmt, "%s: ", fmt_line);
  sprintf(diag_eof_fmt, "%s: ", fmt_eof);

  msg_format = format;
  init_fmt = 1;
}

/* -----------------------------------------------------------------------------
 * format_filename()
 *
 * Remove double backslashes in Windows filename paths for display
 * ----------------------------------------------------------------------------- */
static String *format_filename(const_String_or_char_ptr filename) {
  String *formatted_filename = NewString(filename);
#if defined(_WIN32)
  Replaceall(formatted_filename, "\\\\", "\\");
#endif
  return formatted_filename;
}

/* -----------------------------------------------------------------------------
 * Swig_stringify_with_location()
 *
 * Return a string representation of any DOH object with line and file location
 * information in the appropriate error message format. The string representation
 * is enclosed within [] brackets after the line and file information.
 * ----------------------------------------------------------------------------- */

String *Swig_stringify_with_location(DOH *object) {
  String *str = NewStringEmpty();

  if (!init_fmt)
    Swig_error_msg_format(DEFAULT_ERROR_MSG_FORMAT);

  if (object) {
    int line = Getline(object);
    String *formatted_filename = format_filename(Getfile(object));
    if (line > 0) {
      Printf(str, diag_line_fmt, formatted_filename, line);
    } else {
      Printf(str, diag_eof_fmt, formatted_filename);
    }
    if (Len(object) == 0) {
      Printf(str, "[EMPTY]");
    } else {
      Printf(str, "[%s]", object);
    }
    Delete(formatted_filename);
  } else {
    Printf(str, "[NULL]");
  }

  return str;
}

/* -----------------------------------------------------------------------------
 * Swig_diagnostic()
 *
 * Issue a diagnostic message on stdout.
 * ----------------------------------------------------------------------------- */

void Swig_diagnostic(const_String_or_char_ptr filename, int line, const char *fmt, ...) {
  va_list ap;
  String *formatted_filename = NULL;

  if (!init_fmt)
    Swig_error_msg_format(DEFAULT_ERROR_MSG_FORMAT);

  va_start(ap, fmt);
  formatted_filename = format_filename(filename);
  if (line > 0) {
    Printf(stdout, diag_line_fmt, formatted_filename, line);
  } else {
    Printf(stdout, diag_eof_fmt, formatted_filename);
  }
  vPrintf(stdout, fmt, ap);
  va_end(ap);
  Delete(formatted_filename);
}

