/*      Copyright (C) 2001, 2002, 2003, 2004 Stijn van Dongen
 *
 * This file is part of Zoem. You can redistribute and/or modify Zoem under the
 * terms of the GNU General Public License;  either version 2 of the License or
 * (at your option) any later  version.  You should have received a copy of the
 * GPL along with Zoem, in the file COPYING.
*/

#include <stdio.h>

#include "entry.h"
#include "iface.h"
#include "util.h"
#include "ops.h"
#include "version.h"
#include "key.h"
#include "filter.h"

#include "util/io.h"
#include "util/opt.h"
#include "util/ting.h"
#include "util/types.h"
#include "util/err.h"
#include "util/minmax.h"

const char *zoemVersion
=
"zoem %s, %s\n"
"Copyright (c) 2001, 2002, 2003, 2004 Stijn van Dongen.\n"
"zoem comes with NO WARRANTY to the extent permitted by law.\n"
"You may redistribute copies of zoem under the terms\n"
"of the GNU General Public License.\n"
;


const char* usagelines[] =
{  "Usage: zoem -i fname[.azm] [options]"
,  "options:"
,  ""
,  "-o <fnout>       default output file name: <fname.device> or <fname.ozm>"
,  "-d <device>      set zoem key \\__device__ to <device>)"
,  "-s <key=val>     set zoem key <key> to <val> (repeated use allowed)"
,  "-e <any>         evaluate any (stdout output) and exit"
,  "-E <any>         evaluate any (stdout output) and proceed"
,  "-x               if error occurs, enter interactive mode"
,  "-l {all|zoem|legend|trace|macro|session|filter|parmode} (list and exit)"
,  ""
,  "-nsegment <int>  maximum segment depth"
,  "-nstack <int>    maximum stack depth"
,  "-nuser <int>     size of user dictionary stack"
,  "-ndollar <int>   size of dollar dictionary stack"
,  "--stress-write   for stress-testing zoem via write#3 (testing only)"
,  "--unsafe         allow system calls, prompt for confirmation"
,  "--unsafe-silent  allow system calls silently"
,  "--system-success require system calls to succeed"
,  "-amcs <int>      alloc maximum chunk size (testing only)"
,  "-amic <int>      alloc maximum invocation count (testing only)"
,  "-hf <label>      hash function to use (testing only)"
,  "-tl <int>        tablength"
,  ""
,  "--trace          default trace"
,  "--trace-all-long"
,  "--trace-all-short"
,  "--trace-regex"
,  "--trace-keys"
,  "-trace <k>       use mask k for tracing"
,  "-l trace         show the meaning of each trace bit"
,  "--stats          when done, print key table statistics"
,  NULL
}  ;


int sort_it_out
(  int      entry_flags
,  mcxTing* fnbase
,  mcxTing* fnentry
,  mcxTing* fnout
,  mcxTing* device
)  ;


int main
(  int   argc
,  char* argv[]
)
   {  mcxTing     *fnbase     =  mcxTingEmpty(NULL, 20)
   ;  mcxTing     *fnout      =  mcxTingEmpty(NULL, 20)
   ;  mcxTing     *fnentry    =  mcxTingEmpty(NULL, 20)
   ;  mcxTing     *device     =  mcxTingEmpty(NULL, 20)
   ;  mcxTing     *xxpr       =  mcxTingEmpty(NULL, 20)
   ;  mcxTing     *listees    =  mcxTingEmpty(NULL, 1)
   ;  mcxTing     *vars       =  mcxTingEmpty(NULL, 30)
   ;  int         a           =  1
   ;  mcxbits     trace_flags =  0
   ;  mcxbits     entry_flags =  0
   ;  int stack_depth = -1, segment_depth = -1
   ,  n_user_scopes=-1, n_dollar_scopes=-1
   ;  const char* me = "zoem"
   ;  long amcs = 0
   ;  long amic = 0

   ;  while(a < argc)
      {  if (!strcmp(argv[a], "-d"))
         {  if (++a >= argc)
            goto arg_missing;
         ;  mcxTingWrite(device, argv[a])
      ;  }
         else if (!strcmp(argv[a], "-buser"))
         {  if (++a >= argc)
            goto arg_missing;
         ;  buckets_user = atoi(argv[a])
      ;  }
         else if (!strcmp(argv[a], "-bzoem"))
         {  if (++a >= argc)
            goto arg_missing;
         ;  buckets_zoem = atoi(argv[a])
      ;  }
         else if (!strcmp(argv[a], "-tl"))
         {  if (++a >= argc)
            goto arg_missing;
         ;  ztablength = atoi(argv[a])
         ;  ztablength = MIN(4,ztablength)
         ;  ztablength = MAX(0,ztablength)
      ;  }
         else if (!strcmp(argv[a], "-chunk"))
         {  if (++a >= argc)
            goto arg_missing;
         ;  chunk_size = strtol(argv[a], NULL, 10)
         ;  if (chunk_size < 0)  /* impossible but anyway */
            chunk_size = 0
      ;  }
         else if (!strcmp(argv[a], "-nsegment"))
         {  if (++a >= argc)
            goto arg_missing;
         ;  segment_depth = atoi(argv[a])
      ;  }
         else if (!strcmp(argv[a], "-hf"))
         {  if (++a < argc)
            {  if (yamHashFie(argv[a]))
               {  mcxErr(me, "hash function <%s> not supported", argv[a])
               ;  mcxExit(1)
            ;  }
            }
            else
            goto arg_missing;
      ;  }
         else if (!strcmp(argv[a], "-amic"))
         {  if (++a >= argc)
            goto arg_missing;
         ;  amic = atol(argv[a])
      ;  }
         else if (!strcmp(argv[a], "-amcs"))
         {  if (++a >= argc)
            goto arg_missing;
         ;  amcs = atol(argv[a])
      ;  }
         else if (!strcmp(argv[a], "-nstack"))
         {  if (++a >= argc)
            goto arg_missing;
         ;  stack_depth = atoi(argv[a])
      ;  }
         else if (!strcmp(argv[a], "-ndollar"))
         {  if (++a >= argc)
            goto arg_missing;
         ;  n_dollar_scopes = atoi(argv[a])
      ;  }
         else if (!strcmp(argv[a], "--split"))
         {  mcxTingWrite(fnout, "stdout")
         ;  entry_flags |= ENTRY_SPLIT
      ;  }
         else if (!strcmp(argv[a], "-nuser"))
         {  if (++a >= argc)
            goto arg_missing;
         ;  n_user_scopes = atoi(argv[a])
      ;  }
         else if (!strcmp(argv[a], "-e"))
         {  if (a++ + 1 >= argc)
            goto arg_missing;
         ;  mcxTingPrint(xxpr, "%s\n\\@{\\N}", argv[a])
         ;  entry_flags  |= ENTRY_EXXIT
      ;  }
         else if (!strcmp(argv[a], "-x"))
         entry_flags |= ENTRY_DEBUG
      ;  else if (!strcmp(argv[a], "-E"))
         {  if (a++ + 1 >= argc)
            goto arg_missing;
         ;  mcxTingPrint(xxpr, "%s\n\\@{\\N}", argv[a])
      ;  }
         else if (!strcmp(argv[a], "-s"))
         {  if (a++ + 1 >= argc)
            goto arg_missing;
         ;  mcxTingAppend(vars, argv[a])
         ;  mcxTingAppend(vars, "\036")
      ;  }
         else if (!strcmp(argv[a], "--trace"))
         {  trace_flags |= ZOEM_TRACE_DEFAULT
      ;  }
         else if
         (  !strcmp(argv[a], "--unsafe")
         || !strcmp(argv[a], "--unsafe-ask")
         )
         systemAccess = SYSTEM_UNSAFE
      ;  else if (!strncmp(argv[a], "--allow=", 8))
         system_allow
         =  system_allow
         ?  mcxTingPrintAfter(system_allow, "%s:", argv[a]+8)
         :  mcxTingPrint(system_allow, ":%s:", argv[a]+8)
      ;  else if (!strcmp(argv[a], "--system-honor"))
         systemHonor = 1
      ;  else if (!strcmp(argv[a], "--unsafe-silent"))
         systemAccess = SYSTEM_UNSAFE_SILENT
      ;  else if (!strcmp(argv[a], "--stress-write"))
         stressWrite = 1
      ;  else if (!strcmp(argv[a], "--err-out"))
            yamErrorFile(stdout)
         ,  mcxErrorFile(stdout)
      ;  else if (!strcmp(argv[a], "--trace-all-long"))
         trace_flags |= ZOEM_TRACE_ALL_LONG
      ;  else if (!strcmp(argv[a], "--trace-keys"))
         trace_flags |= ZOEM_TRACE_KEYS
      ;  else if (!strcmp(argv[a], "--trace-all-short"))
         trace_flags |= ZOEM_TRACE_ALL
      ;  else if (!strcmp(argv[a], "--trace-regex"))
         trace_flags |= ZOEM_TRACE_REGEX
      ;  else if (!strcmp(argv[a], "--stats"))
         trace_flags |= ZOEM_TRACE_HASH
      ;  else if (!strcmp(argv[a], "--version"))
         {  fprintf(stdout, zoemVersion, zoemDateTag, zoemNumTag)
         ;  exit(0)
      ;  }
         else if (!strcmp(argv[a], "-trace"))
         {  if (a++ + 1 < argc)
            {  int k = atoi(argv[a])
            ;  if (k == -1)
               trace_flags = ZOEM_TRACE_ALL
            ;  else if (k == -2)
               trace_flags = ZOEM_TRACE_ALL_LONG
            ;  else
               trace_flags |= k
         ;  }
            else goto arg_missing;
      ;  }
         else if (!strcmp(argv[a], "-I"))
         {  if (a++ + 1 >= argc)
            goto arg_missing
         ;  mcxTingWrite(fnbase, argv[a])
         ;  mcxTingWrite(fnentry, argv[a])
      ;  }
         else if (!strcmp(argv[a], "-i"))
         {  if (a++ + 1 >= argc)
            goto arg_missing;
         ;  mcxTingWrite(fnbase, argv[a])
      ;  }
         else if (!strcmp(argv[a], "-o"))
         {  if (a++ + 1 >= argc)
            goto arg_missing;
         ;  mcxTingWrite(fnout, argv[a])
      ;  }
         else if (!strcmp(argv[a], "-l"))
         {  if (a++ + 1 >= argc)
            goto arg_missing;
         ;  mcxTingAppend(listees, argv[a])
         ;  mcxTingAppend(listees, ";")
      ;  }
         else if (!strcmp(argv[a], "-h"))
         {  help:
            mcxUsage(stdout, me, usagelines)
         ;  mcxExit(0)
      ;  }
         else if (0)
         {  arg_missing:
         ;  mcxErr(me, "flag %s needs argument\n", argv[argc-1])
         ;  mcxExit(1)
      ;  }
         else
         {  goto help
      ;  }
         a++
   ;  }

      if (amcs || amic)
      mcxAllocLimits(amcs, amic)

   ;  if (listees->len > 0)
      {  yamOpList(listees->str)
      ;  filterList(listees->str)
      ;  yamKeyList(listees->str)
      ;  if (strstr(listees->str, "parmode"))
         {  mcxIOlistParmodes()
         ;  fprintf
            (  stdout
            ,  "Zoem's default mode is %d, modes can be or'ed\n"
            ,  MCX_READLINE_DOT
            )
      ;  }
         if (strstr(listees->str, "trace"))
         showTracebits()
      ;  mcxExit(0)
   ;  }

      entry_flags = sort_it_out(entry_flags, fnbase, fnentry, fnout, device)

   ;  if (stack_depth > 0 || segment_depth > 0)
      yamSegUlimit(segment_depth, stack_depth)

   ;  if (n_user_scopes > 0 || n_dollar_scopes > 0)
      yamKeyUlimit(n_user_scopes, n_dollar_scopes)

   ;  yamEntry
      (  fnentry->str
      ,  fnbase->str
      ,  chunk_size     /* it's a global var, but that's for dofile#2 */
      ,  fnout->str
      ,  device->str
      ,  ZOEM_FILTER_DEVICE
      ,  trace_flags
      ,  entry_flags
      ,  vars
      ,  xxpr
      )

   ;  mcxTingFree(&vars)
   ;  mcxTingFree(&xxpr)
   ;  mcxTingFree(&fnbase)
   ;  mcxTingFree(&fnout)
   ;  mcxTingFree(&fnentry)
   ;  mcxTingFree(&device)
   ;  mcxTingFree(&listees)

   ;  return 0
;  }



int sort_it_out
(  int      entry_flags
,  mcxTing* fnbase
,  mcxTing* fnentry
,  mcxTing* fnout
,  mcxTing* device
)
   {  if (!fnbase->len && !fnout->len)
      {  mcxTingWrite(fnbase, "-")
      ;  mcxTingWrite(fnentry, "-")
      ;  mcxTingWrite(fnout, "-")      /* should *not* be used */
      ;  entry_flags |= ENTRY_STDIA
   ;  }
     /* all three set in this case */

      if (!fnbase->len && !fnentry->len)
         mcxTingWrite(fnbase, "-")
      ,  mcxTingWrite(fnentry, "-")
   ;  else if (fnentry->len)
      mcxTingWrite(fnbase, fnentry->str)
        /*
         * now fnbase surely has a value;
         * fnentry perhaps not (fnbase->len && !fnentry->len)
        */
   ;  else if (!fnentry->len)
      {  if (!strcmp(fnbase->str, "-") || !strcmp(fnbase->str, "stdin"))
         mcxTingWrite(fnentry, fnbase->str)
      ;  else
         {  if (strstr(fnbase->str, ".azm") == fnbase->str + fnbase->len -4)
            mcxTingDelete(fnbase, -5, 4)
         ;  mcxTingPrint(fnentry, "%s.azm", fnbase->str)
      ;  }
      }

      if
      (  (  !strcmp(fnentry->str, "-")
         || !strcmp(fnentry->str, "stdin")
         )
      && !(entry_flags & ENTRY_EXXIT)
      )
      entry_flags |= ENTRY_STDIN

   ;  if (!fnout->len)
      {  if (entry_flags & (ENTRY_STDIN | ENTRY_STDIA))
         mcxTingWrite(fnout, "-")
      ;  else
         mcxTingPrint
         (fnout,"%s.%s", fnbase->str, device->len ? device->str : "ozm")
   ;  }

      if
      (  (  !strcmp(fnout->str, "-")
         || !strcmp(fnout->str, "stdout")
         )
      && !(entry_flags & ENTRY_EXXIT)
      )
      entry_flags |= ENTRY_STDOUT

; if (0) fprintf(stderr, "%d", entry_flags)
   ;  return entry_flags
;  }

