/* XRACER (C) 1999-2000 Richard W.M. Jones <rich@annexia.org> and other AUTHORS
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: log.c,v 1.4 2000/01/18 23:24:21 rich Exp $
 */

/* The logging system is a fundamental part of the XRacer library.
 * Normally, all messages displayed by XRacer go through this library
 * from which they are redirected to the display and/or to stderr.
 * Unlike other libraries, you *may* call entry points into the library
 * *before* the library has been initialized (ie. before xrLogInit is
 * called).
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

#if HAVE_UNISTD_H
#include <unistd.h>
#endif

#if HAVE_ERRNO_H
#include <errno.h>
#endif

#if HAVE_STRING_H
#include <string.h>
#endif

#include "xracer.h"
#include "xracer-text.h"
#include "xracer-log.h"

/* We store messages on a linked list. The head of the
 * linked list (MESSAGE_HEAD) is the oldest message.
 * The last message on the list (LAST_MESSAGE) is the
 * most recent message.
 */
struct message
{
  struct message *next;		/* Linked list pointer. */
  char *message;		/* The message. */
  double entry_time;		/* The time we put the message on the list. */
};

struct message *message_head = 0, *last_message = 0;
static int nr_messages = 0;

static int stderr_threshold = LOG_DEBUG;
static int display_threshold = LOG_DEBUG;

static void *font = 0;
static int text_height;

#define MAX_TIME 20		/* Max. time to remain on the list. */
#define MAX_MESSAGES 10		/* Max. number of messages on the list. */
#define MAX_MESSAGE_LEN 70	/* Max. message length for display. */

#define LOG_X_OFFSET 16
#define LOG_Y_OFFSET (/*-16*/ - text_height)

/* Scroll up the log messages. */
static void
scroll_messages ()
{
  struct message *m;

  xrLogAssert (nr_messages > 0 && message_head && last_message);

  m = message_head->next;
  free (message_head->message);
  free (message_head);
  message_head = m;

  if (m == 0) last_message = 0;

  nr_messages --;
}

/* Add a message to the list. */
static void
add_message (const char *message)
{
  int len, truncate = 0;
  struct message *m;

  /* If we have run out of space on the list, scroll the
   * list up first.
   */
  if (nr_messages >= MAX_MESSAGES)
    scroll_messages ();

  m = xmalloc (sizeof (struct message));
  m->next = 0;

  /* Copy the message into another buffer. */
  len = strlen (message);
  if (len > MAX_MESSAGE_LEN)
    {
      len = MAX_MESSAGE_LEN;
      truncate = 1;
    }
  m->message = xmalloc ((len+1) * sizeof (char));
  memcpy (m->message, message, len * sizeof (char));
  m->message[len] = '\0';
  if (truncate)
    {
      m->message[len-2] = m->message[len-1] = '.';
    }

  /* Set the time that we entered this message. */
  m->entry_time = xrCurrentTime;

  /* Append the message to the list. */
  if (last_message == 0)
    message_head = last_message = m;
  else
    {
      last_message->next = m;
      last_message = m;
    }

  nr_messages ++;
}

/* Convert log level number to string. */
static const char *
level_to_string (int level)
{
  static const char *str[4] = { "debug", "info", "warning", "error" };
  xrLogAssert (0 <= level && level <= 3);
  return str [level];
}

/* This function displays the actual log on screen. */
void
xrLogDisplay ()
{
  int n;
  struct message *m;

  /* Throw away very old messages. */
  while (nr_messages > 0
	 && xrCurrentTime - message_head->entry_time > MAX_TIME)
    scroll_messages ();

  if (nr_messages == 0) return;

  for (n = 0, m = message_head; m; n++, m = m->next)
    {
      xrTextPuts (font, m->message,
		  LOG_X_OFFSET, n * text_height - LOG_Y_OFFSET);
    }
}

/* Assertion failed. */
void
xrLogAssertFail (const char *file, int line, const char *expr)
{
  xrLogFatalWithLine(file, line, "assertion failed: %s", expr);
}

/* Unimplemented function called. */
void
xrLogNotImplWithLine (const char *file, int line)
{
  xrLogFatalWithLine(file, line, "function not implemented");
}

/* Fatal error. */
void
xrLogFatalWithLine (const char *file, int line, const char *fs, ...)
{
  va_list args;

  fprintf (stderr, "xracer:%s:%d: fatal error: ", file, line);

  va_start (args, fs);
  vfprintf (stderr, fs, args);
  va_end (args);

  fprintf (stderr, "\n");
  exit (1);
}

/* Log message. */
void
xrLogWithLine (const char *file, int line, int level, const char *fs, ...)
{
  va_list args;
  char message [1024];

  if (level >= stderr_threshold &&
      level >= display_threshold)
    {
      va_start (args, fs);
      vsnprintf (message, sizeof message, fs, args);
      va_end (args);

      /* Log to std. error. */
      if (level >= stderr_threshold)
        fprintf (stderr,
                 "xracer:%s:%d: %s: %s\n",
                 file, line, level_to_string (level), message);

      /* Log message to screen. */
      if (level >= display_threshold)
        add_message (message);
    }
}

/* System error message. */
void
xrLogPerrorWithLine (const char *file, int line, const char *fs, ...)
{
  va_list args;
  char message [1024], *error_string;
  const int level = LOG_ERROR;
  int err = errno;

  if (level >= stderr_threshold &&
      level >= display_threshold)
    {
      va_start (args, fs);
      vsnprintf (message, sizeof message, fs, args);
      va_end (args);

      error_string = strerror (err);
      if (strlen (message) + strlen (error_string) + 2 < sizeof message)
        {
          strcat (message, ": ");
          strcat (message, error_string);
        }

      /* Log to std. error. */
      if (level >= stderr_threshold)
        fprintf (stderr,
                 "xracer:%s:%d: %s: %s\n",
                 file, line, level_to_string (level), message);

      /* Log message to screen. */
      if (level >= display_threshold)
        add_message (message);
    }
}

/* Initialize library. Note that other entry points (except xrLogDisplay)
 * may have been called *before* this function is called. So don't
 * initialize anything except fonts here.
 */
void
xrLogInit ()
{
  font = xrTextFindFont ("crillee", 14);
  xrLogAssert (font != 0);

  text_height = xrTextGetHeight (font);
}

int
xrLogSetStderrThreshold (int new_threshold)
{
  int t = stderr_threshold;

  stderr_threshold = new_threshold;
  return t;
}

int
xrLogSetDisplayThreshold (int new_threshold)
{
  int t = display_threshold;

  display_threshold = new_threshold;
  return t;
}
