// -*- c-basic-offset: 2; related-file-name: "../include/lcdf/error.hh" -*-
/*
 * error.{cc,hh} -- flexible classes for error reporting
 * Eddie Kohler
 *
 * Copyright (c) 1999-2000 Massachusetts Institute of Technology
 * Copyright (c) 2001-2006 Eddie Kohler
 *
 * 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, subject to the conditions
 * listed in the Click LICENSE file. These conditions include: you must
 * preserve this copyright notice, and you cannot mention the copyright
 * holders in advertising related to the Software without their permission.
 * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
 * notice is a summary of the Click LICENSE file; the license in that file is
 * legally binding.
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <lcdf/error.hh>
#include <lcdf/straccum.hh>
#include <lcdf/hashmap.hh>
#include <errno.h>
#include <ctype.h>
#include <algorithm>
#ifndef __KERNEL__
# include <stdlib.h>
#endif

struct ErrorHandler::Conversion {
  String name;
  ConversionHook hook;
  Conversion *next;
};
static ErrorHandler::Conversion *error_items;

const int ErrorHandler::OK_RESULT = 0;
const int ErrorHandler::ERROR_RESULT = -EINVAL;

int
ErrorHandler::min_verbosity() const
{
  return 0;
}

void
ErrorHandler::debug(const char *format, ...)
{
  va_list val;
  va_start(val, format);
  verror(ERR_DEBUG, String(), format, val);
  va_end(val);
}

void
ErrorHandler::message(const char *format, ...)
{
  va_list val;
  va_start(val, format);
  verror(ERR_MESSAGE, String(), format, val);
  va_end(val);
}

int
ErrorHandler::warning(const char *format, ...)
{
  va_list val;
  va_start(val, format);
  verror(ERR_WARNING, String(), format, val);
  va_end(val);
  return ERROR_RESULT;
}

int
ErrorHandler::error(const char *format, ...)
{
  va_list val;
  va_start(val, format);
  verror(ERR_ERROR, String(), format, val);
  va_end(val);
  return ERROR_RESULT;
}

int
ErrorHandler::fatal(const char *format, ...)
{
  va_list val;
  va_start(val, format);
  verror(ERR_FATAL, String(), format, val);
  va_end(val);
  return ERROR_RESULT;
}

int
ErrorHandler::fatal(int exit_status, const char *format, ...)
{
  assert(exit_status >= 0 && exit_status < 0x1000);
  va_list val;
  va_start(val, format);
  verror((Seriousness) (ERR_MIN_FATAL + (exit_status << ERRVERBOSITY_SHIFT)), String(), format, val);
  va_end(val);
  return ERROR_RESULT;
}

void
ErrorHandler::ldebug(const String &where, const char *format, ...)
{
  va_list val;
  va_start(val, format);
  verror(ERR_DEBUG, where, format, val);
  va_end(val);
}

void
ErrorHandler::lmessage(const String &where, const char *format, ...)
{
  va_list val;
  va_start(val, format);
  verror(ERR_MESSAGE, where, format, val);
  va_end(val);
}

int
ErrorHandler::lwarning(const String &where, const char *format, ...)
{
  va_list val;
  va_start(val, format);
  verror(ERR_WARNING, where, format, val);
  va_end(val);
  return ERROR_RESULT;
}

int
ErrorHandler::lerror(const String &where, const char *format, ...)
{
  va_list val;
  va_start(val, format);
  verror(ERR_ERROR, where, format, val);
  va_end(val);
  return ERROR_RESULT;
}

int
ErrorHandler::lfatal(const String &where, const char *format, ...)
{
  va_list val;
  va_start(val, format);
  verror(ERR_FATAL, where, format, val);
  va_end(val);
  return ERROR_RESULT;
}

int
ErrorHandler::lfatal(int exit_status, const String &where, const char *format, ...)
{
  assert(exit_status >= 0 && exit_status < 0x1000);
  va_list val;
  va_start(val, format);
  verror((Seriousness) (ERR_MIN_FATAL + (exit_status << ERRVERBOSITY_SHIFT)), where, format, val);
  va_end(val);
  return ERROR_RESULT;
}

String
ErrorHandler::make_text(Seriousness seriousness, const char *format, ...)
{
  va_list val;
  va_start(val, format);
  String s = make_text(seriousness, format, val);
  va_end(val);
  return s;
}

#define NUMBUF_SIZE	128
#define ErrH		ErrorHandler

static char *
do_number(unsigned long num, char *after_last, int base, int flags)
{
  const char *digits =
    ((flags & ErrH::UPPERCASE) ? "0123456789ABCDEF" : "0123456789abcdef");
  char *pos = after_last;
  while (num) {
    *--pos = digits[num % base];
    num /= base;
  }
  if (pos == after_last)
    *--pos = '0';
  return pos;
}

static char *
do_number_flags(char *pos, char *after_last, int base, int flags,
		int precision, int field_width)
{
  // remove ALTERNATE_FORM for zero results in base 16
  if ((flags & ErrH::ALTERNATE_FORM) && base == 16 && *pos == '0')
    flags &= ~ErrH::ALTERNATE_FORM;
  
  // account for zero padding
  if (precision >= 0)
    while (after_last - pos < precision)
      *--pos = '0';
  else if (flags & ErrH::ZERO_PAD) {
    if ((flags & ErrH::ALTERNATE_FORM) && base == 16)
      field_width -= 2;
    if ((flags & ErrH::NEGATIVE)
	|| (flags & (ErrH::PLUS_POSITIVE | ErrH::SPACE_POSITIVE)))
      field_width--;
    while (after_last - pos < field_width)
      *--pos = '0';
  }
  
  // alternate forms
  if ((flags & ErrH::ALTERNATE_FORM) && base == 8 && pos[1] != '0')
    *--pos = '0';
  else if ((flags & ErrH::ALTERNATE_FORM) && base == 16) {
    *--pos = ((flags & ErrH::UPPERCASE) ? 'X' : 'x');
    *--pos = '0';
  }
  
  // sign
  if (flags & ErrH::NEGATIVE)
    *--pos = '-';
  else if (flags & ErrH::PLUS_POSITIVE)
    *--pos = '+';
  else if (flags & ErrH::SPACE_POSITIVE)
    *--pos = ' ';
  
  return pos;
}

String
ErrorHandler::make_text(Seriousness seriousness, const char *s, va_list val)
{
  StringAccum msg;

  char numbuf[NUMBUF_SIZE];	// for numerics
  numbuf[NUMBUF_SIZE-1] = 0;

  String placeholder;		// to ensure temporaries aren't destroyed

  // prepend 'warning: ' to every line if appropriate
  String begin_lines;
  if (seriousness >= ERR_MIN_WARNING && seriousness < ERR_MIN_ERROR) {
    begin_lines = String::stable_string("warning: ", 9);
    msg << begin_lines;
  }
  
  // declare and initialize these here to make gcc shut up about possible 
  // use before initialization
  int flags = 0;
  int field_width = -1;
  int precision = -1;
  int width_flag = 0;
  int base = 10;
  while (1) {
    
    const char *pct = strchr(s, '%');

    // handle begin_lines
    const char *nl;
    while (begin_lines && (nl = strchr(s, '\n')) && (!pct || nl < pct)) {
      msg.append(s, nl + 1 - s);
      if (nl[1])
	msg.append(begin_lines.data(), begin_lines.length());
      s = nl + 1;
    }
    
    if (!pct) {
      if (*s)
	msg << s;
      break;
    }
    if (pct != s) {
      msg.append(s, pct - s);
      s = pct;
    }
    
    // parse flags
    flags = 0;
   flags:
    switch (*++s) {
     case '#': flags |= ALTERNATE_FORM; goto flags;
     case '0': flags |= ZERO_PAD; goto flags;
     case '-': flags |= LEFT_JUST; goto flags;
     case ' ': flags |= SPACE_POSITIVE; goto flags;
     case '+': flags |= PLUS_POSITIVE; goto flags;
    }
    
    // parse field width
    field_width = -1;
    if (*s == '*') {
      field_width = va_arg(val, int);
      if (field_width < 0) {
	field_width = -field_width;
	flags |= LEFT_JUST;
      }
      s++;
    } else if (*s >= '0' && *s <= '9')
      for (field_width = 0; *s >= '0' && *s <= '9'; s++)
	field_width = 10*field_width + *s - '0';
    
    // parse precision
    precision = -1;
    if (*s == '.') {
      s++;
      precision = 0;
      if (*s == '*') {
	precision = va_arg(val, int);
	s++;
      } else if (*s >= '0' && *s <= '9')
	for (; *s >= '0' && *s <= '9'; s++)
	  precision = 10*precision + *s - '0';
    }
    
    // parse width flags
    width_flag = 0;
   width_flags:
    switch (*s) {
     case 'h':
      width_flag = 'h'; s++; goto width_flags;
     case 'l':
      width_flag = (width_flag == 'l' ? 'q' : 'l'); s++; goto width_flags;
     case 'L': case 'q':
      width_flag = 'q'; s++; goto width_flags;
    }
    
    // conversion character
    // after switch, data lies between `s1' and `s2'
    const char *s1 = 0, *s2 = 0;
    base = 10;
    switch (*s++) {
      
     case 's': {
       s1 = va_arg(val, const char *);
       if (!s1)
	 s1 = "(null)";
       if (flags & ALTERNATE_FORM) {
	 placeholder = String(s1).printable();
	 s1 = placeholder.c_str();
       }
       for (s2 = s1; *s2 && precision != 0; s2++)
	 if (precision > 0)
	   precision--;
       break;
     }

     case 'c': {
       int c = va_arg(val, int);
       // check for extension of 'signed char' to 'int'
       if (c < 0)
	 c += 256;
       // assume ASCII
       if (c == '\n')
	 strcpy(numbuf, "\\n");
       else if (c == '\t')
	 strcpy(numbuf, "\\t");
       else if (c == '\r')
	 strcpy(numbuf, "\\r");
       else if (c == '\0')
	 strcpy(numbuf, "\\0");
       else if (c < 0 || c >= 256)
	 strcpy(numbuf, "(bad char)");
       else if (c < 32 || c >= 0177)
	 sprintf(numbuf, "\\%03o", c);
       else
	 sprintf(numbuf, "%c", c);
       s1 = numbuf;
       s2 = strchr(numbuf, 0);
       break;
     }
     
     case '%': {
       numbuf[0] = '%';
       s1 = numbuf;
       s2 = s1 + 1;
       break;
     }

     case 'd':
     case 'i':
      flags |= SIGNED;
     case 'u':
     number: {
       // protect numbuf from overflow
       if (field_width > NUMBUF_SIZE)
	 field_width = NUMBUF_SIZE;
       if (precision > NUMBUF_SIZE-4)
	 precision = NUMBUF_SIZE-4;
       
       s2 = numbuf + NUMBUF_SIZE;
       
       unsigned long num;
#ifdef HAVE_INT64_TYPES
       if (width_flag == 'q') {
	 uint64_t qnum = va_arg(val, uint64_t);
	 if ((flags & SIGNED) && (int64_t)qnum < 0)
	   qnum = -(int64_t)qnum, flags |= NEGATIVE;
	 String q = cp_unparse_unsigned64(qnum, base, flags & UPPERCASE);
	 s1 = s2 - q.length();
	 memcpy((char *)s1, q.data(), q.length());
	 goto got_number;
       }
#endif
       if (width_flag == 'h') {
	 num = (unsigned short)va_arg(val, int);
	 if ((flags & SIGNED) && (short)num < 0)
	   num = -(short)num, flags |= NEGATIVE;
       } else if (width_flag == 'l') {
	 num = va_arg(val, unsigned long);
	 if ((flags & SIGNED) && (long)num < 0)
	   num = -(long)num, flags |= NEGATIVE;
       } else {
	 num = va_arg(val, unsigned int);
	 if ((flags & SIGNED) && (int)num < 0)
	   num = -(int)num, flags |= NEGATIVE;
       }
       s1 = do_number(num, (char *)s2, base, flags);

#ifdef HAVE_INT64_TYPES
      got_number:
#endif
       s1 = do_number_flags((char *)s1, (char *)s2, base, flags,
			    precision, field_width);
       break;
     }
     
     case 'o':
      base = 8;
      goto number;
      
     case 'X':
      flags |= UPPERCASE;
     case 'x':
      base = 16;
      goto number;
      
     case 'p': {
       void *v = va_arg(val, void *);
       s2 = numbuf + NUMBUF_SIZE;
       s1 = do_number((unsigned long)v, (char *)s2, 16, flags);
       s1 = do_number_flags((char *)s1, (char *)s2, 16, flags | ALTERNATE_FORM,
			    precision, field_width);
       break;
     }

#ifndef __KERNEL__
     case 'e': case 'f': case 'g':
     case 'E': case 'F': case 'G': {
       char format[80], *f = format, new_numbuf[NUMBUF_SIZE];
       *f++ = '%';
       if (flags & ALTERNATE_FORM)
	 *f++ = '#';
       if (precision >= 0)
	 f += sprintf(f, ".%d", precision);
       *f++ = s[-1];
       *f++ = 0;

       int len = sprintf(new_numbuf, format, va_arg(val, double));

       s2 = numbuf + NUMBUF_SIZE;
       s1 = s2 - len;
       memcpy((char *)s1, new_numbuf, len); // note: no terminating \0
       s1 = do_number_flags((char *)s1, (char *)s2, 10, flags & ~ALTERNATE_FORM, -1, field_width);
       break;
     }
#endif
     
     case '{': {
       const char *rbrace = strchr(s, '}');
       if (!rbrace || rbrace == s)
	 assert(0 /* Bad %{ in error */);
       String name(s, rbrace - s);
       s = rbrace + 1;
       for (Conversion *item = error_items; item; item = item->next)
	 if (item->name == name) {
	   placeholder = item->hook(flags, VA_LIST_REF(val));
	   s1 = placeholder.data();
	   s2 = s1 + placeholder.length();
	   goto got_result;
	 }
       assert(0 /* Bad %{ in error */);
       break;
     }
     
     default:
      assert(0 /* Bad % in error */);
      break;
      
    }

    // add result of conversion
   got_result:
    int slen = s2 - s1;
    if (slen > field_width) field_width = slen;
    char *dest = msg.extend(field_width);
    if (flags & LEFT_JUST) {
      memcpy(dest, s1, slen);
      memset(dest + slen, ' ', field_width - slen);
    } else {
      memcpy(dest + field_width - slen, s1, slen);
      memset(dest, (flags & ZERO_PAD ? '0' : ' '), field_width - slen);
    }
  }

  return msg.take_string();
}

String
ErrorHandler::decorate_text(Seriousness, const String &landmark, const String &text)
{
  String new_text = text;
  
  // handle landmark
  if (landmark
      && !(landmark.length() > 2 && landmark[0] == '\\'
	   && landmark.back() == '\\')) {
    // ignore special-purpose landmarks (begin and end with a backslash `\')
    // fix landmark: skip trailing spaces and trailing colon
    int i, len = landmark.length();
    for (i = len - 1; i >= 0; i--)
      if (!isspace((unsigned char) landmark[i]))
	break;
    if (i >= 0 && landmark[i] == ':')
      i--;

    // prepend landmark, unless all spaces
    if (i >= 0)
      new_text = prepend_lines(landmark.substring(0, i+1) + ": ", new_text);
  }

  return new_text;
}

int
ErrorHandler::verror(Seriousness seriousness, const String &where,
		     const char *s, va_list val)
{
  String text = make_text(seriousness, s, val);
  text = decorate_text(seriousness, where, text);
  handle_text(seriousness, text);
  return count_error(seriousness, text);
}

int
ErrorHandler::verror_text(Seriousness seriousness, const String &where,
			  const String &text)
{
  // text is already made
  String dec_text = decorate_text(seriousness, where, text);
  handle_text(seriousness, dec_text);
  return count_error(seriousness, dec_text);
}

void
ErrorHandler::set_error_code(int)
{
}

String
ErrorHandler::prepend_lines(const String &prepend, const String &text)
{
  if (!prepend)
    return text;
  
  StringAccum sa;
  const char *begin = text.begin();
  const char *end = text.end();
  while (begin < end) {
    const char *nl = std::find(begin, end, '\n');
    if (nl < end)
      nl++;
    sa << prepend << text.substring(begin, nl);
    begin = nl;
  }
  
  return sa.take_string();
}


//
// BASE ERROR HANDLER
//

int
BaseErrorHandler::count_error(Seriousness seriousness, const String &)
{
  if (seriousness < ERR_MIN_WARNING)
    /* do nothing */;
  else if (seriousness < ERR_MIN_ERROR)
    _nwarnings++;
  else
    _nerrors++;
  return (seriousness < ERR_MIN_WARNING ? OK_RESULT : ERROR_RESULT);
}


#ifndef __KERNEL__
//
// FILE ERROR HANDLER
//

FileErrorHandler::FileErrorHandler(FILE *f, const String &context)
  : _f(f), _context(context)
{
}

void
FileErrorHandler::handle_text(Seriousness seriousness, const String &message)
{
  String text = prepend_lines(_context, message);
  if (text && text.back() != '\n')
    text += '\n';
  fwrite(text.data(), 1, text.length(), _f);
  
  if (seriousness >= ERR_MIN_FATAL) {
    int exit_status = (seriousness - ERR_MIN_FATAL) >> ERRVERBOSITY_SHIFT;
    exit(exit_status);
  }
}
#endif


//
// SILENT ERROR HANDLER
//

void
SilentErrorHandler::handle_text(Seriousness, const String &)
{
}


//
// STATIC ERROR HANDLERS
//

static ErrorHandler *the_default_handler = 0;
static ErrorHandler *the_ignore_handler = 0;

ErrorHandler::Conversion *
ErrorHandler::add_conversion(const String &name, ConversionHook hook)
{
  if (Conversion *c = new Conversion) {
    c->name = name;
    c->hook = hook;
    c->next = error_items;
    error_items = c;
    return c;
  } else
    return 0;
}

int
ErrorHandler::remove_conversion(ErrorHandler::Conversion *conv)
{
  Conversion **pprev = &error_items;
  for (Conversion *c = error_items; c; pprev = &c->next, c = *pprev)
    if (c == conv) {
      *pprev = c->next;
      delete c;
      return 0;
    }
  return -1;
}

ErrorHandler *
ErrorHandler::static_initialize(ErrorHandler *default_handler)
{
  the_default_handler = default_handler;
  return the_default_handler;
}

void
ErrorHandler::static_cleanup()
{
  delete the_default_handler;
  delete the_ignore_handler;
  the_default_handler = the_ignore_handler = 0;
  while (error_items) {
    Conversion *next = error_items->next;
    delete error_items;
    error_items = next;
  }
}

bool
ErrorHandler::has_default_handler()
{
  return the_default_handler ? true : false;
}

ErrorHandler *
ErrorHandler::default_handler()
{
  assert(the_default_handler != 0);
  return the_default_handler;
}

ErrorHandler *
ErrorHandler::ignore_handler()
{
  if (!the_ignore_handler)
    the_ignore_handler = new SilentErrorHandler;
  return the_ignore_handler;
}

void
ErrorHandler::set_default_handler(ErrorHandler *errh)
{
  the_default_handler = errh;
}


//
// ERROR VENEER
//

int
ErrorVeneer::nwarnings() const
{
  return _errh->nwarnings();
}

int
ErrorVeneer::nerrors() const
{
  return _errh->nerrors();
}

void
ErrorVeneer::reset_counts()
{
  _errh->reset_counts();
}

String
ErrorVeneer::make_text(Seriousness seriousness, const char *s, va_list val)
{
  return _errh->make_text(seriousness, s, val);
}

String
ErrorVeneer::decorate_text(Seriousness seriousness, const String &landmark, const String &text)
{
  return _errh->decorate_text(seriousness, landmark, text);
}

void
ErrorVeneer::handle_text(Seriousness seriousness, const String &text)
{
  _errh->handle_text(seriousness, text);
}

int
ErrorVeneer::count_error(Seriousness seriousness, const String &text)
{
  return _errh->count_error(seriousness, text);
}


//
// CONTEXT ERROR HANDLER
//

ContextErrorHandler::ContextErrorHandler(ErrorHandler *errh,
					 const String &context,
					 const String &indent,
					 const String &context_landmark)
  : ErrorVeneer(errh), _context(context), _indent(indent),
    _context_landmark(context_landmark)
{
}

String
ContextErrorHandler::decorate_text(Seriousness seriousness, const String &landmark, const String &text)
{
  String context_lines;
  if (_context) {
    // do not print context or indent if underlying ErrorHandler doesn't want
    // context
    if (_errh->min_verbosity() > ERRVERBOSITY_CONTEXT)
      _context = _indent = String();
    else {
      context_lines = _errh->decorate_text(ERR_MESSAGE, (_context_landmark ? _context_landmark : landmark), _context);
      if (context_lines && context_lines.back() != '\n')
	context_lines += '\n';
      _context = String();
    }
  }
  return context_lines + _errh->decorate_text(seriousness, (landmark ? landmark : _context_landmark), prepend_lines(_indent, text));
}


//
// PREFIX ERROR HANDLER
//

PrefixErrorHandler::PrefixErrorHandler(ErrorHandler *errh,
				       const String &prefix)
  : ErrorVeneer(errh), _prefix(prefix)
{
}

String
PrefixErrorHandler::decorate_text(Seriousness seriousness, const String &landmark, const String &text)
{
  return _errh->decorate_text(seriousness, landmark, prepend_lines(_prefix, text));
}


//
// LANDMARK ERROR HANDLER
//

LandmarkErrorHandler::LandmarkErrorHandler(ErrorHandler *errh, const String &landmark)
  : ErrorVeneer(errh), _landmark(landmark)
{
}

String
LandmarkErrorHandler::decorate_text(Seriousness seriousness, const String &lm, const String &text)
{
  if (lm)
    return _errh->decorate_text(seriousness, lm, text);
  else
    return _errh->decorate_text(seriousness, _landmark, text);
}


//
// VERBOSE FILTER ERROR HANDLER
//

VerboseFilterErrorHandler::VerboseFilterErrorHandler(ErrorHandler *errh, int min_verbosity)
  : ErrorVeneer(errh), _min_verbosity(min_verbosity)
{
}

int
VerboseFilterErrorHandler::min_verbosity() const
{
  int m = _errh->min_verbosity();
  return (m >= _min_verbosity ? m : _min_verbosity);
}

void
VerboseFilterErrorHandler::handle_text(Seriousness s, const String &text)
{
  if ((s & ERRVERBOSITY_MASK) >= _min_verbosity)
    _errh->handle_text(s, text);
}


//
// BAIL ERROR HANDLER
//

#ifndef __KERNEL__

BailErrorHandler::BailErrorHandler(ErrorHandler *errh, Seriousness s)
  : ErrorVeneer(errh), _exit_seriousness(s)
{
}

void
BailErrorHandler::handle_text(Seriousness s, const String &text)
{
  _errh->handle_text(s, text);
  if (s >= _exit_seriousness)
    exit(1);
}

#endif
