/* vi: set ts=2 shiftwidth=2 expandtab:
 *
 * Copyright (C) 2003-2007  Simon Baldwin and Mark J. Tilford
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License
 * as published by the Free Software Foundation.
 *
 * 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
 */

/*
 * Module notes:
 *
 * o Implement smarter selective module tracing.
 */

#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <errno.h>

#include "scare.h"
#include "scprotos.h"


/* Assorted definitions and constants. */
static const sc_char NUL = '\0';
static const sc_char SPACE = ' ';

/*
 * sc_trace()
 *
 * Debugging trace function; printf wrapper that writes to stderr.
 */
void
sc_trace (const sc_char *format, ...)
{
  va_list ap;
  assert (format);

  va_start (ap, format);
  vfprintf (stderr, format, ap);
  va_end (ap);
}


/*
 * sc_error()
 * sc_fatal()
 *
 * Error reporting functions.  sc_error() prints a message and continues.
 * sc_fatal() prints a message, then calls abort().
 */
void
sc_error (const sc_char *format, ...)
{
  va_list ap;
  assert (format);

  fprintf (stderr, "scare: ");
  va_start (ap, format);
  vfprintf (stderr, format, ap);
  va_end (ap);
}

void
sc_fatal (const sc_char *format, ...)
{
  va_list ap;
  assert (format);

  fprintf (stderr, "scare: internal error: ");
  va_start (ap, format);
  vfprintf (stderr, format, ap);
  va_end (ap);
  fprintf (stderr, "scare: aborting...\n");
  abort ();
}


/*
 * sc_malloc()
 * sc_realloc()
 * sc_free()
 *
 * Non-failing wrappers around malloc functions.
 */
void *
sc_malloc (size_t size)
{
  void *ptr;
  ptr = malloc (size);
  if (!ptr)
    sc_fatal ("sc_malloc: requested %lu bytes\n", (sc_uint) size);
  return ptr;
}

void *
sc_realloc (void *pointer, size_t size)
{
  void *ptr;
  ptr = realloc (pointer, size);
  if (!ptr)
    sc_fatal ("sc_realloc: requested %lu bytes\n", (sc_uint) size);
  return ptr;
}

void
sc_free (void *pointer)
{
  free (pointer);
}


/*
 * sc_strncasecmp()
 * sc_strcasecmp()
 *
 * Strncasecmp and strcasecmp are not ANSI functions, so here are local
 * definitions to do the same jobs.
 */
sc_int
sc_strncasecmp (const sc_char *s1, const sc_char *s2, sc_int n)
{
  sc_int index_;
  assert (s1 && s2);

  for (index_ = 0; index_ < n; index_++)
    {
      sc_int diff;

      diff = tolower (s1[index_]) - tolower (s2[index_]);
      if (diff < 0 || diff > 0)
        return diff < 0 ? -1 : 1;
    }

  return 0;
}

sc_int
sc_strcasecmp (const sc_char *s1, const sc_char *s2)
{
  sc_int s1len, s2len, result;
  assert (s1 && s2);

  s1len = strlen (s1);
  s2len = strlen (s2);

  result = sc_strncasecmp (s1, s2, s1len < s2len ? s1len : s2len);
  if (result < 0 || result > 0)
    return result;
  else
    return s1len < s2len ? -1 : s1len > s2len ? 1 : 0;
}


/*
 * sc_platform_rand()
 * sc_congruential_rand()
 * sc_set_random_handler()
 *
 * Internal random number generation functions.  We offer two: one is a self-
 * seeding wrapper around the platform's rand(), which should generate good
 * random numbers but with a sequence that is platform-dependent; the other
 * is a linear congruential generator with a long period that is guaranteed
 * to return the same sequence for all platforms.  The default is the first,
 * with the latter intended for predictability of game actions.
 */
static sc_int
sc_platform_rand (sc_uint new_seed)
{
  static sc_bool is_seeded = FALSE;

  /* If reseeding, seed with the value supplied, note seeded, and return 0. */
  if (new_seed > 0)
    {
      srand (new_seed);
      is_seeded = TRUE;
      return 0;
    }
  else
    {
      /* If not explicitly seeded yet, generate a seed from time(). */
      if (!is_seeded)
        {
          srand ((sc_uint) time (NULL));
          is_seeded = TRUE;
        }

      /* Return the next rand() number in the sequence. */
      return rand ();
    }
}

static sc_int
sc_congruential_rand (sc_uint new_seed)
{
  static sc_bool is_seeded = FALSE;
  static sc_uint rand_state = 1;

  /* If reseeding, seed with the value supplied, and note seeded. */
  if (new_seed > 0)
    {
      rand_state = new_seed;
      is_seeded = TRUE;
      return 0;
    }
  else
    {
      /* If not explicitly seeded yet, generate a seed from time(). */
      if (!is_seeded)
        {
          rand_state = (sc_uint) time (NULL);
          is_seeded = TRUE;
        }

      /*
       * Advance random state, using constants from Parks & Miller (1988).
       * To keep the values the same for both 32 and 64 bit longs, mask out
       * any bits above the bottom 32.
       */
      rand_state = (rand_state * 16807 + 2147483647) & 0xffffffff;

      /*
       * Discard the lowest bit as a way to map 32-bits unsigned to a 32-bit
       * positive signed.
       */
      return rand_state >> 1;
    }
}


/* Function pointer for the actual random number generator in use. */
static sc_int (*sc_rand_function) (sc_uint) = sc_platform_rand;

/*
 * sc_set_congruential_random()
 * sc_set_platform_random()
 * sc_is_congruential_random()
 * sc_seed_random()
 * sc_rand()
 * sc_randomint()
 *
 * Public interface to random functions; control and reseed the random
 * handler in use, generate a random number, and a convenience function to
 * generate a random value within a given range.
 */
void
sc_set_congruential_random (void)
{
  sc_rand_function = sc_congruential_rand;
}

void
sc_set_platform_random (void)
{
  sc_rand_function = sc_platform_rand;
}

sc_bool
sc_is_congruential_random (void)
{
  return sc_rand_function == sc_congruential_rand;
}

void
sc_seed_random (sc_uint new_seed)
{
  /* Ignore zero values of new_seed by simply using 1 instead. */
  sc_rand_function (new_seed > 0 ? new_seed : 1);
}

sc_int
sc_rand (void)
{
  sc_int retval;

  /* Passing zero indicates this is not a seed operation. */
  retval = sc_rand_function (0);
  return retval;
}

sc_int
sc_randomint (sc_int low, sc_int high)
{
  /*
   * If the range is invalid, just return the low value given.  This mimics
   * Adrift under the same conditions, and also guards against division by
   * zero in the mod operation.
   */
  return (high < low) ? low : low + sc_rand () % (high - low + 1);
}


/*
 * sc_strempty()
 *
 * Return TRUE if a string is either zero-length or contains only whitespace.
 */
sc_bool
sc_strempty (const sc_char *string)
{
  sc_int index_;
  assert (string);

  /* Scan for any non-space character. */
  for (index_ = 0; string[index_] != NUL; index_++)
    {
      if (!isspace (string[index_]))
        return FALSE;
    }

  /* None found, so string is empty. */
  return TRUE;
}


/*
 * sc_trim_string()
 *
 * Trim leading and trailing whitespace from a string.  Modifies the string
 * in place, and returns the string address for convenience.
 */
sc_char *
sc_trim_string (sc_char *string)
{
  sc_int index_;
  assert (string);

  for (index_ = strlen (string) - 1;
       index_ >= 0 && isspace (string[index_]); index_--)
    string[index_] = NUL;

  for (index_ = 0; isspace (string[index_]);)
    index_++;
  memmove (string, string + index_, strlen (string) - index_ + 1);

  return string;
}


/*
 * sc_normalize_string()
 *
 * Trim a string, and set all runs of whitespace to a single space character.
 * Modifies the string in place, and returns the string address for
 * convenience.
 */
sc_char *
sc_normalize_string (sc_char *string)
{
  sc_int index_;
  assert (string);

  /* Trim all leading and trailing spaces. */
  string = sc_trim_string (string);

  /* Compress multiple whitespace runs into a single space character. */
  for (index_ = 0; string[index_] != NUL; index_++)
    {
      if (isspace (string[index_]))
        {
          sc_int cursor;

          string[index_] = SPACE;
          for (cursor = index_ + 1; isspace (string[cursor]);)
            cursor++;
          memmove (string + index_ + 1,
                   string + cursor, strlen (string + cursor) + 1);
        }
    }

  return string;
}


/*
 * sc_compare_word()
 *
 * Return TRUE if the first word in the string is word, case insensitive.
 */
sc_bool
sc_compare_word (const sc_char *string, const sc_char *word)
{
  sc_int length;
  assert (string && word);

  /* Return TRUE if string starts with word, then space or string end. */
  length = strlen (word);
  return sc_strncasecmp (string, word, length) == 0
         && (string[length] == NUL || isspace (string[length]));
}


/*
 * sc_hash()
 *
 * Hash a string, hashpjw algorithm, from 'Compilers, principles, techniques,
 * and tools', page 436, unmodulo'ed and somewhat restyled.
 */
sc_uint
sc_hash (const sc_char *string)
{
  sc_int index_;
  sc_uint hash;
  assert (string);

  hash = 0;
  for (index_ = 0; string[index_] != NUL; index_++)
    {
      sc_uint temp;

      hash = (hash << 4) + string[index_];
      temp = hash & 0xf0000000;
      if (temp != 0)
        {
          hash = hash ^ (temp >> 24);
          hash = hash ^ temp;
        }
    }

  return hash;
}
