#include <stdlib.h>
#include <string.h>

#include "queue.h"

#define IS_CONTIGUOUS(q) ((q)->start <= (q)->end)

#define IS_FULL(q) (queue_size(q) + 1 >= (q)->capacity)


Queue *queue_new() {

    Queue *q = calloc(1, sizeof(Queue));
    return q;

}

void queue_delete(Queue *q) {

    queue_clear(q);
    free(q);

}

void queue_init(Queue *q) {

    memset(q, 0, sizeof(Queue));

}

void queue_clear(Queue *q) {

    if (q->elements) {
        free(q->elements);
        queue_init(q);
    }

}

void queue_grow(Queue *q, size_t n) {

    if (n == 0) {
        n = QUEUE_DEFAULT_CAPACITY;
    }

    if (n <= q->capacity) {
        return;
    }

    size_t len = n * sizeof(void *);

    if (IS_CONTIGUOUS(q)) {
        q->elements = realloc(q->elements, len);
    }
    else {
        void **old_elements = q->elements;
        void **new_elements = malloc(len);
        size_t chunk = q->capacity - q->start;
        memcpy(new_elements, old_elements + q->start, chunk * sizeof(void *));
        memcpy(new_elements + chunk, old_elements, q->end * sizeof(void *));
        free(old_elements);
        q->elements = new_elements;
        q->start = 0;
        q->end += chunk;
    }

    q->capacity = n;

}

void queue_add(Queue *q, void *element) {

    size_t end;
    size_t next_end;

    if (IS_FULL(q)) {
        queue_grow(q, q->capacity << 1);
        end = q->end;
        next_end = end + 1;  // modulo not needed; growth ensures that there is space after end pointer
    }
    else {
        end = q->end;
        next_end = (end + 1) % q->capacity;
    }

    q->elements[end] = element;
    q->end = next_end;

}

void queue_add_front(Queue *q, void *element) {

    size_t next_start;

    if (IS_FULL(q)) {
        queue_grow(q, q->capacity << 1);
        // Since the queue was full, q->start is guaranteed to be 0 after growth
        // Therefore the element will be placed at the last index of the array
        next_start = q->capacity - 1;  
    }
    else {
        next_start = (q->start + q->capacity - 1) % q->capacity;
    }

    q->elements[next_start] = element;
    q->start = next_start;

}

void *queue_remove(Queue *q) {

    if (q->start == q->end) {
        return NULL;
    }

    void *element = q->elements[q->start];
    q->start = (q->start + 1) % q->capacity;

    return element;

}
