/* $Id: main.c,v 1.6 2008/10/31 18:52:19 kiesling Exp $ */

/*
  This file is part of ctalk.
  Copyright  2005-2008 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 program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program 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 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 <sys/stat.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#ifdef __DJGPP__
#include "djgpp.h"
#include <dpmi.h>
#include <crt0.h>
#endif
#include "object.h"
#include "message.h"
#include "ctalk.h"
#include "classlib.h"

/* 
 *    CLASSLIBDIR and PKGNAME should be defined in CFLAGs.
 *    If not, use the include directories with the PACKAGE name to
 *    search for class library headers, and use appname[] for the 
 *    package name.
 */
#ifdef CLASSLIBDIR 
char *classlibdir = CLASSLIBDIR;
#else
char *classlibdir = NULL;
#endif

char *libcachedir = NULL;

#ifdef PKGNAME
char *pkgname = PKGNAME;
#else
char *pkgname = NULL;
#endif

static int preprocess_only_opt = FALSE;  /* Preprocess only.                */
static int help_opt = FALSE;             /* Print help message and exit.    */
static int version_opt = FALSE;          /* Print version and exit.         */
static int keep_pragma_opt = FALSE;      /* Keep pragmas in output file.    */
int nolibinc_opt = FALSE;             /* Don't include ctalk's headers.     */
int nolinemarker_opt = FALSE;         /* Don't output line markers.         */
int verbose_opt = FALSE;              /* Verbose warnings.                  */
int warn_extension_opt = FALSE;       /* Print warning for some extensions. */
int warn_classlib_opt = FALSE;        /* Print class library loading.       */
int print_libdirs_opt = FALSE;        /* Print library directories.         */
char *user_include_dirs[MAXUSERDIRS]; /* User-defined include dirs.         */

/* 
 *   Option to specify the compiler's system include directory, if the 
 *   program can't determine it.  For GCC on UNIX, the directory should 
 *   be:
 *   /usr/lib/gcc-lib/<target>/<version>/include/
 *
 *   On older SunOS systems, GCC and its libraries might be under 
 *   /opt.  
 */
int systeminc_opt = FALSE;
char *systeminc_dirs[MAXUSERDIRS]; 
int n_system_inc_dirs = 0;
int n_user_include_dirs = 0;

static char apppath[FILENAME_MAX]; /* Path to the application, from argv[0].*/
static char *source_files[MAXARGS]; /* Source files from the command args.  */
static int n_input_files, input_idx;
char appname[FILENAME_MAX],        /* Ctalk executable name.                */
  input_source_file[FILENAME_MAX];/* Name of the current source file.       */

extern I_PASS interpreter_pass;

char *get_class_lib_dir (void) {
  return classlibdir;
}

char *get_package_name (void) {
  return pkgname;
}

                                    /* Output file options.                  */
extern int outputfile_opt;          /* Declared in tempio.c                  */
extern char 
output_file[FILENAME_MAX];

static char p_source_file[FILENAME_MAX]; /* Name of preprocessed input file. */
char ctpp_ofn[FILENAME_MAX];      /* File name of any preprocessor output.   */

int main (int argc, char **argv) {

  char *input;
  struct stat statbuf;
  int r_stat,                       /* Results from stat ().                 */
    r;                              /* Results from other lib calls.         */
  long long source_stat_size;       /* Size of the source file, from stat(). */
  CLASSLIB *declarations;

  outputfile_opt = FALSE;

#ifdef MALLOC_TRACE
  mtrace ();
#endif

  args (argv, argc);
  if (version_opt) {
    printf ("%s\n", VERSION);
    exit (0);
  }
  if (print_libdirs_opt) {
    print_libdirs ();
    exit (0);
  }

  if (help_opt || ! n_input_files)
    exit_help ();

  init_error_location ();
  /*
   *  Make sure we have the name of a source file for error
   *  checking during the initialization.
   */
  __ctalkRtSaveSourceFileName (source_files[0]);

  init_library_include_stack ();
  init_library_paths ();
  init_tmp_files ();

  init_cvars ();
  init_frames ();
  init_message_stack ();
  init_return_vals ();
  init_method_stack ();
  init_new_method_stack ();
  init_method_buffer ();
  init_control_blocks ();
  init_loops ();
  init_parser_frame_ptr ();
  init_parsers ();
  init_output_vars ();
  init_imported_method_queue ();
  init_fn_templates ();
  init_fn_template_cache ();
  init_lib_cache_path ();

  if (outputfile_opt == TRUE) {
    save_previous_output (output_file);
    if ((r_stat = stat (output_file, &statbuf)) != -1) {
      if ((r = unlink (output_file)) == -1)
 	_error ("%s: %s: %s\n", appname, output_file, strerror (errno));
    }
  }

#ifdef __DJGPP__
  sbrk (DJGPP_HEAP_SIZE);
  _crt0_startup_flags |= _CRT0_FLAG_PRESERVE_FILENAME_CASE;
#endif

  /* Do not generate any code before this. */
  if (!preprocess_only_opt)
    init_default_classes ();

  for (input_idx = 0; input_idx < n_input_files; input_idx++) {

    strcpy (input_source_file, source_files[input_idx]);

    /* Pass 1 - Preprocessing. */

    if (stat (input_source_file, &statbuf) != 0) {
      delete_class_library ();
      _error ("%s: %s.\n", input_source_file, strerror (errno));
    }

    /*
     *  Fills in ctpp_ofn file name.
     */
    if (preprocess (input_source_file, TRUE) == ERROR) {
      cleanup (TRUE);
      return ERROR;
    }

    if (preprocess_only_opt) {
      preprocess_to_output (input_source_file);
      cleanup (FALSE);
      continue;
    }

    strcpy (p_source_file, ctpp_ofn);

    /* Pass 2 - Register variables. */

    interpreter_pass = var_pass;

    parse_vars (p_source_file);
    resolve_incomplete_types ();

    /* Pass 3 - Parsing. */

    interpreter_pass = parsing_pass;

    if (stat (p_source_file, &statbuf) != 0)
      _error ("%s source file %s: %s.", appname, input_source_file,
	      strerror (errno));

    source_stat_size = statbuf.st_size;

    if ((input = (char *)calloc (source_stat_size + 1, sizeof(char))) == NULL)
      _error ("%s: %s.", p_source_file, strerror (errno));

    push_input_declaration (p_source_file, p_source_file, 0);
    input_prototypes (p_source_file);

    r = read_file (input, p_source_file);

    input = parse (input, source_stat_size);

    free (input);
    
    if (file_exists (p_source_file))
      unlink (p_source_file);

    declarations = pop_input_declaration ();
    free (declarations);

    cleanup (FALSE);
  }
  
  cleanup_template_cache (); 
  delete_class_library ();

#ifdef MALLOC_TRACE
  muntrace ();
#endif

  return 0;
}

int args (char **a, int c) {

  char *p;
  int i;

  strcpy (apppath, a[0]);
  *input_source_file = *output_file = 0;

  if (((p = rindex (a[0], '/')) != NULL) ||
      ((p = rindex (a[0], '\\')) != NULL)) {
    strcpy (appname, ++p);
  } else {
    strcpy (appname, a[0]);
  }

  if (!pkgname)
    pkgname = appname;

  for (i = 1, n_input_files = 0; i < c; i++) {
    if (a[i][0] == '-') {
      switch (a[i][1])
	{
	case '-':
	  i += extended_arg (a[i]);
	  break;
	case 'v':
	  verbose_opt = TRUE;
	  break;
	case 'I':
	  if (a[i][2]) {
	    user_include_dirs[n_user_include_dirs++] = strdup  (&a[i][2]);
	  } else {
	    user_include_dirs[n_user_include_dirs++] = strdup  (a[++i]);
	  }
	  if (!is_dir (user_include_dirs[n_user_include_dirs - 1])) {
	    printf ("Include directory %s not found.\n",
		    user_include_dirs[n_user_include_dirs - 1]);
	    exit_help ();
	  }
	  break;
	case 'E':
	  preprocess_only_opt = TRUE;
	  break;
	case 'h':
	  help_opt = TRUE;
	  break;
	case 's':
	  systeminc_opt = TRUE;
	  if (a[i][2]) {
	    systeminc_dirs[n_system_inc_dirs++] = strdup  (&a[i][2]);
	  } else {
	    if (a[i+1]) {
	      systeminc_dirs[n_system_inc_dirs++] = strdup  (a[++i]);
	    } else {
	      exit_help ();
	    }
	  }
	  if ((!file_exists (systeminc_dirs[n_system_inc_dirs - 1])) ||
	      !is_dir (systeminc_dirs[n_system_inc_dirs - 1]))
	    exit_help ();
	  break;
	case 'o':
	  outputfile_opt = TRUE;
	  if (a[i][2]) {
	    strcpy (output_file, &a[i][2]);
	  } else {
	    strcpy (output_file, a[++i]);
	  }
	  break;
	case 'V':
	  version_opt = TRUE;
	  break;
	case 'P':
	  nolinemarker_opt = TRUE;
	  break;
	}
    } else {
      source_files[n_input_files++] = strdup (a[i]);
    }
  }

  return 0;
}

int extended_arg (char *s) {

  if (!strcmp (s, "--nolibinc")) {
    nolibinc_opt = TRUE;
    return SUCCESS;
  }

  if (!strcmp (s, "--keeppragmas")) {
    keep_pragma_opt = TRUE;
    return SUCCESS;
  }

  if (!strcmp (s, "--printlibdirs")) {
    print_libdirs_opt = TRUE;
    return SUCCESS;
  }

  if (!strcmp (s, "--warnextension")) {
    warn_extension_opt = TRUE;
    return SUCCESS;
  }

  if (!strcmp (s, "--warnclasslibs")) {
    warn_classlib_opt = TRUE;
    return SUCCESS;
  }

  if (!strcmp (s, "--help"))
    exit_help ();

  /* Unknown option. */
  exit_help ();

  return SUCCESS;
}

void exit_help (void) {

  printf (_("Usage: %s [-E] [-h | --help] | [-I <dir>] [-P] [-s <dir>] [-o <filename>] [-v] [--keeppragmas] [--nolibinc] [--warnextension]  input_files\n"),
	  appname);
  printf (_("-E               Preprocess the input and exit.\n"));
  printf (_("-h               Print help message and exit.\n"));
  printf (_("--help\n"));
  printf (_("-I <dir>         Add <dir> to ctalk include search path.\n"));
  printf (_("--keeppragmas    Write pragmas untranslated to the output.\n"));
  printf (_("--printlibdirs   Print library directories and exit.\n"));
  printf (_("--nolibinc       Do not include ctalk's system headers in the input.\n"));
  printf (_("-o <file>        Write output to <file> instead of standard output.\n"));
  printf (_("-P               Do not output line number information.\n"));
  printf (_("-s <dir>         Add <dir> to compiler system include search path.\n"));
  printf (_("-V               Print the ctalk version number and exit.\n"));
  printf (_("-v               Print verbose warnings.\n"));
  printf (_("--warnextension  Print warning for some compiler extensions.\n"));
  printf (_("Report bugs to ctalk@ctalklang.org.\n"));
  exit (0);
}

int preprocess (char *fn, int include_lib) {
 
  char cmd[MAXMSG],
    argbuf[MAXMSG],
    tmpbuf[MAXMSG],
    cachebuf[MAXMSG],
    oldcache[FILENAME_MAX];
  char cmdbuf[2];
  char *lib_tmp_fn = NULL;     /* Avoid a warning. */
  int inc_idx;
  int retval;
  FILE *P;

  *argbuf = 0;

  /*
   *  If there are cached macros, include them.
   */

  if (macro_cache_path ()) {
    strcpy (oldcache, macro_cache_path ());
    if (file_exists (oldcache)) {
      sprintf (cachebuf, "-imacros %s ", macro_cache_path ());
      strcat (argbuf, cachebuf);
    }
  } else {
    *oldcache = 0;
  }

  /*
   *  Includes with -I, then includes with -s.
   *  Use args from the command line if necessary.
   */
  for (inc_idx = 0; inc_idx < n_user_include_dirs; inc_idx++) {
    sprintf (tmpbuf, "-I%s ", user_include_dirs[inc_idx]);
    strcat (argbuf, tmpbuf);
  }
  for (inc_idx = 0; inc_idx < n_system_inc_dirs; inc_idx++) {
    sprintf (tmpbuf, "-I%s ", systeminc_dirs[inc_idx]);
    strcat (argbuf, tmpbuf);
  }
  if (nolinemarker_opt) strcat (argbuf, "-P ");

  if (!preprocess_only_opt && !nolibinc_opt && include_lib) {
    lib_tmp_fn = ctalk_lib_include ();
    sprintf (tmpbuf, "-include %s ", lib_tmp_fn);
    strcat (argbuf, tmpbuf);
  }

  sprintf (cachebuf, "-dF %s ", new_macro_cache_path ());
  strcat (argbuf, cachebuf);

#ifndef __CYGWIN__
  if (include_lib)
      strcat (argbuf, "-move-includes ");
#endif
  strcpy (ctpp_ofn, ctpp_name (fn, TRUE));

#ifdef __DJGPP__
  sprintf (cmd, "%s %s \"%s\" %s",
 	   which (CTPP_BIN), argbuf, fn, ctpp_ofn);
#else
  sprintf (cmd, "%s %s %s %s",
 	   which (CTPP_BIN), argbuf, fn, ctpp_ofn);
#endif

  if ((P = popen (cmd, "r")) != NULL) {
    while (fread (cmdbuf, sizeof (char), 1, P))
      printf ("%s", cmdbuf);
  } else {
    printf ("%s: %s.\n", fn, strerror (errno));
    return ERROR;
  }

  if (!preprocess_only_opt && 
      !nolibinc_opt && 
      include_lib && 
      lib_tmp_fn) {
    unlink (lib_tmp_fn);
  }

  if ((retval = pclose (P)) == ERROR) {
    cleanup (TRUE);
    exit (retval);
  }

  if (*oldcache && file_exists (oldcache)) unlink (oldcache);

  /*
   *  If the preprocessor encounters a fatal error, it
   *  will not produce an output file.
   */
  if (file_exists (ctpp_ofn)) {
    return SUCCESS;
  } else {
    return ERROR;
  }
}

int preprocess_to_output (char *fn) {

  if (outputfile_opt) {
    rename_file (ctpp_ofn, output_file);
  } else {
    rename_file (ctpp_ofn, preprocess_name (fn, FALSE));
  }

  return 0;
}

#define TMPFILE_PFX     "c."         /* From tempio.c. */

extern int error_line;               /* From lex.c. */

/*
 *  Add an ending line marker to ctalklib.  Returns the name
 *  of the temporary file.
 */

char *ctalk_lib_include (void) {

  FILE *outfile;
  char ctalklibpath[FILENAME_MAX];
  char lineinfo_buf[MAXMSG];
  char *libinput;
  static char fn[FILENAME_MAX];
  int r, r_w, stat_size;
#ifdef DJGPP
  int tmp_ext;
#endif

#ifdef DJGPP
  for (tmp_ext = 0; ; tmp_ext++) {
    sprintf (fn, "%s/c.%p", P_tmpdir, (void *)tmp_ext);
    if (!file_exists (fn))
      break;
  }
#else
  sprintf (fn, "%s/c.XXXXXX", P_tmpdir);
  mkstemp (fn);
#endif

  sprintf (ctalklibpath, "%s/%s", classlibdir, "ctalklib");

  stat_size = file_size (ctalklibpath);

  if ((libinput = (char *)calloc (stat_size + 1, sizeof(char))) == NULL)
    _error ("%s: %s.", ctalklibpath, strerror (errno));

  r = read_file (libinput, ctalklibpath);

  if ((outfile = fopen (fn, FILE_WRITE_MODE)) == NULL)
    _error ("%s: %s.\n", fn, strerror (errno));

  if ((r_w = fwrite (libinput, sizeof (char), r, outfile)) != r)
    _error ("%s: %s.\n", fn, strerror (errno));

  if (!nolinemarker_opt)
    sprintf (lineinfo_buf, "# %d \"%s\" %d\n", 0,
	     __source_filename (), 1);

  r = strlen (lineinfo_buf);

  if ((r_w = fwrite (lineinfo_buf, sizeof (char), r, outfile)) != r)
    _error ("%s: %s.\n", fn, strerror (errno));

  fclose (outfile);
  free (libinput);
  return fn;
}
