#include "call/exit_eval.h"

#include "php_embed_runtime.h"

#include <Zend/zend_execute.h>
#include <Zend/zend_exceptions.h>

static char *
valaphp_embed_exit_code_for_string(const char *construct_name, const char *message)
{
    gchar *encoded = g_base64_encode((const guchar *) message, strlen(message));
    gchar *code = g_strdup_printf("%s(base64_decode('%s'));", construct_name, encoded);
    g_free(encoded);
    return code;
}

static char *
valaphp_embed_exit_code(const char *construct_name, Phpzval *value, gboolean has_value)
{
    const char *safe_construct_name = g_strcmp0(construct_name, "die") == 0 ? "die" : "exit";

    if (!has_value || value == NULL || php_zval_is_null(value))
        return g_strdup_printf("%s;", safe_construct_name);

    if (php_zval_get_value_type(value) == PHP_VALUE_TYPE_STRING) {
        gchar *message = php_zval_as_string(value);
        gchar *code = valaphp_embed_exit_code_for_string(safe_construct_name, message != NULL ? message : "");
        g_free(message);
        return code;
    }

    return g_strdup_printf("%s(%ld);", safe_construct_name, (long) php_zval_to_int(value));
}

int
valaphp_embed_exit_eval(const char *construct_name, Phpzval *value, gboolean has_value, char **error_message)
{
    if (error_message != NULL)
        *error_message = NULL;

    if (!valaphp_embed_ensure_initialized(error_message))
        return -1;

    char *code = valaphp_embed_exit_code(construct_name, value, has_value);
    zval retval;
    ZVAL_UNDEF(&retval);
    zend_result result = SUCCESS;
    int exit_status = 0;

    EG(exit_status) = 0;
    zend_try {
        result = zend_eval_string(code, &retval, "valaphp exit/die");
    } zend_catch {
        result = SUCCESS;
    } zend_end_try();

    exit_status = EG(exit_status);

    if (!Z_ISUNDEF(retval))
        zval_ptr_dtor(&retval);

    g_free(code);

    if (EG(exception) && (zend_is_unwind_exit(EG(exception)) || zend_is_graceful_exit(EG(exception)))) {
        zend_clear_exception();
    }

    if (EG(exception)) {
        if (error_message != NULL)
            *error_message = g_strdup_printf("PHP %s failed", g_strcmp0(construct_name, "die") == 0 ? "die" : "exit");
        if (EG(exception))
            zend_clear_exception();
        return -1;
    }

    return exit_status;
}

int
valaphp_embed_exit_eval_no_args(const char *construct_name, char **error_message)
{
    return valaphp_embed_exit_eval(construct_name, NULL, FALSE, error_message);
}

int
valaphp_embed_exit_eval_int(const char *construct_name, int status, char **error_message)
{
    if (error_message != NULL)
        *error_message = NULL;

    if (!valaphp_embed_ensure_initialized(error_message))
        return -1;

    const char *safe_construct_name = g_strcmp0(construct_name, "die") == 0 ? "die" : "exit";
    char *code = g_strdup_printf("%s(%d);", safe_construct_name, status);
    zval retval;
    ZVAL_UNDEF(&retval);

    EG(exit_status) = 0;
    zend_try {
        zend_eval_string(code, &retval, "valaphp exit/die");
    } zend_catch {
    } zend_end_try();

    int exit_status = EG(exit_status);
    if (!Z_ISUNDEF(retval))
        zval_ptr_dtor(&retval);
    g_free(code);

    if (EG(exception) && (zend_is_unwind_exit(EG(exception)) || zend_is_graceful_exit(EG(exception))))
        zend_clear_exception();

    if (EG(exception)) {
        if (error_message != NULL)
            *error_message = g_strdup_printf("PHP %s failed", safe_construct_name);
        zend_clear_exception();
        return -1;
    }

    return exit_status;
}

int
valaphp_embed_exit_eval_string(const char *construct_name, const char *message, char **error_message)
{
    if (error_message != NULL)
        *error_message = NULL;

    if (!valaphp_embed_ensure_initialized(error_message))
        return -1;

    const char *safe_construct_name = g_strcmp0(construct_name, "die") == 0 ? "die" : "exit";
    char *code = valaphp_embed_exit_code_for_string(safe_construct_name, message != NULL ? message : "");
    zval retval;
    ZVAL_UNDEF(&retval);

    EG(exit_status) = 0;
    zend_try {
        zend_eval_string(code, &retval, "valaphp exit/die");
    } zend_catch {
    } zend_end_try();

    int exit_status = EG(exit_status);
    if (!Z_ISUNDEF(retval))
        zval_ptr_dtor(&retval);
    g_free(code);

    if (EG(exception) && (zend_is_unwind_exit(EG(exception)) || zend_is_graceful_exit(EG(exception))))
        zend_clear_exception();

    if (EG(exception)) {
        if (error_message != NULL)
            *error_message = g_strdup_printf("PHP %s failed", safe_construct_name);
        zend_clear_exception();
        return -1;
    }

    return exit_status;
}
