/*************************************************************************/
/*                                                                       */
/*                Objective Caml LablTk library                          */
/*                                                                       */
/*         Francois Rouaix, Francois Pessaux and Jun Furuse              */
/*               projet Cristal, INRIA Rocquencourt                      */
/*            Jacques Garrigue, Kyoto University RIMS                    */
/*                                                                       */
/*   Copyright 1999 Institut National de Recherche en Informatique et    */
/*   en Automatique and Kyoto University.  All rights reserved.          */
/*   This file is distributed under the terms of the GNU Library         */
/*   General Public License, with the special exception on linking       */
/*   described in file ../../../LICENSE.                                 */
/*                                                                       */
/*************************************************************************/

/* $Id$ */

#include <stdlib.h>

#include <tcl.h>
#include <tk.h>
#include <mlvalues.h>
#include <alloc.h>
#include <memory.h>
#ifdef HAS_UNISTD
#include <unistd.h>
#endif
#include "camltk.h"

/* The Tcl interpretor */
Tcl_Interp *cltclinterp = NULL;

/* Copy a list of strings from the C heap to Caml */
value copy_string_list(int argc, char **argv)
{
  value res;
  int i;
  value oldres = Val_unit, str = Val_unit;

  Begin_roots2 (oldres, str);
    res = Val_int(0); /* [] */
    for (i = argc-1; i >= 0; i--) {
      oldres = res;
      str = copy_string(argv[i]);
      res = alloc(2, 0);
      Field(res, 0) = str;
      Field(res, 1) = oldres;
    }
  End_roots();
  return res;
}

/*
 * Calling Tcl from Caml
 *   this version works on an arbitrary Tcl command,
 *   and does parsing and substitution
 */
CAMLprim value camltk_tcl_eval(value str)
{
  int code;
  char *cmd = NULL;

  CheckInit();
  
  /* Tcl_Eval may write to its argument, so we take a copy
   * If the evaluation raises a Caml exception, we have a space
   * leak
   */
  Tcl_ResetResult(cltclinterp);
  cmd = string_to_c(str);
  code = Tcl_Eval(cltclinterp, cmd);
  stat_free(cmd);

  switch (code) {
  case TCL_OK:
    return copy_string(cltclinterp->result);
  case TCL_ERROR:
    tk_error(cltclinterp->result);
  default:  /* TCL_BREAK, TCL_CONTINUE, TCL_RETURN */
    tk_error("bad tcl result");
  }
}


/* 
 * Calling Tcl from Caml
 *   direct call, argument is TkArgs vect
  type TkArgs =
      TkToken of string
    | TkTokenList of TkArgs list                (* to be expanded *)
    | TkQuote of TkArgs                         (* mapped to Tcl list *)
 * NO PARSING, NO SUBSTITUTION
 */

/* 
 * Compute the size of the argument (of type TkArgs). 
 * TkTokenList must be expanded,
 * TkQuote count for one.
 */
int argv_size(value v)
{
  switch (Tag_val(v)) {
  case 0:                       /* TkToken */
    return 1;
  case 1:                       /* TkTokenList */
    { int n;
      value l;
      for (l=Field(v,0), n=0; Is_block(l); l=Field(l,1))
        n+=argv_size(Field(l,0));
      return n;
    }
  case 2:                       /* TkQuote */
    return 1;
  default:                      /* should not happen */
    Assert(0);
    return 0;
  }
}

/* 
 * Memory of allocated Tcl lists.
 * We should not need more than MAX_LIST
 */
#define MAX_LIST 256
static char *tcllists[MAX_LIST];

static int startfree = 0;
/* If size is lower, do not allocate */
static char *quotedargv[16];

/* Fill a preallocated vector arguments, doing expansion and all.
 * Assumes Tcl will 
 *  not tamper with our strings
 *  make copies if strings are "persistent"
 */
int fill_args (char **argv, int where, value v)
{
  switch (Tag_val(v)) {
  case 0:
    argv[where] = String_val(Field(v,0));
    return (where + 1);
  case 1:
    { value l;
      for (l=Field(v,0); Is_block(l); l=Field(l,1))
        where = fill_args(argv,where,Field(l,0));
      return where;
    }
  case 2:
    { char **tmpargv;
      int size = argv_size(Field(v,0));
      if (size < 16)
        tmpargv = &quotedargv[0];
      else
        tmpargv = (char **)stat_alloc((size + 1) * sizeof(char *));
      fill_args(tmpargv,0,Field(v,0));
      tmpargv[size] = NULL;
      argv[where] = Tcl_Merge(size,tmpargv);
      tcllists[startfree++] = argv[where]; /* so we can free it later */
      if (size >= 16) 
        stat_free((char *)tmpargv);
      return (where + 1);
    }
  default:                      /* should not happen */
    Assert(0);
    return 0;
  }
}

/* v is an array of TkArg */
CAMLprim value camltk_tcl_direct_eval(value v)
{
  int i;
  int size;                     /* size of argv */
  char **argv;
  int result;
  Tcl_CmdInfo info;
  int wherewasi,whereami;       /* positions in tcllists array */

  CheckInit();

  /* walk the array to compute final size for Tcl */
  for(i=0,size=0;i<Wosize_val(v);i++)
    size += argv_size(Field(v,i));

  /* +2: one slot for NULL
         one slot for "unknown" if command not found */
  argv = (char **)stat_alloc((size + 2) * sizeof(char *));

  wherewasi = startfree; /* should be zero except when nested calls */
  Assert(startfree < MAX_LIST);

  /* Copy */
  {
    int where;
    for(i=0, where=0;i<Wosize_val(v);i++)
      where = fill_args(argv,where,Field(v,i));
    argv[size] = NULL;
    argv[size + 1] = NULL;
  }

  Begin_roots_block ((value *) argv, size + 2);

  whereami = startfree;

    /* Eval */
    Tcl_ResetResult(cltclinterp);
    if (Tcl_GetCommandInfo(cltclinterp,argv[0],&info)) { /* command found */
#if (TCL_MAJOR_VERSION >= 8)
      /* info.proc might be a NULL pointer
       * We should probably attempt an Obj invocation, but the following quick
       * hack is easier.
       */
      if (info.proc == NULL) {
        Tcl_DString buf;
        char *string;
        Tcl_DStringInit(&buf);
        Tcl_DStringAppend(&buf, argv[0], -1);
        for (i=1; i<size; i++) {
          Tcl_DStringAppend(&buf, " ", -1);
          Tcl_DStringAppend(&buf, argv[i], -1);
        }
        /* fprintf(stderr,"80 compat: %s\n", argv[0]); */
        result = Tcl_Eval(cltclinterp, Tcl_DStringValue(&buf));
        Tcl_DStringFree(&buf);
      }
      else
        result = (*info.proc)(info.clientData,cltclinterp,size,argv);
#else
      result = (*info.proc)(info.clientData,cltclinterp,size,argv);
#endif
    } else {/* implement the autoload stuff */
      if (Tcl_GetCommandInfo(cltclinterp,"unknown",&info)) { /* unknown found */
        for (i = size; i >= 0; i--)
          argv[i+1] = argv[i];
        argv[0] = "unknown";
        result = (*info.proc)(info.clientData,cltclinterp,size+1,argv);
      } else { /* ah, it isn't there at all */
        result = TCL_ERROR;
        Tcl_AppendResult(cltclinterp, "Unknown command \"", 
                         argv[0], "\"", NULL);
      }
    }
  End_roots ();

  /* Free the various things we allocated */
  stat_free((char *)argv);
  for (i=wherewasi; i<whereami; i++)
    free(tcllists[i]);
  startfree = wherewasi;
  
  switch (result) {
  case TCL_OK:
    return copy_string (cltclinterp->result);
  case TCL_ERROR:
    tk_error(cltclinterp->result);
  default:  /* TCL_BREAK, TCL_CONTINUE, TCL_RETURN */
    tk_error("bad tcl result");
  }
}