package com.hithomelabs.princeton1.module4;

import java.util.Iterator;

import javax.annotation.Nonnull;

public class ArrayQueue<E> extends Queue<E>{

    // insertion from tail, removal from head
    public static final int DEFAULT_CAPACITY = 10;
    private int capacity;
    private int tail;
    private int head;
    private int size;
    private E[] arr;

    public ArrayQueue(){
        this(DEFAULT_CAPACITY);
    }

    public ArrayQueue(int capacity){
        this.capacity = capacity;
        arr = (E[]) new Object[this.capacity];
        size = 0;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public E dequeue() {
        if(isEmpty())
            return null;
        else{
            E element = arr[head];
            // Garbage collection
            arr[head] = null;
            head = (head+1)%capacity;
            size = size - 1;
            if(capacity >= 40 && size < capacity/4){
                capacity = capacity/2;
                resize(capacity, capacity*2);
            }
            return element;
        }
    }

    @Override
    public void enqueue(E element) {
        // We plan capacity expansion if needed
        if (size == capacity){
            capacity = capacity * 2;
            resize(capacity, capacity/2);
        }
        arr[tail] = element;
        tail = (tail + 1) % capacity;
        ++size;
    }

    // When resize takes place always the original array is full, so copy the complete array as is
    private void resize(int capacity, int oldCapacity) {
        E[] resizedArr = (E[]) new Object[capacity];
        for(int i = 0; i < size; i++) {
            resizedArr[i] = arr[head];
            // halving because capacity has now doubled
            arr[head] = null;
            head = (head + 1) % oldCapacity;
        }
        arr = resizedArr;
        // When resizing takes place, we bring the head to 0 and the tail to size
        // tail is where new inserts will be made and head will be where elements will be removed
        tail = size;
        head = 0;

    }

    @Override
    public int size() {
        return size;
    }

    @Override
    @Nonnull
    public Iterator<E> iterator() {
        return new Iterator<E>() {
            int counter = size;
            int pointer = head;
            @Override
            public boolean hasNext() {
                return counter != 0;
            }

            @Override
            public E next() {
                E element = arr[pointer];
                pointer = (pointer + 1)% capacity;
                --counter;
                return element;
            }
        };
    }
}
