/* -----------------------------------------------------------------------------
 * This file is part of SWIG, which is licensed as a whole under version 3
 * (or any later version) of the GNU General Public License. Some additional
 * terms also apply to certain portions of SWIG. The full details of the SWIG
 * license and copyrights can be found in the LICENSE and COPYRIGHT files
 * included with the SWIG source code as distributed by the SWIG developers
 * and at https://www.swig.org/legal.html.
 *
 * file.c
 *
 *     This file implements a file-like object that can be built around an
 *     ordinary FILE * or integer file descriptor.
 * ----------------------------------------------------------------------------- */

#include "dohint.h"

#ifdef DOH_INTFILE
#include <unistd.h>
#endif
#include <errno.h>

typedef struct {
  FILE *filep;
  int fd;
  int closeondel;
} DohFile;

/* -----------------------------------------------------------------------------
 * open_files_list_instance
 * open_files_list_add
 * open_files_list_remove
 *
 * Singleton list containing all the files that have been opened by DohNewFile.
 * Open file pointers are held in the list as strings so as to not affect the
 * reference count of the underlying DOH objects.
 * ----------------------------------------------------------------------------- */

static DOHList *open_files_list_instance(void) {
  static DOHList *all_open_files = 0;
  if (!all_open_files)
    all_open_files = DohNewList();
  return all_open_files;
}

static void open_files_list_add(DohFile *f) {
  DOHList *all_open_files = open_files_list_instance();
  DOHString *sf = NewStringf("%p", f);
  Append(all_open_files, sf);
  Delete(sf);
}

static void open_files_list_remove(DohFile *f) {
  int i;
  int removed = 0;
  DOHList *all_open_files = open_files_list_instance();
  DOHString *sf = NewStringf("%p", f);
  for (i = 0; i < DohLen(all_open_files); i++) {
    DOHString *sf_i = Getitem(all_open_files, i);
    if (Strcmp(sf, sf_i) == 0) {
      DohDelitem(all_open_files, i);
      removed = 1;
      break;
    }
  }
  Delete(sf);
  assert(removed);
  (void)removed;
}

/* -----------------------------------------------------------------------------
 * DohCloseAllOpenFiles()
 *
 * Close all opened files, to be called on program termination
 * ----------------------------------------------------------------------------- */

void DohCloseAllOpenFiles(void) {
  int i;
  DOHList *all_open_files = open_files_list_instance();
  for (i = 0; i < DohLen(all_open_files); i++) {
    DohFile *f = 0;
    DOHString *sf = Getitem(all_open_files, i);
    int check = sscanf(Char(sf), "%p", (void **)&f);
    assert(check == 1);
    (void)check;
    if (f->closeondel) {
      if (f->filep) {
        check = fclose(f->filep);
        assert(check == 0);
      }
      f->closeondel = 0;
      f->filep = 0;
    }
  }
  DohClear(all_open_files);
}

/* -----------------------------------------------------------------------------
 * DelFile()
 * ----------------------------------------------------------------------------- */

static void DelFile(DOH *fo) {
  DohFile *f = (DohFile *)ObjData(fo);
  if (f->closeondel) {
    if (f->filep) {
      fclose(f->filep);
    }
#ifdef DOH_INTFILE
    if (f->fd) {
      close(f->fd);
    }
#endif
    open_files_list_remove(f);
  }
  DohFree(f);
}

/* -----------------------------------------------------------------------------
 * File_read()
 * ----------------------------------------------------------------------------- */

static int File_read(DOH *fo, void *buffer, int len) {
  DohFile *f = (DohFile *)ObjData(fo);

  if (f->filep) {
    return (int)fread(buffer, 1, len, f->filep);
  } else if (f->fd) {
#ifdef DOH_INTFILE
    return read(f->fd, buffer, len);
#endif
  }
  return -1;
}

/* -----------------------------------------------------------------------------
 * File_write()
 * ----------------------------------------------------------------------------- */

static int File_write(DOH *fo, const void *buffer, int len) {
  DohFile *f = (DohFile *)ObjData(fo);
  if (f->filep) {
    int ret = (int)fwrite(buffer, 1, len, f->filep);
    int err = (ret != len) ? ferror(f->filep) : 0;
    return err ? -1 : ret;
  } else if (f->fd) {
#ifdef DOH_INTFILE
    return write(f->fd, buffer, len);
#endif
  }
  return -1;
}

/* -----------------------------------------------------------------------------
 * File_seek()
 * ----------------------------------------------------------------------------- */

static int File_seek(DOH *fo, long offset, int whence) {
  DohFile *f = (DohFile *)ObjData(fo);
  if (f->filep) {
    return fseek(f->filep, offset, whence);
  } else if (f->fd) {
#ifdef DOH_INTFILE
    return lseek(f->fd, offset, whence);
#endif
  }
  return -1;
}

/* -----------------------------------------------------------------------------
 * File_tell()
 * ----------------------------------------------------------------------------- */

static long File_tell(DOH *fo) {
  DohFile *f = (DohFile *)ObjData(fo);
  if (f->filep) {
    return ftell(f->filep);
  } else if (f->fd) {
#ifdef DOH_INTFILE
    return lseek(f->fd, 0, SEEK_CUR);
#endif
  }
  return -1;
}

/* -----------------------------------------------------------------------------
 * File_putc()
 * ----------------------------------------------------------------------------- */

static int File_putc(DOH *fo, int ch) {
  DohFile *f = (DohFile *)ObjData(fo);
  if (f->filep) {
    return fputc(ch, f->filep);
  } else if (f->fd) {
#ifdef DOH_INTFILE
    char c;
    c = (char)ch;
    return write(f->fd, &c, 1);
#endif
  }
  return -1;
}

/* -----------------------------------------------------------------------------
 * File_getc()
 * ----------------------------------------------------------------------------- */

static int File_getc(DOH *fo) {
  DohFile *f = (DohFile *)ObjData(fo);
  if (f->filep) {
    return fgetc(f->filep);
  } else if (f->fd) {
#ifdef DOH_INTFILE
    unsigned char c;
    if (read(f->fd, &c, 1) < 0)
      return EOF;
    return c;
#endif
  }
  return EOF;
}

/* -----------------------------------------------------------------------------
 * File_ungetc()
 *
 * Put a character back onto the input
 * ----------------------------------------------------------------------------- */

static int File_ungetc(DOH *fo, int ch) {
  DohFile *f = (DohFile *)ObjData(fo);
  if (f->filep) {
    return ungetc(ch, f->filep);
  } else if (f->fd) {
#ifdef DOH_INTFILE
    /* Not implemented yet */
#endif
  }
  return -1;
}

static DohFileMethods FileFileMethods = {
  File_read,
  File_write,
  File_putc,
  File_getc,
  File_ungetc,
  File_seek,
  File_tell,
};

static DohObjInfo DohFileType = {
  "DohFile",        /* objname      */
  DelFile,          /* doh_del      */
  0,                /* doh_copy     */
  0,                /* doh_clear    */
  0,                /* doh_str      */
  0,                /* doh_data     */
  0,                /* doh_dump     */
  0,                /* doh_len      */
  0,                /* doh_hash     */
  0,                /* doh_cmp      */
  0,                /* doh_equal    */
  0,                /* doh_first    */
  0,                /* doh_next     */
  0,                /* doh_setfile  */
  0,                /* doh_getfile  */
  0,                /* doh_setline  */
  0,                /* doh_getline  */
  0,                /* doh_mapping  */
  0,                /* doh_sequence */
  &FileFileMethods, /* doh_file     */
  0,                /* doh_string   */
  0,                /* doh_reserved */
  0,                /* clientdata */
};

/* -----------------------------------------------------------------------------
 * NewFile()
 *
 * Create a new file from a given filename and mode.
 * If newfiles is non-zero, the filename is added to the list of new files.
 * ----------------------------------------------------------------------------- */

DOH *DohNewFile(DOHString *filename, const char *mode, DOHList *newfiles) {
  DohFile *f;
  DOH *obj;
  FILE *file;
  char *filen;

  filen = Char(filename);
  file = fopen(filen, mode);
  if (!file)
    return 0;

  f = (DohFile *)DohMalloc(sizeof(DohFile));
  if (newfiles)
    Append(newfiles, filename);
  f->filep = file;
  f->fd = 0;
  f->closeondel = 1;
  obj = DohObjMalloc(&DohFileType, f);
  open_files_list_add(f);
  return obj;
}

/* -----------------------------------------------------------------------------
 * NewFileFromFile()
 *
 * Create a file object from an already open FILE *.
 * ----------------------------------------------------------------------------- */

DOH *DohNewFileFromFile(FILE *file) {
  DohFile *f;
  f = (DohFile *)DohMalloc(sizeof(DohFile));
  f->filep = file;
  f->fd = 0;
  f->closeondel = 0;
  return DohObjMalloc(&DohFileType, f);
}

/* -----------------------------------------------------------------------------
 * NewFileFromFd()
 *
 * Create a file object from an already open FILE *.
 * ----------------------------------------------------------------------------- */

DOH *DohNewFileFromFd(int fd) {
  DohFile *f;
  f = (DohFile *)DohMalloc(sizeof(DohFile));
  f->filep = 0;
  f->fd = fd;
  f->closeondel = 0;
  return DohObjMalloc(&DohFileType, f);
}

/* -----------------------------------------------------------------------------
 * FileErrorDisplay()
 *
 * Display cause of one of the NewFile functions failing.
 * ----------------------------------------------------------------------------- */

void DohFileErrorDisplay(DOHString *filename) {
  Printf(stderr, "Unable to open file %s: %s\n", filename, strerror(errno));
}
