/*----------------------------------------------------------------------------*
 | This file is part of DEU (Doom Editing Utilities), created by the DEU team:|
 | Raphael Quinet, Brendon Wyber, Ted Vessenes and others.  See README.1ST or |
 | the "about" dialog box for full credits.                                   |
 |                                                                            |
 | DEU is an open project: if you think that you can contribute, please join  |
 | the DEU team.  You will be credited for any code (or ideas) included in    |
 | the next version of the program.                                           |
 |                                                                            |
 | If you want to make any modifications and re-distribute them on your own,  |
 | you must follow the conditions of the DEU license.  Read the file LICENSE  |
 | in this directory or README.1ST in the top directory.  If do not have a    |
 | copy of these files, you can request them from any member of the DEU team, |
 | or by mail: Raphael Quinet, Rue des Martyrs 9, B-4550 Nandrin (Belgium).   |
 |                                                                            |
 | This program comes with absolutely no warranty.  Use it at your own risks! |
 *----------------------------------------------------------------------------*

 D_MISC.C - Error logs, memory management, sounds, selection of objects, etc.

*/

/* the includes */
#include "deu.h"
#include "d_config.h"
#include "d_wads.h"
#include "g_gfx.h"
#ifdef DEU_UNIX
#include <sys/types.h>
#include <sys/time.h>
#else
#include <dos.h>
#include <time.h>
#endif
#if defined (__TURBOC__)
#include <alloc.h>
#elif defined(__GO32__)
#include <pc.h>
#endif
#include "d_misc.h"

/* global variable */
FILE  *logfile = NULL;           /* file pointer to the error log */


/*
   Wait for a specified number of milliseconds.
*/

void MSleep(int msec)
{
#ifdef DEU_UNIX
  struct timeval tv;

  tv.tv_sec  = msec / 1000;
  tv.tv_usec = 1000 * (msec % 1000);
  (void) select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &tv);
#else
  delay(msec);
#endif
}


/*
   Play a fascinating tune.
*/

void Beep(void)
{
#ifndef DEU_UNIX
  if (Config.quieter == FALSE)
    {
      sound(640);
      delay(100);
      nosound();
      delay(10);
    }
#endif
}


/*
   Play a sound.
*/

void PlaySound(int freq, int msec)
{
#ifndef DEU_UNIX
  if (Config.quiet == FALSE)
    {
      sound(freq);
      delay(msec);
      nosound();
    }
#endif
}


/*
   Check if a file exists and is readable.
*/

Bool Exists(char *filename)
{
  FILE *test;

  if ((test = fopen(filename, "rb")) == NULL)
    return FALSE;
  fclose(test);
  return TRUE;
}


/*
   Return a pointer to the first character of the file extension, or NULL
   if the file name contains no dot (or if there are more than three
   characters after the dot).
*/

char *GetFileExtension(char *filename)
{
  char *dotp;
  char *p;

  if (filename == NULL)
    return NULL;
  dotp = NULL;
  for (p = filename; *p; p++)
    {
      if (*p == '.')
        dotp = p + 1;
#ifdef DEU_DOS
      else if (*p == '\\')
        dotp = NULL;
#else
      else if (*p == '/')
        dotp = NULL;
      if (dotp != NULL && p == dotp + 3)
        dotp == NULL;
#endif
    }
  return dotp;
}


/*
   Report a non-fatal error.
*/

void ProgWarning(char *errstr, ...)
{
  va_list args;

  va_start(args, errstr);
  Beep();
  if (InGfxMode() == TRUE)
    {
      /*! do something */
      Beep();
      Beep();
      Beep();
    }
  else
    {
      printf("\nWarning: ");
      vprintf(errstr, args);
      printf("\n");
    }
  if (Config.debug == TRUE && logfile != NULL)
    {
      fprintf(logfile, "\nWarning: *** ");
      vfprintf(logfile, errstr, args);
      fprintf(logfile, " ***\n");
      fclose(logfile);
    }
  va_end(args);
}


/*
   Terminate the program reporting an error.
*/

void ProgError(char *errstr, ...)
{
  va_list args;

  Beep();
  Beep();
  if (InGfxMode() == TRUE)
    {
      /*! the error should be reported in graphic mode first, if possible */
      sleep(1);
      TermGfx();
    }
  va_start(args, errstr);
  printf("\nProgram Error: *** ");
  vprintf(errstr, args);
  printf(" ***\n");
  if (Config.debug == TRUE && logfile != NULL)
    {
      fprintf(logfile, "\nProgram Error: *** ");
      vfprintf(logfile, errstr, args);
      fprintf(logfile, " ***\n");
    }
  va_end(args);
  exit(5);
}


/*
   Write a message in the log file.
*/

void LogMessage(char *logstr, ...)
{
  va_list  args;
  time_t   tval;
  char    *tstr;

  if (Config.debug == TRUE && logfile != NULL)
    {
      va_start(args, logstr);
      /* if the messsage begins with ":", output the current date & time first */
      if (logstr[0] == ':')
        {
          time(&tval);
          tstr = ctime(&tval);
          tstr[strlen(tstr) - 1] = '\0';
          fprintf(logfile, "%s", tstr);
        }
      vfprintf(logfile, logstr, args);
      /* write the message immediately, in case something bad happens next */
      fflush(logfile);
      va_end(args);
    }
}


void OpenLogFile(char *logfilename)
{
  if (Config.debug == TRUE)
    {
      logfile = fopen(logfilename, "a");
      if (logfile == NULL)
        printf("Warning: Could not open log file \"%s\"", logfilename);
      LogMessage(": Starting DEU %s\n", DEU_VERSION);
    }
}



void CloseLogFile(void)
{
  if (logfile != NULL)
    {
      LogMessage(": The end!\n\n\n");
      fclose(logfile);
    }
}


/*
   Test if an object is in the selection list.
*/

Bool IsSelected(SelPtr list, Int16 objnum)
{
  SelPtr cur;

  for (cur = list; cur; cur = cur->next)
    if (cur->objnum == objnum)
      return TRUE;
  return FALSE;
}


/*
   Add an object to the selection list.
*/

void SelectObject(SelPtr *list, Int16 objnum)
{
  SelPtr cur;

#ifdef DEBUG
  if (objnum < 0)
    ProgError("BUG: SelectObject called with %d", objnum);
#endif
  cur = (SelPtr) GetMemory(sizeof(struct SelectionList));
  cur->next = *list;
  cur->objnum = objnum;
  *list = cur;
}


/*
   Remove an object from the selection list.
*/

void UnSelectObject(SelPtr *list, Int16 objnum)
{
  SelPtr cur, prev;

#ifdef DEBUG
  if (objnum < 0)
    ProgError("BUG: UnSelectObject called with %d", objnum);
#endif
  prev = NULL;
  cur = *list;
  while (cur)
    {
      if (cur->objnum == objnum)
        {
          if (prev)
            prev->next = cur->next;
          else
            *list = cur->next;
          FreeMemory(cur);
          if (prev)
            cur = prev->next;
          else
            cur = NULL;
        }
      else
        {
          prev = cur;
          cur = cur->next;
        }
    }
}


/*
   Forget the selection list.
*/

void ForgetSelection(SelPtr *list)
{
  SelPtr cur, prev;
  
  cur = *list;
  while (cur)
    {
      prev = cur;
      cur = cur->next;
      FreeMemory(prev);
    }
  *list = NULL;
}



/*
   Note from RQ:
      In order to prevent memory fragmentation on large blocks (greater
      than 1K), the size of all blocks is rounded up to 8K.  Thus,
      "realloc" will move the block if and only if it has grown or shrunk
      enough to cross a 8K boundary.
      I don't do that for smaller blocks (smaller than 1K), because this
      would waste too much space if these blocks were rounded up to 8K.
      There are lots of "malloc"'s for very small strings (9 characters)
      or filenames, etc.
   Thanks to Craig Smith (bcs@cs.tamu.edu) for his ideas about memory
      fragmentation.
   Note 2 from RQ:
      This feature will only be compiled if you define MEMORY_LOWFRAG.
      By default, this symbol is not defined.
*/

#ifdef MEMORY_LOWFRAG
#define SIZE_THRESHOLD  1024
#define SIZE_OF_BLOCK   4095  /* actually, this is (size - 1) */
#endif


/*
   Allocate memory with error checking.
*/

void *GetMemory(size_t size)
{
  void *ret;

#ifdef MEMORY_LOWFRAG
  /* limit fragmentation on large blocks */
  if (size >= (size_t) SIZE_THRESHOLD)
    size = (size + (size_t) SIZE_OF_BLOCK) & ~((size_t) SIZE_OF_BLOCK);
#endif
  ret = malloc(size);
  if (!ret)
    ProgError("out of memory (cannot allocate %u bytes)", size);
  return ret;
}


/*
   Reallocate memory with error checking.
*/

void *ResizeMemory(void *old, size_t size)
{
  void *ret;

#ifdef MEMORY_LOWFRAG
  /* limit fragmentation on large blocks */
  if (size >= (size_t) SIZE_THRESHOLD)
    size = (size + (size_t) SIZE_OF_BLOCK) & ~((size_t) SIZE_OF_BLOCK);
#endif
  ret = realloc(old, size);
  if (!ret)
    ProgError("out of memory (cannot reallocate %u bytes)", size);
  return ret;
}



/*
   Free memory.
*/

void FreeMemory(void *ptr)
{
  /* just a wrapper around free(), but provide an entry point */
  /* for memory debugging routines... */
  free(ptr);
}


/*
   Allocate memory from the far heap with error checking.
*/

void huge *GetFarMemory(UInt32 size)
{
  void huge *ret;

#ifdef MEMORY_LOWFRAG
  /* limit fragmentation on large blocks */
  if (size >= (UInt32) SIZE_THRESHOLD)
    size = (size + (UInt32) SIZE_OF_BLOCK) & ~((UInt32) SIZE_OF_BLOCK);
#endif
#ifdef __TURBOC__
  ret = farmalloc(size);
#else
  ret = malloc(size);
#endif
  if (!ret)
    ProgError("out of memory (cannot allocate %lu far bytes)", size);
  return ret;
}



/*
   Reallocate memory from the far heap with error checking.
*/

void huge *ResizeFarMemory(void huge *old, UInt32 size)
{
  void huge *ret;

#ifdef MEMORY_LOWFRAG
  /* limit fragmentation on large blocks */
  if (size >= (UInt32) SIZE_THRESHOLD)
    size = (size + (UInt32) SIZE_OF_BLOCK) & ~((UInt32) SIZE_OF_BLOCK);
#endif
#ifdef __TURBOC__
  ret = farrealloc(old, size);
#else
  ret = realloc(old, size);
#endif
  if (!ret)
    ProgError("out of memory (cannot reallocate %lu far bytes)", size);
  return ret;
}



/*
   Free memory from the far heap.
*/

void FreeFarMemory(void huge *ptr)
{
  /* just a wrapper around farfree(), but provide an entry point */
  /* for memory debugging routines... */
#ifdef __TURBOC__
  farfree(ptr);
#else
  free(ptr);
#endif
}


/*
   Duplicate a string in a newly allocated location.
*/

char *StrDup(char *s)
{
  char *ret;

  if (!s)
    ProgError("BUG: cannot duplicate NULL pointer");
  ret = (char *)GetMemory(strlen(s) + 1);
  return strcpy(ret, s);
}


#ifndef DEU_DOS
/*
   Convert a string to upper case, overwriting its space.
*/

char *strupr(char *s)
{
  char *cp;

  for (cp = s; *cp != '\0'; cp++)
    *cp = (char) toupper((int)*cp);
  return s;
}
#endif

/* end of file */
