package cc.redberry.pipe.util;

import cc.redberry.pipe.OutputPort;

import java.util.Arrays;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicInteger;

public final class Chunk<T> implements Iterable<T> {
    final Object[] data;
    final int size;

    public Chunk(Object[] data, int size) {
        this.data = data;
        this.size = size;
    }

    public Chunk(Object[] data) {
        this.data = data;
        this.size = data.length;
    }

    public Object[] toArray() {
        if (data.length == size)
            return data;
        else
            return Arrays.copyOf(data, size);
    }

    @SuppressWarnings("unchecked")
    public T get(int index) {
        return (T) data[index];
    }

    public int size() {
        return size;
    }

    @Override
    public Iterator<T> iterator() {
        return new It();
    }

    public OutputPort<T> outputPort() {
        return new Op();
    }

    /**
     * Reads a chunk from input port.
     *
     * <p>Normally returns chunk with {@code size == chunkSize}. Or chunk with {@code size < chunkSize} if there are no
     * elements left in port after this operation or {@code null} if there are no elements in port.</p>
     *
     * @param port      output port to read from
     * @param chunkSize desired chunk size
     * @return returns chunk with {@code size == chunkSize}, or chunk with {@code size < chunkSize} if there are no
     * elements left in port after this operation, or {@code null} if there are no elements in port
     */
    public static <T> Chunk<T> readChunk(OutputPort<T> port, int chunkSize) {
        Object[] buffer = new Object[chunkSize];
        int size = 0;
        for (; size < chunkSize; ++size) {
            Object element = port.take();
            if (element == null)
                break;
            buffer[size] = element;
        }
        if (size == 0)
            return null;
        return new Chunk<>(buffer, size);
    }

    private final class It implements Iterator<T> {
        int pointer = 0;

        @Override
        public boolean hasNext() {
            return pointer < size;
        }

        @Override
        public T next() {
            return get(pointer++);
        }

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

    private final class Op implements OutputPort<T> {
        final AtomicInteger pointer = new AtomicInteger();

        @Override
        public T take() {
            int index = pointer.getAndIncrement();
            if (index >= size)
                return null;
            return get(index);
        }
    }
}
