#include "call/stream_resource.h"

#include "php_embed_runtime.h"

static gboolean
resource_param(guint resource_id, zval *param, char **error_message)
{
    if (!valaphp_embed_resource_id_to_zval(resource_id, param)) {
        if (error_message != NULL)
            *error_message = g_strdup("PHP stream resource is closed or unknown");
        return FALSE;
    }
    return TRUE;
}

guint
valaphp_embed_stream_fopen(const char *path, const char *mode, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return 0;

    zval params[2];
    ZVAL_STRING(&params[0], path != NULL ? path : "");
    ZVAL_STRING(&params[1], mode != NULL ? mode : "");

    zval return_value;
    if (!valaphp_embed_call_php_function("fopen", params, 2, &return_value, error_message)) {
        zval_ptr_dtor(&params[0]);
        zval_ptr_dtor(&params[1]);
        return 0;
    }

    guint resource_id = 0;
    if (Z_TYPE(return_value) == IS_RESOURCE) {
        resource_id = valaphp_embed_store_resource_zval(&return_value);
    } else if (error_message != NULL) {
        *error_message = g_strdup("PHP fopen returned non-resource");
    }

    zval_ptr_dtor(&return_value);
    zval_ptr_dtor(&params[0]);
    zval_ptr_dtor(&params[1]);
    return resource_id;
}

guint
valaphp_embed_stream_socket_client(const char *target, gint *err_no, char **err_str, gdouble timeout, gint flags, char **error_message)
{
    if (err_no != NULL)
        *err_no = 0;
    if (err_str != NULL)
        *err_str = g_strdup("");

    if (!valaphp_embed_ensure_initialized(error_message))
        return 0;

    zval params[5];
    ZVAL_STRING(&params[0], target != NULL ? target : "");
    ZVAL_LONG(&params[1], 0);
    ZVAL_STRING(&params[2], "");
    ZVAL_DOUBLE(&params[3], timeout);
    ZVAL_LONG(&params[4], flags);
    ZVAL_MAKE_REF(&params[1]);
    ZVAL_MAKE_REF(&params[2]);

    zval return_value;
    if (!valaphp_embed_call_php_function("stream_socket_client", params, 5, &return_value, error_message)) {
        for (guint i = 0; i < 5; i++)
            zval_ptr_dtor(&params[i]);
        return 0;
    }

    zval *php_err_no = Z_ISREF(params[1]) ? Z_REFVAL(params[1]) : &params[1];
    zval *php_err_str = Z_ISREF(params[2]) ? Z_REFVAL(params[2]) : &params[2];
    if (err_no != NULL)
        *err_no = (gint) zval_get_long(php_err_no);
    if (err_str != NULL) {
        g_free(*err_str);
        *err_str = valaphp_embed_zval_to_allocated_string(php_err_str);
    }

    guint resource_id = 0;
    if (Z_TYPE(return_value) == IS_RESOURCE) {
        resource_id = valaphp_embed_store_resource_zval(&return_value);
    } else if (error_message != NULL) {
        const char *message = (err_str != NULL && *err_str != NULL && (*err_str)[0] != '\0') ? *err_str : "PHP stream_socket_client returned non-resource";
        *error_message = g_strdup(message);
    }

    zval_ptr_dtor(&return_value);
    for (guint i = 0; i < 5; i++)
        zval_ptr_dtor(&params[i]);
    return resource_id;
}

gboolean
valaphp_embed_stream_fclose(guint resource_id, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return FALSE;

    zval params[1];
    if (!resource_param(resource_id, &params[0], error_message))
        return FALSE;

    zval return_value;
    if (!valaphp_embed_call_php_function("fclose", params, 1, &return_value, error_message)) {
        zval_ptr_dtor(&params[0]);
        return FALSE;
    }

    gboolean result = zend_is_true(&return_value) ? TRUE : FALSE;
    zval_ptr_dtor(&return_value);
    zval_ptr_dtor(&params[0]);
    if (result)
        valaphp_embed_resource_id_remove(resource_id);
    return result;
}

char *
valaphp_embed_stream_fread(guint resource_id, gint length, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return NULL;

    zval params[2];
    if (!resource_param(resource_id, &params[0], error_message))
        return NULL;
    ZVAL_LONG(&params[1], length);

    zval return_value;
    if (!valaphp_embed_call_php_function("fread", params, 2, &return_value, error_message)) {
        zval_ptr_dtor(&params[0]);
        return NULL;
    }

    char *result = NULL;
    if (Z_TYPE(return_value) == IS_FALSE) {
        result = g_strdup("");
    } else {
        result = valaphp_embed_zval_to_allocated_string(&return_value);
    }

    zval_ptr_dtor(&return_value);
    zval_ptr_dtor(&params[0]);
    return result;
}

gint
valaphp_embed_stream_fwrite(guint resource_id, const char *data, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return -1;

    zval params[2];
    if (!resource_param(resource_id, &params[0], error_message))
        return -1;
    ZVAL_STRING(&params[1], data != NULL ? data : "");

    zval return_value;
    if (!valaphp_embed_call_php_function("fwrite", params, 2, &return_value, error_message)) {
        zval_ptr_dtor(&params[0]);
        zval_ptr_dtor(&params[1]);
        return -1;
    }

    gint result = Z_TYPE(return_value) == IS_FALSE ? -1 : (gint) zval_get_long(&return_value);
    zval_ptr_dtor(&return_value);
    zval_ptr_dtor(&params[0]);
    zval_ptr_dtor(&params[1]);
    return result;
}

gboolean
valaphp_embed_stream_fflush(guint resource_id, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return FALSE;

    zval params[1];
    if (!resource_param(resource_id, &params[0], error_message))
        return FALSE;

    zval return_value;
    if (!valaphp_embed_call_php_function("fflush", params, 1, &return_value, error_message)) {
        zval_ptr_dtor(&params[0]);
        return FALSE;
    }

    gboolean result = zend_is_true(&return_value) ? TRUE : FALSE;
    zval_ptr_dtor(&return_value);
    zval_ptr_dtor(&params[0]);
    return result;
}

gboolean
valaphp_embed_stream_set_blocking(guint resource_id, gboolean enable, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return FALSE;

    zval params[2];
    if (!resource_param(resource_id, &params[0], error_message))
        return FALSE;
    ZVAL_BOOL(&params[1], enable ? 1 : 0);

    zval return_value;
    if (!valaphp_embed_call_php_function("stream_set_blocking", params, 2, &return_value, error_message)) {
        zval_ptr_dtor(&params[0]);
        return FALSE;
    }

    gboolean result = zend_is_true(&return_value) ? TRUE : FALSE;
    zval_ptr_dtor(&return_value);
    zval_ptr_dtor(&params[0]);
    return result;
}

gint
valaphp_embed_stream_select_read(guint resource_id, gint seconds, gint microseconds, char **error_message)
{
    if (!valaphp_embed_ensure_initialized(error_message))
        return -1;

    zval resource;
    if (!resource_param(resource_id, &resource, error_message))
        return -1;

    zval params[5];
    array_init(&params[0]);
    add_next_index_zval(&params[0], &resource);
    ZVAL_NULL(&params[1]);
    ZVAL_NULL(&params[2]);
    ZVAL_LONG(&params[3], seconds);
    ZVAL_LONG(&params[4], microseconds);
    ZVAL_MAKE_REF(&params[0]);
    ZVAL_MAKE_REF(&params[1]);
    ZVAL_MAKE_REF(&params[2]);

    zval return_value;
    if (!valaphp_embed_call_php_function("stream_select", params, 5, &return_value, error_message)) {
        for (guint i = 0; i < 5; i++)
            zval_ptr_dtor(&params[i]);
        return -1;
    }

    gint result = Z_TYPE(return_value) == IS_FALSE ? -1 : (gint) zval_get_long(&return_value);
    zval_ptr_dtor(&return_value);
    for (guint i = 0; i < 5; i++)
        zval_ptr_dtor(&params[i]);
    return result;
}

gboolean
valaphp_embed_stream_is_resource(guint resource_id)
{
    zval value;
    if (!valaphp_embed_resource_id_to_zval(resource_id, &value))
        return FALSE;

    gboolean result = Z_TYPE(value) == IS_RESOURCE ? TRUE : FALSE;
    zval_ptr_dtor(&value);
    return result;
}

void
valaphp_embed_stream_resource_release(guint resource_id)
{
    valaphp_embed_resource_id_remove(resource_id);
}
