/* ----------------------------------------------------------------------------- 
 * 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.
 *
 * include.c
 *
 * The functions in this file are used to manage files in the SWIG library.
 * General purpose functions for opening, including, and retrieving pathnames
 * are provided.
 * ----------------------------------------------------------------------------- */

#include "swig.h"

static List   *directories = 0;	        /* List of include directories */
static String *lastpath = 0;	        /* Last file that was included */
static List   *pdirectories = 0;        /* List of pushed directories  */
static int     dopush = 1;		/* Whether to push directories */
static int file_debug = 0;

/* This functions determine whether to push/pop dirs in the preprocessor */
void Swig_set_push_dir(int push) {
  dopush = push;
}

int Swig_get_push_dir(void) {
  return dopush;
}

/* -----------------------------------------------------------------------------
 * Swig_add_directory()
 *
 * Adds a directory to the SWIG search path.
 * ----------------------------------------------------------------------------- */

List *Swig_add_directory(const_String_or_char_ptr dirname) {
  String *adirname;
  if (!directories)
    directories = NewList();
  assert(directories);
  if (dirname) {
    adirname = NewString(dirname);
    Append(directories,adirname);
    Delete(adirname);
  }
  return directories;
}

/* -----------------------------------------------------------------------------
 * Swig_push_directory()
 *
 * Inserts a directory at the front of the SWIG search path.  This is used by
 * the preprocessor to grab files in the same directory as other included files.
 * ----------------------------------------------------------------------------- */

void Swig_push_directory(const_String_or_char_ptr dirname) {
  String *pdirname;
  if (!Swig_get_push_dir())
    return;
  if (!pdirectories)
    pdirectories = NewList();
  assert(pdirectories);
  pdirname = NewString(dirname);
  assert(pdirname);
  Insert(pdirectories,0,pdirname);
  Delete(pdirname);
}

/* -----------------------------------------------------------------------------
 * Swig_pop_directory()
 *
 * Pops a directory off the front of the SWIG search path.  This is used by
 * the preprocessor.
 * ----------------------------------------------------------------------------- */

void Swig_pop_directory(void) {
  if (!Swig_get_push_dir())
    return;
  if (!pdirectories)
    return;
  Delitem(pdirectories, 0);
}

/* -----------------------------------------------------------------------------
 * Swig_last_file()
 * 
 * Returns the full pathname of the last file opened. 
 * ----------------------------------------------------------------------------- */

String *Swig_last_file(void) {
  assert(lastpath);
  return lastpath;
}

/* -----------------------------------------------------------------------------
 * Swig_search_path_any() 
 * 
 * Returns a list of the current search paths.
 * ----------------------------------------------------------------------------- */

static List *Swig_search_path_any(int syspath) {
  String *filename;
  List   *slist;
  int     i, ilen;

  slist = NewList();
  assert(slist);
  filename = NewStringEmpty();
  assert(filename);
  Printf(filename, ".%s", SWIG_FILE_DELIMITER);
  Append(slist, filename);
  Delete(filename);
  
  /* If there are any pushed directories.  Add them first */
  if (pdirectories) {
    ilen = Len(pdirectories);
    for (i = 0; i < ilen; i++) {
      filename = NewString(Getitem(pdirectories,i));
      Append(filename,SWIG_FILE_DELIMITER);
      Append(slist,filename);
      Delete(filename);
    }
  }
  /* Add system directories next */
  ilen = Len(directories);
  for (i = 0; i < ilen; i++) {
    filename = NewString(Getitem(directories,i));
    Append(filename,SWIG_FILE_DELIMITER);
    if (syspath) {
      /* If doing a system include, put the system directories first */
      Insert(slist,i,filename);
    } else {
      /* Otherwise, just put the system directories after the pushed directories (if any) */
      Append(slist,filename);
    }
    Delete(filename);
  }
  return slist;
}

List *Swig_search_path(void) {
  return Swig_search_path_any(0);
}



/* -----------------------------------------------------------------------------
 * Swig_open()
 *
 * open a file, optionally looking for it in the include path.  Returns an open  
 * FILE * on success.
 * ----------------------------------------------------------------------------- */

static FILE *Swig_open_file(const_String_or_char_ptr name, int sysfile, int use_include_path) {
  FILE *f;
  String *filename;
  List *spath = 0;
  char *cname;
  int i, ilen, nbytes;
  char bom[3];

  if (!directories)
    directories = NewList();
  assert(directories);

  cname = Char(name);
  filename = NewString(cname);
  assert(filename);
  if (file_debug) {
    Printf(stdout, "  Open: %s\n", filename);
  }
  f = fopen(Char(filename), "r");
  if (!f && use_include_path) {
    spath = Swig_search_path_any(sysfile);
    ilen = Len(spath);
    for (i = 0; i < ilen; i++) {
      Clear(filename);
      Printf(filename, "%s%s", Getitem(spath, i), cname);
      f = fopen(Char(filename), "r");
      if (f)
	break;
    }
    Delete(spath);
  }
  if (f) {
    Delete(lastpath);
    lastpath = filename;

    /* Skip the UTF-8 BOM if it's present */
    nbytes = (int)fread(bom, 1, 3, f);
    if (nbytes == 3 && bom[0] == (char)0xEF && bom[1] == (char)0xBB && bom[2] == (char)0xBF) {
      /* skip */
    } else {
      fseek(f, 0, SEEK_SET);
    }
  }
  return f;
}

/* Open a file - searching the include paths to find it */
FILE *Swig_include_open(const_String_or_char_ptr name) {
  return Swig_open_file(name, 0, 1);
}

/* Open a file - does not use include paths to find it */
FILE *Swig_open(const_String_or_char_ptr name) {
  return Swig_open_file(name, 0, 0);
}



/* -----------------------------------------------------------------------------
 * Swig_read_file()
 * 
 * Reads data from an open FILE * and returns it as a string.
 * ----------------------------------------------------------------------------- */

String *Swig_read_file(FILE *f) {
  int len;
  char buffer[4096];
  String *str = NewStringEmpty();

  assert(str);
  while (1) {
    size_t c = fread(buffer, 1, sizeof(buffer), f);
    if (c > 0) Write(str, buffer, (int)c);
    if (c < sizeof(buffer)) break;
  }
  len = Len(str);
  /* Add a newline if not present on last line -- the preprocessor seems to 
   * rely on \n and not EOF terminating lines */
  if (len) {
    char *cstr = Char(str);
    if (cstr[len - 1] != '\n') {
      Append(str, "\n");
    }
  }
  return str;
}

/* -----------------------------------------------------------------------------
 * Swig_include()
 *
 * Opens a file and returns it as a string.
 * ----------------------------------------------------------------------------- */

static String *Swig_include_any(const_String_or_char_ptr name, int sysfile) {
  FILE *f;
  String *str;
  String *file;

  f = Swig_open_file(name, sysfile, 1);
  if (!f)
    return 0;
  str = Swig_read_file(f);
  fclose(f);
  Seek(str, 0, SEEK_SET);
  file = Copy(Swig_last_file());
  Setfile(str, file);
  Delete(file);
  Setline(str, 1);
  return str;
}

String *Swig_include(const_String_or_char_ptr name) {
  return Swig_include_any(name, 0);
}

String *Swig_include_sys(const_String_or_char_ptr name) {
  return Swig_include_any(name, 1);
}

/* -----------------------------------------------------------------------------
 * Swig_insert_file()
 *
 * Copies the contents of a file into another file
 * ----------------------------------------------------------------------------- */

int Swig_insert_file(const_String_or_char_ptr filename, File *outfile) {
  char buffer[4096];
  int nbytes;
  FILE *f = Swig_include_open(filename);

  if (!f)
    return -1;
  while ((nbytes = Read(f, buffer, 4096)) > 0) {
    Write(outfile, buffer, nbytes);
  }
  fclose(f);
  return 0;
}

/* -----------------------------------------------------------------------------
 * Swig_register_filebyname()
 *
 * Register a "named" file with the core.  Named files can become targets
 * for %insert directives and other SWIG operations.  This function takes
 * the place of the f_header, f_wrapper, f_init, and other global variables
 * in SWIG1.1
 * ----------------------------------------------------------------------------- */

static Hash *named_files = 0;

void Swig_register_filebyname(const_String_or_char_ptr filename, File *outfile) {
  if (!named_files)
    named_files = NewHash();
  Setattr(named_files, filename, outfile);
}

/* -----------------------------------------------------------------------------
 * Swig_filebyname()
 *
 * Get a named file
 * ----------------------------------------------------------------------------- */

File *Swig_filebyname(const_String_or_char_ptr filename) {
  if (!named_files)
    return 0;
  return Getattr(named_files, filename);
}

/* -----------------------------------------------------------------------------
 * Swig_file_extension()
 *
 * Returns the extension of a file
 * ----------------------------------------------------------------------------- */

String *Swig_file_extension(const_String_or_char_ptr filename) {
  String *name = Swig_file_filename(filename);
  const char *c = strrchr(Char(name), '.');
  String *extension = c ? NewString(c) : NewString("");
  Delete(name);
  return extension;
}

/* -----------------------------------------------------------------------------
 * Swig_file_basename()
 *
 * Returns the filename with the extension removed.
 * ----------------------------------------------------------------------------- */

String *Swig_file_basename(const_String_or_char_ptr filename) {
  String *extension = Swig_file_extension(filename);
  String *basename = NewStringWithSize(filename, Len(filename) - Len(extension));
  Delete(extension);
  return basename;
}

/* -----------------------------------------------------------------------------
 * Swig_file_filename()
 *
 * Return the file name with any leading path stripped off
 * ----------------------------------------------------------------------------- */
String *Swig_file_filename(const_String_or_char_ptr filename) {
  const char *delim = SWIG_FILE_DELIMITER;
  const char *c = strrchr(Char(filename), *delim);
  return c ? NewString(c + 1) : NewString(filename);
}

/* -----------------------------------------------------------------------------
 * Swig_file_dirname()
 *
 * Return the name of the directory associated with a file
 * ----------------------------------------------------------------------------- */
String *Swig_file_dirname(const_String_or_char_ptr filename) {
  const char *delim = SWIG_FILE_DELIMITER;
  const char *c = strrchr(Char(filename), *delim);
  return c ? NewStringWithSize(filename, (int)(c - Char(filename) + 1)) : NewString("");
}

/*
 * Swig_file_debug()
 */
void Swig_file_debug_set(void) {
  file_debug = 1;
}
