package tc.oc.commons.bukkit.util;

import gnu.trove.TLongCollection;
import gnu.trove.iterator.TLongIterator;

import java.util.NoSuchElementException;

/**
 * Minimal implementation of a FIFO queue of longs, stored efficiently in a primitive array.
 * Does not implement {@link gnu.trove.queue.TLongQueue} because it would be more trouble
 * than its worth to us.
 */
public class LongDeque {
    public static final int DEFAULT_CAPACITY = 16;

    private final long noEntryValue;
    private long[] buf;
    private int head, tail;

    public LongDeque() {
        this(DEFAULT_CAPACITY, 0);
    }

    public LongDeque(int capacity, long noEntryValue) {
        this.noEntryValue = noEntryValue;
        buf = new long[capacity];
    }

    private int advance(int index) {
        index++;
        return index < buf.length ? index : 0;
    }

    private void ensureCapacity(int delta) {
        int capacity = size() + delta;
        if (capacity >= buf.length) {
            // Find the smallest power of 2 that is big enough
            capacity |= (capacity >>> 1);
            capacity |= (capacity >>> 2);
            capacity |= (capacity >>> 4);
            capacity |= (capacity >>> 8);
            capacity |= (capacity >>> 16);
            capacity++;

            long[] bigger = new long[capacity];

            if(tail > head) {
                System.arraycopy(buf, head, bigger, 0, tail - head);
                tail -= head;
            } else {
                System.arraycopy(buf, head, bigger, 0, buf.length - head);
                System.arraycopy(buf, 0, bigger, buf.length - head, tail);
                tail += buf.length - head;
            }

            head = 0;
            buf = bigger;
        }
    }

    /**
     * Return head
     */
    public long peek() {
        if(isEmpty()) {
            return getNoEntryValue();
        } else {
            return buf[head];
        }
    }

    /**
     * Return head, throw if empty
     */
    public long element() {
        if(isEmpty()) throw new NoSuchElementException("queue is empty");
        return peek();
    }

    /**
     * Remove from head
     */
    public long poll() {
        if(isEmpty()) {
            return getNoEntryValue();
        } else {
            long value = buf[head];
            head = advance(head);
            return value;
        }
    }

    /**
     * Remove from head, throw if empty
     */
    public long remove() {
        if(isEmpty()) throw new NoSuchElementException("queue is empty");
        return poll();
    }

    private void addUnchecked(long value) {
        buf[tail] = value;
        tail = advance(tail);
    }

    /**
     * Add to head
     */
    public void offer(long value) {
        add(value);
    }

    /**
     * Add to head
     */
    public void add(long value) {
        ensureCapacity(1);
        addUnchecked(value);
    }

    public void addAll(TLongCollection values) {
        ensureCapacity(values.size());
        for(TLongIterator iter = values.iterator(); iter.hasNext();) {
            addUnchecked(iter.next());
        }
    }

    public void clear() {
        buf = new long[DEFAULT_CAPACITY];
        head = tail = 0;
    }

    public long getNoEntryValue() {
        return noEntryValue;
    }

    public int size() {
        int n = tail - head;
        if(n < 0) n += buf.length;
        return n;
    }

    public boolean isEmpty() {
        return head == tail;
    }

    public boolean contains(long entry) {
        for(int i = head; i != tail; i = advance(i)) {
            if(buf[i] == entry) return true;
        }
        return false;
    }
}
