#include <glib.h>
#include <php.h>
#include <Zend/zend_exceptions.h>
#include <Zend/zend_constants.h>
#include <string.h>

#include <phpef-runtime-core.h>

#include "phpef_runtime_embed_value_conversion.h"

void phpef_runtime_embed_ensure_initialized(void);

static gboolean
phpef_runtime_embed_call_zend_function(const char *function_name, zval *params, uint32_t param_count, zval *return_value)
{
    zval callback;
    ZVAL_STRING(&callback, function_name);
    ZVAL_UNDEF(return_value);

    int status = SUCCESS;
    zend_try {
        status = call_user_function(EG(function_table), NULL, &callback, return_value, param_count, params);
    } zend_catch {
        status = FAILURE;
    } zend_end_try();

    zval_ptr_dtor(&callback);

    if (status != SUCCESS || EG(exception)) {
        if (EG(exception)) {
            zend_clear_exception();
        }
        if (!Z_ISUNDEF_P(return_value)) {
            zval_ptr_dtor(return_value);
        }
        return FALSE;
    }

    return TRUE;
}

static gboolean
phpef_runtime_embed_call_zend_function_arg1_string(const char *function_name, const char *arg1, zval *return_value)
{
    zval params[1];
    ZVAL_STRING(&params[0], arg1 != NULL ? arg1 : "");

    gboolean ok = phpef_runtime_embed_call_zend_function(function_name, params, 1, return_value);

    zval_ptr_dtor(&params[0]);
    return ok;
}

char *
phpef_runtime_embed_call_php_function_typed_string_arg1_string(const char *function_name, const char *arg1)
{
    phpef_runtime_embed_ensure_initialized();

    zval return_value;
    gboolean ok = phpef_runtime_embed_call_zend_function_arg1_string(function_name, arg1, &return_value);
    if (!ok) {
        return g_strdup("");
    }

    zend_string *string_value = zval_get_string(&return_value);
    char *result = g_strdup(ZSTR_VAL(string_value));
    zend_string_release(string_value);
    zval_ptr_dtor(&return_value);
    return result;
}

gint64
phpef_runtime_embed_call_php_function_typed_int_arg1_string(const char *function_name, const char *arg1)
{
    phpef_runtime_embed_ensure_initialized();

    zval return_value;
    gboolean ok = phpef_runtime_embed_call_zend_function_arg1_string(function_name, arg1, &return_value);
    if (!ok) {
        return 0;
    }

    zend_long result = zval_get_long(&return_value);
    zval_ptr_dtor(&return_value);
    return (gint64) result;
}

PhpefRuntimeCoreZval *
phpef_runtime_embed_call_php_function(const char *function_name, PhpefRuntimeCoreZval **args, gint args_length)
{
    phpef_runtime_embed_ensure_initialized();

    if (args_length < 0) {
        return phpef_runtime_core_zval_from_bool(FALSE);
    }

    zval *params = NULL;
    if (args_length > 0) {
        params = g_new0(zval, (guint) args_length);
        for (gint i = 0; i < args_length; i++) {
            phpef_runtime_embed_phpefzval_to_zendzval(args[i], &params[i]);
        }
    }

    zval return_value;
    gboolean ok = phpef_runtime_embed_call_zend_function(function_name, params, (uint32_t) args_length, &return_value);

    for (gint i = 0; i < args_length; i++) {
        zval_ptr_dtor(&params[i]);
    }
    g_free(params);

    if (!ok) {
        return phpef_runtime_core_zval_from_bool(FALSE);
    }

    PhpefRuntimeCoreZval *result = phpef_runtime_embed_zendzval_to_phpefzval(&return_value);
    zval_ptr_dtor(&return_value);
    return result;
}

PhpefRuntimeCoreZval *
phpef_runtime_embed_lookup_constant(const char *name)
{
    phpef_runtime_embed_ensure_initialized();

    if (name == NULL || name[0] == '\0') {
        return NULL;
    }

    zval *constant_value = NULL;
    zend_try {
        constant_value = zend_get_constant_str(name, strlen(name));
    } zend_catch {
        constant_value = NULL;
    } zend_end_try();

    if (EG(exception)) {
        zend_clear_exception();
        return NULL;
    }
    if (constant_value == NULL) {
        return NULL;
    }

    return phpef_runtime_embed_zendzval_to_phpefzval(constant_value);
}
