
#include "php.h"
#include "string.h"
#include "main/php_main.h"
#include "Zend/zend_API.h"
#include "Zend/zend_variables.h"
#include "zend_exceptions.h"
#include "zend_interfaces.h"
#include "zend_object_handlers.h"
#include "ext/pcre/php_pcre.h"
#include "ext/standard/php_string.h"
#include "ext/standard/php_var.h"
#include "ext/standard/php_smart_str.h"
#include "ext/standard/php_array.h"

#include "php_r3.h"
#include "ct_helper.h"
#include "r3_functions.h"
#include "r3_controller.h"
#include "r3_mux.h"

zend_class_entry *ce_r3_mux;

const zend_function_entry mux_methods[] = {
  PHP_ME(Mux, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
  PHP_ME(Mux, __destruct,  NULL, ZEND_ACC_PUBLIC|ZEND_ACC_DTOR) 
  PHP_ME(Mux, getId, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, add, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, any, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, compile, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, sort, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, dispatch, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, length, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, mount, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, appendRoute, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, appendPCRERoute, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, match, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, getRoutes, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, setRoutes, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, getRoute, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, getSubMux, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, getRequestMethodConstant, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, export, NULL, ZEND_ACC_PUBLIC)

  PHP_ME(Mux, get, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, post, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, put, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, delete, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, head, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, patch, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Mux, options, NULL, ZEND_ACC_PUBLIC)

  PHP_ME(Mux, __set_state, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
  PHP_ME(Mux, generate_id, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
  PHP_FE_END
};

void r3_init_mux(TSRMLS_D) {
    zend_class_entry ce;
    INIT_CLASS_ENTRY(ce, "R3\\Mux", mux_methods);
    ce_r3_mux = zend_register_internal_class(&ce TSRMLS_CC);
    zend_declare_property_null(ce_r3_mux, "id", strlen("id"), ZEND_ACC_PUBLIC TSRMLS_CC);
    zend_declare_property_null(ce_r3_mux, "routes", strlen("routes"), ZEND_ACC_PUBLIC TSRMLS_CC);
    zend_declare_property_null(ce_r3_mux, "routesById", strlen("routesById"), ZEND_ACC_PUBLIC TSRMLS_CC);
    zend_declare_property_null(ce_r3_mux, "staticRoutes", strlen("staticRoutes"), ZEND_ACC_PUBLIC TSRMLS_CC);
    zend_declare_property_null(ce_r3_mux, "submux", strlen("submux"), ZEND_ACC_PUBLIC TSRMLS_CC);
    zend_declare_property_bool(ce_r3_mux, "expand", strlen("expand"), 1, ZEND_ACC_PUBLIC TSRMLS_CC);
    zend_declare_property_long(ce_r3_mux, "id_counter", strlen("id_counter"), 0, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC TSRMLS_CC);
}

zend_class_entry ** get_pattern_compiler_ce(TSRMLS_D) {
    zend_class_entry **ce_pattern_compiler = NULL;
    if ( zend_lookup_class( "R3\\PatternCompiler", strlen("R3\\PatternCompiler") , &ce_pattern_compiler TSRMLS_CC) == FAILURE ) {
        php_error(E_ERROR, "Class R3\\PatternCompiler not found.");
        return NULL;
    }
    if ( ce_pattern_compiler == NULL || *ce_pattern_compiler == NULL ) {
        php_error(E_ERROR, "Class R3\\PatternCompiler not found.");
        return NULL;
    }
    return ce_pattern_compiler;
}

// Returns compiled route zval
zval * compile_route_pattern(zval *z_pattern, zval *z_options, zend_class_entry **ce_pattern_compiler TSRMLS_DC)
{
    // zend_class_entry **ce_pattern_compiler;
    if ( ce_pattern_compiler == NULL ) {
        ce_pattern_compiler = get_pattern_compiler_ce(TSRMLS_C);
        if ( ce_pattern_compiler == NULL ) {
            return NULL;
        }
    }

    zval *z_compiled_route = NULL; // will be an array
    zend_call_method( NULL, *ce_pattern_compiler, NULL, "compile", strlen("compile"), &z_compiled_route, 2, z_pattern, z_options TSRMLS_CC );

    if ( z_compiled_route == NULL ) {
        return NULL;
    } else if ( Z_TYPE_P(z_compiled_route) == IS_NULL ) {
        zval_ptr_dtor(&z_compiled_route);
        return NULL;
    }
    if ( Z_TYPE_P(z_compiled_route) != IS_ARRAY ) {
        zval_ptr_dtor(&z_compiled_route);
        return NULL;
    }
    return z_compiled_route;
}

static void var_export(zval *return_value, zval *what TSRMLS_DC)
{
    smart_str buf = {0};
    php_var_export_ex(&what, 0, &buf TSRMLS_CC);
    smart_str_0 (&buf);
    ZVAL_STRINGL(return_value, buf.c, buf.len, 0);
}

PHP_METHOD(Mux, __construct) {
    zval *z_routes = NULL, *z_routes_by_id , *z_submux = NULL, *z_static_routes = NULL;

    MAKE_STD_ZVAL(z_routes);
    MAKE_STD_ZVAL(z_routes_by_id);
    MAKE_STD_ZVAL(z_static_routes);
    MAKE_STD_ZVAL(z_submux);

    array_init(z_routes);
    array_init(z_routes_by_id);
    array_init(z_static_routes);
    array_init(z_submux);

    zend_update_property(ce_r3_mux, this_ptr, "routes", sizeof("routes")-1, z_routes TSRMLS_CC);
    zend_update_property(ce_r3_mux, this_ptr, "routesById", sizeof("routesById")-1, z_routes_by_id TSRMLS_CC);
    zend_update_property(ce_r3_mux, this_ptr, "staticRoutes", sizeof("staticRoutes")-1, z_static_routes TSRMLS_CC);
    zend_update_property(ce_r3_mux, this_ptr, "submux", sizeof("submux")-1, z_submux TSRMLS_CC);
}

PHP_METHOD(Mux, __destruct) {
    zval * val;
    val = zend_read_property(ce_r3_mux, getThis(), "routes", sizeof("routes")-1, 1 TSRMLS_CC);
    zval_ptr_dtor(&val);

    val = zend_read_property(ce_r3_mux, getThis(), "routesById", sizeof("routesById")-1, 1 TSRMLS_CC);
    zval_ptr_dtor(&val);

    val = zend_read_property(ce_r3_mux, getThis(), "staticRoutes", sizeof("staticRoutes")-1, 1 TSRMLS_CC);
    zval_ptr_dtor(&val);

    val = zend_read_property(ce_r3_mux, getThis(), "submux", sizeof("submux")-1, 1 TSRMLS_CC);
    zval_ptr_dtor(&val);
}

PHP_METHOD(Mux, generate_id) {
    zval * z_counter = NULL;
    long counter = 0;
    z_counter = zend_read_static_property(ce_r3_mux, "id_counter", strlen("id_counter") , 0 TSRMLS_CC);
    if ( z_counter != NULL ) {
        counter = Z_LVAL_P(z_counter);
    }
    counter++;
    Z_LVAL_P(z_counter) = counter;
    RETURN_LONG(counter);
}

PHP_METHOD(Mux, __set_state) {
    zval *z_array;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &z_array) == FAILURE) {
        RETURN_FALSE;
    }

    object_init_ex(return_value, ce_r3_mux);
    // XXX: put this back if we have problem
    // CALL_METHOD(Mux, __construct, return_value, return_value);

    zval **z_id = NULL;
    zval **z_routes = NULL;
    zval **z_static_routes = NULL;
    zval **z_routes_by_id = NULL;
    zval **z_submux = NULL;
    zval **z_expand = NULL;

    if ( zend_hash_quick_find(Z_ARRVAL_P(z_array), "id", sizeof("id"), zend_inline_hash_func(ZEND_STRS("id")), (void**)&z_id) == SUCCESS ) {
        zend_update_property(ce_r3_mux, return_value, "id", sizeof("id")-1, *z_id TSRMLS_CC);
    }


    if ( zend_hash_quick_find(Z_ARRVAL_P(z_array), "routes", sizeof("routes"), zend_inline_hash_func(ZEND_STRS("routes")), (void**)&z_routes) == SUCCESS ) {
        Z_ADDREF_PP(z_routes);
        zend_update_property(ce_r3_mux, return_value, "routes", sizeof("routes")-1, *z_routes TSRMLS_CC);
    }

    if ( zend_hash_quick_find(Z_ARRVAL_P(z_array), "staticRoutes", sizeof("staticRoutes"), zend_inline_hash_func(ZEND_STRS("staticRoutes")), (void**)&z_static_routes) == SUCCESS ) {
        Z_ADDREF_PP(z_static_routes);
        zend_update_property(ce_r3_mux, return_value, "staticRoutes", sizeof("staticRoutes")-1, *z_static_routes TSRMLS_CC);
    }

    if ( zend_hash_quick_find(Z_ARRVAL_P(z_array), "routesById", sizeof("routesById"), zend_inline_hash_func(ZEND_STRS("routesById")), (void**)&z_routes_by_id) == SUCCESS ) {
        Z_ADDREF_PP(z_routes_by_id);
        zend_update_property(ce_r3_mux, return_value, "routesById", sizeof("routesById")-1, *z_routes_by_id TSRMLS_CC);
    }

    if ( zend_hash_quick_find(Z_ARRVAL_P(z_array), "submux", sizeof("submux"), zend_inline_hash_func(ZEND_STRS("submux")), (void**)&z_submux) == SUCCESS ) {
        Z_ADDREF_PP(z_submux);
        zend_update_property(ce_r3_mux, return_value, "submux", sizeof("submux")-1, *z_submux TSRMLS_CC);
    }

    if ( zend_hash_quick_find(Z_ARRVAL_P(z_array), "expand", sizeof("expand"), zend_inline_hash_func(ZEND_STRS("expand")), (void**)&z_expand) == SUCCESS ) {
        zend_update_property(ce_r3_mux, return_value, "expand", sizeof("expand")-1, *z_expand TSRMLS_CC);
    }
}


/**
 * get_mux_function_entry("method", sizeof("method"), zend_inline_hash_func(ZEND_STRS("pattern")));
 */
inline zend_function * get_mux_function_entry(char * method_name, int method_name_len, ulong h) {
    zend_function *fe;
    if ( zend_hash_quick_find( &ce_r3_mux->function_table, method_name, method_name_len, h, (void **) &fe) == SUCCESS ) {
        return fe;
    }
    php_error(E_ERROR, "%s method not found", method_name);
    return NULL;
}

inline zval * call_mux_method(zval * object , char * method_name , int method_name_len, int param_count, zval* arg1, zval* arg2, zval* arg3 TSRMLS_DC)
{
    zend_function *fe;
    if ( zend_hash_find( &ce_r3_mux->function_table, method_name, method_name_len, (void **) &fe) == FAILURE ) {
        php_error(E_ERROR, "%s method not found", method_name);
    }
    // call export method
    zval *z_retval = NULL;
    zend_call_method_with_3_params( &object, ce_r3_mux, &fe, method_name, method_name_len, &z_retval, param_count, arg1, arg2, arg3 TSRMLS_CC );
    return z_retval;
}


PHP_METHOD(Mux, get) {
    zval *z_pattern = NULL, *z_callback = NULL, *z_options = NULL;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz|a", &z_pattern, &z_callback, &z_options) == FAILURE) {
        RETURN_FALSE;
    }
    if ( z_options == NULL ) {
        MAKE_STD_ZVAL(z_options);
        array_init_size(z_options, 1);
    } else if ( Z_TYPE_P(z_options) == IS_NULL ) {
        array_init_size(z_options, 1);
    }

    // $options['method'] = REQ_METHOD_GET;
    add_assoc_long(z_options, "method", REQ_METHOD_GET);

    // $this->add($pattern, $callback, $options);
    zval * z_retval = call_mux_method( this_ptr, "add" , sizeof("add"), 3 , z_pattern, z_callback, z_options TSRMLS_CC);
    if ( z_retval ) {
        *return_value = *z_retval;
        zval_copy_ctor(return_value);
    }
    // zval_ptr_dtor(&z_retval);
}





PHP_METHOD(Mux, put) {
    zval *z_pattern = NULL, *z_callback = NULL, *z_options = NULL;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz|a", &z_pattern, &z_callback, &z_options) == FAILURE) {
        RETURN_FALSE;
    }
    if ( z_options == NULL ) {
        MAKE_STD_ZVAL(z_options);
        array_init_size(z_options, 1);
    } else if ( Z_TYPE_P(z_options) == IS_NULL ) {
        array_init_size(z_options, 1);
    }

    add_assoc_long(z_options, "method", REQ_METHOD_PUT);

    // $this->add($pattern, $callback, $options);
    zval * z_retval = call_mux_method( getThis(), "add" , sizeof("add"), 3 , z_pattern, z_callback, z_options TSRMLS_CC);
    if ( z_retval ) {
        *return_value = *z_retval;
        zval_copy_ctor(return_value);
    }
    // zval_ptr_dtor(&z_retval);
}

PHP_METHOD(Mux, delete) {
    zval *z_pattern = NULL, *z_callback = NULL, *z_options = NULL;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz|a", &z_pattern, &z_callback, &z_options) == FAILURE) {
        RETURN_FALSE;
    }
    if ( z_options == NULL ) {
        MAKE_STD_ZVAL(z_options);
        array_init_size(z_options, 1);
    } else if ( Z_TYPE_P(z_options) == IS_NULL ) {
        array_init_size(z_options, 1);
    }

    add_assoc_long(z_options, "method", REQ_METHOD_DELETE);

    // $this->add($pattern, $callback, $options);
    zval * z_retval = call_mux_method( getThis(), "add" , sizeof("add"), 3 , z_pattern, z_callback, z_options TSRMLS_CC);
    if ( z_retval ) {
        *return_value = *z_retval;
        zval_copy_ctor(return_value);
    }
    // zval_ptr_dtor(&z_retval);
}


PHP_METHOD(Mux, post) {
    zval *z_pattern = NULL, *z_callback = NULL, *z_options = NULL;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz|a", &z_pattern, &z_callback, &z_options) == FAILURE) {
        RETURN_FALSE;
    }
    if ( z_options == NULL ) {
        MAKE_STD_ZVAL(z_options);
        array_init_size(z_options, 1);
    } else if ( Z_TYPE_P(z_options) == IS_NULL ) {
        array_init_size(z_options, 1);
    }

    add_assoc_long(z_options, "method", REQ_METHOD_POST);

    // $this->add($pattern, $callback, $options);
    zval * z_retval = call_mux_method( getThis(), "add" , sizeof("add"), 3 , z_pattern, z_callback, z_options TSRMLS_CC);
    if ( z_retval ) {
        *return_value = *z_retval;
        zval_copy_ctor(return_value);
    }
}


PHP_METHOD(Mux, patch) {
    zval *z_pattern = NULL, *z_callback = NULL, *z_options = NULL;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz|a", &z_pattern, &z_callback, &z_options) == FAILURE) {
        RETURN_FALSE;
    }
    if ( z_options == NULL ) {
        MAKE_STD_ZVAL(z_options);
        array_init_size(z_options, 1);
    } else if ( Z_TYPE_P(z_options) == IS_NULL ) {
        array_init_size(z_options, 1);
    }

    add_assoc_long(z_options, "method", REQ_METHOD_PATCH);

    // $this->add($pattern, $callback, $options);
    zval * z_retval = call_mux_method( getThis(), "add" , sizeof("add"), 3 , z_pattern, z_callback, z_options TSRMLS_CC);
    if ( z_retval ) {
        *return_value = *z_retval;
        zval_copy_ctor(return_value);
    }
}

PHP_METHOD(Mux, head) {
    zval *z_pattern = NULL, *z_callback = NULL, *z_options = NULL;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz|a", &z_pattern, &z_callback, &z_options) == FAILURE) {
        RETURN_FALSE;
    }
    if ( z_options == NULL ) {
        MAKE_STD_ZVAL(z_options);
        array_init_size(z_options, 1);
    } else if ( Z_TYPE_P(z_options) == IS_NULL ) {
        array_init_size(z_options, 1);
    }

    add_assoc_long(z_options, "method", REQ_METHOD_HEAD);

    // $this->add($pattern, $callback, $options);
    zval * z_retval = call_mux_method( getThis(), "add" , sizeof("add"), 3 , z_pattern, z_callback, z_options TSRMLS_CC);
    if ( z_retval ) {
        *return_value = *z_retval;
        zval_copy_ctor(return_value);
    }
}

PHP_METHOD(Mux, options) {
    zval *z_pattern = NULL, *z_callback = NULL, *z_options = NULL;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz|a", &z_pattern, &z_callback, &z_options) == FAILURE) {
        RETURN_FALSE;
    }
    if ( z_options == NULL ) {
        MAKE_STD_ZVAL(z_options);
        array_init_size(z_options, 1);
    } else if ( Z_TYPE_P(z_options) == IS_NULL ) {
        array_init_size(z_options, 1);
    }

    add_assoc_long(z_options, "method", REQ_METHOD_OPTIONS);

    // $this->add($pattern, $callback, $options);
    zval * z_retval = call_mux_method( getThis(), "add" , sizeof("add"), 3 , z_pattern, z_callback, z_options TSRMLS_CC);
    if ( z_retval ) {
        *return_value = *z_retval;
        zval_copy_ctor(return_value);
    }
}


PHP_METHOD(Mux, mount) {
    char *pattern;
    int  pattern_len;

    zval *z_mux = NULL;
    zval *z_options = NULL;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|a", &pattern, &pattern_len, &z_mux, &z_options) == FAILURE) {
        RETURN_FALSE;
    }
    if ( Z_TYPE_P(z_mux) == IS_NULL ) {
        RETURN_FALSE;
    }

    if ( Z_TYPE_P(z_mux) == IS_OBJECT ) {
        if ( instanceof_function( Z_OBJCE_P(z_mux), ce_r3_controller TSRMLS_CC) ) {
            zval *rv;
            zend_call_method(&z_mux, ce_r3_controller, NULL, "expand", strlen("expand"), &rv, 0, NULL, NULL TSRMLS_CC );
            z_mux = rv;
        }
    }


    Z_ADDREF_P(z_mux);

    if ( z_options == NULL ) {
        MAKE_STD_ZVAL(z_options);
        array_init(z_options);
    } else if ( Z_TYPE_P(z_options) == IS_NULL ) {
        array_init(z_options);
    }


    zend_class_entry **ce_pattern_compiler = get_pattern_compiler_ce(TSRMLS_C);
    if ( ce_pattern_compiler == NULL ) {
        RETURN_FALSE;
    }

    zval *z_routes;
    zval *z_expand;
    zval *z_mux_routes;

    z_routes = zend_read_property( ce_r3_mux, getThis(), "routes", sizeof("routes")-1, 1 TSRMLS_CC);
    z_expand = zend_read_property( ce_r3_mux, getThis(), "expand", sizeof("expand")-1, 1 TSRMLS_CC);


    // TODO: merge routesById and staticRoutes properties


    if ( Z_BVAL_P(z_expand) ) {
        // fetch routes from $mux
        //
        z_mux_routes = zend_read_property( ce_r3_mux, z_mux, "routes", sizeof("routes")-1, 1 TSRMLS_CC);

        HashPosition route_pointer;
        HashTable    *mux_routes_hash;
        mux_routes_hash = Z_ARRVAL_P(z_mux_routes);
        zval **z_mux_route;


        // iterate mux
        for(zend_hash_internal_pointer_reset_ex(mux_routes_hash, &route_pointer); 
                zend_hash_get_current_data_ex(mux_routes_hash, (void**) &z_mux_route, &route_pointer) == SUCCESS; 
                zend_hash_move_forward_ex(mux_routes_hash, &route_pointer)) 
        {
            // zval for new route
            zval *z_new_routes;

            // zval for route item
            zval **z_is_pcre; // route[0]
            zval **z_route_pattern;
            zval **z_route_callback;
            zval **z_route_options;
            zval **z_route_original_pattern; // for PCRE pattern

            if ( zend_hash_index_find( Z_ARRVAL_PP(z_mux_route), 0, (void**) &z_is_pcre) == FAILURE ) {
                continue;
            }
            if ( zend_hash_index_find( Z_ARRVAL_PP(z_mux_route), 1, (void**) &z_route_pattern) == FAILURE ) {
                continue;
            }
            if ( zend_hash_index_find( Z_ARRVAL_PP(z_mux_route), 2, (void**) &z_route_callback) == FAILURE ) {
                continue;
            }
            if ( zend_hash_index_find( Z_ARRVAL_PP(z_mux_route), 3, (void**) &z_route_options) == FAILURE ) {
                continue;
            }

            // Z_ADDREF_P(z_route_callback);
            // Z_ADDREF_P(z_route_options); // reference it so it will not be recycled.
            MAKE_STD_ZVAL(z_new_routes);
            array_init(z_new_routes);

            if ( Z_BVAL_PP(z_is_pcre) ) {
                // $newPattern = $pattern . $route[3]['pattern'];

                if ( zend_hash_quick_find( Z_ARRVAL_PP(z_route_options), "pattern", sizeof("pattern"), zend_inline_hash_func(ZEND_STRS("pattern")), (void**) &z_route_original_pattern) == FAILURE ) {
                    php_error( E_ERROR, "Can not compile pattern, original pattern not found");
                }

                char new_pattern[120] = { 0 };
                int  new_pattern_len;
                strncat( new_pattern, pattern , pattern_len );
                strncat( new_pattern, Z_STRVAL_PP(z_route_original_pattern) , Z_STRLEN_PP(z_route_original_pattern) );

                new_pattern_len = pattern_len + Z_STRLEN_PP(z_route_original_pattern);

                zval *z_new_pattern = NULL;
                MAKE_STD_ZVAL(z_new_pattern);
                ZVAL_STRINGL(z_new_pattern, new_pattern, new_pattern_len, 1);

                // TODO: merge options

                // $routeArgs = PatternCompiler::compile($newPattern, 
                //     array_merge_recursive($route[3], $options) );
                zval *z_compiled_route = compile_route_pattern(z_new_pattern, *z_route_options, ce_pattern_compiler TSRMLS_CC);


                if ( z_compiled_route == NULL || Z_TYPE_P(z_compiled_route) == IS_NULL ) {
                    php_error( E_ERROR, "Cannot compile pattern: %s", new_pattern);
                }


                zval **z_compiled_route_pattern;
                if ( zend_hash_quick_find( Z_ARRVAL_P(z_compiled_route) , "compiled", sizeof("compiled"), zend_inline_hash_func(ZEND_STRS("compiled")), (void**)&z_compiled_route_pattern) == FAILURE ) {
                    php_error( E_ERROR, "compiled pattern not found: %s", new_pattern);
                }

                zend_hash_quick_update( Z_ARRVAL_P(z_compiled_route), "pattern", sizeof("pattern"), zend_inline_hash_func(ZEND_STRS("pattern")), &z_new_pattern, sizeof(zval *), NULL);


                Z_ADDREF_PP(z_compiled_route_pattern);
                Z_ADDREF_PP(z_route_callback);
                Z_ADDREF_P(z_compiled_route);
                Z_ADDREF_P(z_new_routes);

                // create new route and append to mux->routes
                add_index_bool(z_new_routes, 0 , 1); // pcre flag == false
                add_index_zval(z_new_routes, 1, *z_compiled_route_pattern);
                add_index_zval(z_new_routes, 2 , *z_route_callback);
                add_index_zval(z_new_routes, 3, z_compiled_route);
                add_next_index_zval(z_routes, z_new_routes);

            } else {


                //  $this->routes[] = array(
                //      false,
                //      $pattern . $route[1],
                //      $route[2],
                //      $options,
                //  );
                char new_pattern[120] = { 0 };
                strncat(new_pattern, pattern, pattern_len);

                strncat(new_pattern, Z_STRVAL_PP(z_route_pattern), Z_STRLEN_PP(z_route_pattern) );

                int new_pattern_len = pattern_len + Z_STRLEN_PP(z_route_pattern);

                // Merge the mount options with the route options
                zval *z_new_route_options;
                MAKE_STD_ZVAL(z_new_route_options);
                array_init(z_new_route_options);
                php_array_merge(Z_ARRVAL_P(z_new_route_options), Z_ARRVAL_P(z_options), 0 TSRMLS_CC);
                php_array_merge(Z_ARRVAL_P(z_new_route_options), Z_ARRVAL_P(*z_route_options), 0 TSRMLS_CC);

                Z_ADDREF_PP(z_route_callback);
                Z_ADDREF_P(z_new_route_options);
                Z_ADDREF_P(z_new_routes);

                /* make the array: [ pcreFlag, pattern, callback, options ] */
                add_index_bool(z_new_routes, 0 , 0); // pcre flag == false
                add_index_stringl(z_new_routes, 1 , new_pattern , new_pattern_len, 1);
                add_index_zval( z_new_routes, 2 , *z_route_callback);
                add_index_zval( z_new_routes, 3 , z_new_route_options);
                add_next_index_zval(z_routes, z_new_routes);
            }
        }

    } else {
        zend_function *fe_getid = NULL; // method entry
        zend_function *fe_add   = NULL; // method entry

        if ( zend_hash_quick_find(&ce_r3_mux->function_table, "getid", sizeof("getid"), zend_inline_hash_func(ZEND_STRS("getid")), (void **) &fe_getid) == FAILURE ) {
            php_error(E_ERROR, "Cannot call method Mux::getid()");
            RETURN_FALSE;
        }
        if ( zend_hash_quick_find(&ce_r3_mux->function_table, "add", sizeof("add"), zend_inline_hash_func(ZEND_STRS("add")), (void **) &fe_add) == FAILURE ) {
            php_error(E_ERROR, "Cannot call method Mux::add()");
            RETURN_FALSE;
        }

        // $muxId = $mux->getId();
        // $this->add($pattern, $muxId, $options);
        // $this->submux[ $muxId ] = $mux;
        // zval *z_mux_id = call_mux_method(z_mux, "getid", sizeof("getid") ,  );

        zval *z_mux_id = NULL;
        zend_call_method( &z_mux, ce_r3_mux, &fe_getid, "getid", strlen("getid"), &z_mux_id, 0, NULL, NULL TSRMLS_CC );

        if ( z_mux_id == NULL || Z_TYPE_P(z_mux_id) == IS_NULL ) {
            php_error(E_ERROR, "Mux id is required. got NULL.");
        }

        // create pattern
        zval *z_pattern = NULL;
        MAKE_STD_ZVAL(z_pattern);
        ZVAL_STRINGL(z_pattern, pattern, pattern_len, 1); // duplicate

        zval *z_retval = NULL;
        zend_call_method_with_3_params( &this_ptr, ce_r3_mux, &fe_add, "add",
                strlen("add"), &z_retval, 3, z_pattern, z_mux_id, z_options TSRMLS_CC);


        zval *z_submux_array = zend_read_property( ce_r3_mux, this_ptr , "submux", sizeof("submux") - 1, 1 TSRMLS_CC);
        add_index_zval(z_submux_array, Z_LVAL_P(z_mux_id) , z_mux);

        // release zvals
        zval_ptr_dtor(&z_mux_id);
        // zval_ptr_dtor(&z_pattern);
    }
}

PHP_METHOD(Mux, getSubMux) {
    long submux_id = 0;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &submux_id ) == FAILURE) {
        RETURN_FALSE;
    }

    zval *z_submux_array;
    zval **z_submux;
    z_submux_array = zend_read_property( Z_OBJCE_P(this_ptr), this_ptr, "submux", sizeof("submux")-1, 1 TSRMLS_CC);

    if ( zend_hash_index_find( Z_ARRVAL_P(z_submux_array),  submux_id , (void**) &z_submux) == SUCCESS ) {
        *return_value = **z_submux;
        zval_copy_ctor(return_value);
        return;
    }
    RETURN_FALSE;
}


PHP_METHOD(Mux, getRequestMethodConstant) {
    char *req_method        = NULL, *mthit, *mthp;
    long req_method_const   = 0;
    long req_method_len;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &req_method, &req_method_len) != FAILURE) {
        mthit = mthp = estrndup(req_method, strlen(req_method));
        while(( *mthit = toupper(*mthit))) mthit++;

        if (strcmp(mthp, "GET") == 0) {
            req_method_const = REQ_METHOD_GET;
        } else if (strcmp(mthp, "POST") == 0) {
            req_method_const = REQ_METHOD_POST;
        } else if (strcmp(mthp, "PUT") == 0) {
            req_method_const = REQ_METHOD_PUT;
        } else if (strcmp(mthp, "DELETE") == 0) {
            req_method_const = REQ_METHOD_DELETE;
        } else if (strcmp(mthp, "HEAD") == 0) {
            req_method_const = REQ_METHOD_HEAD;
        } else if (strcmp(mthp, "OPTIONS") == 0) {
            req_method_const = REQ_METHOD_OPTIONS;
        } else if (strcmp(mthp, "PATCH") == 0) {
            req_method_const = REQ_METHOD_PATCH;
        }

        efree(req_method);
    }

    RETURN_LONG(req_method_const);
}

PHP_METHOD(Mux, getRoute) {
    char * route_id = NULL;
    int    route_id_len = 0;

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

    zval *z_routes_by_id = NULL;
    zval **z_route = NULL;
    z_routes_by_id = zend_read_property( ce_r3_mux , this_ptr, "routesById", sizeof("routesById")-1, 1 TSRMLS_CC);

    // php_var_dump(&z_routes_by_id, 1 TSRMLS_CC);
    if ( zend_hash_find( Z_ARRVAL_P(z_routes_by_id) , route_id, route_id_len + 1, (void**) &z_route ) == SUCCESS ) {
        *return_value = **z_route;
        zval_copy_ctor(return_value);
        return;
    }
    RETURN_NULL();
}

PHP_METHOD(Mux, setRoutes) {
    zval * routes;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &routes ) == FAILURE) {
        RETURN_FALSE;
    }
    zend_update_property(ce_r3_mux , this_ptr, "routes", sizeof("routes")-1, routes TSRMLS_CC);
    RETURN_TRUE;
}

PHP_METHOD(Mux, getRoutes) {
    zval *z_routes;
    z_routes = zend_read_property(ce_r3_mux, this_ptr, "routes", sizeof("routes")-1, 1 TSRMLS_CC);
    *return_value = *z_routes;
    zval_copy_ctor(return_value);
}

PHP_METHOD(Mux, export) {
    smart_str buf = {0};
    php_var_export_ex(&this_ptr, 0, &buf TSRMLS_CC);
    smart_str_0 (&buf);
    RETVAL_STRINGL(buf.c, buf.len, 0);
}

PHP_METHOD(Mux, getId) {
    zval *z_id;
    long counter = 0;

    z_id = zend_read_property(ce_r3_mux, getThis(), "id", sizeof("id")-1, 1 TSRMLS_CC);

    if ( z_id != NULL && Z_TYPE_P(z_id) != IS_NULL ) {
        RETURN_LONG( Z_LVAL_P(z_id) );
    }

    zval *rv = NULL;
    zend_call_method( NULL, ce_r3_mux, NULL, "generate_id", strlen("generate_id"), &rv, 0, NULL, NULL TSRMLS_CC );

    if ( rv ) {
        counter = Z_LVAL_P(rv);
        zend_update_property_long(ce_r3_mux, this_ptr, "id" , sizeof("id") - 1, counter TSRMLS_CC);
        zval_ptr_dtor(&rv);
    }
    RETURN_LONG(counter);
}

PHP_METHOD(Mux, length) {
    zval *z_routes;
    z_routes = zend_read_property(ce_r3_mux, this_ptr, "routes", sizeof("routes")-1, 1 TSRMLS_CC);

    long length = zend_hash_num_elements( Z_ARRVAL_P(z_routes) );

    RETURN_LONG(length);
}

PHP_METHOD(Mux, sort) {
    zval *z_routes;
    z_routes = zend_read_property(Z_OBJCE_P(this_ptr) , this_ptr, "routes", sizeof("routes")-1, 1 TSRMLS_CC);

    zval *retval_ptr = NULL;

    zval *z_sort_callback = NULL;
    MAKE_STD_ZVAL(z_sort_callback);
    ZVAL_STRING( z_sort_callback, "r3_sort_routes" , 1 );

    Z_SET_ISREF_P(z_routes);
    zend_call_method( NULL, NULL, NULL, "usort", strlen("usort"), &retval_ptr, 2, 
            z_routes, z_sort_callback TSRMLS_CC );
    zval_ptr_dtor(&z_sort_callback);
    if (retval_ptr) {
        zval_ptr_dtor(&retval_ptr);
    }
}

PHP_METHOD(Mux, compile) {
    char *filename;
    int  filename_len;
    zend_bool sort_before_compile = 1;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|b", &filename, &filename_len, &sort_before_compile) == FAILURE) {
        RETURN_FALSE;
    }

    if (sort_before_compile) {
        zval *z_routes;
        z_routes = zend_read_property(ce_r3_mux, this_ptr, "routes", sizeof("routes")-1, 1 TSRMLS_CC);

        Z_SET_ISREF_P(z_routes);
        // duplicated code to sort method
        zval *rv = NULL;
        zval *z_sort_callback = NULL;
        MAKE_STD_ZVAL(z_sort_callback);
        ZVAL_STRING( z_sort_callback, "r3_sort_routes" , 1 );

        zend_call_method( NULL, NULL, NULL, "usort", strlen("usort"), &rv, 2, 
                z_routes, z_sort_callback TSRMLS_CC );
        zval_ptr_dtor(&z_sort_callback); // recycle sort callback zval
        // php_error(E_ERROR,"route sort failed.");
        // zend_update_property(ce_r3_mux, getThis(), "routes", sizeof("routes")-1, z_routes TSRMLS_CC);
    }

    // $code = '<?php return ' . $this->export() . ';';

    // get export method function entry
    zend_function *fe_export;
    if ( zend_hash_quick_find( &Z_OBJCE_P(this_ptr)->function_table, "export", sizeof("export"), zend_inline_hash_func(ZEND_STRS("export")),  (void **) &fe_export) == FAILURE ) {
        php_error(E_ERROR, "export method not found");
    }

    // call export method
    zval *compiled_code = NULL;
    zend_call_method( &this_ptr, Z_OBJCE_P(this_ptr) , &fe_export, "export", strlen("export"), &compiled_code, 0, NULL, NULL TSRMLS_CC );

    if ( compiled_code == NULL || Z_TYPE_P(compiled_code) == IS_NULL ) {
        php_error(E_ERROR, "Cannot compile routes.");
    }


    int  buf_len = Z_STRLEN_P(compiled_code) + strlen("<?php return ;") + 1;
    char *buf = (char* ) ecalloc(buf_len, sizeof(char));
    strncat(buf, "<?php return ", strlen("<?php return ") );
    strncat(buf, Z_STRVAL_P(compiled_code), Z_STRLEN_P(compiled_code));
    strncat(buf, ";", 1);
    *(buf + buf_len - 1) = '\0';


    // memcpytrncat(buf, "\0", 1);
    zval *z_code = NULL;
    zval *z_filename = NULL;
    zval *retval = NULL;
    MAKE_STD_ZVAL(z_code);
    MAKE_STD_ZVAL(z_filename);
    ZVAL_STRING(z_code, buf, 1);
    // CHECK_ZVAL_STRING(z_code);
    ZVAL_STRINGL(z_filename, filename, filename_len, 1);

    zend_call_method( NULL, NULL, NULL, "file_put_contents", strlen("file_put_contents"), &retval, 2, z_filename, z_code TSRMLS_CC );
    zval_ptr_dtor(&z_filename);
    zval_ptr_dtor(&z_code);
    zval_ptr_dtor(&compiled_code);

    if (retval) {
        *return_value = *retval;
        zval_copy_ctor(return_value);
    }
}

PHP_METHOD(Mux, dispatch) {
    char *path;
    int  path_len;
    zval *z_path;

    zval *z_return_route = NULL;

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

    MAKE_STD_ZVAL(z_path);
    ZVAL_STRINGL(z_path, path, path_len, 1);

    zend_function *fe; // method entry
    zend_hash_quick_find( &ce_r3_mux->function_table, "match",    sizeof("match"), zend_inline_hash_func(ZEND_STRS("match")),    (void **) &fe);
    zend_call_method( &this_ptr, ce_r3_mux, &fe, "match", strlen("match"), &z_return_route, 1, z_path, NULL TSRMLS_CC );

    if ( ! z_return_route || Z_TYPE_P(z_return_route) == IS_NULL ) {
        zval_ptr_dtor(&z_path);
        RETURN_NULL();
    }


    // read data from matched route
    zval **z_pcre;
    zval **z_pattern;
    zval **z_callback;
    zval **z_options;

    zend_hash_index_find( Z_ARRVAL_P(z_return_route) , 0 , (void**) &z_pcre );
    zend_hash_index_find( Z_ARRVAL_P(z_return_route) , 1 , (void**) &z_pattern );
    zend_hash_index_find( Z_ARRVAL_P(z_return_route) , 2 , (void**) &z_callback );
    zend_hash_index_find( Z_ARRVAL_P(z_return_route) , 3 , (void**) &z_options );

    zval *z_submux_array = NULL;
    zval **z_submux = NULL;

    zval *z_retval = NULL;

    // dispatch to submux if the callback is an ID.
    if ( Z_TYPE_PP(z_callback) == IS_LONG ) {
        z_submux_array = zend_read_property( ce_r3_mux, this_ptr, "submux", sizeof("submux")-1, 1 TSRMLS_CC);

        if ( z_submux_array == NULL ) {
            zend_throw_exception(ce_r3_exception, "submux property is null", 0 TSRMLS_CC);
            return;
        }

        if ( zend_hash_index_find( Z_ARRVAL_P(z_submux_array),  Z_LVAL_PP(z_callback) , (void**) &z_submux) == FAILURE ) {
            zend_throw_exception(ce_r3_exception, "submux not found", 0 TSRMLS_CC);
            return;
        }


        if ( z_submux == NULL || *z_submux == NULL ) {
            zend_throw_exception(ce_r3_exception, "submux not found", 0 TSRMLS_CC);
            return;
        }

        // php_var_dump(z_submux, 1 TSRMLS_CC);

        //  $matchedString = $route[3]['vars'][0];
        //  return $submux->dispatch(substr($path, strlen($matchedString))
        if ( Z_BVAL_PP(z_pcre) ) {
            zval **z_route_vars = NULL;
            zval **z_route_vars_0 = NULL;
            zval *z_substr;

            if ( zend_hash_quick_find( Z_ARRVAL_PP(z_options) , "vars", sizeof("vars"), zend_inline_hash_func(ZEND_STRS("vars")), (void**) &z_route_vars ) == FAILURE ) {
                php_error(E_ERROR, "require route vars");
                RETURN_FALSE;
            }
            if ( zend_hash_index_find( Z_ARRVAL_PP(z_options) , 0 , (void**) &z_route_vars_0 ) == FAILURE ) {
                php_error(E_ERROR, "require route vars[0]");
                RETURN_FALSE;
            }

            MAKE_STD_ZVAL(z_substr);
            ZVAL_STRING(z_substr, path + Z_STRLEN_PP(z_route_vars_0), 1);

            z_retval = call_mux_method( *z_submux, "dispatch" , sizeof("dispatch"), 1 , z_substr, NULL, NULL TSRMLS_CC);
            zval_ptr_dtor(&z_substr);

            if (z_retval) {
                Z_ADDREF_P(z_retval);
                *return_value = *z_retval;
                zval_copy_ctor(return_value);
            }
            // zval_ptr_dtor(&z_return_route);
            RETURN_FALSE;
            return;

        } else {
            zval *z_substr;

            MAKE_STD_ZVAL(z_substr);
            ZVAL_STRING(z_substr, path + Z_STRLEN_PP(z_pattern), 1);

            //     return $submux->dispatch(
            //         substr($path, strlen($route[1]))
            //     );

            z_retval = call_mux_method( *z_submux, "dispatch" , sizeof("dispatch"), 1 , z_substr, NULL, NULL TSRMLS_CC);
            zval_ptr_dtor(&z_substr);

            if ( z_retval ) {
                Z_ADDREF_P(z_retval);
                *return_value = *z_retval;
                zval_copy_ctor(return_value);
            }
            // zval_ptr_dtor(&z_return_route);
            return;

        }
    }

    if ( z_return_route ) {
        *return_value = *z_return_route;
        zval_copy_ctor(return_value);
    }
    zval_ptr_dtor(&z_path);
    zval_ptr_dtor(&z_return_route);
    return;
}

PHP_METHOD(Mux, match) {
    char *path;
    int  path_len;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &path, &path_len) == FAILURE) {
        RETURN_FALSE;
    }

    zval **z_route_pp = NULL;
    zval *z_route = NULL;
    if ( zend_hash_find( Z_ARRVAL_P( zend_read_property(ce_r3_mux, this_ptr, "staticRoutes", sizeof("staticRoutes") - 1, 1 TSRMLS_CC) ), path, path_len, (void**)&z_route_pp) == SUCCESS ) {
        if ( Z_TYPE_PP(z_route_pp) != IS_NULL ) {
            *return_value = **z_route_pp;
            Z_ADDREF_PP(z_route_pp);
            zval_copy_ctor(return_value);
            return;
        }
    }
    z_route = php_r3_match(zend_read_property(ce_r3_mux , this_ptr , "routes", sizeof("routes")-1, 1 TSRMLS_CC), path, path_len TSRMLS_CC);
    if ( z_route != NULL ) {
        *return_value = *z_route;
        zval_copy_ctor(return_value);
        Z_ADDREF_P(z_route);
        return;
    }
    RETURN_NULL();
}

PHP_METHOD(Mux, appendRoute) {
    char *pattern;
    int  pattern_len;
    zval *z_callback;
    zval *z_options;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa|a", &pattern, &pattern_len, &z_callback, &z_options) == FAILURE) {
        RETURN_FALSE;
    }

    zval *z_routes;
    zval *z_new_routes;

    if ( z_options == NULL ) {
        MAKE_STD_ZVAL(z_options);
        array_init(z_options);
    }
    if ( Z_TYPE_P(z_options) == IS_NULL ) {
        array_init(z_options);
    }

    z_routes = zend_read_property(Z_OBJCE_P(this_ptr), this_ptr, "routes", sizeof("routes")-1, 1 TSRMLS_CC);

    MAKE_STD_ZVAL(z_new_routes);
    array_init_size(z_new_routes, 4);

    add_index_bool(z_new_routes, 0 , 0); // pcre flag == false
    add_index_stringl( z_new_routes, 1 , pattern , pattern_len, 1);
    add_index_zval( z_new_routes, 2 , z_callback);
    add_index_zval( z_new_routes, 3 , z_options);

    add_next_index_zval(z_routes, z_new_routes);
}


PHP_METHOD(Mux, appendPCRERoute) {
    char *pattern;
    int  pattern_len;
    zval *z_callback;
    zval *z_options;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa|a", &pattern, &pattern_len, &z_callback, &z_options) == FAILURE) {
        RETURN_FALSE;
    }

    if ( z_options == NULL ) {
        MAKE_STD_ZVAL(z_options);
        array_init(z_options);
    } else if ( Z_TYPE_P(z_options) == IS_NULL ) {
        array_init(z_options);
    }

    zval *z_pattern = NULL;
    zval *z_routes;

    MAKE_STD_ZVAL(z_pattern);
    ZVAL_STRINGL(z_pattern, pattern, pattern_len, 1);

    z_routes = zend_read_property(Z_OBJCE_P(this_ptr), getThis(), "routes", sizeof("routes")-1, 1 TSRMLS_CC);

    zend_class_entry **ce_pattern_compiler = get_pattern_compiler_ce(TSRMLS_C);
    if ( ce_pattern_compiler == NULL ) {
        RETURN_FALSE;
    }

    zval *rv = NULL;
    zend_call_method( NULL, *ce_pattern_compiler, NULL, "compile", strlen("compile"), &rv, 1, z_pattern, NULL TSRMLS_CC );

    if ( rv == NULL || Z_TYPE_P(rv) == IS_NULL ) {
        zend_throw_exception(ce_r3_exception, "Can not compile route pattern", 0 TSRMLS_CC);
        RETURN_FALSE;
    }
    add_next_index_zval(z_routes, rv);
}


inline void mux_add_route(INTERNAL_FUNCTION_PARAMETERS)
{
    char *pattern;
    int  pattern_len;

    zval *z_callback = NULL;
    zval *z_options = NULL;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|a", &pattern, &pattern_len, &z_callback, &z_options) == FAILURE) {
        RETURN_FALSE;
    }

    // $pcre = strpos($pattern,':') !== false;
    char *found = find_place_holder(pattern, pattern_len);


    if ( z_options == NULL ) {
        MAKE_STD_ZVAL(z_options);
        array_init(z_options);
    } else if ( Z_TYPE_P(z_options) == IS_NULL ) {
        // make it as an array
        array_init(z_options);
    }

    // Generalize callback variable
    if ( Z_TYPE_P(z_callback) == IS_STRING ) {
        if ( strpos( Z_STRVAL_P(z_callback), ":" ) != -1 ) {
            zval *delim;
            MAKE_STD_ZVAL(delim);
            ZVAL_STRINGL(delim, ":" , 1 , 1);

            zval *rv;
            MAKE_STD_ZVAL(rv);
            array_init(rv);
            php_explode(delim, z_callback, rv, 2);

            z_callback = rv;
            // zval_copy_ctor(z_callback);
            zval_ptr_dtor(&delim);
        }
    }


    zval * z_routes = zend_read_property(ce_r3_mux, this_ptr, "routes", sizeof("routes")-1, 1 TSRMLS_CC);

    // PCRE pattern here
    if ( found ) {
        zval *z_pattern = NULL;
        MAKE_STD_ZVAL(z_pattern);
        ZVAL_STRINGL(z_pattern, pattern, pattern_len, 1);

        zval *z_compiled_route = compile_route_pattern(z_pattern, z_options, NULL TSRMLS_CC);
        if ( z_compiled_route == NULL ) {
            zend_throw_exception(ce_r3_exception, "Unable to compile route pattern.", 0 TSRMLS_CC);
            RETURN_FALSE;
        }
        zval_ptr_dtor(&z_pattern);

        zval **z_compiled_route_pattern;
        if ( zend_hash_quick_find( Z_ARRVAL_P(z_compiled_route) , "compiled", sizeof("compiled"), zend_inline_hash_func(ZEND_STRS("compiled")), (void**)&z_compiled_route_pattern) == FAILURE ) {
            zend_throw_exception(ce_r3_exception, "Unable to find compiled pattern.", 0 TSRMLS_CC);
            RETURN_FALSE;
        }
        Z_ADDREF_P(z_callback);

        zval *z_new_routes;
        MAKE_STD_ZVAL(z_new_routes);
        array_init_size(z_new_routes, 4);

        add_index_bool(z_new_routes, 0 , 1); // pcre flag == false
        add_index_zval(z_new_routes, 1, *z_compiled_route_pattern);
        add_index_zval(z_new_routes, 2 , z_callback);
        add_index_zval(z_new_routes, 3, z_compiled_route);
        add_next_index_zval(z_routes, z_new_routes);


        zval **z_route_id;
        if ( zend_hash_quick_find( Z_ARRVAL_P(z_options) , "id", sizeof("id"), zend_inline_hash_func(ZEND_STRS("id")), (void**)&z_route_id) == SUCCESS ) {
            zval * z_routes_by_id = zend_read_property(ce_r3_mux, this_ptr, "routesById", sizeof("routesById")-1, 1 TSRMLS_CC);
            add_assoc_zval(z_routes_by_id, Z_STRVAL_PP(z_route_id), z_new_routes);
        }
    } else {
        Z_ADDREF_P(z_options); // reference it so it will not be recycled.
        Z_ADDREF_P(z_callback);

        zval *z_new_route;
        MAKE_STD_ZVAL(z_new_route);
        array_init_size(z_new_route, 4);

        /* make the array: [ pcreFlag, pattern, callback, options ] */
        add_index_bool(z_new_route, 0 , 0); // pcre flag == false
        add_index_stringl( z_new_route, 1 , pattern, pattern_len , 1 );
        add_index_zval( z_new_route, 2 , z_callback);
        add_index_zval( z_new_route, 3 , z_options);
        add_next_index_zval(z_routes, z_new_route);

        // if there is no option specified in z_options, we can add the route to our static route hash
        if ( zend_hash_num_elements(Z_ARRVAL_P(z_options)) ) {
            zval * z_static_routes = zend_read_property(ce_r3_mux, this_ptr, "staticRoutes", sizeof("staticRoutes")-1, 1 TSRMLS_CC);
            if ( z_static_routes ) {
                add_assoc_zval(z_static_routes, pattern, z_new_route);
            }
        }

        zval **z_route_id;
        if ( zend_hash_quick_find( Z_ARRVAL_P(z_options) , "id", sizeof("id"), zend_inline_hash_func(ZEND_STRS("id")), (void**)&z_route_id) == SUCCESS ) {
            zval * z_routes_by_id = zend_read_property(ce_r3_mux, this_ptr, "routesById", sizeof("routesById")-1, 1 TSRMLS_CC);

            /*
            zval *id_route = NULL;
            ALLOC_ZVAL(id_route);
            ZVAL_COPY_VALUE(id_route, z_new_route);
            zval_copy_ctor(id_route);
            add_assoc_zval(z_routes_by_id, Z_STRVAL_PP(z_route_id), id_route);
            */

            add_assoc_zval(z_routes_by_id, Z_STRVAL_PP(z_route_id), z_new_route);
        }
    }
}


PHP_METHOD(Mux, add) {
    mux_add_route(INTERNAL_FUNCTION_PARAM_PASSTHRU);
}

PHP_METHOD(Mux, any) {
    mux_add_route(INTERNAL_FUNCTION_PARAM_PASSTHRU);
}

