#include <kernel/kernel.h>

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

#include <limits.h>
#include <type.h>

#define SYSLOGFILE   (LOGDIR + "/System.log")
#define LOGD_DTD     ("/usr/System/sys/logchannel.dtd")
#define LOG_CHANNELS ("/usr/System/sys/logd_channels.unq")

inherit COMMON_AUTO;

private mapping channels;
private mapping levelname;
private object  chan_dtd;

/* Prototypes */
void write_syslog(string logstring, varargs int level, string channel);

private void read_channels(mixed* unq);

static void create(varargs int clone) {
  if(clone)
    error("Can't clone LOGD!");

  levelname = ([ "fatal" : LOG_ERR_FATAL,
		 "fatalerr" : LOG_ERR_FATAL,
		 "fatal error" : LOG_ERR_FATAL,
		 "fatalerror" : LOG_ERR_FATAL,
		 "err" : LOG_ERR,
		 "error" : LOG_ERR,
		 "warn" : LOG_WARN,
		 "warning" : LOG_WARN,
		 "debug" : LOG_DEBUG,
		 "debugmsg" : LOG_DEBUG,
		 "message" : LOG_DEBUG,
		 "normal" : LOG_NORMAL,
		 "verbose" : LOG_VERBOSE,
		 "ultra" : LOG_ULTRA_VERBOSE,
		 "ultraverbose" : LOG_ULTRA_VERBOSE,
		 "ultra verbose" : LOG_ULTRA_VERBOSE,
		 "everything" : LOG_ULTRA_VERBOSE,
		 ]);
}

void destructed(int clone) {
  if(previous_program() == OBJECTD) {
    destruct_object(chan_dtd);
  }
}

void start_channels(void) {
  string dtd_file;
  string channels_file;
  mixed* unq;

  if(channels)
    error("Channels already started up in LOGD!");

  channels = ([ ]);

  dtd_file = read_file(LOGD_DTD);
  if(strlen(dtd_file) > MAX_STRING_SIZE - 3)
    error("LOGD's DTD file is too long!");
  channels_file = read_file(LOG_CHANNELS);
  if(!channels_file)
    error("Can't read channels file ('" + LOG_CHANNELS + "')");
  if(strlen(channels_file) > MAX_STRING_SIZE - 3)
    error("LOGD's Channels file is too long!");

  if(!find_object(UNQ_PARSER)) { compile_object(UNQ_PARSER); }
  if(!find_object(UNQ_DTD)) { compile_object(UNQ_DTD); }

  chan_dtd = clone_object(UNQ_DTD);
  chan_dtd->load(dtd_file);

  unq = UNQ_PARSER->unq_parse_with_dtd(channels_file, chan_dtd);

  read_channels(unq);
}

void write_syslog(string logstring, varargs int level, string channel) {
  string to_write;
  mixed* the_time;
  mixed  chan_lev;

  if(!level)
    level = LOG_ERR_FATAL;  /* Highest priority */

  if(!channel) {
    channel = previous_program();
  } else if(!SYSTEM())
    error("Only System files can impersonate other channels!");

  if(channels) {
    chan_lev = channels[channel];
  }

  /* If it's a path, include only the final filename in the log */
  if(channel[0] == "/"[0]) {
    string* comp;
    int     ctr;

    comp = explode(channel, "/");
    if(comp) {
      ctr = sizeof(comp) - 1;

      while(ctr >= 0) {
	if(comp[ctr]) {
	  channel = comp[ctr];
	  break;
	}
	ctr--;
      }
    }
  }

  if(channels && chan_lev) {
    if(level < chan_lev) {
      /* Won't write to file */
      /* TODO: decide whether to send out on channeld channel(s) */
      return;
    }

    /* Change output string */
    channel += "(" + chan_lev + ")";
  } else {
    if(level < LOG_NORMAL) {
      /* Channel isn't explicitly set, do nothing for now */
      return;
    }

    channel += "(unset)";
  }

  to_write = channel;

  to_write += " ||| ";
  if(this_user()) {
    if(this_user()->get_Name()) {
      to_write += this_user()->get_Name();
    } else {
      to_write += "(nameless)";
    }
  } else {
    to_write += "(no user)";
  }

  the_time = millitime();
  to_write += " ||| " + ctime(the_time[0]) + " / "
    + (string)the_time[1] + " [" + (string)level + "]\n"
    + "  --> " + logstring + "\n";
  if(catch(write_file(SYSLOGFILE, to_write))) {
    /* Failure -- try to deal with it */
    send_message("Help!  Log failure!\n");
    write_file("/helplog.txt", "Log failure!  Bad one!\n");
  }
  if(find_object(CHANNELD))
    CHANNELD->string_to_channel(CHANNEL_LOG, to_write, level);
}

private void read_channels(mixed* unq) {
  while(sizeof(unq)) {
    if(unq[0] != "channel"
       || sizeof(unq[1]) != 2) {
      error("Label '" + unq[0] + "/" + sizeof(unq[1])
	    + "' does not appear to be a LOGD channel!");
    }

    if(unq[1][0][0] != "name"
       || unq[1][1][0] != "level") {
      error("Name/level structure (" + unq[1][0][0] + "/" + unq[1][1][0]
	    + ") appears incorrect in LOGD channel!");
    }

    if(typeof(unq[1][0][1]) != T_STRING
       || typeof(unq[1][1][1]) != T_INT) {
      error("Name must be a string and level must be an int in LOGD channel!");
    }

    channels[unq[1][0][1]] = unq[1][1][1];

    unq = unq[2..];
  }
}

int channel_sub(string channel) {
  if(!SYSTEM())
    return -1;

  if(!channels[channel])
    return -1;
  return channels[channel];
}

void set_channel_sub(string channel, int level) {
  if(SYSTEM())
    channels[channel] = level;
}

mixed get_level_by_name(string name) {
  return levelname[name];
}
