/*
 * itclStubs.c --
 *
 *      This file contains the C-implemeted part of Itcl object-system
 *      Itcl
 *
 * Copyright (c) 2006 by Arnulf P. Wiedemann
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include "itclInt.h"

#if TCL_MAJOR_VERSION < 9
#   define Tcl_ObjCmdProc2 Tcl_ObjCmdProc
#   define Tcl_CreateObjCommand2 Tcl_CreateObjCommand
#   define objClientData2 objClientData
#endif

static void ItclDeleteStub(void *cdata);
static Tcl_ObjCmdProc2 ItclHandleStubCmd;

/*
 * ------------------------------------------------------------------------
 *  Itcl_IsStub()
 *
 *  Checks the given Tcl command to see if it represents an autoloading
 *  stub created by the "stub create" command.  Returns non-zero if
 *  the command is indeed a stub.
 * ------------------------------------------------------------------------
 */
int
Itcl_IsStub(
    Tcl_Command cmdPtr)      /* command being tested */
{
    Tcl_CmdInfo cmdInfo;

    /*
     *  This may be an imported command, but don't try to get the
     *  original.  Just check to see if this particular command
     *  is a stub.  If we really want the original command, we'll
     *  find it at a higher level.
     */
    if (Tcl_GetCommandInfoFromToken(cmdPtr, &cmdInfo)) {
	if (cmdInfo.deleteProc == ItclDeleteStub) {
	    return 1;
	}
    }
    return 0;
}

/*
 * ------------------------------------------------------------------------
 *  Itcl_StubCreateCmd()
 *
 *  Invoked by Tcl whenever the user issues a "stub create" command to
 *  create an autoloading stub for imported commands.  Handles the
 *  following syntax:
 *
 *    stub create <name>
 *
 *  Creates a command called <name>.  Executing this command will cause
 *  the real command <name> to be autoloaded.
 * ------------------------------------------------------------------------
 */
int
Itcl_StubCreateCmd(
    TCL_UNUSED(void *),      /* not used */
    Tcl_Interp *interp,      /* current interpreter */
    int objc,		/* number of arguments */
    Tcl_Obj *const objv[])   /* argument objects */
{
    Tcl_Command cmdPtr;
    char *cmdName;
    Tcl_CmdInfo cmdInfo;

    ItclShowArgs(1, "Itcl_StubCreateCmd", objc, objv);
    if (objc != 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "name");
	return TCL_ERROR;
    }
    cmdName = Tcl_GetString(objv[1]);

    /*
     *  Create a stub command with the characteristic ItclDeleteStub
     *  procedure.  That way, we can recognize this command later
     *  on as a stub.  Save the cmd token as client data, so we can
     *  get the full name of this command later on.
     */
    cmdPtr = Tcl_CreateObjCommand2(interp, cmdName,
	ItclHandleStubCmd, NULL,
	(Tcl_CmdDeleteProc*)ItclDeleteStub);

    Tcl_GetCommandInfoFromToken(cmdPtr, &cmdInfo);
    cmdInfo.objClientData2 = cmdPtr;
    Tcl_SetCommandInfoFromToken(cmdPtr, &cmdInfo);

    return TCL_OK;
}


/*
 * ------------------------------------------------------------------------
 *  Itcl_StubExistsCmd()
 *
 *  Invoked by Tcl whenever the user issues a "stub exists" command to
 *  see if an existing command is an autoloading stub.  Handles the
 *  following syntax:
 *
 *    stub exists <name>
 *
 *  Looks for a command called <name> and checks to see if it is an
 *  autoloading stub.  Returns a boolean result.
 * ------------------------------------------------------------------------
 */
int
Itcl_StubExistsCmd(
    TCL_UNUSED(void *),      /* not used */
    Tcl_Interp *interp,      /* current interpreter */
    int objc,		/* number of arguments */
    Tcl_Obj *const objv[])   /* argument objects */
{
    Tcl_Command cmdPtr;
    char *cmdName;

    if (objc != 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "name");
	return TCL_ERROR;
    }
    cmdName = Tcl_GetString(objv[1]);

    cmdPtr = Tcl_FindCommand(interp, cmdName, NULL, 0);

    Tcl_SetBooleanObj(Tcl_GetObjResult(interp),
	    (cmdPtr != NULL) && Itcl_IsStub(cmdPtr));
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------------
 *  ItclHandleStubCmd()
 *
 *  Invoked by Tcl to handle commands created by "stub create".
 *  Calls "auto_load" with the full name of the current command to
 *  trigger autoloading of the real implementation.  Then, calls the
 *  command to handle its function.  If successful, this command
 *  returns TCL_OK along with the result from the real implementation
 *  of this command.  Otherwise, it returns TCL_ERROR, along with an
 *  error message in the interpreter.
 * ------------------------------------------------------------------------
 */
static int
ItclHandleStubCmd(
    void *clientData,	/* command token for this stub */
    Tcl_Interp *interp,      /* current interpreter */
    Tcl_Size objc,           /* number of arguments */
    Tcl_Obj *const objv[])   /* argument objects */
{
    Tcl_Command cmdPtr;
    Tcl_Obj **cmdlinev;
    Tcl_Obj *objAutoLoad[2];
    Tcl_Obj *objPtr;
    Tcl_Obj *cmdNamePtr;
    Tcl_Obj *cmdlinePtr;
    char *cmdName;
    int result;
    int loaded;
    Tcl_Size cmdlinec;

    ItclShowArgs(1, "ItclHandleStubCmd", objc, objv);
    cmdPtr = (Tcl_Command) clientData;
    cmdNamePtr = Tcl_NewStringObj(NULL, 0);
    Tcl_IncrRefCount(cmdNamePtr);
    Tcl_GetCommandFullName(interp, cmdPtr, cmdNamePtr);
    cmdName = Tcl_GetString(cmdNamePtr);

    /*
     *  Try to autoload the real command for this stub.
     */
    objAutoLoad[0] = Tcl_NewStringObj("::auto_load", TCL_INDEX_NONE);
    objAutoLoad[1] = cmdNamePtr;
    result = Tcl_EvalObjv(interp, 2, objAutoLoad, 0);
    if (result != TCL_OK) {
	Tcl_DecrRefCount(cmdNamePtr);
	return TCL_ERROR;
    }

    objPtr = Tcl_GetObjResult(interp);
    result = Tcl_GetIntFromObj(interp, objPtr, &loaded);
    if ((result != TCL_OK) || !loaded) {
	Tcl_ResetResult(interp);
	Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
	    "can't autoload \"", cmdName, "\"", (char *)NULL);
	Tcl_DecrRefCount(cmdNamePtr);
	return TCL_ERROR;
    }

    /*
     *  At this point, the real implementation has been loaded.
     *  Invoke the command again with the arguments passed in.
     */
    cmdlinePtr = Itcl_CreateArgs(interp, cmdName, objc - 1, objv + 1);
    (void)Tcl_ListObjGetElements(NULL, cmdlinePtr,
	&cmdlinec, &cmdlinev);

    Tcl_DecrRefCount(cmdNamePtr);
    Tcl_ResetResult(interp);
    ItclShowArgs(1, "ItclHandleStubCmd", cmdlinec - 1, cmdlinev + 1);
    result = Tcl_EvalObjv(interp, cmdlinec - 1, cmdlinev + 1, TCL_EVAL_DIRECT);
    Tcl_DecrRefCount(cmdlinePtr);
    Tcl_DecrRefCount(objAutoLoad[0]);
    return result;
}


/*
 * ------------------------------------------------------------------------
 *  ItclDeleteStub()
 *
 *  Invoked by Tcl whenever a stub command is deleted.  This procedure
 *  does nothing, but its presence identifies a command as a stub.
 * ------------------------------------------------------------------------
 */

static void
ItclDeleteStub(
    TCL_UNUSED(void *))      /* not used */
{
    /* do nothing */
}

