package queue;

import java.util.function.Predicate;

/**
 * @author Doschennikov Nikita (me@fymio.us)
 */
public abstract class AbstractQueue implements Queue {

    protected int size;

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

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

    @Override
    public void clear() {
        clearImpl();
        size = 0;
    }

    @Override
    public int count(Object element) {
        assert element != null : "Element must not be null";
        int cnt = 0;
        for (int i = 0; i < size; i++) {
            if (element.equals(get(i))) cnt++;
        }
        return cnt;
    }

    @Override
    public int countIf(Predicate<Object> predicate) {
        assert predicate != null : "Predicate must not be null";
        int cnt = 0;
        for (int i = 0; i < size; i++) {
            if (predicate.test(get(i))) cnt++;
        }
        return cnt;
    }

    @Override
    public int indexIf(Predicate<Object> predicate) {
        assert predicate != null : "Predicate must not be null";
        for (int i = 0; i < size; i++) {
            if (predicate.test(get(i))) return i;
        }
        return -1;
    }

    @Override
    public int lastIndexIf(Predicate<Object> predicate) {
        assert predicate != null : "Predicate must not be null";
        for (int i = size - 1; i >= 0; i--) {
            if (predicate.test(get(i))) return i;
        }
        return -1;
    }

    @Override
    public int indexOf(Object element) {
        assert element != null : "Predicate must not be null";
        for (int i = 0; i < size; i++) {
            if (get(i).equals(element)) return i;
        }
        return -1;
    }

    @Override
    public int lastIndexOf(Object element) {
        assert element != null : "Predicate must not be null";
        for (int i = size - 1; i >= 0; i--) {
            if (get(i).equals(element)) return i;
        }
        return -1;
    }

    @Override
    public boolean contains(Object element) {
        assert element != null : "Element must not be null";
        return indexOf(element) != -1;
    }

    @Override
    public boolean removeFirst(Object element) {
        assert element != null : "Element must not be null";
        int originalSize = size;
        boolean found = false;
        for (int i = 0; i < originalSize; i++) {
            Object elem = dequeue();
            if (!found && element.equals(elem)) {
                found = true;
            } else {
                enqueue(elem);
            }
        }
        return found;
    }

    @Override
    public Queue getNth(int n) {
        Queue result = createEmpty();
        for (int i = 0; i < size; i++) {
            Object elem = get(i);
            if ((i + 1) % n == 0) result.enqueue(elem);
        }
        return result;
    }

    @Override
    public Queue removeNth(int n) {
        Queue result = createEmpty();
        int originalSize = size;
        for (int i = 0; i < originalSize; i++) {
            if ((i + 1) % n == 0) result.enqueue(get(i));
        }
        Queue temp = createEmpty();
        for (int i = 0; i < originalSize; i++) {
            Object elem = dequeue();
            if ((i + 1) % n != 0) temp.enqueue(elem);
        }
        while (!temp.isEmpty()) enqueue(temp.dequeue());
        return result;
    }

    @Override
    public void dropNth(int n) {
        int originalSize = size;
        Queue temp = createEmpty();
        for (int i = 0; i < originalSize; i++) {
            Object elem = dequeue();
            if ((i + 1) % n != 0) temp.enqueue(elem);
        }
        while (!temp.isEmpty()) enqueue(temp.dequeue());
    }

    @Override
    public void removeIf(java.util.function.Predicate<Object> predicate) {
        assert predicate != null : "Predicate must not be null";
        int originalSize = size;
        for (int i = 0; i < originalSize; i++) {
            Object elem = dequeue();
            if (!predicate.test(elem)) enqueue(elem);
        }
    }

    @Override
    public void retainIf(java.util.function.Predicate<Object> predicate) {
        assert predicate != null : "Predicate must not be null";
        removeIf(predicate.negate());
    }

    @Override
    public void removeAll(Object element) {
        assert element != null : "Element must not be null";
        removeIf(element::equals);
    }

    @Override
    public void retainAll(Object element) {
        assert element != null : "Element must not be null";
        removeIf(e -> !element.equals(e));
    }

    protected abstract AbstractQueue createEmpty();

    protected abstract Object get(int i);

    protected abstract void clearImpl();
}
