#include "call/static_call.h"

#include "php_embed_runtime.h"

static gboolean
call_function(const char *function_name, zval *params, uint32_t param_count, zval *return_value, char **error_message)
{
    return valaphp_embed_call_php_function(function_name, params, param_count, return_value, error_message);
}

static void
destroy_params(zval *params, uint32_t param_count)
{
    for (uint32_t i = 0; i < param_count; i++)
        zval_ptr_dtor(&params[i]);
}

static char *
return_string(zval *return_value)
{
    return valaphp_embed_zval_to_allocated_string(return_value);
}

static gint
return_int(zval *return_value)
{
    if (Z_TYPE_P(return_value) == IS_FALSE)
        return -1;
    return (gint) zval_get_long(return_value);
}

static gdouble
return_double(zval *return_value)
{
    return zval_get_double(return_value);
}

static gboolean
return_bool(zval *return_value)
{
    return zend_is_true(return_value) ? TRUE : FALSE;
}

char *
valaphp_embed_call_no_args_to_string(const char *function_name, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return NULL;

    zval return_value;
    if (!call_function(function_name, NULL, 0, &return_value, error_message))
        return NULL;
    char *result = return_string(&return_value);
    zval_ptr_dtor(&return_value);
    return result;
}

char *
valaphp_embed_call_no_args_output_to_string(const char *function_name, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return NULL;

    return valaphp_embed_call_php_function_output_to_string(function_name, NULL, 0, error_message);
}

char *
valaphp_embed_call_no_args_output_to_string_and_print(const char *function_name, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return NULL;

    char *result = valaphp_embed_call_no_args_output_to_string(function_name, error_message);
    if (result != NULL && result[0] != '\0')
        zend_write(result, strlen(result));
    return result;
}

gboolean
valaphp_embed_call_no_args_to_bool(const char *function_name, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return FALSE;

    zval return_value;
    if (!call_function(function_name, NULL, 0, &return_value, error_message))
        return FALSE;
    gboolean result = return_bool(&return_value);
    zval_ptr_dtor(&return_value);
    return result;
}

char *
valaphp_embed_call_bool_to_string(const char *function_name, gboolean value, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return NULL;

    zval params[1];
    ZVAL_BOOL(&params[0], value ? 1 : 0);
    zval return_value;
    if (!call_function(function_name, params, 1, &return_value, error_message))
        return NULL;
    char *result = return_string(&return_value);
    zval_ptr_dtor(&return_value);
    return result;
}

gdouble
valaphp_embed_call_bool_to_double(const char *function_name, gboolean value, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return 0.0;

    zval params[1];
    ZVAL_BOOL(&params[0], value ? 1 : 0);
    zval return_value;
    if (!call_function(function_name, params, 1, &return_value, error_message))
        return 0.0;
    gdouble result = return_double(&return_value);
    zval_ptr_dtor(&return_value);
    return result;
}

char *
valaphp_embed_call_int_to_string(const char *function_name, gint64 value, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return NULL;

    zval params[1];
    ZVAL_LONG(&params[0], (zend_long) value);
    zval return_value;
    if (!call_function(function_name, params, 1, &return_value, error_message))
        return NULL;
    char *result = return_string(&return_value);
    zval_ptr_dtor(&return_value);
    return result;
}

gint
valaphp_embed_call_int_to_int(const char *function_name, gint64 value, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return 0;

    zval params[1];
    ZVAL_LONG(&params[0], (zend_long) value);
    zval return_value;
    if (!call_function(function_name, params, 1, &return_value, error_message))
        return 0;
    gint result = return_int(&return_value);
    zval_ptr_dtor(&return_value);
    return result;
}

gboolean
valaphp_embed_call_int_to_bool(const char *function_name, gint64 value, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return FALSE;

    zval params[1];
    ZVAL_LONG(&params[0], (zend_long) value);
    zval return_value;
    if (!call_function(function_name, params, 1, &return_value, error_message))
        return FALSE;
    gboolean result = return_bool(&return_value);
    zval_ptr_dtor(&return_value);
    return result;
}

gdouble
valaphp_embed_call_double_to_double(const char *function_name, gdouble value, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return 0.0;

    zval params[1];
    ZVAL_DOUBLE(&params[0], value);
    zval return_value;
    if (!call_function(function_name, params, 1, &return_value, error_message))
        return 0.0;
    gdouble result = return_double(&return_value);
    zval_ptr_dtor(&return_value);
    return result;
}

gdouble
valaphp_embed_call_double_int_int_to_double(const char *function_name, gdouble value, gint precision, gint mode, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return 0.0;

    zval params[3];
    ZVAL_DOUBLE(&params[0], value);
    ZVAL_LONG(&params[1], precision);
    ZVAL_LONG(&params[2], mode);
    zval return_value;
    if (!call_function(function_name, params, 3, &return_value, error_message))
        return 0.0;
    gdouble result = return_double(&return_value);
    zval_ptr_dtor(&return_value);
    return result;
}

char *
valaphp_embed_call_string_to_string(const char *function_name, const char *value, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return NULL;

    zval params[1];
    ZVAL_STRING(&params[0], value != NULL ? value : "");
    zval return_value;
    if (!call_function(function_name, params, 1, &return_value, error_message)) {
        destroy_params(params, 1);
        return NULL;
    }
    char *result = return_string(&return_value);
    zval_ptr_dtor(&return_value);
    destroy_params(params, 1);
    return result;
}

gint
valaphp_embed_call_string_to_int(const char *function_name, const char *value, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return 0;

    zval params[1];
    ZVAL_STRING(&params[0], value != NULL ? value : "");
    zval return_value;
    if (!call_function(function_name, params, 1, &return_value, error_message)) {
        destroy_params(params, 1);
        return 0;
    }
    gint result = return_int(&return_value);
    zval_ptr_dtor(&return_value);
    destroy_params(params, 1);
    return result;
}

gboolean
valaphp_embed_call_string_to_bool(const char *function_name, const char *value, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return FALSE;

    zval params[1];
    ZVAL_STRING(&params[0], value != NULL ? value : "");
    zval return_value;
    if (!call_function(function_name, params, 1, &return_value, error_message)) {
        destroy_params(params, 1);
        return FALSE;
    }
    gboolean result = return_bool(&return_value);
    zval_ptr_dtor(&return_value);
    destroy_params(params, 1);
    return result;
}

char *
valaphp_embed_call_string_int_int_to_string(const char *function_name, const char *value, gint offset, gint length, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return NULL;

    zval params[3];
    ZVAL_STRING(&params[0], value != NULL ? value : "");
    ZVAL_LONG(&params[1], offset);
    ZVAL_LONG(&params[2], length);
    zval return_value;
    if (!call_function(function_name, params, 3, &return_value, error_message)) {
        destroy_params(params, 3);
        return NULL;
    }
    char *result = return_string(&return_value);
    zval_ptr_dtor(&return_value);
    destroy_params(params, 3);
    return result;
}

char *
valaphp_embed_call_double_int_string_string_to_string(const char *function_name, gdouble value, gint decimals, const char *dec_point, const char *thousands_sep, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return NULL;

    zval params[4];
    ZVAL_DOUBLE(&params[0], value);
    ZVAL_LONG(&params[1], decimals);
    ZVAL_STRING(&params[2], dec_point != NULL ? dec_point : ".");
    ZVAL_STRING(&params[3], thousands_sep != NULL ? thousands_sep : ",");
    zval return_value;
    if (!call_function(function_name, params, 4, &return_value, error_message)) {
        destroy_params(params, 4);
        return NULL;
    }
    char *result = return_string(&return_value);
    zval_ptr_dtor(&return_value);
    destroy_params(params, 4);
    return result;
}

char *
valaphp_embed_call_string_string_to_string(const char *function_name, const char *first, const char *second, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return NULL;

    zval params[2];
    ZVAL_STRING(&params[0], first != NULL ? first : "");
    ZVAL_STRING(&params[1], second != NULL ? second : "");
    zval return_value;
    if (!call_function(function_name, params, 2, &return_value, error_message)) {
        destroy_params(params, 2);
        return NULL;
    }
    char *result = return_string(&return_value);
    zval_ptr_dtor(&return_value);
    destroy_params(params, 2);
    return result;
}

char *
valaphp_embed_call_string_string_int_to_string(const char *function_name, const char *first, const char *second, gint third, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return NULL;

    zval params[3];
    ZVAL_STRING(&params[0], first != NULL ? first : "");
    ZVAL_STRING(&params[1], second != NULL ? second : "");
    ZVAL_LONG(&params[2], third);
    zval return_value;
    if (!call_function(function_name, params, 3, &return_value, error_message)) {
        destroy_params(params, 3);
        return NULL;
    }
    char *result = return_string(&return_value);
    zval_ptr_dtor(&return_value);
    destroy_params(params, 3);
    return result;
}

char *
valaphp_embed_call_string_string_string_to_string(const char *function_name, const char *first, const char *second, const char *third, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return NULL;

    zval params[3];
    ZVAL_STRING(&params[0], first != NULL ? first : "");
    ZVAL_STRING(&params[1], second != NULL ? second : "");
    ZVAL_STRING(&params[2], third != NULL ? third : "");
    zval return_value;
    if (!call_function(function_name, params, 3, &return_value, error_message)) {
        destroy_params(params, 3);
        return NULL;
    }
    char *result = return_string(&return_value);
    zval_ptr_dtor(&return_value);
    destroy_params(params, 3);
    return result;
}

gint
valaphp_embed_call_string_string_to_int_or_minus_one(const char *function_name, const char *first, const char *second, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return -1;

    zval params[2];
    ZVAL_STRING(&params[0], first != NULL ? first : "");
    ZVAL_STRING(&params[1], second != NULL ? second : "");
    zval return_value;
    if (!call_function(function_name, params, 2, &return_value, error_message)) {
        destroy_params(params, 2);
        return -1;
    }
    gint result = return_int(&return_value);
    zval_ptr_dtor(&return_value);
    destroy_params(params, 2);
    return result;
}

gboolean
valaphp_embed_call_string_string_to_bool(const char *function_name, const char *first, const char *second, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return FALSE;

    zval params[2];
    ZVAL_STRING(&params[0], first != NULL ? first : "");
    ZVAL_STRING(&params[1], second != NULL ? second : "");
    zval return_value;
    if (!call_function(function_name, params, 2, &return_value, error_message)) {
        destroy_params(params, 2);
        return FALSE;
    }
    gboolean result = return_bool(&return_value);
    zval_ptr_dtor(&return_value);
    destroy_params(params, 2);
    return result;
}

gboolean
valaphp_embed_call_string_bool_to_bool(const char *function_name, const char *value, gboolean flag, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return FALSE;

    zval params[2];
    ZVAL_STRING(&params[0], value != NULL ? value : "");
    ZVAL_BOOL(&params[1], flag ? 1 : 0);
    zval return_value;
    if (!call_function(function_name, params, 2, &return_value, error_message)) {
        destroy_params(params, 2);
        return FALSE;
    }
    gboolean result = return_bool(&return_value);
    zval_ptr_dtor(&return_value);
    destroy_params(params, 2);
    return result;
}
