#include "php_wrenchboard_api.h"
#include "php_wrenchboard_api_ns.h"
#include "wrenchboard.h"
#include "cfg.h"
#include "clog.h"
#include "vars.h"

#include "safestring.h"

#include <string.h>
#include <map>

#include <ext/standard/info.h>

zend_object_handlers wrenchboard_object_handlers;

typedef struct _wrenchboard_object {
    WrenchBoard *wrenchboard;
    zend_object std;
} wrenchboard_object;

static inline wrenchboard_object *php_wrenchboard_obj_from_obj(zend_object *obj) {
    return (wrenchboard_object*)((char*)(obj) - XtOffsetOf(wrenchboard_object, std));
}

#define Z_TSTOBJ_P(zv)  php_wrenchboard_obj_from_obj(Z_OBJ_P((zv)))

zend_class_entry *wrenchboard_ce;

#define SET_ENV( env ) \
if (NULL != (token = zend_hash_str_find(_SERVER, env, sizeof(env)-1))) {\
    setenv( env, Z_STRVAL_P(token), 1 ); \
    logfmt( logINFO, env " = %s", Z_STRVAL_P(token) ); \
} else {\
    unsetenv( env );\
}

// ------------------------------------------------------------------
// ------------------------------------------------------------------
PHP_METHOD(WrenchBoard, __construct)
{
    HashTable *_SERVER;
    zval *token;
    zval *id = getThis();
    wrenchboard_object *intern;

    intern = Z_TSTOBJ_P(id);
    if(intern != NULL) {
        intern->wrenchboard = new WrenchBoard();
        // Load some _SERVER data into environment
        zend_string *_server = zend_string_init("_SERVER", strlen("_SERVER"), 0);
        zend_is_auto_global(_server);
        if (Z_TYPE(PG(http_globals)[TRACK_VARS_SERVER]) == IS_UNDEF) {
            logfmt( logINFO, "_SERVER not found" );
        } else {
            logfmt( logINFO, "_SERVER found" );
            _SERVER = Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]);
            SET_ENV( "REMOTE_ADDR" );
            SET_ENV( "SERVER_NAME" );
            SET_ENV( "HTTP_COOKIE" );
            SET_ENV( "QUERY_STRING" );
            SET_ENV( "HTTP_X_FORWARDED_FOR" );
        }
        zend_string_release(_server);
        intern->wrenchboard->logMessage("Constructor finished");
    }
}

// ------------------------------------------------------------------ // ------------------------------------------------------------------
PHP_METHOD(WrenchBoard, wrenchboard_api)
{
    zval *param, *param_out;
    zval *id = getThis();
    wrenchboard_object *intern;
    CVars input, output;

    /* if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &param, &param_out) == FAILURE) {
        // Bad parameters
	    RETURN_NULL();
    } */
    // https://wiki.php.net/rfc/fast_zpp
    ZEND_PARSE_PARAMETERS_START(2, 2)
        Z_PARAM_ARRAY_EX(param, 0, 1)
        Z_PARAM_ARRAY_EX(param_out, 0, 1)
    ZEND_PARSE_PARAMETERS_END();

    intern = Z_TSTOBJ_P(id);
    if(intern != NULL) {
        // Convert input

        HashTable *hash;
        zend_string *key, val;
        ulong num_key;
        uint key_len;
        zval *value;
        convert_to_array_ex( param );
        hash = HASH_OF( param );
        zend_hash_internal_pointer_reset( hash );
        ZEND_HASH_FOREACH_KEY_VAL(hash, num_key, key, value) {
            if (key) { //HASH_KEY_IS_STRING
                if (Z_TYPE_P(value) == IS_STRING) { 
                    input[ ZSTR_VAL(key) ] = Z_STRVAL_P(value);
                    /*
                } else if (Z_TYPE_P(value) == IS_LONG) {
                    input[ ZSTR_VAL(key) ] = Z_LVAL_P(value);
                } else if (Z_TYPE_P(value) == IS_DOUBLE) {
                    input[ ZSTR_VAL(key) ] = Z_DVAL_P(value);
                } else if (Z_TYPE_P(value) == _IS_BOOL) {
                    input[ ZSTR_VAL(key) ] = Z_BVAL(value) ? 1L : 0L;
                } else if (Z_TYPE_P(value) == IS_NULL) {
                    input[ ZSTR_VAL(key) ] = ""; */
                } else {
                    //logfmt( logINFO, "Item %s has unsupported value type", ZSTR_VAL(key) );
                    convert_to_string(value);
                    input[ ZSTR_VAL(key) ] = Z_STRVAL_P(value);
                }
            }
        } ZEND_HASH_FOREACH_END();

        long retval = intern->wrenchboard->wrenchboard_api(input, output);

        // Convert output
        convert_to_array_ex( param_out ); 
        CVars::iterator i;
        const int out_size = output.size(); // 500
        const int out_value_size = 500;
        char out_key[out_value_size], out_value[out_value_size];

        if (!output.empty()) {
            // array_init(param_out); // deferences variable & nothing comes out :(
            // for ( i=output.begin(); i != output.end() && j<out_size ; i++ ) {
            while (!output.empty()) {
                bzero(out_key, out_value_size);
                bzero(out_value, out_value_size);
                i = output.begin();
                logfmt( logINFO, "RET: %s=%s", i->first.c_str(), i->second.c_str() );
                strsafecpy( out_key, i->first.c_str(), out_value_size );
                strsafecpy( out_value, i->second.c_str(), out_value_size );
                if (strlen(out_key)>0) {
                    /*
                    char *str; 
                    str = estrdup(i->second.c_str()); 
                    add_assoc_string( param_out, out_key, str, 0); 
                    */
                    //add_assoc_string( param_out, out_key, out_value);
                    add_assoc_string_ex( param_out, out_key, strlen(out_key), out_value);
                }
                //SAFE_STRING
                //efree(out_value);
                output.erase(output.begin());
            }
            //RETURN_ZVAL(param_out, 0, 0); // Possible workaround to return array
        }
        logfmt( logDEBUG, "About to return %ld", retval );
        RETURN_LONG(retval);
    }

    RETURN_NULL();
}

// ------------------------------------------------------------------ // ------------------------------------------------------------------
PHP_METHOD(WrenchBoard, cfgReadChar)
{
    char *parameter = NULL;
    size_t parameter_len = 0;
    zval *id = getThis();
    wrenchboard_object *intern;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &parameter, &parameter_len) == FAILURE) {
        RETURN_NULL();
    }
    
    intern = Z_TSTOBJ_P(id);
    if(intern != NULL) {
        std::stringstream msg;
        msg << "PHP_METHOD(WrenchBoard, cfgReadChar):";
        msg << (char*)parameter;
        intern->wrenchboard->logMessage(msg.str().c_str());
        std::string s = intern->wrenchboard->cfgReadChar((char*)parameter);
        msg << " = " << s;
        intern->wrenchboard->logMessage(msg.str().c_str());
        RETURN_STRING(s.c_str());
    }
    RETURN_NULL();
}

// DEBUG:
// gdb --args php -f i.php
// sysctl -w kernel.core_pattern=/opt/mainsite/wrenchboard/www/core.%e.%p.%h.%t
// ------------------------------------------------------------------ // ------------------------------------------------------------------
PHP_METHOD(WrenchBoard, cfgReadLong)
{
    char *parameter = NULL;
    size_t parameter_len = 0;
    zval *id = getThis();
    wrenchboard_object *intern;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &parameter, &parameter_len) == FAILURE) {
        RETURN_NULL();
    }

    intern = Z_TSTOBJ_P(id);
    if(intern != NULL) {
        intern->wrenchboard->logMessage("PHP_METHOD(WrenchBoard, cfgReadLong)");
        long l = intern->wrenchboard->cfgReadLong((char*)parameter);
        RETURN_LONG(l);
    }
    RETURN_NULL();
}

// ------------------------------------------------------------------ // ------------------------------------------------------------------
PHP_METHOD(WrenchBoard, logMessage)
{
    char *message = NULL;
    size_t message_len = 0;
    zval *id = getThis();
    wrenchboard_object *intern;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &message, &message_len) == FAILURE) {
        RETURN_FALSE;
    }

    intern = Z_TSTOBJ_P(id);
    if(intern != NULL) {
        intern->wrenchboard->logMessage((char*)message);
        RETURN_TRUE;
    }
    RETURN_FALSE;
}

// ------------------------------------------------------------------ // ------------------------------------------------------------------

// https://www.phpinternalsbook.com/php7/extensions_design/php_functions.html
// https://github.com/php/php-src/blob/master/Zend/zend_builtin_functions_arginfo.h

// ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args)
    // ZEND_ARG_INFO(pass_by_ref, name)
    // ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null)
    // ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null)
    // ZEND_ARG_CALLABLE_INFO(pass_by_ref, name, allow_null)
    // ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null)
    // ZEND_ARG_VARIADIC_INFO(pass_by_ref, name)
// ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_void, 0, 0, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_string, 0, 0, 1)
    ZEND_ARG_TYPE_INFO(0, str, IS_STRING, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_api, 0, 0, 2)
    //ZEND_ARG_TYPE_INFO(1, in, IS_ARRAY, 0)
    //ZEND_ARG_TYPE_INFO(1, out, IS_ARRAY, 0)
    ZEND_ARG_ARRAY_INFO(1, in, 0)
    ZEND_ARG_ARRAY_INFO(1, out, 0)
ZEND_END_ARG_INFO()

// ------------------------------------------------------------------ // ------------------------------------------------------------------
const zend_function_entry wrenchboard_methods[] = {
    PHP_ME(WrenchBoard, __construct,      arginfo_void,   ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
    PHP_ME(WrenchBoard,  wrenchboard_api, arginfo_api,    ZEND_ACC_PUBLIC)
    PHP_ME(WrenchBoard,  cfgReadChar,     arginfo_string, ZEND_ACC_PUBLIC)
    PHP_ME(WrenchBoard,  cfgReadLong,     arginfo_string, ZEND_ACC_PUBLIC)
    PHP_ME(WrenchBoard,  logMessage,      arginfo_string, ZEND_ACC_PUBLIC)
    PHP_FE_END
};

// ------------------------------------------------------------------ // ------------------------------------------------------------------
zend_object *wrenchboard_object_new(zend_class_entry *ce TSRMLS_DC)
{
    wrenchboard_object *intern = (wrenchboard_object*)ecalloc(1,
            sizeof(wrenchboard_object) +
            zend_object_properties_size(ce));

    zend_object_std_init(&intern->std, ce TSRMLS_CC);
    object_properties_init(&intern->std, ce);

    intern->std.handlers = &wrenchboard_object_handlers;

    return &intern->std;
}

// ------------------------------------------------------------------ // ------------------------------------------------------------------
static void wrenchboard_object_destroy(zend_object *object)
{
    wrenchboard_object *my_obj;
    my_obj = (wrenchboard_object*)((char *)
        object - XtOffsetOf(wrenchboard_object, std));

    // Call __destruct() from user-land.
    zend_objects_destroy_object(object);
}

static void wrenchboard_object_free(zend_object *object)
{
    wrenchboard_object *my_obj;
    my_obj = (wrenchboard_object *)((char *)
        object - XtOffsetOf(wrenchboard_object, std));
    delete my_obj->wrenchboard;
    // Free the object using Zend macro.
    zend_object_std_dtor(object); 
}

// ------------------------------------------------------------------ // ------------------------------------------------------------------
PHP_MINIT_FUNCTION(twrenchboardapit)
{
    zend_class_entry ce;
    INIT_NS_CLASS_ENTRY(ce, WRENCHBOARD_API_NS, "WrenchBoard", wrenchboard_methods);
    wrenchboard_ce = zend_register_internal_class(&ce TSRMLS_CC);
    wrenchboard_ce->create_object = wrenchboard_object_new;

    memcpy(&wrenchboard_object_handlers, 
        zend_get_std_object_handlers(), 
        sizeof(wrenchboard_object_handlers));

    // Handler for free'ing the object.
    wrenchboard_object_handlers.free_obj = wrenchboard_object_free;

    // Handler for the destructor.
    wrenchboard_object_handlers.dtor_obj = wrenchboard_object_destroy; 

    // Offset into the engine.
    wrenchboard_object_handlers.offset = XtOffsetOf(wrenchboard_object, std); 

    return SUCCESS;
}

// ------------------------------------------------------------------ // ------------------------------------------------------------------
PHP_MINFO_FUNCTION(twrenchboardapit)
{
    php_info_print_table_start();
    php_info_print_table_row(2, "WRENCHBOARD API support", "enabled");
    php_info_print_table_row(2, "Log File", WRENCHBOARD_LOG);
    php_info_print_table_row(2, "Config File", WRENCHBOARD_CONFIG"wrenchboard_api.conf");
    php_info_print_table_row(2, "Build Date", __DATE__);
    php_info_print_table_row(2, "Build Time", __TIME__);
    php_info_print_table_row(2, "Namespace", WRENCHBOARD_API_NS);
    php_info_print_table_row(2, "Version", PHP_WRENCHBOARD_API_EXTVER);
    php_info_print_table_end();
}

// ------------------------------------------------------------------ // ------------------------------------------------------------------
zend_module_entry twrenchboardapit_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_WRENCHBOARD_API_EXTNAME,
    NULL,                  /* Functions */
    PHP_MINIT(twrenchboardapit),
    NULL,                  /* MSHUTDOWN */
    NULL,                  /* RINIT */
    NULL,                  /* RSHUTDOWN */
    PHP_MINFO(twrenchboardapit), /* MINFO */
#if ZEND_MODULE_API_NO >= 20010901
    PHP_WRENCHBOARD_API_EXTVER,
#endif
    STANDARD_MODULE_PROPERTIES
};

//#ifdef COMPILE_DL_WRENCHBOARD_API
extern "C" {
  ZEND_GET_MODULE(twrenchboardapit)
}
//#endif
