/*************************************************************************
*									 *
*	 YAP Prolog 							 *
*									 *
*	Yap Prolog was developed at NCCUP - Universidade do Porto	 *
*									 *
* Copyright L.Damas, V.S.Costa and Universidade do Porto 1985-1997	 *
*									 *
**************************************************************************
*									 *
* File:		mem.c *
* Last rev:	5/2/88							 *
* mods:									 *
* comments:	Input/Output C implemented predicates			 *
*									 *
*************************************************************************/
#ifdef SCCS
static char SccsId[] = "%W% %G%";
#endif

/*
 * This file includes the definition of a socket related IO.
 *
 */

#include "sysbits.h"

#if !MAY_READ
static int MemGetc(int);

/* read from memory */
static int MemGetc(int sno) {
  register StreamDesc *s = &GLOBAL_Stream[sno];
  Int ch;
  int spos;

  spos = s->u.mem_string.pos;
  if (spos == s->u.mem_string.max_size) {
    return -1;
  } else {
    ch = s->u.mem_string.buf[spos];
    s->u.mem_string.pos = ++spos;
  }
  return ch;
}

/* peek from memory */
int Yap_MemPeekc(int sno) {
  StreamDesc *s = &GLOBAL_Stream[sno];
  Int ch;
  int spos;

  spos = s->u.mem_string.pos;
  if (spos == s->u.mem_string.max_size) {
    return -1;
  } else {
    ch = s->u.mem_string.buf[spos];
  }
  return ch;
}

#endif

#if !MAY_WRITE
static int MemPutc(int, int);

/* static */
static int MemPutc(int sno, int ch) {
  StreamDesc *s = &GLOBAL_Stream[sno];
#if MAC || _MSC_VER
  if (ch == 10) {
    ch = '\n';
  }
#endif
  s->u.mem_string.buf[s->u.mem_string.pos++] = ch;
  if (s->u.mem_string.pos >= s->u.mem_string.max_size - 8) {
    int new_src;

    /* oops, we have reached an overflow */
    Int new_max_size = s->u.mem_string.max_size + Yap_page_size;
    char *newbuf;
    if ((newbuf = (ADDR)realloc(s->u.mem_string.buf,
                                new_max_size * sizeof(char))) != NULL) {
      new_src = MEM_BUF_MALLOC;
    } else {
      if (GLOBAL_Stream[sno].u.mem_string.error_handler) {
        CACHE_REGS
        LOCAL_Error_Size = new_max_size * sizeof(char);
        save_machine_regs();
        longjmp(*(jmp_buf *)GLOBAL_Stream[sno].u.mem_string.error_handler, 1);
      } else {
        Yap_Error(RESOURCE_ERROR_HEAP, TermNil,
                  "YAP could not grow heap for writing to string");
      }
      return -1;
    }
    s->u.mem_string.buf = newbuf;
    s->u.mem_string.max_size = new_max_size;
    s->u.mem_string.src = new_src;
  }
  count_output_char(ch, s);
  return ((int)ch);
}

#endif

bool Yap_set_stream_to_buf(StreamDesc *st, const char *buf, size_t nchars) {
  FILE *f;
  stream_flags_t flags;

#if MAY_READ
  // like any file stream.
  st->file = f = fmemopen((void *)buf, nchars, "r");
  flags = Input_Stream_f | InMemory_Stream_f | Seekable_Stream_f;
#else
  st->file = f = NULL;
  flags = Input_Stream_f | InMemory_Stream_f;
#endif
  Yap_initStream(st - GLOBAL_Stream, f, NULL, TermNil, LOCAL_encoding, flags,
                 AtomRead);
// like any file stream.
#if !MAY_READ
  /* currently these streams are not seekable */
  st->status = Input_Stream_f | InMemory_Stream_f;
  st->u.mem_string.pos = 0;
  st->u.mem_string.buf = (char *)buf;
  st->u.mem_string.max_size = nchars;
  st->u.mem_string.error_handler = NULL;
// st->u.mem_string.src = src; check new assets coode
#endif
  Yap_DefaultStreamOps(st);
  return true;
}

int Yap_open_buf_read_stream(const char *buf, size_t nchars, encoding_t *encp,
                             memBufSource src) {
  CACHE_REGS
  int sno;
  StreamDesc *st;
  FILE *f;
  encoding_t encoding;
  stream_flags_t flags;

  sno = GetFreeStreamD();
  if (sno < 0)
    return (PlIOError(RESOURCE_ERROR_MAX_STREAMS, TermNil,
                      "new stream not available for open_mem_read_stream/1"));
  st = GLOBAL_Stream + sno;
  if (encp)
    encoding = *encp;
  else
    encoding = LOCAL_encoding;
#if MAY_READ
  // like any file stream.
  f = st->file = fmemopen((void *)buf, nchars, "r");
  flags = Input_Stream_f | InMemory_Stream_f | Seekable_Stream_f;
#else
  st->file = f = NULL;
  flags = Input_Stream_f | InMemory_Stream_f;
#endif
  Yap_initStream(sno, f, NULL, TermNil, encoding, flags, AtomRead);
// like any file stream.
#if !MAY_READ
  /* currently these streams are not seekable */
  st->status = Input_Stream_f | InMemory_Stream_f;
  st->u.mem_string.pos = 0;
  st->u.mem_string.buf = (char *)buf;
  st->u.mem_string.max_size = nchars;
  st->u.mem_string.error_handler = NULL;
  st->u.mem_string.src = src;
#endif
  Yap_DefaultStreamOps(st);
  UNLOCK(st->streamlock);
  return sno;
}

static Int
open_mem_read_stream(USES_REGS1) /* $open_mem_read_stream(+List,-Stream) */
{
  Term t, ti;
  int sno;
  char buf0[YAP_FILENAME_MAX + 1];
  const char *buf;

  ti = Deref(ARG1);
  buf = Yap_TextTermToText(ti, buf0, 0, LOCAL_encoding);
  if (!buf) {
    return false;
  }
  sno = Yap_open_buf_read_stream(buf, strlen(buf) + 1, &LOCAL_encoding,
                                 MEM_BUF_MALLOC);
  t = Yap_MkStream(sno);
  return Yap_unify(ARG2, t);
}

// open a buffer for writing, currently just ignores buf and nchars.

int Yap_open_buf_write_stream(encoding_t enc, memBufSource src) {
  CACHE_REGS
  int sno;
  StreamDesc *st;

  sno = GetFreeStreamD();
  if (sno < 0)
    return -1;
  st = GLOBAL_Stream + sno;
  st->status = Output_Stream_f | InMemory_Stream_f|FreeOnClose_Stream_f;
  st->linepos = 0;
  st->charcount = 0;
  st->linecount = 1;
  st->encoding = enc;
  Yap_DefaultStreamOps(st);
#if MAY_WRITE
#if HAVE_OPEN_MEMSTREAM
  st->file = open_memstream(&st->nbuf, &st->nsize);
  // setbuf(st->file, NULL);
  st->status |= Seekable_Stream_f;
#else
  st->file = fmemopen((void *)buf, nchars, "w");
  st->nsize = nchars;
  st->nbuf = buf;
  if (!st->nbuf) {
    return -1;
  }
#endif
#else
  st->nbuf = st->u.mem_string.buf = malloc(PLGETC_BUF_SIZE);
  st->u.mem_string.src == MEM_BUF_MALLOC;
  st->u.mem_string.src = 1;
  st->u.mem_string.max_size = PLGETC_BUF_SIZE - 1;
  st->u.mem_string.pos = 0;
#endif
  UNLOCK(st->streamlock);
  return sno;
}

int Yap_OpenBufWriteStream(USES_REGS1) {

  return Yap_open_buf_write_stream(
      GLOBAL_Stream[LOCAL_c_output_stream].encoding, 0);
}

static Int
open_mem_write_stream(USES_REGS1) /* $open_mem_write_stream(-Stream) */
{
  Term t;
  int sno;

  sno = Yap_OpenBufWriteStream(PASS_REGS1);
  if (sno == -1)
    return (PlIOError(SYSTEM_ERROR_INTERNAL, TermNil,
                      "new stream not available for open_mem_read_stream/1"));
  t = Yap_MkStream(sno);
  GLOBAL_Stream[sno].status |= InMemory_Stream_f;
  return (Yap_unify(ARG1, t));
}

/**
 * Yap_PeekMemwriteStream() shows the current buffer for a memory stream.
 *
 * @param sno, the in-memory stream
 *
 * @return temporary buffer, discarded by close and may be moved away
 * by other writes..
 */
char *Yap_MemExportStreamPtr(int sno) {
#if MAY_WRITE
  char *s;
  if (fflush(GLOBAL_Stream[sno].file) == 0) {
    fseek(GLOBAL_Stream[sno].file, 0, SEEK_END);
    s = GLOBAL_Stream[sno].nbuf;
    return s;
  }
  return NULL;
#else
  GLOBAL_Stream[sno].u.mem_string.buf[GLOBAL_Stream[sno].u.mem_string.pos] =
      '\0';
  return GLOBAL_Stream[sno].u.mem_string.buf;
#endif
}

static Int peek_mem_write_stream(
    USES_REGS1) { /* '$peek_mem_write_stream'(+GLOBAL_Stream,?S0,?S) */
  Int sno =
      Yap_CheckStream(ARG1, (Output_Stream_f |
              InMemory_Stream_f), "close/2");
  Int i;
  Term tf = ARG2;
  CELL *HI;
  const char *ptr;

  if (sno < 0)
    return (FALSE);
restart:
  HI = HR;
#if MAY_WRITE
  if (fflush(GLOBAL_Stream[sno].file) == 0) {
    i = fseek(GLOBAL_Stream[sno].file, 0, SEEK_END);
    ptr = GLOBAL_Stream[sno].nbuf;
  }
#else
  ptr = GLOBAL_Stream[sno].u.mem_string.buf;
  i = GLOBAL_Stream[sno].u.mem_string.pos;
#endif
  while (i > 0) {
    --i;
    tf = MkPairTerm(MkIntTerm(ptr[i]), tf);
    if (HR + 1024 >= ASP) {
      UNLOCK(GLOBAL_Stream[sno].streamlock);
      HR = HI;
      if (!Yap_gcl((ASP - HI) * sizeof(CELL), 3, ENV, Yap_gcP())) {
        UNLOCK(GLOBAL_Stream[sno].streamlock);
        Yap_Error(RESOURCE_ERROR_STACK, TermNil, LOCAL_ErrorMessage);
        return (FALSE);
      }
      i = GLOBAL_Stream[sno].u.mem_string.pos;
      tf = ARG2;
      LOCK(GLOBAL_Stream[sno].streamlock);
      goto restart;
    }
  }
  UNLOCK(GLOBAL_Stream[sno].streamlock);
  return (Yap_unify(ARG3, tf));
}

void Yap_MemOps(StreamDesc *st) {
#if MAY_WRITE
  st->stream_putc = FilePutc;
#else
  st->stream_putc = MemPutc;
#endif

#if MAY_READ
  st->stream_getc = PlGetc;
#else
  st->stream_getc = MemGetc;
#endif
}

bool Yap_CloseMemoryStream(int sno) {
  if ((GLOBAL_Stream[sno].status & Output_Stream_f)) {
#if MAY_WRITE
    fflush(GLOBAL_Stream[sno].file);
    fclose(GLOBAL_Stream[sno].file);
    if (GLOBAL_Stream[sno].status & FreeOnClose_Stream_f)
      free(GLOBAL_Stream[sno].nbuf);
#else
    if (GLOBAL_Stream[sno].u.mem_string.src == MEM_BUF_MALLOC) {
      free(GLOBAL_Stream[sno].u.mem_string.buf);
    }
#endif
  } else {
#if MAY_READ
    fclose(GLOBAL_Stream[sno].file);
    if (GLOBAL_Stream[sno].status & FreeOnClose_Stream_f)
      free(GLOBAL_Stream[sno].nbuf);
#else
    if (GLOBAL_Stream[sno].u.mem_string.src == MEM_BUF_MALLOC) {
      free(GLOBAL_Stream[sno].u.mem_string.buf);
    }
#endif
  }
  return true;
}

void Yap_InitMems(void) {
  CACHE_REGS
  Yap_InitCPred("open_mem_read_stream", 2, open_mem_read_stream, SyncPredFlag);
  Yap_InitCPred("open_mem_write_stream", 1, open_mem_write_stream,
                SyncPredFlag);
  Yap_InitCPred("peek_mem_write_stream", 3, peek_mem_write_stream,
                SyncPredFlag);
}