// 
// Decompiled by Procyon v0.6.0
// 

package org.jline.utils;

import java.nio.charset.CodingErrorAction;
import java.nio.charset.CoderResult;
import java.nio.ByteBuffer;
import java.nio.charset.CharsetEncoder;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.io.Writer;
import java.nio.CharBuffer;
import java.io.Reader;

public class PumpReader extends Reader
{
    private static final int EOF = -1;
    private static final int DEFAULT_BUFFER_SIZE = 4096;
    private final CharBuffer readBuffer;
    private final CharBuffer writeBuffer;
    private final Writer writer;
    private boolean closed;
    
    public PumpReader() {
        this(4096);
    }
    
    public PumpReader(final int bufferSize) {
        final char[] buf = new char[Math.max(bufferSize, 2)];
        this.readBuffer = CharBuffer.wrap(buf);
        this.writeBuffer = CharBuffer.wrap(buf);
        this.writer = new Writer(this);
        this.readBuffer.limit();
    }
    
    public java.io.Writer getWriter() {
        return this.writer;
    }
    
    public java.io.InputStream createInputStream(final Charset charset) {
        return new InputStream(this, charset);
    }
    
    private boolean waitForMoreInput() throws InterruptedIOException {
        if (!this.writeBuffer.hasRemaining()) {
            throw new AssertionError((Object)"No space in write buffer");
        }
        final int oldRemaining = this.readBuffer.remaining();
        while (!this.closed) {
            this.notifyAll();
            try {
                this.wait();
            }
            catch (final InterruptedException e) {
                throw new InterruptedIOException();
            }
            if (this.readBuffer.remaining() > oldRemaining) {
                return true;
            }
        }
        return false;
    }
    
    private boolean wait(final CharBuffer buffer) throws InterruptedIOException {
        while (!buffer.hasRemaining()) {
            if (this.closed) {
                return false;
            }
            this.notifyAll();
            try {
                this.wait();
                continue;
            }
            catch (final InterruptedException e) {
                throw new InterruptedIOException();
            }
            break;
        }
        return true;
    }
    
    private boolean waitForInput() throws InterruptedIOException {
        return this.wait(this.readBuffer);
    }
    
    private void waitForBufferSpace() throws InterruptedIOException, ClosedException {
        if (!this.wait(this.writeBuffer) || this.closed) {
            throw new ClosedException();
        }
    }
    
    private static boolean rewind(final CharBuffer buffer, final CharBuffer other) {
        if (buffer.position() > other.position()) {
            other.limit();
        }
        if (buffer.position() == buffer.capacity()) {
            buffer.rewind();
            buffer.limit();
            return true;
        }
        return false;
    }
    
    private boolean rewindReadBuffer() {
        final boolean rw = rewind(this.readBuffer, this.writeBuffer) && this.readBuffer.hasRemaining();
        this.notifyAll();
        return rw;
    }
    
    private void rewindWriteBuffer() {
        rewind(this.writeBuffer, this.readBuffer);
        this.notifyAll();
    }
    
    @Override
    public synchronized boolean ready() {
        return this.readBuffer.hasRemaining();
    }
    
    public synchronized int available() {
        int count = this.readBuffer.remaining();
        if (this.writeBuffer.position() < this.readBuffer.position()) {
            count += this.writeBuffer.position();
        }
        return count;
    }
    
    @Override
    public synchronized int read() throws IOException {
        if (!this.waitForInput()) {
            return -1;
        }
        final int b = this.readBuffer.get();
        this.rewindReadBuffer();
        return b;
    }
    
    private int copyFromBuffer(final char[] cbuf, final int off, int len) {
        len = Math.min(len, this.readBuffer.remaining());
        this.readBuffer.get(cbuf, off, len);
        return len;
    }
    
    @Override
    public synchronized int read(final char[] cbuf, final int off, final int len) throws IOException {
        if (len == 0) {
            return 0;
        }
        if (!this.waitForInput()) {
            return -1;
        }
        int count = this.copyFromBuffer(cbuf, off, len);
        if (this.rewindReadBuffer() && count < len) {
            count += this.copyFromBuffer(cbuf, off + count, len - count);
            this.rewindReadBuffer();
        }
        return count;
    }
    
    @Override
    public synchronized int read(final CharBuffer target) throws IOException {
        if (!target.hasRemaining()) {
            return 0;
        }
        if (!this.waitForInput()) {
            return -1;
        }
        int count = this.readBuffer.read(target);
        if (this.rewindReadBuffer() && target.hasRemaining()) {
            count += this.readBuffer.read(target);
            this.rewindReadBuffer();
        }
        return count;
    }
    
    private void encodeBytes(final CharsetEncoder encoder, final ByteBuffer output) throws IOException {
        final int oldPos = output.position();
        CoderResult result = encoder.encode(this.readBuffer, output, false);
        final int encodedCount = output.position() - oldPos;
        if (result.isUnderflow()) {
            final boolean hasMoreInput = this.rewindReadBuffer();
            boolean reachedEndOfInput = false;
            if (encodedCount == 0 && !hasMoreInput) {
                reachedEndOfInput = !this.waitForMoreInput();
            }
            result = encoder.encode(this.readBuffer, output, reachedEndOfInput);
            if (result.isError()) {
                result.throwException();
            }
            if (!reachedEndOfInput && output.position() - oldPos == 0) {
                throw new AssertionError((Object)"Failed to encode any chars");
            }
            this.rewindReadBuffer();
        }
        else if (result.isOverflow()) {
            if (encodedCount == 0) {
                throw new AssertionError((Object)"Output buffer has not enough space");
            }
        }
        else {
            result.throwException();
        }
    }
    
    synchronized int readBytes(final CharsetEncoder encoder, final byte[] b, final int off, final int len) throws IOException {
        if (!this.waitForInput()) {
            return 0;
        }
        final ByteBuffer output = ByteBuffer.wrap(b, off, len);
        this.encodeBytes(encoder, output);
        return output.position() - off;
    }
    
    synchronized void readBytes(final CharsetEncoder encoder, final ByteBuffer output) throws IOException {
        if (!this.waitForInput()) {
            return;
        }
        this.encodeBytes(encoder, output);
    }
    
    synchronized void write(final char c) throws IOException {
        this.waitForBufferSpace();
        this.writeBuffer.put(c);
        this.rewindWriteBuffer();
    }
    
    synchronized void write(final char[] cbuf, int off, int len) throws IOException {
        while (len > 0) {
            this.waitForBufferSpace();
            final int count = Math.min(len, this.writeBuffer.remaining());
            this.writeBuffer.put(cbuf, off, count);
            off += count;
            len -= count;
            this.rewindWriteBuffer();
        }
    }
    
    synchronized void write(final String str, int off, int len) throws IOException {
        final char[] buf = this.writeBuffer.array();
        while (len > 0) {
            this.waitForBufferSpace();
            final int count = Math.min(len, this.writeBuffer.remaining());
            str.getChars(off, off + count, buf, this.writeBuffer.position());
            this.writeBuffer.position();
            off += count;
            len -= count;
            this.rewindWriteBuffer();
        }
    }
    
    synchronized void flush() {
        if (this.readBuffer.hasRemaining()) {
            this.notifyAll();
        }
    }
    
    @Override
    public synchronized void close() throws IOException {
        this.closed = true;
        this.notifyAll();
    }
    
    private static class Writer extends java.io.Writer
    {
        private final PumpReader reader;
        
        private Writer(final PumpReader reader) {
            this.reader = reader;
        }
        
        @Override
        public void write(final int c) throws IOException {
            this.reader.write((char)c);
        }
        
        @Override
        public void write(final char[] cbuf, final int off, final int len) throws IOException {
            this.reader.write(cbuf, off, len);
        }
        
        @Override
        public void write(final String str, final int off, final int len) throws IOException {
            this.reader.write(str, off, len);
        }
        
        @Override
        public void flush() throws IOException {
            this.reader.flush();
        }
        
        @Override
        public void close() throws IOException {
            this.reader.close();
        }
    }
    
    private static class InputStream extends java.io.InputStream
    {
        private final PumpReader reader;
        private final CharsetEncoder encoder;
        private final ByteBuffer buffer;
        
        private InputStream(final PumpReader reader, final Charset charset) {
            this.reader = reader;
            this.encoder = charset.newEncoder().onUnmappableCharacter(CodingErrorAction.REPLACE).onMalformedInput(CodingErrorAction.REPLACE);
            (this.buffer = ByteBuffer.allocate((int)Math.ceil(this.encoder.maxBytesPerChar() * 2.0f))).limit();
        }
        
        @Override
        public int available() throws IOException {
            return (int)(this.reader.available() * (double)this.encoder.averageBytesPerChar()) + this.buffer.remaining();
        }
        
        @Override
        public int read() throws IOException {
            if (!this.buffer.hasRemaining() && !this.readUsingBuffer()) {
                return -1;
            }
            return this.buffer.get() & 0xFF;
        }
        
        private boolean readUsingBuffer() throws IOException {
            this.buffer.clear();
            this.reader.readBytes(this.encoder, this.buffer);
            this.buffer.flip();
            return this.buffer.hasRemaining();
        }
        
        private int copyFromBuffer(final byte[] b, final int off, int len) {
            len = Math.min(len, this.buffer.remaining());
            this.buffer.get(b, off, len);
            return len;
        }
        
        @Override
        public int read(final byte[] b, int off, int len) throws IOException {
            if (len == 0) {
                return 0;
            }
            int read;
            if (this.buffer.hasRemaining()) {
                read = this.copyFromBuffer(b, off, len);
                if (read == len) {
                    return len;
                }
                off += read;
                len -= read;
            }
            else {
                read = 0;
            }
            if (len >= this.buffer.capacity()) {
                read += this.reader.readBytes(this.encoder, b, off, len);
            }
            else if (this.readUsingBuffer()) {
                read += this.copyFromBuffer(b, off, len);
            }
            return (read == 0) ? -1 : read;
        }
        
        @Override
        public void close() throws IOException {
            this.reader.close();
        }
    }
}
