/*      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 <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdarg.h>

#include "key.h"
#include "read.h"
#include "util.h"

#include "util/ting.h"
#include "util/io.h"
#include "util/minmax.h"
#include "util/types.h"
#include "util/array.h"
#include "util/hash.h"


const char *strSession[]
=  {
   "\\__device__     name of device (given by -d)",
   "\\__fnbase__     base name of entry file (given by -i/-I)",
   "\\__fnentry__    name of entry file (given by -i/-I)",
   "\\__fnin__       name of current input file",
   "\\__fnout__      name of current output file",
   "\\__line__       index of current input line",
   "\\__parmode__    paragraph slurping mode for interactive sessions",
   "\\__searchpath__ search path for macro packages (e.g. man.zmm)",
   "\\__split__      user space toggle for chapter mode indicator",
   "\\__version__    version of zoem, formatted as e.g. 2003, 2004-010",
   NULL
   }
;


int   n_scope_user   = 100;
int   n_scope_dollar = 100;


void yamKeyUlimit
(  int   user
,  int   dollar
)
   {  if (user >= 0)
      n_scope_user = user
   ;  if (dollar >= 0)
      n_scope_dollar = dollar
;  }


typedef struct keyScope
{  struct keyScope*   down
;  mcxHash*           table
;
}  keyScope           ;


static   keyScope*    usrScope_g        =  NULL;    /* user keys         */
static   keyScope*    usrScope_top      =  NULL;    /* user keys         */

static   int          n_usrScopes_g        =  0;

static   keyScope*    dollarScope_g     =  NULL;    /* dollar keys       */
static   keyScope*    dollarScope_top   =  NULL;    /* dollar keys       */

static   int          n_dollarScopes_g  =  0;


void yamKeyList
(  const char* mode
)
   {  mcxbool listAll = strstr(mode, "all") != NULL
   ;  if (listAll || strstr(mode, "session"))  
      {  int m
      ;  fputs("\nPredefined session variables\n", stdout)
      ;  for (m=0;strSession[m];m++)
         fprintf(stdout, "%s\n", strSession[m])
   ;  }
;  }


void  yamKeySet
(  const char* key
,  const char* val
)
   {  mcxTing*  keytxt   =  mcxTingNew(key)

   ;  if (yamKeyInsert(keytxt, val) != keytxt)
      mcxTingFree(&keytxt)
;  }


void  yamKeyDef
(  const char* key
,  const char* val
)
   {  mcxTing*  keytxt   =  mcxTingNew(key)

   ;  if (yamKeyInsert(keytxt, val) != keytxt)
      {  yamErr("yamKeyDef", "overwriting key <%s>", keytxt->str)
      ;  mcxTingFree(&keytxt)
   ;  }
   }


mcxTing* yamKeyInsert
(  mcxTing*        key
,  const char*    valstr
)
   {  keyScope* scope = *(key->str) == '$' ? dollarScope_top : usrScope_top
   ;  mcxKV* kv   =  mcxHashSearch
                     (  key
                     ,  scope->table
                     ,  MCX_DATUM_INSERT
                     )
   ;  if (!kv)
         yamErr("yamKeyInsert", "panic&|PBD cannot insert key")
      ,  mcxExit(1)

   ;  else
      {  if (kv->val)
         mcxTingWrite((mcxTing*) (kv->val), valstr)
      ;  else
         kv->val     =  mcxTingNew(valstr)
   ;  }

   ;  return (mcxTing*) kv->key
;  }


mcxTing* yamKeyDelete
(  mcxTing*  key
)
   {  keyScope* scope = *(key->str) == '$' ? dollarScope_top : usrScope_top
   ;  mcxKV*   kv
            =  (mcxKV*) mcxHashSearch
               (  key
               ,  scope->table
               ,  MCX_DATUM_DELETE
               )
   ;  if (kv)
      {  mcxTing* val = (mcxTing*) kv->val
      ;  mcxTing* key = (mcxTing*) kv->key
      ;  mcxTingFree(&key)
      ;  return val
   ;  }
      return NULL
;  }


mcxTing* yamKeyGetLocal
(  mcxTing*  key
)
   {  keyScope* scope = *(key->str) == '$' ? dollarScope_top : usrScope_top
   ;  mcxKV*   kv
      =  (mcxKV*) mcxHashSearch
         (  key
         ,  scope->table
         ,  MCX_DATUM_FIND
         )
   ;  if (kv)
      return (mcxTing*) kv->val
   ;  return NULL
;  }


mcxTing* yamKeyGet
(  mcxTing*  key
)
   {  keyScope* scope = *(key->str) == '$' ? dollarScope_top : usrScope_top

   ;  while (scope)
      {  mcxKV*   kv
         =  (mcxKV*) mcxHashSearch
            (  key
            ,  scope->table
            ,  MCX_DATUM_FIND
            )
      ;  if (kv)
         return (mcxTing*) kv->val
      ;  scope = scope->down
   ;  }
      return NULL
;  }


void yamScopeFree
(  char type
)
   {  keyScope* scope =  type == '$' ? dollarScope_top : usrScope_top
   ;  keyScope* next

   ;  while (scope)
      {  next = scope->down
      ;  mcxHashFree(&scope->table, mcxTingFree_v, mcxTingFree_v)
      ;  mcxFree(scope)
      ;  scope = next
   ;  }
;  }


void mod_key_exit
(  void
)
   {  yamScopeFree('$')
   ;  yamScopeFree('u')
;  }


void mod_key_init
(  int n
)
   {  usrScope_g           =  mcxAlloc(sizeof(keyScope), EXIT_ON_FAIL)
   ;  usrScope_g->table    =  yamHashNew(n)
   ;  mcxHashSetOpts(usrScope_g->table, 0.25, -1)
   ;  usrScope_g->down     =  NULL
   ;  usrScope_top         =  usrScope_g
   ;  n_usrScopes_g        =  1

   ;  dollarScope_g        =  mcxAlloc(sizeof(keyScope), EXIT_ON_FAIL)
   ;  dollarScope_g->table =  yamHashNew(16)
   ;  mcxHashSetOpts(dollarScope_g->table, 0.25, -1)
   ;  dollarScope_g->down  =  NULL
   ;  dollarScope_top      =  dollarScope_g
   ;  n_dollarScopes_g     =  1
;  }


/*
 * fixme make acces functions for keyScope
*/

mcxstatus yamScopePush
(  char* type
)
   {  keyScope* top        =  mcxAlloc(sizeof(keyScope), EXIT_ON_FAIL)
   ;  const char* me       =  "yamScopePush"
   ;  top->table           =  yamHashNew(16)

   ;  if (!strcmp(type, "user"))
      {  top->down         =  usrScope_top
      ;  usrScope_top      =  top
      ;  n_usrScopes_g++
      ;  if (n_usrScopes_g > n_scope_user)
         {  yamErr
            (me, "no more than <%d> stacked user scopes allowed", n_scope_user)
         ;  return STATUS_FAIL
      ;  }
      }
      else if (!strcmp(type, "dollar"))
      {  top->down         =  dollarScope_top
      ;  dollarScope_top   =  top
      ;  n_dollarScopes_g++
      ;  if (n_dollarScopes_g > n_scope_dollar)
         {  yamErr
            (  me
            ,  "no more than <%d> stacked dollar scopes allowed"
            ,  n_scope_dollar
            )
         ;  return STATUS_FAIL
      ;  }
      }
      else
      {  yamErr(me, "no such stack <%s>", type)
      ;  mcxHashFree(&(top->table), mcxTingFree_v, mcxTingFree_v)
      ;  mcxFree(top)
      ;  return STATUS_FAIL
   ;  }
      return STATUS_OK
;  }


mcxstatus yamScopePop
(  char* type
)
   {  keyScope* top  =     !strcmp(type, "user")
                        ?  usrScope_top
                        :     !strcmp(type, "dollar")
                           ?  dollarScope_top 
                           :  NULL
   ;  if (!top)
      {  yamErr("yamScopePop", "no such stack <%s>", type)
      ;  return STATUS_FAIL
   ;  }
      else if (!top->down)
      {  yamErr
         (  "yamScopePop"
         ,  "you rascal is trying to pop the bottom <%s> scope!"
         ,  type
         )
      ;  return STATUS_FAIL
   ;  }

      mcxHashFree(&(top->table), mcxTingFree_v, mcxTingFree_v)

   ;  if (!strcmp(type, "user"))
      {  usrScope_top = usrScope_top->down
      ;  n_usrScopes_g--
   ;  }
      else if (!strcmp(type, "dollar"))
      {  dollarScope_top = dollarScope_top->down
      ;  n_dollarScopes_g--
   ;  }

      mcxFree(top)
   ;  return STATUS_OK
;  }


void yamKeyStats
(  void
)
   {  mcxHashStats(stdout, usrScope_g->table)
;  }

