#include <assert.h>
#include <stdio.h>

#include "jim.h"
#include "jimautoconf.h"
#include "jim-subcmd.h"

static void JimInterpDelProc(Jim_Interp *interp, void *privData)
{
    Jim_FreeInterp((Jim_Interp *)privData);
}

/* Everything passing between interpreters must be converted to a string */
static Jim_Obj *JimInterpCopyObj(Jim_Interp *target, Jim_Obj *obj)
{
    const char *rep;
    int len;

    rep = Jim_GetString(obj, &len);
    return Jim_NewStringObj(target, rep, len);
}

#define JimInterpCopyResult(to, from) Jim_SetResult((to), JimInterpCopyObj((to), Jim_GetResult((from))))

static int interp_cmd_eval(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
    int ret;
    Jim_Interp *child = Jim_CmdPrivData(interp);
    Jim_Obj *scriptObj;
    Jim_Obj *targetScriptObj;

    scriptObj = Jim_ConcatObj(interp, argc, argv);
    targetScriptObj = JimInterpCopyObj(child, scriptObj);
    Jim_FreeNewObj(interp, scriptObj);

    Jim_IncrRefCount(targetScriptObj);
    ret = Jim_EvalObj(child, targetScriptObj);
    Jim_DecrRefCount(child, targetScriptObj);

    JimInterpCopyResult(interp, child);
    return ret;
}

static int interp_cmd_delete(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
    return Jim_DeleteCommand(interp, argv[0]);
}

static void JimInterpDelAlias(Jim_Interp *interp, void *privData)
{
    Jim_Interp *parent = Jim_GetAssocData(interp, "interp.parent");
    Jim_DecrRefCount(parent, (Jim_Obj *)privData);
}

static int JimInterpAliasProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
    int i, ret;
    Jim_Interp *parent = Jim_GetAssocData(interp, "interp.parent");
    Jim_Obj *targetPrefixObj = Jim_CmdPrivData(interp);
    Jim_Obj *targetScriptObj;

    assert(parent);

    /* Build the complete command */
    targetScriptObj = Jim_DuplicateObj(parent, targetPrefixObj);
    for (i = 1; i < argc; i++) {
        Jim_ListAppendElement(parent, targetScriptObj,
            JimInterpCopyObj(parent, argv[i]));
    }

    Jim_IncrRefCount(targetScriptObj);
    ret = Jim_EvalObj(parent, targetScriptObj);
    Jim_DecrRefCount(parent, targetScriptObj);

    JimInterpCopyResult(interp, parent);
    return ret;
}

static int interp_cmd_alias(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
    Jim_Interp *child = Jim_CmdPrivData(interp);
    Jim_Obj *aliasPrefixList;

    /* The prefix list will be held inside the child, but it still belongs
     * to the parent!
     */

    aliasPrefixList = Jim_NewListObj(interp, argv + 1, argc - 1);
    Jim_IncrRefCount(aliasPrefixList);

    Jim_CreateCommand(child, Jim_String(argv[0]), JimInterpAliasProc, aliasPrefixList, JimInterpDelAlias);
    return JIM_OK;
}

static const jim_subcmd_type interp_command_table[] = {
    {   "eval",
        "script ...",
        interp_cmd_eval,
        1,
        -1,
        /* Description: Concat the args and evaluate the script in the interpreter */
    },
    {   "delete",
        NULL,
        interp_cmd_delete,
        0,
        0,
        JIM_MODFLAG_FULLARGV,
        /* Description: Delete this interpreter */
    },
    {   "alias",
        "childcmd parentcmd ?arg ...?",
        interp_cmd_alias,
        2,
        -1,
        /* Description: Create an alias which refers to a script in the parent interpreter */
    },
    { NULL }
};

static int JimInterpSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
    return Jim_CallSubCmd(interp, Jim_ParseSubCmd(interp, interp_command_table, argc, argv), argc, argv);
}

static void JimInterpCopyVariable(Jim_Interp *target, Jim_Interp *source, const char *var, const char *default_value)
{
    Jim_Obj *value = Jim_GetGlobalVariableStr(source, var, JIM_NONE);
    const char *str;

    str = value ? Jim_String(value) : default_value;
    if (str) {
        Jim_SetGlobalVariableStr(target, var, Jim_NewStringObj(target, str, -1));
    }
}

/**
 * [interp] creates a new interpreter.
 */
static int JimInterpCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
    Jim_Interp *child;
    char buf[34];
    int i;
    static const char * const copyvars[] = {
        "argv", "argc", "argv0", "jim::argv0", "jim::exe", "jim::lineedit", NULL
    };

    if (argc != 1) {
        Jim_WrongNumArgs(interp, 1, argv, "");
        return JIM_ERR;
    }

    /* Create the interpreter command */
    child = Jim_CreateInterp();
    Jim_RegisterCoreCommands(child);
    Jim_InitStaticExtensions(child);

    /* Copy some core variables to the new interpreter */
    for (i = 0; copyvars[i]; i++) {
        JimInterpCopyVariable(child, interp, copyvars[i], NULL);
    }

    /* Allow the child interpreter to find the parent */
    Jim_SetAssocData(child, "interp.parent", NULL, interp);

    snprintf(buf, sizeof(buf), "interp.handle%ld", Jim_GetId(interp));
    Jim_CreateCommand(interp, buf, JimInterpSubCmdProc, child, JimInterpDelProc);
    Jim_SetResult(interp, Jim_MakeGlobalNamespaceName(interp, Jim_NewStringObj(interp, buf, -1)));
    return JIM_OK;
}

int Jim_interpInit(Jim_Interp *interp)
{
    Jim_PackageProvideCheck(interp, "interp");
    Jim_CreateCommand(interp, "interp", JimInterpCommand, NULL, NULL);

    return JIM_OK;
}
