/*
 * tunix.c - user interface for Unix.
 */

#include "../h/gsupport.h"
#include "../h/version.h"
#include "tproto.h"
#include "tglobals.h"

static void execute (char *ofile, char *efile, char *args[]);
static void usage (void);
static char *libpath (char *prog, char *envname);

static void txrun(char *(*func)(FILE*, char*), char *source, char *args[]);
static char *copyfile(FILE *f, char *srcfile);
static char *savefile(FILE *f, char *srcprog);
static void cleanup(void);

static char **rfiles;		/* list of files removed by cleanup() */

/*
 * for old method of hardwiring iconx path; not normally used.
 */
static char patchpath[MaxPath+18] = "%PatchStringHere->";

/*
 * getopt() variables
 */
extern int optind;		/* index into parent argv vector */
extern int optopt;		/* character checked for validity */
extern char *optarg;		/* argument associated with option */

/*
 *  main program
 */
int main(int argc, char *argv[]) {
   int nolink = 0;			/* suppress linking? */
   int errors = 0;			/* translator and linker errors */
   char **tfiles, **tptr;		/* list of files to translate */
   char **lfiles, **lptr;		/* list of files to link */
   char **rptr;				/* list of files to remove */
   char *efile = NULL;			/* stderr file */
   char buf[MaxPath];			/* file name construction buffer */
   char *ppath;				/* ICONPROFILE value */
   int c, n;
   char ch;
   struct fileparts *fp;

   /*
    * Initialize globals.
    */
   initglob();				/* general global initialization */
   ipath = libpath(argv[0], "IPATH");	/* set library search paths */
   lpath = libpath(argv[0], "LPATH");
   if (strlen(patchpath) > 18)
      iconxloc = patchpath + 18;	/* use stated iconx path if patched */
   else
      iconxloc = relfile(argv[0], "/../iconx");	/* otherwise infer it */
   ppath = getenv("ICONPROFILE");
   if (ppath != NULL && ppath[0] != '\0')
      profile = 1;			/* ICONPROFILE implies -p */

   /*
    * Process options.
    * IMPORTANT:  When making changes here,
    * also update usage() function and man page.
    */
   while ((c = getopt(argc, argv, "+ce:f:o:pstuv:ENP:VX:")) != EOF)
      switch (c) {
         case 'c':			/* -c: compile only (no linking) */
            nolink = 1;
            break;
         case 'e':			/* -e file: [undoc] redirect stderr */
            efile = optarg;
            break;
         case 'f':			/* -f features: enable features */
            if (strchr(optarg, 's') || strchr(optarg, 'a'))
               strinv = 1;		/* this is the only icont feature */
            break;
         case 'o':			/* -o file: name output file */
            ofile = optarg;
            break;
         case 'p':			/* -p: enable icode profiling */
            profile = 1;
            break;
         case 's':			/* -s: suppress informative messages */
            silent = 1;
            verbose = 0;
            break;
         case 't':			/* -t: turn on procedure tracing */
            trace = -1;
            break;
         case 'u':			/* -u: warn about undeclared ids */
            uwarn = 1;
            break;
         case 'v':			/* -v n: set verbosity level */
            if (sscanf(optarg, "%d%c", &verbose, &ch) != 1)
               quitf("bad operand to -v option: %s", optarg);
            if (verbose == 0)
               silent = 1;
            break;
         case 'E':			/* -E: preprocess only */
            pponly = 1;
            nolink = 1;
            break;
         case 'P':			/* -P program: execute from argument */
            txrun(savefile, optarg, &argv[optind]);
            break;	/*NOTREACHED*/
         case 'N':			/* -N: don't embed iconx path */
            iconxloc = "";
            break;
         case 'V':			/* -V: print version information */
            fprintf(stderr, "%s  (%s %d/%d, %s)\n",
               Version, Config, IntBits, WordBits, __DATE__);
            if (optind == argc)
               exit(0);
            break;
         case 'X':			/* -X srcfile: execute single srcfile */
            txrun(copyfile, optarg, &argv[optind]);
            break;	/*NOTREACHED*/
         default:
         case 'x':			/* -x illegal until after file list */
            usage();
         }

   /*
    * If argv[0] ends in "icon" (instead of "icont" or anything else),
    * process as "icon [options] sourcefile [arguments]" scripting shortcut.
    */
   n = strlen(argv[0]);
   if (n >= 4 && strcmp(argv[0]+n-4, "icon") == 0) {
      if (optind < argc)
         txrun(copyfile, argv[optind], &argv[optind+1]);
      else
         usage();
      }

   /*
    * Allocate space for lists of file names.
    */
   n = argc - optind + 1;
   tptr = tfiles = alloc(n * sizeof(char *));
   lptr = lfiles = alloc(n * sizeof(char *));
   rptr = rfiles = alloc(2 * n * sizeof(char *));

   /*
    * Scan file name arguments.
    */
   while (optind < argc)  {
      if (strcmp(argv[optind], "-x") == 0)	/* stop at -x */
         break;
      else if (strcmp(argv[optind], "-") == 0) {
         *tptr++ = "-";				/* "-" means standard input */
         *lptr++ = *rptr++ = "stdin.u1";
         *rptr++ = "stdin.u2";
         }
      else {
         fp = fparse(argv[optind]);		/* parse file name */
         if (*fp->ext == '\0' || smatch(fp->ext, SourceSuffix)) {
            makename(buf, SourceDir, argv[optind], SourceSuffix);
            *tptr++ = salloc(buf);		/* translate the .icn file */
            makename(buf, TargetDir, argv[optind], U1Suffix);
            *lptr++ = *rptr++ = salloc(buf);	/* link & remove .u1 */
            makename(buf, TargetDir, argv[optind], U2Suffix);
            *rptr++ = salloc(buf);		/* also remove .u2 */
            }
         else if (smatch(fp->ext, U1Suffix) || smatch(fp->ext, U2Suffix)
               || smatch(fp->ext, USuffix)) {
            makename(buf, TargetDir, argv[optind], U1Suffix);
            *lptr++ = salloc(buf);
            }
         else
            quitf("bad argument %s", argv[optind]);
         }
      optind++;
      }

   *tptr = *lptr = *rptr = NULL;	/* terminate filename lists */
   if (lptr == lfiles)
      usage();				/* error -- no files named */

   /*
    * Translate .icn files to make .u1 and .u2 files.
    */
   if (tptr > tfiles) {
      if (!pponly)
         report("Translating");
      errors = trans(tfiles, TargetDir);
      if (errors > 0)			/* exit if errors seen */
         exit(EXIT_FAILURE);
      }

   /*
    * Link .u1 and .u2 files to make an executable.
    */
   if (nolink)				/* exit if no linking wanted */
      exit(EXIT_SUCCESS);

   if (ofile == NULL)  {		/* if no -o file, synthesize a name */
      ofile = salloc(makename(buf, TargetDir, lfiles[0], IcodeSuffix));
      }
   else {				/* add extension in necessary */
      fp = fparse(ofile);
      if (*fp->ext == '\0' && *IcodeSuffix != '\0') /* if no ext given */
         ofile = salloc(makename(buf, NULL, ofile, IcodeSuffix));
   }

   report("Linking");
   errors = ilink(lfiles, ofile);	/* link .u files to make icode file */

   /*
    * Finish by removing intermediate files.
    *  Execute the linked program if so requested and if there were no errors.
    */
   cleanup();				/* delete intermediate files */
   if (errors > 0) {			/* exit if linker errors seen */
      remove(ofile);
      exit(EXIT_FAILURE);
      }
   if (optind < argc)  {
      report("Executing");
      execute (ofile, efile, argv + optind + 1);
      }
   exit(EXIT_SUCCESS);
   return 0;
   }

/*
 * execute - execute iconx to run the icon program
 */
static void execute(char *ofile, char *efile, char *args[]) {
   int n;
   char **argv, **p;
   char buf[MaxPath+10];

   /*
    * Build argument vector.
    */
   for (n = 0; args[n] != NULL; n++)	/* count arguments */
      ;
   p = argv = alloc((n + 5) * sizeof(char *));

   *p++ = ofile;			/* pass icode file name */
   while ((*p++ = *args++) != 0)	/* copy args into argument vector */
      ;
   *p = NULL;

   /*
    * Redirect stderr if requested.
    */
   if (efile != NULL) {
      close(fileno(stderr));
      if (strcmp(efile, "-") == 0) {
         if (dup(fileno(stdout)) < 0)
            quit("could not merge standard output with standard error\n");
         }
      else if (freopen(efile, "w", stderr) == NULL)
         quitf("could not redirect stderr to %s\n", efile);
      }
   
   /*
    * Export $ICONX to specify the path to iconx.
    */
   sprintf(buf, "ICONX=%s", iconxloc);
   putenv(buf);

   /*
    * Execute the generated program.
    */
   execv(ofile, argv);
   quitf("could not execute %s", ofile);
   }

void report(char *s) {
   char *c = (strchr(s, '\n') ? "" : ":\n") ;
   if (!silent)
      fprintf(stderr, "%s%s", s, c);
   }

/*
 * Print a usage message and abort the run.
 */
static void usage(void) {
   fprintf(stderr, "usage: icon  sourcefile   [args]\n");
   fprintf(stderr, "       icon  -P 'program' [args]\n");
   fprintf(stderr, "       icont %s\n",
      "[-cpstuENV] [-f s] [-o ofile] [-v i] file ... [-x args]");
   exit(EXIT_FAILURE);
   }

/*
 * Return path after appending lib directory.
 */
static char *libpath(char *prog, char *envname) {
   char buf[1000], *s;

   s = getenv(envname);
   if (s != NULL)
      strcpy(buf, s);
   else
      strcpy(buf, ".");
   strcat(buf, ":");
   strcat(buf, relfile(prog, "/../../lib"));
   return salloc(buf);
   }

/*
 * Translate, link, and execute a source file.
 * Does not return under any circumstances.
 */
static void txrun(char *(*func)(FILE*, char*), char *source, char *args[]) {
   int omask;
   char c1, c2;
   char *flist[2], *progname;
   char srcfile[MaxPath], u1[MaxPath], u2[MaxPath];
   char icode[MaxPath], buf[MaxPath + 20];
   static char abet[] = "abcdefghijklmnopqrstuvwxyz";
   FILE *f;

   silent = 1;			/* don't issue commentary while translating */
   uwarn = 1;			/* do diagnose undeclared identifiers */
   omask = umask(0077);		/* remember umask; keep /tmp files private */

   /*
    * Create a temporary file named /tmp/innnnnxx.icn.
    */
   srand(time(NULL));
   c1 = abet[rand() % (sizeof(abet) - 1)];
   c2 = abet[rand() % (sizeof(abet) - 1)];
   sprintf(srcfile, "/tmp/i%d%c%c.icn", getpid(), c1, c2);
   f = fopen(srcfile, "w");
   if (f == NULL)
      quitf("cannot open for writing: %s", srcfile);

   /*
    * Derive other names and arrange for cleanup on exit.
    */
   rfiles = alloc(5 * sizeof(char *));
   rfiles[0] = srcfile;
   makename(rfiles[1] = u1, NULL, srcfile, U1Suffix);
   makename(rfiles[2] = u2, NULL, srcfile, U2Suffix);
   makename(rfiles[3] = icode, NULL, srcfile, IcodeSuffix);
   rfiles[4] = NULL;
   atexit(cleanup);

   /*
    * If source is from a file, prepend its directory to LPATH.
    */
   if (func == copyfile) {
      sprintf(buf, "%s/..", source);
      canonize(buf);
      strcat(buf, ":");
      strcat(buf, lpath);
      free(lpath);
      lpath = salloc(buf);
      }

   /*
    * Copy the source file, then translate to produce .u1 and .u2 files.
    */
   progname = func(f, source);
   fclose(f);
   flist[0] = srcfile;
   flist[1] = NULL;
   if (trans(flist, SourceDir) > 0)
      exit(EXIT_FAILURE);

   /*
    * Link to make an icode file.
    */
   flist[0] = u1;
   if (ilink(flist, icode) > 0)
      exit(EXIT_FAILURE);

   /*
    * Execute the icode file.
    */
   rfiles[3] = NULL;			/* don't delete icode yet */
   cleanup();				/* but delete the others */
   sprintf(buf, "ICODE_TEMP=%s:%s", icode, progname);
   putenv(buf);				/* tell iconx to delete icode */
   umask(omask);			/* reset original umask */
   execute(icode, NULL, args);		/* execute the program */
   quitf("could not execute %s", icode);
   }

/*
 * Dump a string to a file, prefixed by  $line 0 "[inline]".
 */
static char *savefile(FILE *f, char *srcprog) {
   static char *progname = "[inline]";
   fprintf(f, "$line 0 \"%s\"\n", progname);
   fwrite(srcprog, 1, strlen(srcprog), f);
   return progname;
   }

/*
 * Copy a source file for later translation, adding  $line 0 "filename".
 */
static char *copyfile(FILE *f, char *srcfile) {
   int c;
   FILE *e;

   if (strcmp(srcfile, "-") == 0) {
      e = stdin;
      srcfile = "stdin";
      }
   else {
      if ((e = fopen(srcfile, "r")) == NULL)
         quitf("cannot open: %s", srcfile);
      }
   fprintf(f, "$line 0 \"%s\"\n", srcfile);
   while ((c = getc(e)) != EOF)
      putc(c, f);
   fclose(e);
   return srcfile;
   }

/*
 * Deletes the files listed in rfiles[].
 */
static void cleanup(void) {
   char **p;

   for (p = rfiles; *p; p++)
      remove(*p);
   }
