#include "../common.h"

#include "../php/iterators/php_deque_iterator.h"
#include "../php/handlers/php_deque_handlers.h"
#include "../php/classes/php_deque_ce.h"

#include "ds_deque.h"

static inline void ds_deque_increment_head(ds_deque_t *deque)
{
    deque->head = (deque->head + 1) & (deque->capacity - 1);
}

static inline void ds_deque_decrement_head(ds_deque_t *deque)
{
    deque->head = (deque->head - 1) & (deque->capacity - 1);
}

static inline void ds_deque_increment_tail(ds_deque_t *deque)
{
    deque->tail = (deque->tail + 1) & (deque->capacity - 1);
}

static inline void ds_deque_decrement_tail(ds_deque_t *deque)
{
    deque->tail = (deque->tail - 1) & (deque->capacity - 1);
}

static inline void ds_deque_memmove(
    ds_deque_t *deque,
    zend_long   dst,
    zend_long   src,
    zend_long   length
) {
    memmove(&deque->buffer[dst], &deque->buffer[src], length * sizeof(zval));
}

static inline zend_long ds_deque_get_capacity_for_size(zend_long size)
{
    return (zend_long) ds_next_power_of_2((uint32_t) size, DS_DEQUE_MIN_CAPACITY);
}

ds_deque_t *ds_deque()
{
    ds_deque_t *deque = ecalloc(1, sizeof(ds_deque_t));

    deque->buffer   = ds_allocate_zval_buffer(DS_DEQUE_MIN_CAPACITY);
    deque->capacity = DS_DEQUE_MIN_CAPACITY;
    deque->head     = 0;
    deque->tail     = 0;
    deque->size     = 0;

    return deque;
}

static ds_deque_t *ds_deque_preallocated(zend_long size)
{
    ds_deque_t *deque = ecalloc(1, sizeof(ds_deque_t));

    deque->capacity = ds_deque_get_capacity_for_size(size);
    deque->buffer   = ds_allocate_zval_buffer(deque->capacity);
    deque->head     = 0;
    deque->tail     = 0;
    deque->size     = 0;

    return deque;
}

static ds_deque_t *ds_deque_from_buffer(zval *buffer, zend_long capacity, zend_long size)
{
    ds_deque_t *deque = ecalloc(1, sizeof(ds_deque_t));

    deque->buffer   = buffer;
    deque->capacity = capacity;
    deque->head     = 0;
    deque->tail     = size;
    deque->size     = size;

    return deque;
}

ds_deque_t *ds_deque_clone(ds_deque_t *deque)
{
    zval *source;
    zval *buffer = ds_allocate_zval_buffer(deque->capacity);
    zval *target = buffer;

    DS_DEQUE_FOREACH(deque, source) {
        ZVAL_COPY(target, source);
        target++;
    }
    DS_DEQUE_FOREACH_END();

    return ds_deque_from_buffer(buffer, deque->capacity, deque->size);
}


static inline bool ds_deque_valid_position(ds_deque_t *deque, zend_long index)
{
    if (index < 0 || index >= deque->size) {
        INDEX_OUT_OF_RANGE(index, deque->size);
        return false;
    }

    return true;
}

// Makes sure that the deque's head is at index 0.
void ds_deque_reset_head(ds_deque_t *deque)
{
    if (deque->head == 0) {
        return;
    }

    if (deque->head < deque->tail) {
        ds_deque_memmove(deque, 0, deque->head, deque->size);

    } else {
        zend_long h = deque->head;
        zend_long t = deque->tail;
        zend_long r = deque->capacity - h; // Number of values on the right.

        // Check if there's enough room to push the left partition forward and
        // the wrapped values (right partition) to the front.
        if (r < (h - t)) {
            ds_deque_memmove(deque, r, 0, t);
            ds_deque_memmove(deque, 0, h, r);

        } else {
            // We don't have enough temporary space to work with, so create
            // a new buffer, copy to it, then replace the current buffer.
            zval *buffer = ds_allocate_zval_buffer(deque->capacity);

            memcpy(&buffer[0], &deque->buffer[h], r * sizeof(zval));
            memcpy(&buffer[r], &deque->buffer[0], t * sizeof(zval));

            FREE_AND_REPLACE(deque->buffer, buffer);
        }
    }

    deque->head = 0;
    deque->tail = deque->size;
}

static void ds_deque_reallocate(ds_deque_t *deque, zend_long capacity)
{
    ds_deque_reset_head(deque);

    deque->buffer   = ds_reallocate_zval_buffer(deque->buffer, capacity, deque->capacity, deque->size);
    deque->capacity = capacity;
    deque->head     = 0;
    deque->tail     = deque->size; // Could have been zero before if buffer was full.
}

static inline void ds_deque_double_capacity(ds_deque_t *deque)
{
    ds_deque_reallocate(deque, deque->capacity << 1);
}

void ds_deque_allocate(ds_deque_t *deque, zend_long size)
{
    zend_long capacity = ds_deque_get_capacity_for_size(size);

    // if (capacity == deque->capacity) {
    //     ds_deque_reallocate(deque, capacity << 1);
    // }

    if (capacity > deque->capacity) {
        ds_deque_reallocate(deque, capacity);
    }
}

static inline void ds_deque_auto_truncate(ds_deque_t *deque)
{
    // Automatically truncate if the size of the deque drops to a quarter of the capacity.
    if (deque->size <= deque->capacity / 4) {
        if (deque->capacity / 2 >= DS_DEQUE_MIN_CAPACITY) {
            ds_deque_reallocate(deque, deque->capacity / 2);
        }
    }
}

void ds_deque_clear(ds_deque_t *deque)
{
    zval *val;

    DS_DEQUE_FOREACH(deque, val) {
        zval_ptr_dtor(val);
    }
    DS_DEQUE_FOREACH_END();

    deque->buffer   = ds_reallocate_zval_buffer(deque->buffer, DS_DEQUE_MIN_CAPACITY, deque->capacity, 0);
    deque->head     = 0;
    deque->tail     = 0;
    deque->size     = 0;
    deque->capacity = DS_DEQUE_MIN_CAPACITY;
}

void ds_deque_free(ds_deque_t *deque)
{
    zval *val;

    DS_DEQUE_FOREACH(deque, val) {
        zval_ptr_dtor(val);
    }
    DS_DEQUE_FOREACH_END();

    efree(deque->buffer);
    efree(deque);
}

/**
 * Translates a positional index into a buffer pointer.
 */
static inline zend_long ds_deque_lookup_index(ds_deque_t *deque, zend_long index)
{
    return (deque->head + index) & (deque->capacity - 1);
}

/**
 * Translates a positional index into a buffer index.
 */
static inline zval *ds_deque_lookup(ds_deque_t *deque, zend_long index)
{
    return deque->buffer + ds_deque_lookup_index(deque, index);
}

zval *ds_deque_get(ds_deque_t *deque, zend_long index)
{
    if ( ! ds_deque_valid_position(deque, index)) {
        return NULL;
    }

    return ds_deque_lookup(deque, index);
}

void ds_deque_set(ds_deque_t *deque, zend_long index, zval *value)
{
    if (ds_deque_valid_position(deque, index)) {
        zval *ptr = ds_deque_lookup(deque, index);
        zval_ptr_dtor(ptr);
        ZVAL_COPY(ptr, value);
    }
}

void ds_deque_reverse(ds_deque_t *deque)
{
    if (deque->head < deque->tail) {
        ds_reverse_zval_range(
            deque->buffer + deque->head,
            deque->buffer + deque->tail
        );

    } else {
        zend_long head = deque->head;
        zend_long tail = deque->tail;
        zend_long mask = deque->capacity - 1;

        while (head != tail) {
            tail = (tail - 1) & mask;
            SWAP_ZVAL(
                deque->buffer[head],
                deque->buffer[tail]
            );
            head = (head + 1) & mask;
        }
    }
}

ds_deque_t *ds_deque_reversed(ds_deque_t *deque)
{
    zval *src;
    zval *buf = ds_allocate_zval_buffer(deque->capacity);
    zval *dst = &buf[deque->size - 1];

    DS_DEQUE_FOREACH(deque, src) {
        ZVAL_COPY(dst, src);
        dst--;
    }
    DS_DEQUE_FOREACH_END();

    return ds_deque_from_buffer(buf, deque->capacity, deque->size);
}


void ds_deque_shift(ds_deque_t *deque, zval *return_value)
{
    SET_AS_RETURN_AND_UNDEF(&deque->buffer[deque->head]);
    ds_deque_increment_head(deque);

    deque->size--;
    ds_deque_auto_truncate(deque);
}

void ds_deque_shift_throw(ds_deque_t *deque, zval *return_value)
{
    if (deque->size == 0) {
        NOT_ALLOWED_WHEN_EMPTY();
        return;
    }

    ds_deque_shift(deque, return_value);
}

void ds_deque_pop(ds_deque_t *deque, zval *return_value)
{
    ds_deque_decrement_tail(deque);
    SET_AS_RETURN_AND_UNDEF(&deque->buffer[deque->tail]);

    deque->size--;
    ds_deque_auto_truncate(deque);
}

void ds_deque_pop_throw(ds_deque_t *deque, zval *return_value)
{
    if (deque->size == 0) {
        NOT_ALLOWED_WHEN_EMPTY();
        return;
    }

    ds_deque_pop(deque, return_value);
}

void ds_deque_remove(ds_deque_t *deque, zend_long index, zval *return_value)
{
    if ( ! ds_deque_valid_position(deque, index)) {
        return;
    }

    // Basic shift if it's the first element in the sequence.
    if (index == 0) {
        ds_deque_shift(deque, return_value);
        return;
    }

    // Basic pop if it's the last element in the sequence.
    if (index == deque->size - 1) {
        ds_deque_pop(deque, return_value);
        return;
    }

    // Translate the positional index to a buffer index.
    index = ds_deque_lookup_index(deque, index);

    // Copy the value into the return value, then clear it.
    SET_AS_RETURN_AND_UNDEF(&deque->buffer[index]);

    if (index < deque->tail) {
        // Shift all values between the index and the tail.
        ds_deque_memmove(deque, index, index + 1, deque->tail - index);
        deque->tail--;

    } else {
        // Index comes after tail, and we know at this point that the index
        // is valid, so it must be after the head which has wrapped around.

        // Unshift all values between the head and the index.
        ds_deque_memmove(deque, deque->head + 1, deque->head, index - deque->head);
        deque->head++;
    }

    deque->size--;
    ds_deque_auto_truncate(deque);
}

void ds_deque_unshift_va(ds_deque_t *deque, VA_PARAMS)
{
    ds_deque_allocate(deque, deque->size + argc);
    deque->size += argc;

    while (argc--) {
        ds_deque_decrement_head(deque);
        ZVAL_COPY(&deque->buffer[deque->head], &argv[argc]);
    }
}

void ds_deque_push(ds_deque_t *deque, zval *value)
{
    if (deque->size == deque->capacity) {
        ds_deque_double_capacity(deque);
    }

    ZVAL_COPY(&deque->buffer[deque->tail], value);
    ds_deque_increment_tail(deque);
    deque->size++;
}

void ds_deque_push_va(ds_deque_t *deque, VA_PARAMS)
{
    ds_deque_allocate(deque, deque->size + argc);

    while (argc) {
        ZVAL_COPY(&deque->buffer[deque->tail], argv);
        ds_deque_increment_tail(deque);
        deque->size++;

        argc--;
        argv++;
    }
}

void ds_deque_insert_va(ds_deque_t *deque, zend_long position, VA_PARAMS)
{
    zval *dst;
    zend_long index;

    // Basic push if inserting at the back.
    if (position == deque->size) {
        ds_deque_push_va(deque, VA_ARGS);
        return;
    }

    // Basic unshift if inserting at the front.
    if (position == 0) {
        ds_deque_unshift_va(deque, VA_ARGS);
        return;
    }

    // Check that the insert position is not out of range.
    if ( ! ds_deque_valid_position(deque, position)) {
        return;
    }

    // No op if no values.
    if (argc <= 0) {
        return;
    }

    // Make sure that we have enough room for the new values.
    ds_deque_allocate(deque, deque->size + argc);

    // Translate the positional index to a buffer index.
    index = ds_deque_lookup_index(deque, position);

    if (index <= deque->tail) {
        // The deque is either contiguous or we're inserting in between the
        // start of the buffer and the tail, where the head has wrapped around.

        // Check for overflow, which is possible because the head won't
        // always be at the start of the buffer.
        if ((deque->tail + argc) > deque->capacity) {

            // There isn't enough free space to the right of the tail,
            // so move the entire sequence all the way to the left.
            ds_deque_memmove(deque, 0, deque->head, deque->size);

            index -= deque->head;
            deque->head = 0;
            deque->tail = deque->size;
        }

        // Move the subsequence after the insertion point to the right
        // to make room for the new values.
        ds_deque_memmove(deque, (index + argc), index, (deque->tail - index));
        deque->tail += argc;
        dst = &deque->buffer[index];

    } else {
        // We're inserting between a wrapped around head and the end of the
        // buffer, and it's guaranteed that there's enough free space.
        ds_deque_memmove(deque, (deque->head - argc), deque->head, (index - deque->head));
        deque->head -= argc;
        dst = &deque->buffer[index - argc];
    }

    deque->size += argc;

    // Copy the new values into place.
    while (argc--) {
        ZVAL_COPY(dst++, argv++);
    }
}

static zend_long ds_deque_find_index(ds_deque_t *deque, zval *value)
{
    zend_long head = deque->head;
    zend_long mask = deque->capacity - 1;

    zend_long index;

    for (index = 0; index < deque->size; index++, head++) {
        if (zend_is_identical(value, &deque->buffer[head & mask])) {
            return index;
        }
    }

    return FAILURE;
}

void ds_deque_join(ds_deque_t *deque, char *str, size_t len, zval *return_value)
{
    ds_deque_reset_head(deque);

    ZVAL_STR(
        return_value,
        ds_join_zval_buffer(deque->buffer, deque->size, str, len)
    );
}

void ds_deque_find(ds_deque_t *deque, zval *value, zval *return_value)
{
    zend_long index = ds_deque_find_index(deque, value);

    if (index >= 0) {
        ZVAL_LONG(return_value, index);
    } else {
        ZVAL_FALSE(return_value);
    }
}

bool ds_deque_contains_va(ds_deque_t *deque, VA_PARAMS)
{
    while (argc-- > 0) {
        if (ds_deque_find_index(deque, argv++) == FAILURE) {
            return false;
        }
    }

    return true;
}

void ds_deque_rotate(ds_deque_t *deque, zend_long n)
{
    if (deque->size < 2) {
        return;
    }

    if (n < 0) {
        for (n = llabs(n) % deque->size; n > 0; n--) {

            // Pop, unshift
            ds_deque_decrement_head(deque);
            ds_deque_decrement_tail(deque);

            // Tail is now at last value, head is before the first.
            SWAP_ZVAL(deque->buffer[deque->tail], deque->buffer[deque->head]);
        }
    } else if (n > 0) {
        for (n = n % deque->size; n > 0; n--) {

            // Tail is one past the last value, head is at first value.
            SWAP_ZVAL(deque->buffer[deque->tail], deque->buffer[deque->head]);

            // Shift, push
            ds_deque_increment_head(deque);
            ds_deque_increment_tail(deque);
        }
    }
}

void ds_deque_to_array(ds_deque_t *deque, zval *array)
{
    if (deque->size == 0) {
        array_init(array);
        return;

    } else {
        zval *value;
        array_init_size(array, deque->size);

        DS_DEQUE_FOREACH(deque, value) {
            add_next_index_zval(array, value);
            Z_TRY_ADDREF_P(value);
        }
        DS_DEQUE_FOREACH_END();
    }
}

bool ds_deque_index_exists(ds_deque_t *deque, zend_long index)
{
    return index >= 0 && index < deque->size;
}

bool ds_deque_isset(ds_deque_t *deque, zend_long index, int check_empty)
{
    if (!ds_deque_index_exists(deque, index)) {
        return false;
    }

    return ds_zval_isset(ds_deque_lookup(deque, index), check_empty);
}

zval *ds_deque_get_last(ds_deque_t *deque)
{
    return &deque->buffer[(deque->tail - 1) & (deque->capacity - 1)];
}

zval *ds_deque_get_last_throw(ds_deque_t *deque)
{
    if (deque->size == 0) {
        NOT_ALLOWED_WHEN_EMPTY();
        return NULL;
    }

    return ds_deque_get_last(deque);
}

zval *ds_deque_get_first(ds_deque_t *deque)
{
    return &deque->buffer[deque->head];
}

zval *ds_deque_get_first_throw(ds_deque_t *deque)
{
    if (deque->size == 0) {
        NOT_ALLOWED_WHEN_EMPTY();
        return NULL;
    }

    return ds_deque_get_first(deque);
}

static int iterator_add(zend_object_iterator *iterator, void *puser)
{
    ds_deque_push((ds_deque_t *) puser, iterator->funcs->get_current_data(iterator));
    return ZEND_HASH_APPLY_KEEP;
}

static void add_traversable_to_deque(ds_deque_t *deque, zval *obj)
{
    spl_iterator_apply(obj, iterator_add, deque);
}

static void add_array_to_deque(ds_deque_t *deque, HashTable *arr)
{
    zval *value;
    ZEND_HASH_FOREACH_VAL(arr, value) {
        ds_deque_push(deque, value);
    }
    ZEND_HASH_FOREACH_END();
}

void ds_deque_push_all(ds_deque_t *deque, zval *values)
{
    if ( ! values) {
        return;
    }

    if (ds_is_array(values)) {
        add_array_to_deque(deque, Z_ARRVAL_P(values));
        return;
    }

    if (ds_is_traversable(values)) {
        add_traversable_to_deque(deque, values);
        return;
    }

    ARRAY_OR_TRAVERSABLE_REQUIRED();
}

ds_deque_t *ds_deque_merge(ds_deque_t *deque, zval *values)
{
    if (values && (ds_is_array(values) || ds_is_traversable(values))) {
        ds_deque_t *merged = ds_deque_clone(deque);
        ds_deque_push_all(merged, values);
        return merged;
    }

    ARRAY_OR_TRAVERSABLE_REQUIRED();
    return NULL;
}

void ds_deque_sort_callback(ds_deque_t *deque)
{
    ds_deque_reset_head(deque);
    ds_user_sort_zval_buffer(deque->buffer, deque->size);
}

void ds_deque_sort(ds_deque_t *deque)
{
    ds_deque_reset_head(deque);
    ds_sort_zval_buffer(deque->buffer, deque->size);
}

void ds_deque_apply(ds_deque_t *deque, FCI_PARAMS)
{
    zval *value;
    zval retval;

    DS_DEQUE_FOREACH(deque, value) {
        fci.param_count = 1;
        fci.params      = value;
        fci.retval      = &retval;

        if (zend_call_function(&fci, &fci_cache) == FAILURE || Z_ISUNDEF(retval)) {
            return;
        }

        zval_ptr_dtor(value);
        ZVAL_COPY_VALUE(value, &retval);
    }

    DS_DEQUE_FOREACH_END();
}

ds_deque_t *ds_deque_map(ds_deque_t *deque, FCI_PARAMS)
{
    zval retval;
    zval *value;
    zval *buffer = ds_allocate_zval_buffer(deque->capacity);
    zval *target = buffer;

    DS_DEQUE_FOREACH(deque, value) {
        fci.param_count = 1;
        fci.params      = value;
        fci.retval      = &retval;

        if (zend_call_function(&fci, &fci_cache) == FAILURE || Z_ISUNDEF(retval)) {

            // Release the values copied into the buffer on failure.
            target--;
            while (target > buffer) {
                zval_ptr_dtor(target--);
            }

            zval_ptr_dtor(&retval);
            efree(buffer);
            return NULL;
        }

        ZVAL_COPY(target++, &retval);
        zval_ptr_dtor(&retval);
    }
    DS_DEQUE_FOREACH_END();

    return ds_deque_from_buffer(buffer, deque->capacity, deque->size);
}

ds_deque_t *ds_deque_filter_callback(ds_deque_t *deque, FCI_PARAMS)
{
    if (deque->size == 0) {
        return ds_deque();

    } else {
        zval retval;
        zval *val;
        zval *buf = ds_allocate_zval_buffer(deque->capacity);
        zval *dst = buf;

        DS_DEQUE_FOREACH(deque, val) {
            fci.param_count = 1;
            fci.params      = val;
            fci.retval      = &retval;

            // Catch potential exceptions or other errors during comparison.
            if (zend_call_function(&fci, &fci_cache) == FAILURE || Z_ISUNDEF(retval)) {

                // Release the values copied into the buffer on failure.
                dst--;
                while (dst >= buf) {
                    zval_ptr_dtor(dst--);
                }

                zval_ptr_dtor(&retval);
                efree(buf);
                return NULL;
            }

            // Copy the value into the buffer if the callback returned true.
            if (EXPECTED_BOOL_IS_TRUE(&retval)) {
                ZVAL_COPY(dst++, val);
            }

            zval_ptr_dtor(&retval);
        }

        DS_DEQUE_FOREACH_END();
        return ds_deque_from_buffer(buf, ds_deque_get_capacity_for_size(dst - buf), (dst - buf));
    }
}

ds_deque_t *ds_deque_filter(ds_deque_t *deque)
{
    if (deque->size == 0) {
        return ds_deque();

    } else {
        zval *buf = ds_allocate_zval_buffer(deque->capacity);
        zval *dst = buf;
        zval *src = NULL;

        DS_DEQUE_FOREACH(deque, src) {
            if (zend_is_true(src)) {
                ZVAL_COPY(dst++, src);
            }
        }
        DS_DEQUE_FOREACH_END();

        return ds_deque_from_buffer(buf, ds_deque_get_capacity_for_size(dst - buf), (dst - buf));
    }
}

void ds_deque_reduce(ds_deque_t *deque, zval *initial, zval *return_value, FCI_PARAMS)
{
    zval *value;
    zval carry;
    zval params[2];

    if (initial == NULL) {
        ZVAL_NULL(&carry);
    } else {
        ZVAL_COPY_VALUE(&carry, initial);
    }

    DS_DEQUE_FOREACH(deque, value) {
        ZVAL_COPY_VALUE(&params[0], &carry);
        ZVAL_COPY_VALUE(&params[1], value);

        fci.param_count = 2;
        fci.params      = params;
        fci.retval      = &carry;

        if (zend_call_function(&fci, &fci_cache) == FAILURE || Z_ISUNDEF(carry)) {
            zval_ptr_dtor(&carry);
            ZVAL_NULL(return_value);
            return;
        }

        Z_TRY_DELREF_P(&carry);
    }
    DS_DEQUE_FOREACH_END();
    ZVAL_COPY(return_value, &carry);
}

ds_deque_t *ds_deque_slice(ds_deque_t *deque, zend_long index, zend_long length)
{
    ds_normalize_slice_args(&index, &length, deque->size);

    if (length == 0) {
        return ds_deque();

    } else {
        ds_deque_t *result = ds_deque_preallocated(length);

        for (; length > 0; length--) {
            ds_deque_push(result, ds_deque_lookup(deque, index++));
        }

        return result;
    }
}

void ds_deque_sum(ds_deque_t *deque, zval *return_value)
{
    zval *value;

    ZVAL_LONG(return_value, 0);

    DS_DEQUE_FOREACH(deque, value) {
        DS_ADD_TO_SUM(value, return_value);
    }
    DS_DEQUE_FOREACH_END();
}
