/* $Id: tempio.c,v 1.5 2009/01/04 20:28:40 kiesling Exp $ */

/*
  This file is part of Ctalk.
  Copyright © 2005-2009  Robert Kiesling, ctalk@ctalklang.org.
  Permission is granted to copy this software provided that this copyright
  notice is included in all source code modules.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software Foundation, 
  Inc., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <ctype.h>
#include <errno.h>
#include "object.h"
#include "message.h"
#include "ctalk.h"

#define SOURCE_EXT     ".c"
#define PREPROCESS_EXT ".i"
#define CTPP_EXT       ".ctpp"

int outputfile_opt;               /* User defined output file.             */
char output_file[FILENAME_MAX];   /* Name of the output file.              */
char tmp_output[FILENAME_MAX];    /* Temporary name of old output file.    */

/* From rt_error.c. */
void _error (char *, ...);

/* From substrcpy.c */
char *substrcpy (char *, char *, int, int);

/* From statfile.c */
int file_exists (char *);

/* From tempname.c */
char *__tempname (char *, char *);
char *__rand_ext (void);

extern int outputfile_opt;               /* Declared in main.c.            */
extern char output_file[FILENAME_MAX];

static char *tmp_path[MAXARGS];          /* Tmp paths stack.               */
static int tmp_fileno[MAXARGS];          /* Tmp file numbers.              */
static FILE *tmp_file[MAXARGS];          /* Tmp files descriptors.         */
static int tmp_path_ptr;                 /* Points to first empty entry    */
                                         /* in tmp_paths[].                */
static int max_tmp_paths;                /* The maximum number of temp     */
                                         /* files open in this session.    */
                                         /* Used by cleanup ().            */
static char 
tmp_macro_path[FILENAME_MAX];            /* Pathname of the current macro  */
                                         /* cache.                         */

#define TMPFILE_PFX     "c."             /* Generic tmp prefix.            */
#define TMPDEFINES_PFX  "cm."

void init_tmp_files (void) { 
  tmp_path_ptr = max_tmp_paths = 0; 
  memset ((void *)tmp_macro_path, 0, FILENAME_MAX * sizeof (char));
}

int create_tmp (void) {

  char newpath[FILENAME_MAX], prefix[5], *tempname_ptr;
  int r = ERROR;                            /* Avoid a warning. */

  sprintf (prefix, "%s%d.", TMPFILE_PFX, tmp_path_ptr);

  tempname_ptr = __tempname (P_tmpdir, prefix);
  strcpy (newpath, tempname_ptr);
  free (tempname_ptr);

  if ((tmp_file[tmp_path_ptr] = fopen (newpath, FILE_WRITE_MODE)) != NULL) {
    tmp_fileno[tmp_path_ptr] = fileno (tmp_file[tmp_path_ptr]);
    tmp_path[tmp_path_ptr] = strdup (newpath);
    tmp_path_ptr++;
    if (tmp_path_ptr > max_tmp_paths)
      max_tmp_paths = tmp_path_ptr;
    r = SUCCESS;
  } else {
    __ctalkWarning ("%s: %s.", newpath, strerror (errno));
    r = ERROR;
  }
  return r;
}

#define LAST_TMP_IDX (tmp_path_ptr - 1)

int unlink_tmp (void) {

  if (tmp_path_ptr <= 0)
    return SUCCESS;

  if (tmp_fileno[LAST_TMP_IDX] >= 0) {
    if (fileno (tmp_file[LAST_TMP_IDX]) == tmp_fileno[LAST_TMP_IDX])
      fclose (tmp_file[LAST_TMP_IDX]);
  }
  if (unlink (tmp_path[LAST_TMP_IDX]) == 0) {
    free(tmp_path[LAST_TMP_IDX]);
    tmp_path_ptr--;
  } else {
    _error ("%s: %s.", tmp_path[LAST_TMP_IDX], strerror (errno));
  }
  return SUCCESS;
}

int close_tmp (void) {

  if (fileno (tmp_file[LAST_TMP_IDX]) != tmp_fileno[LAST_TMP_IDX])
    return ERROR;

  if (fclose (tmp_file[LAST_TMP_IDX]) == -1)
    _error ("close_tmp: %s, %s.\n", tmp_path[LAST_TMP_IDX], strerror (errno));

  tmp_fileno[LAST_TMP_IDX] = -1;
  return SUCCESS;
}

int write_tmp (char *s) {

  int r; 

  if (fileno (tmp_file[LAST_TMP_IDX]) == -1)
    _error ("write_tmp: %s: %s.", tmp_path[LAST_TMP_IDX], "Temp file closed.");

  if ((r = fputs (s, tmp_file[LAST_TMP_IDX])) == EOF)
    _error ("write_tmp: %s: %s.", tmp_path[LAST_TMP_IDX], strerror (errno));

  return 0;
}

char *get_tmpname (void) {
  return tmp_path[tmp_path_ptr - 1];
}

void tmp_to_output (void) {
  FILE *tmp_f, *output_f;
  char *buf;
  int output_fileno;
  int r, tmp_size, last = tmp_path_ptr - 1;
  struct stat statbuf;

  if ((tmp_f = fopen (tmp_path[last], FILE_READ_MODE)) == NULL)
    _error ("tmp_to_output %s: %s.\n", tmp_path[last], strerror (errno));

  if ((r = stat (tmp_path[last], &statbuf)) != 0) 
    _error ("%s:%s.\n", tmp_path[last], strerror (errno));

  tmp_size = statbuf.st_size;

  if ((buf = calloc (tmp_size + 1, sizeof (char))) == NULL)
    _error ("%s calloc ():%s.\n", tmp_path[last], strerror (errno));

  if ((r = fread (buf, sizeof (char), tmp_size, tmp_f)) != tmp_size)
    _error ("%s: %s.\n", tmp_path[last], strerror (errno));
  fclose (tmp_f);

  if (strcmp (output_file, "-")) {

    if ((output_f = fopen (output_file, FILE_WRITE_MODE)) == NULL)
      _error ("tmp_to_output %s: %s.\n", tmp_path[last], strerror (errno));
    output_fileno = fileno (output_f);
  } else {
     output_fileno = dup (1);   /* Standard output. */
  }

  write (output_fileno, buf, tmp_size);
  if (strcmp (output_file, "-"))
    close (output_fileno);

  free (buf);
}

int rename_file (char *oldname, char *newname) {

  FILE *old_f, *new_f;
  char c;
  int r;

  if ((old_f = fopen (oldname, FILE_READ_MODE)) == NULL)
    return ERROR;

  if ((new_f = fopen (newname, FILE_WRITE_MODE)) == NULL)
    return ERROR;

  while ((r = fread (&c, sizeof (char), 1, old_f)) != 0)
    fwrite (&c, sizeof (char), 1, new_f);

  fclose (old_f);
  fclose (new_f);

  if ((r = unlink (oldname)) == -1)
    fprintf (stderr, "%s: %s.\n", oldname, strerror (errno));

  return SUCCESS;
}

int copy_file (char *oldname, char *newname) {

  FILE *old_f, *new_f;
  char c;
  int r;

  if ((old_f = fopen (oldname, FILE_READ_MODE)) == NULL)
    return ERROR;

  if ((new_f = fopen (newname, FILE_WRITE_MODE)) == NULL)
    return ERROR;

  while ((r = fread (&c, sizeof (char), 1, old_f)) != 0)
    fwrite (&c, sizeof (char), 1, new_f);

  fclose (old_f);
  fclose (new_f);

  return SUCCESS;
}

int save_previous_output (char *filename) {

  char *ext_ptr;
  char basename[FILENAME_MAX];

  if (file_exists (filename)) {

    if ((ext_ptr = index (filename, '.')) != NULL) {
      substrcpy (basename, filename, 0, ext_ptr - filename);
      sprintf (tmp_output, "%s.%s", basename, "tmp");
      rename_file (filename, tmp_output);
    }
  }
  return SUCCESS;
}

/* 
 *  Clean up any temporary files.  If there's an error, also 
 *  delete the output file if any.  For DJGPP, ensure that the 
 *  temporary file directory entry matches whatever tempnam () 
 *  mapped it to, by comparing it against the generated temporary 
 *  names.
 */

void cleanup (int unlink_output) {

  char tmpdir[FILENAME_MAX];
  char tmpname[FILENAME_MAX];
  DIR *dir;
  struct dirent *d;
#ifdef __DJGPP__
  int i;
#endif

  strcpy (tmpdir, P_tmpdir);
  if ((dir = opendir (tmpdir)) != NULL) {

    while ((d = readdir (dir)) != NULL) {
#ifdef __DJGPP__
      for (i = 0; i < tmp_path_ptr; i++) {
	if (!strncmp (d -> d_name, basename (tmp_path[i]), 3)) {
	  sprintf (tmpname, "%s%s", P_tmpdir, d -> d_name);
	  unlink (tmpname);
	}
      }
#else
      if (!strncmp (d -> d_name, TMPFILE_PFX, strlen (TMPFILE_PFX))) {
	sprintf (tmpname, "%s/%s", P_tmpdir, d -> d_name);
	unlink (tmpname);
      }
#endif
    }
    closedir (dir);
  } else {
    _error ("cleanup: %s: %s", P_tmpdir, strerror (errno));
  }
  
  if (outputfile_opt && unlink_output) {
    if (file_exists (output_file))
      unlink (output_file);
    rename_file (tmp_output, output_file);
  } else {
    if (file_exists (tmp_output))
      unlink (tmp_output);
  }

}

void cleanup_preprocessor_cache (void) {
  char tmpdir[FILENAME_MAX];
  char tmpname[FILENAME_MAX];
  DIR *dir;
  struct dirent *d;
  strcpy (tmpdir, P_tmpdir);
  if ((dir = opendir (tmpdir)) != NULL) {
    while ((d = readdir (dir)) != NULL) {
      if (!strncmp (d -> d_name, TMPDEFINES_PFX, strlen (TMPDEFINES_PFX))) {
	sprintf (tmpname, "%s/%s", P_tmpdir, d -> d_name);
 	unlink (tmpname);
      }
    }
    closedir (dir);
  } else {
    _error ("cleanup_preprocessor_cache: %s: %s", P_tmpdir, strerror (errno));
  }
}

char *preprocess_name (char *fn, int path) {

  static char pname[FILENAME_MAX],
    ppath[FILENAME_MAX],
    *dot,
    *slash;

  if ((slash = rindex (fn, '/')) != NULL)
    strcpy (pname, ++slash);
  else
    strcpy (pname, fn);

  if (((dot = rindex (pname, '.')) != NULL) &&
      (!strncmp (dot, SOURCE_EXT, 2)))
    *dot = 0;

  if (path) {
    sprintf (ppath, "%s/%s%s", P_tmpdir, pname, PREPROCESS_EXT);
    return ppath;
  } else {
    strcat (pname, PREPROCESS_EXT);
    return pname;
  }

  return pname;
}

char *ctpp_name (char *fn, int path) {

  static char pname[FILENAME_MAX],
    ppath[FILENAME_MAX],
    *dot,
    *slash,
    *rand_ext;
  struct stat statbuf;
  int r;

  if ((slash = rindex (fn, '/')) != NULL)
    strcpy (pname, ++slash);
  else
    strcpy (pname, fn);

  if (((dot = rindex (pname, '.')) != NULL) &&
      (!strncmp (dot, SOURCE_EXT, 2)))
    *dot = 0;

  while (1) {
    rand_ext = __rand_ext ();
    if (path) {
      sprintf (ppath, "%s/%s%s.%s", P_tmpdir, pname, CTPP_EXT, rand_ext);
    } else {
      sprintf (ppath, "%s%s.%s", pname, CTPP_EXT, rand_ext);
    }
    free (rand_ext);
    if ((r = stat (ppath, &statbuf)) != 0)
      return ppath;
  }

  return NULL;
}

char *new_macro_cache_path (void) {
  char *tempname_ptr;
  tempname_ptr = __tempname (P_tmpdir, TMPDEFINES_PFX);
  strcpy (tmp_macro_path, tempname_ptr);
  free (tempname_ptr);
  return tmp_macro_path;
}

char *macro_cache_path (void) {
  if (*tmp_macro_path == '\0') return NULL;
  return tmp_macro_path;
}

FILE *fopen_tmp (char *path, char *mode) {
  static FILE *f;  

  if ((f = fopen (path, mode)) == NULL)
    _error ("open_tmp: %s.\n", strerror (errno));
  return f;
}

int fclose_tmp (FILE *f) {
  return fclose (f);
}
