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

package org.jline.utils;

import java.nio.charset.CharsetDecoder;
import java.io.IOException;
import java.nio.charset.CodingErrorAction;
import java.nio.CharBuffer;
import java.nio.ByteBuffer;
import java.nio.charset.CharsetEncoder;
import java.io.Reader;
import java.io.InputStream;
import java.nio.charset.Charset;

public class NonBlocking
{
    public static NonBlockingPumpReader nonBlockingPumpReader() {
        return new NonBlockingPumpReader();
    }
    
    public static NonBlockingPumpReader nonBlockingPumpReader(final int size) {
        return new NonBlockingPumpReader(size);
    }
    
    public static NonBlockingPumpInputStream nonBlockingPumpInputStream() {
        return new NonBlockingPumpInputStream();
    }
    
    public static NonBlockingPumpInputStream nonBlockingPumpInputStream(final int size) {
        return new NonBlockingPumpInputStream(size);
    }
    
    public static NonBlockingInputStream nonBlockingStream(final NonBlockingReader reader, final Charset encoding) {
        return new NonBlockingReaderInputStream(reader, encoding);
    }
    
    public static NonBlockingInputStream nonBlocking(final String name, final InputStream inputStream) {
        if (inputStream instanceof NonBlockingInputStream) {
            return (NonBlockingInputStream)inputStream;
        }
        return new NonBlockingInputStreamImpl(name, inputStream);
    }
    
    public static NonBlockingReader nonBlocking(final String name, final Reader reader) {
        if (reader instanceof NonBlockingReader) {
            return (NonBlockingReader)reader;
        }
        return new NonBlockingReaderImpl(name, reader);
    }
    
    public static NonBlockingReader nonBlocking(final String name, final InputStream inputStream, final Charset encoding) {
        return new NonBlockingInputStreamReader(nonBlocking(name, inputStream), encoding);
    }
    
    private static class NonBlockingReaderInputStream extends NonBlockingInputStream
    {
        private final NonBlockingReader reader;
        private final CharsetEncoder encoder;
        private final ByteBuffer bytes;
        private final CharBuffer chars;
        
        private NonBlockingReaderInputStream(final NonBlockingReader reader, final Charset charset) {
            this.reader = reader;
            this.encoder = charset.newEncoder().onUnmappableCharacter(CodingErrorAction.REPLACE).onMalformedInput(CodingErrorAction.REPLACE);
            this.bytes = ByteBuffer.allocate(4);
            this.chars = CharBuffer.allocate(2);
            this.bytes.limit();
            this.chars.limit();
        }
        
        @Override
        public int available() {
            return (int)(this.reader.available() * this.encoder.averageBytesPerChar()) + this.bytes.remaining();
        }
        
        @Override
        public void close() throws IOException {
            this.reader.close();
        }
        
        @Override
        public int read(final long timeout, final boolean isPeek) throws IOException {
            final Timeout t = new Timeout(timeout);
            while (!this.bytes.hasRemaining() && !t.elapsed()) {
                final int c = this.reader.read(t.timeout());
                if (c == -1) {
                    return -1;
                }
                if (c < 0) {
                    continue;
                }
                if (!this.chars.hasRemaining()) {
                    this.chars.position();
                    this.chars.limit();
                }
                final int l = this.chars.limit();
                this.chars.array()[this.chars.arrayOffset() + l] = (char)c;
                this.chars.limit();
                this.bytes.clear();
                this.encoder.encode(this.chars, this.bytes, false);
                this.bytes.flip();
            }
            if (!this.bytes.hasRemaining()) {
                return -2;
            }
            if (isPeek) {
                return this.bytes.get(this.bytes.position());
            }
            return this.bytes.get();
        }
    }
    
    private static class NonBlockingInputStreamReader extends NonBlockingReader
    {
        private final NonBlockingInputStream input;
        private final CharsetDecoder decoder;
        private final ByteBuffer bytes;
        private final CharBuffer chars;
        
        public NonBlockingInputStreamReader(final NonBlockingInputStream inputStream, final Charset encoding) {
            this(inputStream, ((encoding != null) ? encoding : Charset.defaultCharset()).newDecoder().onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(CodingErrorAction.REPLACE));
        }
        
        public NonBlockingInputStreamReader(final NonBlockingInputStream input, final CharsetDecoder decoder) {
            this.input = input;
            this.decoder = decoder;
            this.bytes = ByteBuffer.allocate(2048);
            this.chars = CharBuffer.allocate(1024);
            this.bytes.limit();
            this.chars.limit();
        }
        
        @Override
        protected int read(final long timeout, final boolean isPeek) throws IOException {
            final Timeout t = new Timeout(timeout);
            while (!this.chars.hasRemaining() && !t.elapsed()) {
                final int b = this.input.read(t.timeout());
                if (b == -1) {
                    return -1;
                }
                if (b < 0) {
                    continue;
                }
                if (!this.bytes.hasRemaining()) {
                    this.bytes.position();
                    this.bytes.limit();
                }
                final int l = this.bytes.limit();
                this.bytes.array()[this.bytes.arrayOffset() + l] = (byte)b;
                this.bytes.limit();
                this.chars.clear();
                this.decoder.decode(this.bytes, this.chars, false);
                this.chars.flip();
            }
            if (!this.chars.hasRemaining()) {
                return -2;
            }
            if (isPeek) {
                return this.chars.get(this.chars.position());
            }
            return this.chars.get();
        }
        
        @Override
        public int readBuffered(final char[] b, final int off, final int len, final long timeout) throws IOException {
            if (b == null) {
                throw new NullPointerException();
            }
            if (off < 0 || len < 0 || off + len < b.length) {
                throw new IllegalArgumentException();
            }
            if (len == 0) {
                return 0;
            }
            if (this.chars.hasRemaining()) {
                final int r = Math.min(len, this.chars.remaining());
                this.chars.get(b, off, r);
                return r;
            }
            final Timeout t = new Timeout(timeout);
            while (!this.chars.hasRemaining() && !t.elapsed()) {
                if (!this.bytes.hasRemaining()) {
                    this.bytes.position();
                    this.bytes.limit();
                }
                final int nb = this.input.readBuffered(this.bytes.array(), this.bytes.limit(), this.bytes.capacity() - this.bytes.limit(), t.timeout());
                if (nb < 0) {
                    return nb;
                }
                this.bytes.limit();
                this.chars.clear();
                this.decoder.decode(this.bytes, this.chars, false);
                this.chars.flip();
            }
            final int nb = Math.min(len, this.chars.remaining());
            this.chars.get(b, off, nb);
            return nb;
        }
        
        @Override
        public void shutdown() {
            this.input.shutdown();
        }
        
        @Override
        public void close() throws IOException {
            this.input.close();
        }
    }
}
