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

package org.jline.utils;

import java.io.InterruptedIOException;
import java.io.IOException;
import java.io.Reader;

public class NonBlockingReaderImpl extends NonBlockingReader
{
    public static final int READ_EXPIRED = -2;
    private Reader in;
    private int ch;
    private String name;
    private boolean threadIsReading;
    private IOException exception;
    private long threadDelay;
    private Thread thread;
    
    public NonBlockingReaderImpl(final String name, final Reader in) {
        this.ch = -2;
        this.threadIsReading = false;
        this.exception = null;
        this.threadDelay = 60000L;
        this.in = in;
        this.name = name;
    }
    
    private synchronized void startReadingThreadIfNeeded() {
        if (this.thread == null) {
            (this.thread = new Thread(this::run)).setName(this.name + " non blocking reader thread");
            this.thread.setDaemon(true);
            this.thread.start();
        }
    }
    
    @Override
    public synchronized void shutdown() {
        if (this.thread != null) {
            this.notify();
        }
    }
    
    @Override
    public void close() throws IOException {
        this.in.close();
        this.shutdown();
    }
    
    @Override
    public synchronized boolean ready() throws IOException {
        return this.ch >= 0 || this.in.ready();
    }
    
    @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.exception != null) {
            assert this.ch == -2;
            final IOException toBeThrown = this.exception;
            this.exception = null;
            throw toBeThrown;
        }
        else {
            if (this.ch >= -1) {
                b[0] = (char)this.ch;
                this.ch = -2;
                return 1;
            }
            if (!this.threadIsReading && timeout <= 0L) {
                return this.in.read(b, off, len);
            }
            final int c = this.read(timeout, false);
            if (c >= 0) {
                b[off] = (char)c;
                return 1;
            }
            return c;
        }
    }
    
    @Override
    protected synchronized int read(final long timeout, final boolean isPeek) throws IOException {
        if (this.exception == null) {
            if (this.ch >= -1) {
                assert this.exception == null;
            }
            else if (!isPeek && timeout <= 0L && !this.threadIsReading) {
                this.ch = this.in.read();
            }
            else {
                if (!this.threadIsReading) {
                    this.threadIsReading = true;
                    this.startReadingThreadIfNeeded();
                    this.notifyAll();
                }
                final Timeout t = new Timeout(timeout);
                while (!t.elapsed()) {
                    try {
                        if (Thread.interrupted()) {
                            throw new InterruptedException();
                        }
                        this.wait(t.timeout());
                    }
                    catch (final InterruptedException e) {
                        this.exception = (IOException)new InterruptedIOException().initCause(e);
                    }
                    if (this.exception != null) {
                        assert this.ch == -2;
                        final IOException toBeThrown = this.exception;
                        if (!isPeek) {
                            this.exception = null;
                        }
                        throw toBeThrown;
                    }
                    else {
                        if (this.ch < -1) {
                            continue;
                        }
                        assert this.exception == null;
                        break;
                    }
                }
            }
            final int ret = this.ch;
            if (!isPeek) {
                this.ch = -2;
            }
            return ret;
        }
        assert this.ch == -2;
        final IOException toBeThrown2 = this.exception;
        if (!isPeek) {
            this.exception = null;
        }
        throw toBeThrown2;
    }
    
    private void run() {
        Log.debug("NonBlockingReader start");
        try {
            while (true) {
                synchronized (this) {
                    boolean needToRead = this.threadIsReading;
                    try {
                        if (!needToRead) {
                            this.wait(this.threadDelay);
                        }
                    }
                    catch (final InterruptedException ex) {}
                    needToRead = this.threadIsReading;
                    if (!needToRead) {
                        return;
                    }
                }
                int charRead = -2;
                IOException failure = null;
                try {
                    charRead = this.in.read();
                }
                catch (final IOException e) {
                    failure = e;
                }
                synchronized (this) {
                    this.exception = failure;
                    this.ch = charRead;
                    this.threadIsReading = false;
                    this.notify();
                }
            }
        }
        catch (final Throwable t) {
            Log.warn("Error in NonBlockingReader thread", t);
            Log.debug("NonBlockingReader shutdown");
            synchronized (this) {
                this.thread = null;
                this.threadIsReading = false;
            }
        }
        finally {
            Log.debug("NonBlockingReader shutdown");
            synchronized (this) {
                this.thread = null;
                this.threadIsReading = false;
            }
        }
    }
    
    public synchronized void clear() throws IOException {
        while (this.ready()) {
            this.read();
        }
    }
}
