/*
  This file implements a th1ish binding for linenoise:

  https://github.com/msteveb/linenoise
*/
#include <assert.h>
#include <memory.h> /* strlen() */
#include "linenoise/linenoise.h"
#ifdef TH1ISH_AMALGAMATION_BUILD
#include "th1ish_amalgamation.h"
#else
#include "th1ish.h"
#endif
#include <stdlib.h> /* free() */

#if 0
#include "../cwal_internal.h" /* only for debuggering */
#endif

#include <stdio.h>
#define MARKER(pfexp)                                                \
    do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \
        printf pfexp; \
    } while(0)

/**
   This is essentially a placeholder structure to a
   unique-per-interpreter handle to the linenoise lib. We need a
   per-instance handle only so that we can implement auto-save when
   the value is cleaned up.
*/
struct ln_native {
    cwal_value * vSelf;
#ifndef NO_COMPLETION
    linenoiseCompletions * lc;
#else
    void * lc;
#endif
};
typedef struct ln_native ln_native;
/**
   Serves as a copy-constructable default instance and as a type ID
   pointer for cwal_new_native() and friends.
*/
static const ln_native ln_native_empty = {NULL, NULL};
static ln_native ln_native_shared = {NULL, NULL};

#define ARGS_IE th1ish_interp * ie = th1ish_args_interp(args); \
    assert(ie)

#define THIS_LN \
    cwal_native * nself = cwal_value_get_native(args->self);    \
    ln_native * self = nself \
        ? (ln_native *)cwal_native_get(nself, &ln_native_empty) : NULL; \
    ARGS_IE; \
    if(!self) return th1ish_toss(ie, CWAL_RC_TYPE,                      \
                                 "'this' is not (or is no longer) "\
                                 "a linenoise instance.")

static int ln_hist_load( cwal_callback_args const * args,
                         cwal_value **rv ){
    int rc;
    cwal_value * vfn = NULL;
    char const * fn;
    char setProp = 0;
    THIS_LN;
    if(!args->argc){
        vfn = cwal_prop_get(self->vSelf,
                            "historyFile", 11);
        fn = vfn ? cwal_value_get_cstr(vfn, NULL) : NULL;
    }else{
        vfn = args->argv[0];
        fn = cwal_value_get_cstr(vfn, NULL);
        if(fn) setProp = 1;
    }
    if(!fn || !*fn){
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "Expecting a non-empty string "
                           "argument or a this.historyFile "
                           "property.");
    }
    rc = linenoiseHistoryLoad(fn);
    if(!rc && setProp){
        cwal_prop_set( args->self, "historyFile", 11, vfn );
    }
    return rc
        ? th1ish_toss(ie, CWAL_RC_ERROR,
                      "linenoiseHistoryLoad('%s') failed "
                      "with code %d.",
                      fn, rc)
        : 0;
}

static int ln_hist_save( cwal_callback_args const * args,
                         cwal_value **rv ){
    cwal_value * hName = NULL;
    char const * fn = NULL;
    THIS_LN;
    if(!args->argc){
        hName = cwal_prop_get(self->vSelf,
                              "historyFile", 11);
        fn = hName ? cwal_value_get_cstr(hName, NULL) : NULL;
    }else{
        fn = cwal_value_get_cstr(args->argv[0], NULL);
    }
    if(!fn || !*fn){
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "Expecting a non-empty string "
                           "argument or a this.historyFile "
                           "property.");
    }
    else{
        int const rc = linenoiseHistorySave(fn);
        return rc
            ? th1ish_toss(ie, CWAL_RC_ERROR,
                          "linenoiseHistorySave('%s') failed "
                          "with code %d.",
                          fn, rc)
            : 0;
    }
}

static int ln_hist_size( cwal_callback_args const * args,
                         cwal_value **rv ){
    if(!args->argc){
        *rv = cwal_new_integer(args->engine,
                               (cwal_int_t)linenoiseHistoryGetMaxLen());
        return *rv ? 0 : CWAL_RC_OOM;
    }else{
        linenoiseHistorySetMaxLen( cwal_value_get_integer(args->argv[0]) );
        *rv = cwal_value_undefined();
        return 0;
    }
}

static int ln_hist_add( cwal_callback_args const * args,
                        cwal_value **rv ){
    ARGS_IE;
    if(!args->argc || !cwal_value_is_string(args->argv[0])){
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "Expecting a string argument.");
    }else{
        cwal_size_t slen = 0;
        char const * str = cwal_value_get_cstr(args->argv[0], &slen);
        cwal_buffer * buf = &ie->buffer;
        cwal_size_t const oldUsed = buf->used;
        int rc = cwal_buffer_append(args->engine, buf, str, slen);
        /* We use a buffer here or this will fail if str is not
           NUL-terminated (an x-string can/will trigger this).  That
           said, we don't really expect X-strings to ever be fed in to
           the CLI history.
        */
        if(rc) return rc;
        else if(buf->used == oldUsed){
            *rv = cwal_value_false();
        }else{
            *rv =
                cwal_new_bool(
                              linenoiseHistoryAdd((char const *)(buf->mem+oldUsed))
                              ? 1 : 0);
            buf->used = oldUsed;
        }
        return 0;
    }
}

#ifndef NO_COMPLETION
static struct {
    th1ish_interp * ie;
    ln_native * ln;
    cwal_function * cb;
    int rc;
} LnCompletion = {
NULL,
NULL,
NULL,
0
};
static void ln_completion_add(linenoiseCompletions *lc, cwal_value const * v) {
    char const * str = v ? cwal_value_get_cstr(v, NULL) : NULL;
    if(str){
        linenoiseAddCompletion(lc,str);
    }
}

static void ln_completion(const char *buf, linenoiseCompletions *lc) {
    int rc;
    cwal_value * str;
    cwal_scope * sc = cwal_scope_current_get(LnCompletion.ie->e);
    cwal_value * rv = NULL;
    str = cwal_new_string_value(LnCompletion.ie->e, buf, 0);
    if(!str){
        LnCompletion.rc = CWAL_RC_OOM;
        return;
    }
    rc =
        cwal_function_call_in_scope(sc, LnCompletion.cb,
                                    LnCompletion.ln->vSelf,
                                    &rv, 1, &str);
    str = NULL;
    if(rc){
        LnCompletion.rc = rc;
    }else if(cwal_value_is_array(rv)){
        cwal_array * ar = cwal_value_get_array(rv);
        if(!ar) return;
        else{
            cwal_size_t i;
            cwal_size_t const n = cwal_array_length_get(ar);
            for(i = 0; i < n; ++i){
                ln_completion_add(lc, cwal_array_get(ar, i));
            }
        }
    }else if(rv){
        ln_completion_add(lc, rv);
    }
}
#endif
/* NO_COMPLETION */

static int ln_columns( cwal_callback_args const * args,
                        cwal_value **rv ){
    *rv = cwal_new_integer(args->engine,
                           (cwal_int_t)linenoiseColumns());
    return *rv ? 0 : CWAL_RC_OOM;
}

static int ln_readline( cwal_callback_args const * args,
                        cwal_value **rv ){
    int rc = 0;
    char const * cprompt = NULL;
    char * z;
    THIS_LN;
    if(!args->argc){
        cwal_value * vprompt =
            cwal_prop_get(self->vSelf,
                          "prompt", 6);
        cprompt = vprompt
            ? cwal_value_get_cstr(vprompt, NULL)
            : NULL;
    }else{
        cprompt = cwal_value_get_cstr(args->argv[0], NULL);
    }
    if(!cprompt) cprompt = "";
#ifndef NO_COMPLETION
    /* Thanks to Andreas Kupries for this trick... */
    {
        /* If this.customCompletions() is-a Function then
           install it has our TAB handler for the duration
           of this lineloise() call.
        */
        cwal_value * cbv = cwal_prop_get(self->vSelf,
                                         "customCompletions", 17);
        LnCompletion.cb = cbv ? cwal_value_get_function(cbv) : 0;
    }
    if(LnCompletion.cb){
        linenoiseSetCompletionCallback(ln_completion);
        LnCompletion.ie = ie;
        LnCompletion.ln = self;
    }
#endif
    z = linenoise(cprompt);
#ifndef NO_COMPLETION
    if(LnCompletion.cb){
        linenoiseSetCompletionCallback(NULL);
        LnCompletion.ie = NULL;
        LnCompletion.cb = NULL;
        rc = LnCompletion.rc;
        LnCompletion.rc = 0;
    }
#endif
    if(rc) return rc;

    if(!z){
        *rv = cwal_value_undefined();
    }
    else{
        *rv = cwal_new_string_value(args->engine,
                                    z, 0);
        if(*rv && *z){
            /* Shame that we can't just _give_ z back to linenoise here,
               since it came from there. */
            linenoiseHistoryAdd(z);
            /* TODO: save here: linenoiseHistorySave(...); */
        }
        free(z) /* reminder: z came from linenoise, i.e. malloc(), not cwal_malloc() */;
    }
    return *rv ? 0 : CWAL_RC_OOM;
}

static int ln_history_list( cwal_callback_args const * args,
                            cwal_value **rv ){
    cwal_array * ar;
    cwal_size_t i;
    int rc = 0;
    int hCount = 0;
    char ** h;
    THIS_LN;
    ar = th1ish_new_array(ie);
    if(!ar) return CWAL_RC_OOM;
    h = linenoiseHistory(&hCount);
    if(hCount>0){
        rc = cwal_array_reserve(ar, (cwal_size_t)hCount);
        if(rc) goto end;
        for( i = 0; i < (cwal_size_t)hCount; ++i ){
            cwal_value * v = cwal_new_string_value(args->engine,
                                                   h[i], 0);
            if(!v){
                rc = CWAL_RC_OOM;
                goto end;
            }else{
                rc = cwal_array_append(ar, v);
                if(rc){
                    cwal_value_unref(v);
                    goto end;
                }
            }
        }
    }

    end:
    if(rc){
        cwal_array_unref(ar);
        return rc;
    }else{
        *rv = cwal_array_value(ar);
        return 0;
    }
}


static void ln_native_finalize( cwal_engine * e, void * v ){

    ln_native * ln = (ln_native *)v;
    cwal_value * hName = cwal_prop_get(ln->vSelf,
                                       "historyFile", 11);
    ln->vSelf = NULL;
    if(hName && cwal_value_is_string(hName)){
        char const * fn = cwal_value_get_cstr(hName, NULL);
        linenoiseHistorySave(fn);
    }
    linenoiseHistoryFree();
    if(&ln_native_shared != ln){
        cwal_free( e, ln );
    }
}

int ln_install_to_interp( th1ish_interp * ie, cwal_value * ns ){
    int rc;
    cwal_value * v;
    cwal_value * mod;
    static char const * modKey = "linenoise";
    static cwal_size_t const keyLen = 9;
    ln_native * ln;
    if(!cwal_props_can(ns)) return CWAL_RC_MISUSE;

    v = cwal_prop_get( ns, modKey, keyLen);
    if(v) return 0;

#if 0
    ln = &ln_native_shared;
#else
    ln = (ln_native*)cwal_malloc(ie->e, sizeof(ln_native));
    if(!ln) return CWAL_RC_OOM;
#endif
    mod = cwal_new_native_value(ie->e, ln, ln_native_finalize,
                                &ln_native_empty);
    if(!mod){
        cwal_free(ie->e, ln);
        return CWAL_RC_OOM;
    }
    ln->vSelf = mod;
    rc = cwal_prop_set( ns, modKey, keyLen, mod );
    if(rc){
        cwal_value_unref(mod);
        goto end;
    }
    cwal_value_prototype_set( mod, th1ish_prototype_object(ie) );

#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(KEY) rc = cwal_prop_set( mod, KEY, strlen(KEY), v ); \
    assert(!rc); if(rc) goto end
#define FUNC2(NAME,FP)                              \
    v = th1ish_new_function2( ie, FP );     \
    CHECKV; \
    SET(NAME);      \
    assert(!rc);                                                 \
    if(rc) goto end

    FUNC2("add", ln_hist_add);
    FUNC2("consoleWidth", ln_columns);
    FUNC2("getHistoryList", ln_history_list);
    FUNC2("load", ln_hist_load);
    FUNC2("read", ln_readline);
    FUNC2("save", ln_hist_save);
    FUNC2("size", ln_hist_size);
    FUNC2("canCompile", th1ish_f_basic_compile_check);
    
#undef SET
#undef FUNC2
#undef CHECKV
    end:
    return rc;
}


/*
  Install th1ish_module_load() support.
*/
/*TH1ISH_MODULE_DECL(linenoiseish);*/
TH1ISH_MODULE_IMPL(linenoiseish,ln_install_to_interp);
TH1ISH_MODULE_IMPL1(linenoiseish,ln_install_to_interp);

#undef MARKER
#undef ARGS_IE
#undef THIS_LN
