#include <kernel/kernel.h>

#include <phantasmal/log.h>
#include <phantasmal/channel.h>
#include <phantasmal/lpc_names.h>

#include <trace.h>

inherit COMMON_AUTO;

private object log;
private string comp_err;
private int    reset_comp_err;
private string last_rt_err;
private string last_st;
private int    in_atomic_error;
private int    in_init_sequence;

static void create(varargs int clone)
{
  log = find_object(LOGD);
  reset_comp_err = 0;
  in_atomic_error = 0;
  in_init_sequence = 1;
}

void runtime_error(string error, int caught, mixed** trace)
{
  int size, i, len;
  string line, progname, function, str, objname, err_str;

  if(!SYSTEM() && !KERNEL())
    return;

  if(reset_comp_err) {
    log->write_syslog("Clearing comp_err in runtime_error!", LOG_VERBOSE);
    comp_err = nil;
    reset_comp_err = 0;
  } else {
    reset_comp_err = 1;
  }

  str = error;

  if(caught != 0) {
    str += " [caught]";
  }
  last_rt_err = str;

  err_str = str + "\n";
  str = "";

  if(sscanf(err_str, "Failed to compile%*s") > 0) {
    return;
  }

  size = sizeof(trace) - 1;
  for(i = 0; i < size; i++) {
    progname = trace[i][TRACE_PROGNAME];
    len = trace[i][TRACE_LINE];
    if(len == 0) {
      line = "    ";
    } else {
      line = "    " + (string)len;
      line = line[strlen(line) - 4 ..];
    }

    function = trace[i][TRACE_FUNCTION];
    len = strlen(function);
    if(progname == AUTO && i != size - 1 && len > 3) {
      switch(function[..2]) {
      case "bad":
      case "_F_":
      case "_Q_":
	continue;
      }
    }
    if(len < 17) {
      function += "                 "[len..];
    }

    objname = trace[i][TRACE_OBJNAME];
    if(progname != objname) {
      len = strlen(progname);
      if(len < strlen(objname) && progname == objname[.. len - 1]
	 && objname[len] == '#') {
	objname = objname[len..];
      }
      str += line + " " + function + " " + progname + " (" + objname
	+ ")\n";
    } else {
      str += line + " " + function + " " + progname + "\n";
    }
  }
  last_st = str;
  str = err_str + str;

  /* If the first character is a $, this is a silent error.  Store it as
   * the last error, but don't log it.
   */
  if (error[0] != '$') {
    if(caught) {
      log->write_syslog("Runtime error: " + str, LOG_WARNING);
    } else {
      log->write_syslog("Runtime error: " + str, LOG_ERROR);
    }
    send_message("Runtime error: " + str);
    if(in_init_sequence) {
      DRIVER->message("Runtime error: " + str);
    }
    if(caught == 0 && this_user()) {
      this_user()->message(str);
    }
    if(!caught && find_object(CHANNELD)) {
      CHANNELD->string_to_channel(CHANNEL_ERR, str);
    }
  }
}

void atomic_error(string error, int atom, mixed** trace)
{
  int size, i, len;
  string line, progname, function, str, objname, err_str;
  object obj;

  if(!SYSTEM() && !KERNEL())
    return;
  
  /* prevents recursion into atomic_error().  Without this the driver
   * will crash if an error occures within atomic_error().
   *
   * I discovered this from experience :(.
   * (kdunwoody)
   *
   * N.B.  I chose to write a driver message, but no log message since you
   * can't write to file from within an atomic, and this seems to count as
   * an atomic.
   */

  if (in_atomic_error) {
    DRIVER->message("Re-entering atomic_error()!  Bailing...\n");

    return;
  }

  in_atomic_error = 1;

  /* if the first character of the error message is '$', this is a silent
   * error -- do not record it */
  if (error[0] == '$') {
    return;
  }

  str = error;

  str += " [atomic]\n";

  err_str = str + "\n";
  str = "";


  size = sizeof(trace) - 1;
  for (i = atom; i < size; i++) {
    progname = trace[i][TRACE_PROGNAME];
    len = trace[i][TRACE_LINE];
    if (len == 0) {
      line = "    ";
    } else {
      line = "    " + len;
      line = line[strlen(line) - 4 ..];
    }
    
    function = trace[i][TRACE_FUNCTION];
    len = strlen(function);
    if (progname == AUTO && i != size - 1 && len > 3) {
      switch (function[.. 2]) {
      case "bad":
      case "_F_":
      case "_Q_":
	continue;
      }
    }
    if (len < 17) {
      function += "                 "[len ..];
    }

    objname = trace[i][TRACE_OBJNAME];
    if (progname != objname) {
      len = strlen(progname);
      if (len < strlen(objname) && progname == objname[.. len - 1] &&
	  objname[len] == '#') {
	objname = objname[len ..];
      }
      str += line + " " + function + " " + progname + " (" + objname +
	")\n";
    } else {
      str += line + " " + function + " " + progname + "\n";
    }
  }
  
  /* 
   * Don't set last_st -- it will just be rolled back almost as
   * soon as this function exits

   last_st = str; 

  */
  str = err_str + str;

  /* 
   * Re-throws the error with the full stack trace.  This was recommended
   * by Par Winzell of Skotos as being a good way to pass the stack trace
   * up to runtime_error(). (kdunwoody)
   */
  error(str);
  in_atomic_error = 0;
}

void compile_error(string file, int line, string error)
{
  if(!SYSTEM() && !KERNEL())
    return;

  if(reset_comp_err) {
    log->write_syslog("Clearing comp_err in compile_error!", LOG_VERBOSE);
    reset_comp_err = 0;
    comp_err = "";
  }
  if(!comp_err) comp_err = "";
  comp_err += file + ":" + line + "  " + error + "\n";

  log->write_syslog("Compile error: " + file + ":" + (string)line
		    + ": " + error);
  send_message("Compile error!");
  if(in_init_sequence) {
    DRIVER->message("Compile error: " + file + ": " + (string)line
		    + ": " + error + "\n");
  }
}

string last_compile_errors(void) {
  if(SYSTEM())
    return comp_err;

  return nil;
}

string last_runtime_error(void) {
  if(SYSTEM())
    return last_rt_err;

  return nil;
}

string last_stack_trace(void) {
  if(SYSTEM())
    return last_st;

  return nil;
}

void clear_errors(void) {
  if(!SYSTEM()) {
    error("Only System callers can clear errord's errors!");
  }

  comp_err = last_rt_err = last_st = nil;
}

void done_with_init(void) {
  if(previous_program() == INITD)
    in_init_sequence = 0;
}
