#include "php_jubabox_api.h"
#include "php_jubabox_api_ns.h"
#include "jubabox.h"
#include "cfg.h"
#include "clog.h"
#include "vars.h"

#include "safestring.h"

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

#include <ext/standard/info.h>

using namespace std;

zend_object_handlers jubabox_object_handlers;

struct jubabox_object {
    zend_object std;
    JubaBox *jubabox;
};

zend_class_entry *jubabox_ce;

void jubabox_free_storage(void *object TSRMLS_DC)
{
    jubabox_object *obj = (jubabox_object *)object;
    delete obj->jubabox; 

    zend_hash_destroy(obj->std.properties);
    FREE_HASHTABLE(obj->std.properties);

    efree(obj);
}

zend_object_value jubabox_create_handler(zend_class_entry *type TSRMLS_DC)
{
    zval *tmp;
    zend_object_value retval;

    jubabox_object *obj = (jubabox_object *)emalloc(sizeof(jubabox_object));
    memset(obj, 0, sizeof(jubabox_object));
    obj->std.ce = type;

    ALLOC_HASHTABLE(obj->std.properties);
    zend_hash_init(obj->std.properties, 0, NULL, ZVAL_PTR_DTOR, 0);
#if PHP_VERSION_ID < 50399
    zend_hash_copy(obj->std.properties, &type->default_properties,
        (copy_ctor_func_t)zval_add_ref, (void *)&tmp, sizeof(zval *));
#else
    object_properties_init((zend_object*) &(obj->std), type);
#endif
    retval.handle = zend_objects_store_put(obj, NULL,
        jubabox_free_storage, NULL TSRMLS_CC);
    retval.handlers = &jubabox_object_handlers;

    return retval;
}

#define SET_ENV( env ) \
if (zend_hash_find(Z_ARRVAL_PP(array), env, sizeof(env), (void **) &token) == SUCCESS) {\
    setenv( env, Z_STRVAL_PP(token), 1 ); \
    logfmt( logINFO, env " = %s", Z_STRVAL_PP(token) ); \
} else {\
    unsetenv( env );\
}

PHP_METHOD(JubaBox, __construct)
{
    JubaBox *jubabox = NULL;
    zval *object = getThis();

    jubabox = new JubaBox();
    jubabox_object *obj = (jubabox_object *)zend_object_store_get_object(object TSRMLS_CC);
    obj->jubabox = jubabox;

    zval **array;
    zval **token;

    zend_is_auto_global("_SERVER", sizeof("_SERVER")-1 TSRMLS_CC);
    if (zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void **) &array) == SUCCESS &&
                Z_TYPE_PP(array) == IS_ARRAY) {
	logfmt( logINFO, "_SERVER found" );
	SET_ENV( "REMOTE_ADDR" );
	SET_ENV( "SERVER_NAME" );
	SET_ENV( "HTTP_COOKIE" );
	SET_ENV( "QUERY_STRING" );
	SET_ENV( "HTTP_X_FORWARDED_FOR" );
    } else {
	logfmt( logINFO, "_SERVER not found" );
    }
}
PHP_METHOD(JubaBox, jubabox_api)
{
    zval *param, *param_out;
    CVars input, output;
    JubaBox *jubabox;
    jubabox_object *obj = (jubabox_object *)zend_object_store_get_object(
	getThis() TSRMLS_CC);
    jubabox = obj->jubabox;
    if (jubabox != NULL) {
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, const_cast<char *>("zz"), &param, &param_out ) == FAILURE ) {
	    // Bad parameters
	    RETURN_NULL();
	}
	if (!PZVAL_IS_REF(param_out)) {
	    // out is not a reference
            /* This is not relevant in PHP 5.4 */
#if PHP_VERSION_ID < 50399
	    zend_error(E_WARNING, "Second parameter wasn't passed by reference");
	    RETURN_NULL();
#endif
	}
	HashTable *hash; 
	char *key;
	uint key_len;
	zval **value;
	
	convert_to_array_ex( &param );
	hash = HASH_OF( param );
	zend_hash_internal_pointer_reset( hash );
	
	while ( zend_hash_get_current_key( hash, &key, /*num_index*/ NULL, /*duplicate*/ false ) == HASH_KEY_IS_STRING ) {
	    zend_hash_get_current_data( hash, (void**)&value );
	    convert_to_string_ex( value );
	    input[ key ] = Z_STRVAL_PP(value);
	    zend_hash_move_forward( hash );
	}
	
	long retval = jubabox->jubabox_api(input, output);
	
	convert_to_array_ex( &param_out ); 
	
	CVars::iterator i; 
	int j = 0;
	const int out_size = output.size(); // 200
        const int out_value_size = 200;
	char out_key[out_value_size], out_value[out_value_size];
	for ( i=output.begin(); i != output.end() && j<out_size ; i++ ) {
            bzero(out_key, out_value_size);
            bzero(out_value, out_value_size);
	    j++;
	    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 );
	    add_assoc_string( param_out, out_key, out_value, true );
	}
	
	RETURN_LONG(retval);
    }
    RETURN_NULL();
}
PHP_METHOD(JubaBox, cfgReadChar)
{
    JubaBox *jubabox;
    jubabox_object *obj = (jubabox_object *)zend_object_store_get_object(
        getThis() TSRMLS_CC);
    jubabox = obj->jubabox;
    if (jubabox != NULL) {
	char *parameter;
	int parameter_len;
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, const_cast<char *>("s"), &parameter, &parameter_len) == FAILURE) {
	    RETURN_NULL();
	}
	if (parameter_len == 0) {
	    RETURN_NULL();
	}
	RETURN_STRING(jubabox->cfgReadChar(parameter),1);
    }
    RETURN_NULL();
}
PHP_METHOD(JubaBox, cfgReadLong)
{
    JubaBox *jubabox;
    jubabox_object *obj = (jubabox_object *)zend_object_store_get_object(
        getThis() TSRMLS_CC);
    jubabox = obj->jubabox;
    if (jubabox != NULL) {
	char *parameter;
	int parameter_len;
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, const_cast<char *>("s"), &parameter, &parameter_len) == FAILURE) {
	    RETURN_NULL();
	}
	if (parameter_len == 0) {
	    RETURN_NULL();
	}
	RETURN_LONG(jubabox->cfgReadLong(parameter));
    }
    RETURN_NULL();
}
PHP_METHOD(JubaBox, logMessage)
{
    JubaBox *jubabox;
    jubabox_object *obj = (jubabox_object *)zend_object_store_get_object(
        getThis() TSRMLS_CC);
    jubabox = obj->jubabox;
    if (jubabox != NULL) {
	char *message;
	int message_len;
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, const_cast<char *>("s"), &message, &message_len) == FAILURE) {
	    RETURN_FALSE;
	}
	if (message_len == 0) {
	    RETURN_FALSE;
	}
	jubabox->logMessage((const char *)message);
    }
    RETURN_TRUE;
}
#if PHP_VERSION_ID < 50399
function_entry jubabox_methods[] = {
#else
zend_function_entry jubabox_methods[] = {
#endif
    PHP_ME(JubaBox,  __construct,     NULL, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
    PHP_ME(JubaBox,  jubabox_api,         NULL, ZEND_ACC_PUBLIC)
    PHP_ME(JubaBox,  cfgReadChar,     NULL, ZEND_ACC_PUBLIC)
    PHP_ME(JubaBox,  cfgReadLong,     NULL, ZEND_ACC_PUBLIC)
    PHP_ME(JubaBox,  logMessage,      NULL, ZEND_ACC_PUBLIC)
    {NULL, NULL, NULL}
};

PHP_MINIT_FUNCTION(jubabox_api)
{
    zend_class_entry ce;
    INIT_NS_CLASS_ENTRY(ce, JUBABOX_API_NS, "JubaBox", jubabox_methods); 
    //INIT_CLASS_ENTRY(ce, "JubaBox", jubabox_methods);
    jubabox_ce = zend_register_internal_class(&ce TSRMLS_CC);
    jubabox_ce->create_object = jubabox_create_handler;
    memcpy(&jubabox_object_handlers,
        zend_get_std_object_handlers(), sizeof(zend_object_handlers));
    jubabox_object_handlers.clone_obj = NULL;
    return SUCCESS;
}

PHP_MINFO_FUNCTION(jubabox_api)
{
    php_info_print_table_start();
    php_info_print_table_row(2, "JUBABOX API support", "enabled");
    php_info_print_table_row(2, "Log File", JUBABOX_LOG);
    php_info_print_table_row(2, "Config File", JUBABOX_CONFIG"jubabox_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, "JUBABOX API Version", PHP_JUBABOX_API_EXTVER);
    php_info_print_table_end();
}

zend_module_entry jubabox_api_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    JUBABOX_API_NS,
    NULL,                  /* Functions */
    PHP_MINIT(jubabox_api),
    NULL,                  /* MSHUTDOWN */
    NULL,                  /* RINIT */
    NULL,                  /* RSHUTDOWN */
    PHP_MINFO(jubabox_api),    /* MINFO */
#if ZEND_MODULE_API_NO >= 20010901
    PHP_JUBABOX_API_EXTVER,
#endif
    STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_JUBABOX_API
extern "C" {
ZEND_GET_MODULE(jubabox_api)
}
#endif
