/*
 * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (https://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.dev.util;

import java.util.Iterator;

/**
 * A ring buffer that supports concurrent access.
 *
 * @param <K> the key type
 */
public class ConcurrentRing<K> {

    /**
     * The ring buffer.
     */
    K[] buffer;

    /**
     * The read position.
     */
    volatile int readPos;

    /**
     * The write position.
     */
    volatile int writePos;

    @SuppressWarnings("unchecked")
    public ConcurrentRing() {
        buffer = (K[]) new Object[4];
    }

    /**
     * Get the first element, or null if none.
     *
     * @return the first element
     */
    public K peekFirst() {
        return buffer[getIndex(readPos)];
    }

    /**
     * Get the last element, or null if none.
     *
     * @return the last element
     */
    public K peekLast() {
        return buffer[getIndex(writePos - 1)];
    }

    /**
     * Add an element at the end.
     *
     * @param obj the element
     */
    public void add(K obj) {
        buffer[getIndex(writePos)] = obj;
        writePos++;
        if (writePos - readPos >= buffer.length) {
            // double the capacity
            @SuppressWarnings("unchecked")
            K[] b2 = (K[]) new Object[buffer.length * 2];
            for (int i = readPos; i < writePos; i++) {
                K x = buffer[getIndex(i)];
                int i2 = i & b2.length - 1;
                b2[i2] = x;
            }
            buffer = b2;
        }
    }

    /**
     * Remove the first element, if it matches.
     *
     * @param obj the element to remove
     * @return true if the element matched and was removed
     */
    public boolean removeFirst(K obj) {
        int p = readPos;
        int idx = getIndex(p);
        if (buffer[idx] != obj) {
            return false;
        }
        buffer[idx] = null;
        readPos = p + 1;
        return true;
    }

    /**
     * Remove the last element, if it matches.
     *
     * @param obj the element to remove
     * @return true if the element matched and was removed
     */
    public boolean removeLast(K obj) {
        int p = writePos;
        int idx = getIndex(p - 1);
        if (buffer[idx] != obj) {
            return false;
        }
        buffer[idx] = null;
        writePos = p - 1;
        return true;
    }

    /**
     * Get the index in the array of the given position.
     *
     * @param pos the position
     * @return the index
     */
    int getIndex(int pos) {
        return pos & (buffer.length - 1);
    }

    /**
     * Get an iterator over all entries.
     *
     * @return the iterator
     */
    public Iterator<K> iterator() {
        return new Iterator<K>() {

            int offset;

            @Override
            public boolean hasNext() {
                return readPos + offset < writePos;
            }

            @Override
            public K next() {
                if (buffer[getIndex(readPos + offset)] == null) {
                    System.out.println("" + readPos);
                    System.out.println("" + getIndex(readPos + offset));
                    System.out.println("null?");
                }
                return buffer[getIndex(readPos + offset++)];
            }

        };
    }

}
