/*
 *	tcltklib.c
 *		Aug. 27, 1997	Y. Shigehiro
 *		Oct. 24, 1997	Y. Matsumoto
 */

#include "ruby.h"
#include "rubysig.h"
#include <stdio.h>
#include <string.h>
#include <tcl.h>
#include <tk.h>

#ifdef __MACOS__
# include <tkMac.h>
# include <Quickdraw.h>
#endif

/* for debug */

#define DUMP1(ARG1) if (debug) { fprintf(stderr, "tcltklib: %s\n", ARG1);}
#define DUMP2(ARG1, ARG2) if (debug) { fprintf(stderr, "tcltklib: ");\
fprintf(stderr, ARG1, ARG2); fprintf(stderr, "\n"); }
/*
#define DUMP1(ARG1)
#define DUMP2(ARG1, ARG2)
*/

/* for callback break & continue */
VALUE eTkCallbackBreak;
VALUE eTkCallbackContinue;

/* from tkAppInit.c */

/*
 * The following variable is a special hack that is needed in order for
 * Sun shared libraries to be used for Tcl.
 */

extern int matherr();
int *tclDummyMathPtr = (int *) matherr;

/*---- module TclTkLib ----*/

/* Tk_ThreadTimer */
typedef struct {
    Tcl_TimerToken token;
    int  flag;
} Tk_TimerData;

/* timer callback */
void _timer_for_tcl (ClientData clientData)
{
    Tk_TimerData *timer = (Tk_TimerData*)clientData;

    timer->flag = 0;
    CHECK_INTS;
#ifdef THREAD 
    if (!thread_critical) thread_schedule();
#endif

    timer->token = Tk_CreateTimerHandler(200, _timer_for_tcl, 
					 (ClientData)timer);
    timer->flag = 1;
}

/* execute Tk_MainLoop */
static VALUE
lib_mainloop(VALUE self)
{
    Tk_TimerData *timer;

    timer = (Tk_TimerData *) ckalloc(sizeof(Tk_TimerData));
    timer->flag = 0;
    timer->token = Tk_CreateTimerHandler(200, _timer_for_tcl, 
					 (ClientData)timer);
    timer->flag = 1;

    DUMP1("start Tk_Mainloop");
    while (Tk_GetNumMainWindows() > 0) {
        Tcl_DoOneEvent(0);
    }
    DUMP1("stop Tk_Mainloop");

#ifdef THREAD
    if (timer->flag) {
      Tk_DeleteTimerHandler(timer->token);
    }
#endif

    return Qnil;
}

/*---- class TclTkIp ----*/
struct tcltkip {
    Tcl_Interp *ip;		/* the interpreter */
    int return_value;		/* return value */
};

/* Tcl command `ruby' */
static VALUE
ip_eval_rescue(VALUE *failed, VALUE einfo)
{
    *failed = einfo;
    return Qnil;
}

static int
#if TCL_MAJOR_VERSION >= 8
ip_ruby(ClientData clientData, Tcl_Interp *interp, 
	int argc, Tcl_Obj *CONST argv[])
#else
ip_ruby(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
#endif
{
    VALUE res;
    int old_trapflg;
    VALUE failed = 0;
    char *arg;
    int  dummy;

    /* ruby command has 1 arg. */
    if (argc != 2) {
	ArgError("wrong # of arguments (%d for 1)", argc);
    }

    /* get C string from Tcl object */
#if TCL_MAJOR_VERSION >= 8
    arg = Tcl_GetStringFromObj(argv[1], &dummy);
#else
    arg = argv[1];
#endif

    /* evaluate the argument string by ruby */
    DUMP2("rb_eval_string(%s)", arg);
    old_trapflg = trap_immediate;
    trap_immediate = 0;
    res = rb_rescue(rb_eval_string, (VALUE)arg, ip_eval_rescue, (VALUE)&failed);
    trap_immediate = old_trapflg;

    Tcl_ResetResult(interp);
    if (failed) {
        VALUE eclass = CLASS_OF(failed);
	Tcl_AppendResult(interp, STR2CSTR(failed), (char*)NULL);
        if (eclass == eTkCallbackBreak) {
	  return TCL_BREAK;
	} else if (eclass == eTkCallbackContinue) {
	  return TCL_CONTINUE;
	} else {
	  return TCL_ERROR;
	}
    }

    /* result must be string or nil */
    if (NIL_P(res)) {
	DUMP1("(rb_eval_string result) nil");
	return TCL_OK;
    }

    /* copy result to the tcl interpreter */
    DUMP2("(rb_eval_string result) %s", STR2CSTR(res));
    DUMP1("Tcl_AppendResult");
    Tcl_AppendResult(interp, STR2CSTR(res), (char *)NULL);

    return TCL_OK;
}

/* destroy interpreter */
static void
ip_free(struct tcltkip *ptr)
{
    DUMP1("Tcl_DeleteInterp");
    Tcl_DeleteInterp(ptr->ip);
    free(ptr);
}

/* create and initialize interpreter */
static VALUE
ip_new(VALUE self)
{
    struct tcltkip *ptr;	/* tcltkip data struct */
    VALUE obj;			/* newly created object */

    /* create object */
    obj = Data_Make_Struct(self, struct tcltkip, 0, ip_free, ptr);
    ptr->return_value = 0;

    /* from Tk_Main() */
    DUMP1("Tcl_CreateInterp");
    ptr->ip = Tcl_CreateInterp();

    /* from Tcl_AppInit() */
    DUMP1("Tcl_Init");
    if (Tcl_Init(ptr->ip) == TCL_ERROR) {
	Fail("Tcl_Init");
    }
    DUMP1("Tk_Init");
    if (Tk_Init(ptr->ip) == TCL_ERROR) {
	Fail("Tk_Init");
    }
    DUMP1("Tcl_StaticPackage(\"Tk\")");
    Tcl_StaticPackage(ptr->ip, "Tk", Tk_Init,
		      (Tcl_PackageInitProc *) NULL);

    /* add ruby command to the interpreter */
#if TCL_MAJOR_VERSION >= 8
    DUMP1("Tcl_CreateObjCommand(\"ruby\")");
    Tcl_CreateObjCommand(ptr->ip, "ruby", ip_ruby, (ClientData *)NULL,
			 (Tcl_CmdDeleteProc *)NULL);
#else
    DUMP1("Tcl_CreateCommand(\"ruby\")");
    Tcl_CreateCommand(ptr->ip, "ruby", ip_ruby, (ClientData *)NULL,
		      (Tcl_CmdDeleteProc *)NULL);
#endif

    return obj;
}

/* eval string in tcl by Tcl_Eval() */
static VALUE
ip_eval(VALUE self, VALUE str)
{
    char *s;
    char *buf;			/* Tcl_Eval requires re-writable string region */
    struct tcltkip *ptr;	/* tcltkip data struct */

    /* get the data struct */
    Data_Get_Struct(self, struct tcltkip, ptr);

    /* call Tcl_Eval() */
    s = STR2CSTR(str);
    buf = ALLOCA_N(char, strlen(s)+1);
    strcpy(buf, s);
    DUMP2("Tcl_Eval(%s)", buf);
    ptr->return_value = Tcl_Eval(ptr->ip, buf);
    if (ptr->return_value == TCL_ERROR) {
	Fail(ptr->ip->result);
    }
    DUMP2("(TCL_Eval result) %d", ptr->return_value);

    /* pass back the result (as string) */
    return(str_new2(ptr->ip->result));
}


static VALUE
ip_toUTF8(VALUE self, VALUE str, VALUE encodename)
{
#ifndef TCL_UTF_MAX
  return str;
#else
  Tcl_Interp *interp;
  Tcl_Encoding encoding;
  Tcl_DString dstr;
  struct tcltkip *ptr;
  char *buff1,*buff2;

  Data_Get_Struct(self,struct tcltkip, ptr);
  interp = ptr->ip;

  encoding = Tcl_GetEncoding(interp,STR2CSTR(encodename));
  buff1 = ALLOCA_N(char,strlen(STR2CSTR(str))+1);
  strcpy(buff1,STR2CSTR(str));

  Tcl_DStringInit(&dstr);
  Tcl_DStringFree(&dstr);
  Tcl_ExternalToUtfDString(encoding,buff1,strlen(buff1),&dstr);
  buff2 = ALLOCA_N(char,Tcl_DStringLength(&dstr)+1);
  strcpy(buff2,Tcl_DStringValue(&dstr));

  Tcl_FreeEncoding(encoding);
  Tcl_DStringFree(&dstr);

  return str_new2(buff2);
#endif
}

static VALUE
ip_fromUTF8(VALUE self, VALUE str, VALUE encodename)
{
#ifndef TCL_UTF_MAX
  return str;
#else
  Tcl_Interp *interp;
  Tcl_Encoding encoding;
  Tcl_DString dstr;
  struct tcltkip *ptr;
  char *buff1,*buff2;

  Data_Get_Struct(self,struct tcltkip, ptr);
  interp = ptr->ip;

  encoding = Tcl_GetEncoding(interp,STR2CSTR(encodename));
  buff1 = ALLOCA_N(char,strlen(STR2CSTR(str))+1);
  strcpy(buff1,STR2CSTR(str));

  Tcl_DStringInit(&dstr);
  Tcl_DStringFree(&dstr);
  Tcl_UtfToExternalDString(encoding,buff1,strlen(buff1),&dstr);
  buff2 = ALLOCA_N(char,Tcl_DStringLength(&dstr)+1);
  strcpy(buff2,Tcl_DStringValue(&dstr));

  Tcl_FreeEncoding(encoding);
  Tcl_DStringFree(&dstr);

  return str_new2(buff2);
#endif
}


static VALUE
ip_invoke(int argc, VALUE *argv, VALUE obj)
{
    struct tcltkip *ptr;	/* tcltkip data struct */
    int i;
    int object = 0;
    Tcl_CmdInfo info;
    char *cmd;
    char **av = (char **)NULL;
#if TCL_MAJOR_VERSION >= 8
    Tcl_Obj **ov = (Tcl_Obj **)NULL;
    Tcl_Obj *resultPtr;
#endif

    /* get the data struct */
    Data_Get_Struct(obj, struct tcltkip, ptr);

    /* get the command name string */
    cmd = STR2CSTR(argv[0]);

    /* map from the command name to a C procedure */
    if (!Tcl_GetCommandInfo(ptr->ip, cmd, &info)) {
	NameError("invalid command name `%s'", cmd);
    }
#if TCL_MAJOR_VERSION >= 8
    object = info.isNativeObjectProc;
#endif

    /* memory allocation for arguments of this command */
    if (object) {
#if TCL_MAJOR_VERSION >= 8
      /* object interface */
      ov = (Tcl_Obj **)ALLOCA_N(Tcl_Obj *, argc+1);
      for (i = 0; i < argc; ++i) {
	char *s = STR2CSTR(argv[i]);
        ov[i] = Tcl_NewStringObj(s, strlen(s));
      }
      ov[argc] = (Tcl_Obj *)NULL;
#endif
    } else {
      /* string interface */
      av = (char **)ALLOCA_N(char *, argc+1);
      for (i = 0; i < argc; ++i) {
	char *s = STR2CSTR(argv[i]);

        av[i] = ALLOCA_N(char, strlen(s)+1);
	strcpy(av[i], s);
      }
      av[argc] = (char *)NULL;
    }

    Tcl_ResetResult(ptr->ip);

    /* Invoke the C procedure */
    if (object) {
#if TCL_MAJOR_VERSION >= 8
      int dummy;
      ptr->return_value = (*info.objProc)(info.objClientData,
					  ptr->ip, argc, ov);

      /* get the string value from the result object */
      resultPtr = Tcl_GetObjResult(ptr->ip);
      Tcl_SetResult(ptr->ip, Tcl_GetStringFromObj(resultPtr, &dummy),
		    TCL_VOLATILE);
#endif
    } else {
      ptr->return_value = (*info.proc)(info.clientData,
				       ptr->ip, argc, av);
    }

    if (ptr->return_value == TCL_ERROR) {
	Fail(ptr->ip->result);
    }

    /* pass back the result (as string) */
    return(str_new2(ptr->ip->result));
}

/* get return code from Tcl_Eval() */
static VALUE
ip_retval(VALUE self)
{
    struct tcltkip *ptr;	/* tcltkip data struct */

    /* get the data strcut */
    Data_Get_Struct(self, struct tcltkip, ptr);

    return (INT2FIX(ptr->return_value));
}

#ifdef __MACOS__
static void
_macinit()
{
  tcl_macQdPtr = &qd; /* setup QuickDraw globals */
  Tcl_MacSetEventProc(TkMacConvertEvent); /* setup event handler */
}
#endif

/*---- initialization ----*/
void Init_tcltklib()
{
    extern VALUE rb_argv0;	/* the argv[0] */

    VALUE lib = rb_define_module("TclTkLib");
    VALUE ip = rb_define_class("TclTkIp", cObject);

    eTkCallbackBreak = rb_define_class("TkCallbackBreak", eStandardError);
    eTkCallbackContinue = rb_define_class("TkCallbackContinue",eStandardError);

    rb_define_module_function(lib, "mainloop", lib_mainloop, 0);

    rb_define_singleton_method(ip, "new", ip_new, 0);
    rb_define_method(ip, "_eval", ip_eval, 1);
    rb_define_method(ip, "_toUTF8",ip_toUTF8,2);
    rb_define_method(ip, "_fromUTF8",ip_fromUTF8,2);
    rb_define_method(ip, "_invoke", ip_invoke, -1);
    rb_define_method(ip, "_return_value", ip_retval, 0);
    rb_define_method(ip, "mainloop", lib_mainloop, 0);

#ifdef __MACOS__
    _macinit();
#endif

    /*---- initialize tcl/tk libraries ----*/
    /* from Tk_Main() */
    DUMP1("Tcl_FindExecutable");
    Tcl_FindExecutable(RSTRING(rb_argv0)->ptr);
}

/* eof */
