package com.jscape.util.collections;

import com.jscape.util.ArrayTools;

/**
 * @author Alex Usun
 */
public class CircularBuffer<T>
        implements Iterable<T> {

    private final T[] data;
    private int position;
    private boolean overflow;

    @SuppressWarnings("unchecked")
    public CircularBuffer(int size) {
        this.data = (T[]) new Object[size];
    }

    public void put(T value) {
        this.data[this.position++] = value;
        adjustPosition();
    }

    public java.util.Iterator<T> iterator() {
        return new Iterator<T>(this.data, this.position, this.overflow);
    }

    public int size() {
        return this.data.length;
    }

    public int filled() {
        return this.overflow ? this.data.length : this.position;
    }

    public boolean isEmpty() {
        return filled() == 0;
    }

    public void reset() {
        this.position = 0;
        this.overflow = false;

        for (int i = 0; i < this.data.length; ++i) {
            this.data[i] = null;
        }
    }

    @Override
    public String toString() {
        return "CircularBuffer {"
               + "data=" + ArrayTools.toString(this.data)
               + ", position=" + this.position
               + ", overflow=" + this.overflow
               + '}';
    }

    private void adjustPosition() {
        if (this.position >= this.data.length) {
            this.position = 0;
            this.overflow = true;
        }
    }

    public static class Iterator<T>
            implements java.util.Iterator<T> {

        private final T[] data;
        private final int initialPosition;
        private final int length;
        private int position;
        private final boolean overflow;
        private int read;

        public Iterator(T[] data, int position, boolean overflow) {
            this.data = data;
            this.initialPosition = overflow ? position : 0;
            this.length = overflow ? data.length : position;
            this.position = this.initialPosition;
            this.overflow = overflow;
        }

        public boolean hasNext() {
            return this.read < this.length;
        }

        public T next() {
            T ret = this.data[this.position++];
            ++this.read;
            adjustPosition();

            return ret;
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

        public void reset() {
            this.position = this.initialPosition;
            this.read = 0;
        }

        @Override
        public String toString() {
            return "Iterator {"
                   + "data=" + ArrayTools.toString(this.data)
                   + ", initialPosition=" + this.initialPosition
                   + ", length=" + this.length
                   + ", position=" + this.position
                   + ", overflow=" + this.overflow
                   + ", read=" + this.read
                   + '}';
        }

        private void adjustPosition() {
            if (this.position >= this.data.length && this.overflow) {
                this.position = 0;
            }
        }

    }
}
