# include "shell.h"

/* we need to control the output destinations a bit carefully
   in the pantasks_server mode.  The server needs to be able to 
   send the output to a specific output device (eg, logging file)
   of to save it within an internal buffer */

/* further notes:
   gprintf (int destination, char *format, ...);

   the destination may be GP_LOG or GP_ERR.  by default, these go to stdout and
   stderr.  either one may be redirected to another file or to a buffer.  as a
   stand-along program, the outfile command redirects the GP_LOG output to an
   alternate output file.  in the server mode, we redirect LOG to a standard log
   file and ERR to a standard error file for server messages.  Commands executed
   by the client have both streams returned to the client in turn, and are in
   turn sent to stderr or the current stdout destination.

   each thread has an independently set output destination.

   option 1: NULL for invalid element:

     a stream is either set to a FILE or an IOBuffer.  when it is set to a FILE,
     the IOBuffer is freed and set to NULL.  when it is an IOBuffer, the file
     pointer is closed and set to NULL.  setting a new FILE results in the 
     IObuffer being freed and an already opened FILE to be flushed and closed.
*/

static gpStream *streams = NULL;
static int Nstreams = 0;

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void gprintInit () {

  int N;
  pthread_t id;

  /* need to use a mutex to prevent two threads from initing simultaneously */
  pthread_mutex_lock (&mutex);

  if (streams == NULL) {
    Nstreams = 2;
    ALLOCATE (streams, gpStream, Nstreams);
  } else {
    Nstreams += 2;
    REALLOCATE (streams, gpStream, Nstreams);
  }

  /* create two output streams for this thread: LOG and ERR */
  id = pthread_self();

  N = Nstreams - 2;
  streams[N].dest = GP_LOG;
  streams[N].file = stdout;
  streams[N].name = strcreate ("stdout");

  ALLOCATE (streams[N].buffer, IOBuffer, 1);
  InitIOBuffer (streams[N].buffer, 64);
  streams[N].mode = GP_FILE;
  streams[N].thread = id;

  N = Nstreams - 1;
  streams[N].dest = GP_ERR;
  streams[N].file = stderr;
  streams[N].name = strcreate ("stderr");

  ALLOCATE (streams[N].buffer, IOBuffer, 1);
  InitIOBuffer (streams[N].buffer, 64);
  streams[N].mode = GP_FILE;
  streams[N].thread = id;

  pthread_mutex_unlock (&mutex);
}

gpStream *gprintGetStream (gpDest dest) {

  int i;
  pthread_t id;

  id = pthread_self();

  /* find the existing output stream which matches */
  for (i = 0; i < Nstreams; i++) {
    if (streams[i].dest != dest) continue;
    if (!pthread_equal (streams[i].thread, id)) continue;
    return (&streams[i]);
  }
  fprintf (stderr, "programming error: gprintInit not called for thread\n");
  abort ();
}

void gprintSetBuffer (gpDest dest) {

  gpStream *stream;

  stream = gprintGetStream (dest);

  /* if we have an open file: flush it, close it, null it */
  if (stream[0].file != NULL) {
    fflush (stream[0].file);
    if (stream[0].file == stdout) goto skip_close;
    if (stream[0].file == stderr) goto skip_close;
    fclose (stream[0].file);

  skip_close:
    stream[0].file = NULL;
  }
  
  if (stream[0].buffer == NULL) {
    ALLOCATE (stream[0].buffer, IOBuffer, 1);
    InitIOBuffer (stream[0].buffer, 64);
  } else {
    FlushIOBuffer (stream[0].buffer);
  }
  
  /* this element may be redundant with the NULL state of file and buffer */
  stream[0].mode = GP_BUFF;
}

IOBuffer *gprintGetBuffer (gpDest dest) {

  gpStream *stream;
  stream = gprintGetStream (dest);
  return (stream[0].buffer);
}

void gprintSetFile (gpDest dest, char *filename) {

  gpStream *stream;
  stream = gprintGetStream (dest);

  /* if we have an open buffer, free it and null it */
  if (stream[0].buffer != NULL) {
    FreeIOBuffer (stream[0].buffer);
    free (stream[0].buffer);
    stream[0].buffer = NULL;
  }

  /* if we have an open file, flush it and close it */
  if (stream[0].file != NULL) {
    fflush (stream[0].file);
    if (stream[0].file == stdout) goto skip_close;
    if (stream[0].file == stderr) goto skip_close;
    fclose (stream[0].file);
  }
skip_close:
  
  free (stream[0].name);

  /* we allow the user to set stdout to ERR and stderr to LOG if they want */
  if (!strcmp (filename, "stdout")) {
    stream[0].file = stdout;
    stream[0].name = strcreate (filename);
    goto skip_open;
  }
  if (!strcmp (filename, "stderr")) {
    stream[0].file = stderr;
    stream[0].name = strcreate (filename);
    goto skip_open;
  }
  
  /* open the file */
  stream[0].file = fopen (filename, "a");
  if (stream[0].file == NULL) {
    fprintf (stderr, "cannot open file %s\n", filename);
    stream[0].file = (dest == GP_LOG) ? stdout : stderr;
    stream[0].name = (dest == GP_LOG) ? strcreate ("stdout") : strcreate("stderr");
  } else {
    stream[0].name = strcreate (filename);
  }

skip_open:
  stream[0].mode = GP_FILE;
}

FILE *gprintGetFile (gpDest dest) {

  gpStream *stream;
  stream = gprintGetStream (dest);
  return (stream[0].file);
}

char *gprintGetName (gpDest dest) {

  gpStream *stream;
  stream = gprintGetStream (dest);
  return (stream[0].name);
}

int gprint (gpDest dest, char *format, ...) {

  int status;
  gpStream *stream;
  va_list argp;  

  stream = gprintGetStream (dest);

  va_start (argp, format);
  
  // gprint (GP_ERR, "GP_FILE: %d\n", GP_FILE);
  // gprint (GP_ERR, "GP_BUFF: %d\n", GP_BUFF);
  // gprint (GP_ERR, "mode: %d\n", stream[0].mode);

  if (stream[0].mode == GP_FILE) {
    // gprint (GP_ERR, "printing to FILE\n");
    status = vfprintf (stream[0].file, format, argp);
    if (status < 0) {
      abort();
    }
  } else {
    // gprint (GP_ERR, "printing to BUFFER\n");
    vPrintIOBuffer (stream[0].buffer, format, argp);
  }
  va_end (argp);
  return (TRUE);
}

int gwrite (char *buffer, int size, int N, gpDest dest) {

  int Nbyte;
  IOBuffer *outbuff;
  gpStream *stream;

  stream = gprintGetStream (dest);

  if (stream[0].mode == GP_FILE) {
    fwrite (buffer, size, N, stream[0].file);
  } else {
    outbuff = stream[0].buffer;
    Nbyte = size * N;
    if (outbuff[0].Nbuffer + Nbyte >= outbuff[0].Nalloc) {
      outbuff[0].Nalloc = outbuff[0].Nbuffer + Nbyte + 64;
      REALLOCATE (outbuff[0].buffer, char, outbuff[0].Nalloc);
    }
    memcpy (&outbuff[0].buffer[outbuff[0].Nbuffer], buffer, Nbyte);
    outbuff[0].Nbuffer += Nbyte;
  }
  return (TRUE);
}

/* I'm going to need to have different output targets for different threads
   we can do this with these functions:

   pthread_t pthread_self(void);
   int pthread_equal(pthread_t thread1, pthread_t thread2);
   // returns TRUE if equal, FALSE if not
   
*/

