package org.sfm.utils;

import java.io.IOException;
import java.io.Reader;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLong;

public class ParallelReader extends Reader {
    private static final int DEFAULT_MAX_READ = 8192;

    private final Reader reader;
    private final DataProducer dataProducer;
    private final char[] buffer;

    private final int bufferMask;
    private final int maxRead;

    private AtomicLong tail = new AtomicLong();
    private AtomicLong head = new AtomicLong();

    private final long capacity;
    private long tailCache;
    private long headCache;

    private final long padding;


    public ParallelReader(Reader reader, Executor executorService) {
        this(reader, executorService, 1 << 13);
    }

    public ParallelReader(Reader reader, Executor executorService, int bufferSize) {
        this(reader, executorService, bufferSize, DEFAULT_MAX_READ);
    }
    public ParallelReader(Reader reader, Executor executorService, int bufferSize, int maxRead) {
        int powerOf2 =  1 << 32 - Integer.numberOfLeadingZeros(bufferSize - 1);
        padding = powerOf2 <= 1024 ? 0 : 64;
        this.reader = reader;
        buffer = new char[powerOf2];
        bufferMask = buffer.length - 1;
        dataProducer = new DataProducer();
        executorService.execute(dataProducer);
        capacity = buffer.length;
        this.maxRead = maxRead;
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        final long currentHead = head.get();
        do {
            if (currentHead < tailCache) {
                int l = read(cbuf, off, len, currentHead, tailCache);

                head.lazySet(currentHead + l);
                return l;
            }

            tailCache = tail.get();
            if (currentHead >= tailCache) {
                if (!dataProducer.run) {
                    if (dataProducer.exception != null) {
                        throw dataProducer.exception;
                    }
                    tailCache = tail.get();
                    if (currentHead >= tailCache) {
                        return -1;
                    }
                }
                waitingStrategy();
            }
        } while(true);
    }

    @Override
    public int read() throws IOException {

        final long currentHead = head.get();
        do {
            if (currentHead < tailCache) {

                int headIndex = (int) (currentHead & bufferMask);

                char c = buffer[headIndex];

                head.lazySet(currentHead + 1);

                return c;
            }

            tailCache = tail.get();
            if (currentHead >= tailCache) {
                if (!dataProducer.run) {
                    if (dataProducer.exception != null) {
                        throw dataProducer.exception;
                    }
                    tailCache = tail.get();
                    if (currentHead >= tailCache) {
                        return -1;
                    }
                }
                waitingStrategy();
            }
        } while(true);
    }

    private void waitingStrategy() {
        Thread.yield();
    }

    private int read(char[] cbuf, int off, int len, long currentHead, long currentTail) {

        int headIndex = (int) (currentHead & bufferMask);
        int usedLength = (int) (currentTail - currentHead);

        int block1Length = Math.min(len, Math.min(usedLength, (int) (capacity - headIndex)));
        //int block2Length =  Math.min(len, usedLength) - block1Length;

        System.arraycopy(buffer, headIndex, cbuf, off, block1Length);
        //System.arraycopy(buffer, 0, cbuf, off+ block1Length, block2Length);

        return block1Length;// + block2Length;
    }

    @Override
    public void close() throws IOException {
        dataProducer.stop();
        reader.close();
    }

    private class DataProducer implements Runnable {
        private volatile boolean run = true;
        private volatile IOException exception;

        @Override
        public void run() {
            long currentTail = tail.get();
            while(run) {

                final long wrapPoint = currentTail - buffer.length;

                if (headCache - padding <= wrapPoint) {
                    headCache = head.get();
                    if (headCache <= wrapPoint) {
                        waitingStrategy();
                        continue;
                    }
                }

                try {
                    int r =  read(currentTail, headCache);
                    if (r == -1) {
                        run = false;
                    } else {
                        currentTail += r;
                        tail.lazySet(currentTail);
                    }
                } catch (IOException e) {
                    exception = e;
                    run = false;
                }
            }
        }

        private int read(long currentTail, long currentHead) throws IOException {
            long used = currentTail - currentHead;
            long available = capacity - used;
            int tailIndex = (int) (currentTail & bufferMask);
            int realEnd = (int) Math.min(tailIndex + available,  capacity);
            int realAvailable = realEnd - tailIndex;

            return reader.read(buffer, tailIndex, Math.min(realAvailable, maxRead));
        }

        public void stop() {
            run = false;
        }
    }
}

@@ -3527,18 +3527,16 @@
         
-//
 int bloc
@@ -3668,18 +3668,16 @@
         
-//
 System.a
@@ -3768,11 +3768,8 @@
 ngth
-;//
  + b

fix test, better behavior for parallel reader