#include "th1ish_cgi.h"

#include <string.h> /* strlen() */
#include <assert.h>

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


/**
   Custom client-side native type to bind to th1ish.
*/
struct cgi_native {
    /* Put your data here. For now we just use a placeholder. */
    cwal_value * vSelf;
    th1ish_cgi cgi;
};
typedef struct cgi_native cgi_native;
/**
   Serves as a copy-constructable default instance and as a type ID
   pointer for cwal_new_native() and friends.
*/
static const cgi_native cgi_native_empty = {NULL, th1ish_cgi_empty_m};


static void cgi_native_finalize( cwal_engine * e, void * v ){
    cgi_native * ln = (cgi_native *)v;
    /* MARKER(("Finalizing cgi_native@%p\n", (void *)ln)); */
    th1ish_cgi_cleanup( &ln->cgi );
    cwal_free( e, v );
}


/**
   A helper function which comes in handy when script code
   subclasses our native or overrides its methods.
*/
static cgi_native * cgi_native_from_value( th1ish_interp * ie,
                                         cwal_value * v ){
    cgi_native * rc = NULL;
    do{
        cwal_native * n = cwal_value_get_native(v);
        if(n && (rc = cwal_native_get(n, &cgi_native_empty))) break;
        else v = th1ish_prototype_get(ie, v);
    }while(v);
    return rc;
}



/* Helper macro for cwal_callback_f() implementations bound to
   cgi_native instances which ensures that the arguments->self value is
   a cgi_native instance... */
#define THIS_CGI                                                 \
    th1ish_cgi * cgi; cgi_native * self;                          \
    th1ish_interp * ie = th1ish_args_interp(args);       \
    assert(ie); \
    self = cgi_native_from_value(ie, args->self);                     \
    cgi = self ? &self->cgi : NULL; \
    if(!cgi) return th1ish_toss(ie, CWAL_RC_TYPE,                  \
                                "'this' is not (or is no longer) " \
                                "a CGI instance.")


static int cgi_cb_urldecode( cwal_callback_args const * args,
                             cwal_value **rv ){
    cwal_size_t slen = 0;
    char const * cstr;
    int rc;
    THIS_CGI;
    cstr = (args->argc)
        ? cwal_value_get_cstr(args->argv[0], &slen)
        : NULL;
    if(!cstr) return th1ish_toss(ie, CWAL_RC_MISUSE,
                                 "Expecting a String argument.");
    rc = cwal_buffer_reserve(args->engine, &cgi->tmpBuf, slen+1);
    if(rc) return rc;
    cgi->tmpBuf.used = 0;
    cwal_buffer_append( args->engine, &cgi->tmpBuf, cstr, slen );
    th1ish_cgi_urldecode_inline( (char *)cgi->tmpBuf.mem, &cgi->tmpBuf.used);
    *rv = cwal_new_string_value(args->engine,
                                cgi->tmpBuf.used ? (char const *)cgi->tmpBuf.mem : NULL,
                                cgi->tmpBuf.used);
    cgi->tmpBuf.used = 0;
    return *rv ? 0 : CWAL_RC_OOM;
}

static int cgi_cb_urlencode( cwal_callback_args const * args,
                             cwal_value **rv ){
    cwal_size_t slen = 0;
    char const * cstr;
    int rc;
    cwal_size_t oldPos;
    THIS_CGI;
    cstr = (args->argc)
        ? cwal_value_get_cstr(args->argv[0], &slen)
        : NULL;
    if(!cstr) return th1ish_toss(ie, CWAL_RC_MISUSE,
                                 "Expecting a String argument.");
    else if(!slen){
        /* Optimization: don't bother with an empty string... */
        if(cwal_value_is_string(args->argv[0])) *rv = args->argv[0];
        else *rv = cwal_new_string_value(args->engine, "", 0) /* cannot fail */;
        return 0;
    }
    oldPos = cgi->tmpBuf.used;
    rc = cwal_buffer_reserve(args->engine, &cgi->tmpBuf, oldPos + slen + 1);
    if(rc) return rc;
    rc = cwal_buffer_printf(args->engine, &cgi->tmpBuf, "%t", cstr);
    if(!rc){
        cwal_size_t const len = cgi->tmpBuf.used - oldPos;
        *rv = cwal_new_string_value(args->engine, len ? ((char const *)cgi->tmpBuf.mem + oldPos) : NULL,
                                    len);
        if(!*rv) rc = CWAL_RC_OOM;
    }
    cgi->tmpBuf.used = oldPos;
    if(cgi->tmpBuf.mem){
        cgi->tmpBuf.mem[oldPos] = 0;
    }
    return rc;
}

static int cgi_cb_html_escape( cwal_callback_args const * args,
                               cwal_value **rv ){
    cwal_size_t slen = 0;
    char const * cstr;
    int rc;
    cwal_size_t oldPos;
    THIS_CGI;
    cstr = (args->argc)
        ? cwal_value_get_cstr(args->argv[0], &slen)
        : NULL;
    if(!cstr) return th1ish_toss(ie, CWAL_RC_MISUSE,
                                 "Expecting a String argument.");
    else if(!slen){
        /* Optimization: don't bother with an empty string... */
        if(cwal_value_is_string(args->argv[0])) *rv = args->argv[0];
        else *rv = cwal_new_string_value(args->engine, "", 0) /* cannot fail */;
        return 0;
    }
    oldPos = cgi->tmpBuf.used;
    rc = cwal_buffer_reserve(args->engine, &cgi->tmpBuf, oldPos + slen + 1);
    if(rc) return rc;
    rc = cwal_buffer_printf(args->engine, &cgi->tmpBuf, "%h", cstr);
    if(!rc){
        cwal_size_t const len = cgi->tmpBuf.used - oldPos;
        *rv = cwal_new_string_value(args->engine,
                                    len ? ((char const *)cgi->tmpBuf.mem + oldPos) : NULL,
                                    len);
        if(!*rv) rc = CWAL_RC_OOM;
    }
    cgi->tmpBuf.used = oldPos;
    if(cgi->tmpBuf.mem){
        cgi->tmpBuf.mem[oldPos] = 0;
    }
    return rc;
}


static int cgi_cb_send( cwal_callback_args const * args,
                        cwal_value **rv ){
    int rc;
    THIS_CGI;
    rc = th1ish_cgi_response_output_all(cgi);
    cwal_native_clear( cwal_value_get_native(self->vSelf), 1 );
    return rc;
}

static int cgi_cb_header_set( cwal_callback_args const * args,
                              cwal_value **rv ){
    char const * key;
    cwal_size_t keyLen = 0;
    int rc;
    THIS_CGI;
    if(args->argc != 2) goto misuse;
    key = args->argc
        ? cwal_value_get_cstr(args->argv[0], &keyLen)
        : NULL;
    if(!key) goto misuse;
    rc = th1ish_cgi_response_header_add( cgi, key, keyLen, args->argv[1] );
    return rc;
    misuse:
    return th1ish_toss(ie, CWAL_RC_MISUSE,
                       "Expecting (key,value) arguments.");
}

static int cgi_cb_cookie_set( cwal_callback_args const * args,
                              cwal_value **rv ){
    char const * key;
    cwal_size_t keyLen = 0;
    int rc;
    cwal_value * v = 0;
    THIS_CGI;
    if(!args->argc) goto misuse;
    if(2==args->argc){
        key = cwal_value_get_cstr(args->argv[0], &keyLen);
        if(!key) goto misuse;
        v = args->argv[1];
    }else if(cwal_value_is_object(args->argv[0])){
        v = args->argv[0];
    }
    else goto misuse;
    /* MARKER(("Setting cookie [%s]\n", key)); */
    rc = th1ish_cgi_cookie_set( cgi, key, keyLen, v );
    return rc;
    misuse:
    return th1ish_toss(ie, CWAL_RC_MISUSE,
                       "Expecting (key,value) or Object arguments.");
}

/**
   Script usage:

   cgi.httpStatus(404, "Not found")
   cgi.httpStatus(404) // uses default message
   var code = cgi.httpStatus()
   assert 'integer' === typename code
*/
static int cgi_cb_response_status( cwal_callback_args const * args,
                                   cwal_value **rv ){
    char const * msg;
    cwal_size_t nMsg = 0;
    cwal_int_t code;
    int rc;
    THIS_CGI;
    if(!args->argc){
        *rv = cwal_new_integer(args->engine,
                               (cwal_int_t)cgi->response.httpCode);
        return *rv ? 0 : CWAL_RC_OOM;
    }
    if((args->argc != 1) && (args->argc != 2)){
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "Expecting no arguments (getter) "
                           "or (integer,string) arguments (setter).");
    }
    code = cwal_value_get_integer(args->argv[0]);
    msg = (args->argc > 1)
        ? cwal_value_get_cstr(args->argv[1], &nMsg)
        : NULL;
    rc = th1ish_cgi_response_status_set( cgi, (int)code, msg );
    return rc;
}


static int cgi_cb_content_type_set( cwal_callback_args const * args,
                                    cwal_value **rv ){
    int rc;
    THIS_CGI;
    if(args->argc != 1) {
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "Expecting Value argument.");
    }
    rc = th1ish_cgi_response_header_add( cgi, "Content-type", 12,
                                         args->argv[0] );
    return rc;
}

/**
   Example of installing a custom native instance as a module...

   Modules "should" install their new feature(s) in the given
   container/namespace value.
*/
int th1ish_cgi_install_to_interp( th1ish_interp * ie, cwal_value * ns ){
    int rc;
    cwal_value * v;
    cwal_value * mod;
    static char const * modKey = "cgi";
    static cwal_size_t const keyLen = 3 /* == strlen(modKey) */;
    cgi_native * cn;
    th1ish_cgi * cgi;
    assert(cwal_props_can(ns)) /* th1ish won't call this function if
                                  ns provided by the caller is not a
                                  container. */;

    /* See if we're already installed... */
    v = cwal_prop_get( ns, modKey, keyLen);
    if(v) return 0;

    cn = (cgi_native*)cwal_malloc(ie->e, sizeof(cgi_native));
    if(!cn) return CWAL_RC_OOM;
    *cn = cgi_native_empty;
    mod = cwal_new_native_value(ie->e, cn, cgi_native_finalize,
                                &cgi_native_empty);
    if(!mod){
        cwal_free(ie->e, cn);
        return CWAL_RC_OOM;
    }
    rc = cwal_prop_set( ns, modKey, keyLen, mod );
    if(rc){
        cwal_value_unref(mod);
        goto end;
    }
    cn->vSelf = mod;
    cgi = &cn->cgi;
    /*Done automatically: 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 FUNC(NAME,FP)                              \
    v = th1ish_new_function2( ie, FP );     \
    CHECKV; \
    SET(NAME);      \
    assert(!rc);                                                 \
    if(rc) goto end

    rc = th1ish_cgi_init(ie, cgi);
    if(rc){
        rc = th1ish_toss(ie, rc, "CGI initialization failed%s%.*s",
                         cgi->err.msg.used ? ": " : "",
                         (int)cgi->err.msg.used,
                         cgi->err.msg.used
                         ? (char const *)cgi->err.msg.mem : "");
        goto end;
    }

    FUNC("httpStatus", cgi_cb_response_status);
    FUNC("htmlEscape", cgi_cb_html_escape);
    FUNC("send", cgi_cb_send);
    FUNC("setContentType", cgi_cb_content_type_set);
    FUNC("setCookie", cgi_cb_cookie_set);
    FUNC("setHeader", cgi_cb_header_set);
    FUNC("urldecode", cgi_cb_urldecode);
    FUNC("urlencode", cgi_cb_urlencode);
    v = cwal_new_string_value(ie->e, "CGI", 3);
    SET("__typename");

    {
        /* Add cgi.request... */
        cwal_value * request = cwal_new_object_value(cgi->e);
        if(!request){
            rc = CWAL_RC_OOM;
            goto end;
        }
        rc = cwal_prop_set( mod, "request", 7, request );
        if(rc){
            cwal_value_unref(request);
            goto end;
        }
#undef SET
#define SET(KEY) rc = cwal_prop_set( request, KEY, strlen(KEY), v );        \
    assert(!rc); if(rc) goto end
    
        v = cgi->request.env;
        if(v){
            SET("ENV");
        }
        v = cgi->request.get;
        if(v){
            SET("GET");
        }
        v = cgi->request.post;
        if(v){
            SET("POST");
        }
        v = cgi->request.cookies;
        if(v){
            SET("COOKIES");
        }
    }
#undef SET
#undef FUNC
#undef CHECKV
    end:
    assert(!rc);
    return rc;
}




/**
  Module initializer for when the module is compiled in a DLL with
  other modules.
*/
TH1ISH_MODULE_IMPL(cgiish,th1ish_cgi_install_to_interp);

/**
   Module initializer for when the module is the only module in the
   DLL.
*/
TH1ISH_MODULE_IMPL1(cgiish,th1ish_cgi_install_to_interp);


#undef THIS_CGI
#undef MARKER
