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

package org.jline.terminal.impl;

import org.jline.terminal.Cursor;
import java.util.function.IntConsumer;
import org.jline.terminal.Size;
import org.jline.terminal.Attributes;
import java.io.IOException;
import org.jline.terminal.Terminal;
import java.nio.charset.Charset;
import java.io.OutputStream;
import java.io.InputStream;
import java.util.concurrent.atomic.AtomicBoolean;
import org.jline.terminal.spi.TerminalProvider;

public class ExternalTerminal extends LineDisciplineTerminal
{
    private final TerminalProvider provider;
    protected final AtomicBoolean closed;
    protected final InputStream masterInput;
    protected final Object lock;
    protected boolean paused;
    protected Thread pumpThread;
    
    public ExternalTerminal(final String name, final String type, final InputStream masterInput, final OutputStream masterOutput, final Charset encoding) throws IOException {
        this(null, name, type, masterInput, masterOutput, encoding, encoding, encoding, encoding, Terminal.SignalHandler.SIG_DFL);
    }
    
    public ExternalTerminal(final TerminalProvider provider, final String name, final String type, final InputStream masterInput, final OutputStream masterOutput, final Charset encoding, final Terminal.SignalHandler signalHandler) throws IOException {
        this(provider, name, type, masterInput, masterOutput, encoding, encoding, encoding, signalHandler, false);
    }
    
    public ExternalTerminal(final TerminalProvider provider, final String name, final String type, final InputStream masterInput, final OutputStream masterOutput, final Charset encoding, final Charset stdinEncoding, final Charset stdoutEncoding, final Charset stderrEncoding, final Terminal.SignalHandler signalHandler) throws IOException {
        this(provider, name, type, masterInput, masterOutput, encoding, stdinEncoding, stdoutEncoding, signalHandler, false);
    }
    
    public ExternalTerminal(final TerminalProvider provider, final String name, final String type, final InputStream masterInput, final OutputStream masterOutput, final Charset encoding, final Terminal.SignalHandler signalHandler, final boolean paused) throws IOException {
        this(provider, name, type, masterInput, masterOutput, encoding, encoding, encoding, signalHandler, paused, null, null);
    }
    
    public ExternalTerminal(final TerminalProvider provider, final String name, final String type, final InputStream masterInput, final OutputStream masterOutput, final Charset encoding, final Charset inputEncoding, final Charset outputEncoding, final Terminal.SignalHandler signalHandler, final boolean paused) throws IOException {
        this(provider, name, type, masterInput, masterOutput, encoding, inputEncoding, outputEncoding, signalHandler, paused, null, null);
    }
    
    public ExternalTerminal(final TerminalProvider provider, final String name, final String type, final InputStream masterInput, final OutputStream masterOutput, final Charset encoding, final Terminal.SignalHandler signalHandler, final boolean paused, final Attributes attributes, final Size size) throws IOException {
        this(provider, name, type, masterInput, masterOutput, encoding, encoding, encoding, signalHandler, paused, attributes, size);
    }
    
    public ExternalTerminal(final TerminalProvider provider, final String name, final String type, final InputStream masterInput, final OutputStream masterOutput, final Charset encoding, final Charset inputEncoding, final Charset outputEncoding, final Terminal.SignalHandler signalHandler, final boolean paused, final Attributes attributes, final Size size) throws IOException {
        super(name, type, masterOutput, encoding, inputEncoding, outputEncoding, signalHandler);
        this.closed = new AtomicBoolean();
        this.lock = new Object();
        this.paused = true;
        this.provider = provider;
        this.masterInput = masterInput;
        if (attributes != null) {
            this.setAttributes(attributes);
        }
        if (size != null) {
            this.setSize(size);
        }
        if (!paused) {
            this.resume();
        }
    }
    
    @Override
    protected void doClose() throws IOException {
        if (this.closed.compareAndSet(false, true)) {
            this.pause();
            super.doClose();
        }
    }
    
    @Override
    public boolean canPauseResume() {
        return true;
    }
    
    @Override
    public void pause() {
        try {
            this.pause(false);
        }
        catch (final InterruptedException ex) {}
    }
    
    @Override
    public void pause(final boolean wait) throws InterruptedException {
        final Thread p;
        synchronized (this.lock) {
            this.paused = true;
            p = this.pumpThread;
        }
        if (p != null) {
            p.interrupt();
            if (wait) {
                p.join();
            }
        }
    }
    
    @Override
    public void resume() {
        synchronized (this.lock) {
            this.paused = false;
            if (this.pumpThread == null) {
                (this.pumpThread = new Thread(this::pump, this.toString() + " input pump thread")).setDaemon(true);
                this.pumpThread.start();
            }
        }
    }
    
    @Override
    public boolean paused() {
        synchronized (this.lock) {
            return this.paused;
        }
    }
    
    public void pump() {
        try {
            final byte[] buf = new byte[1024];
            while (true) {
                final int c = this.masterInput.read(buf);
                if (c >= 0) {
                    this.processInputBytes(buf, 0, c);
                }
                if (c < 0 || this.closed.get()) {
                    break;
                }
                synchronized (this.lock) {
                    if (this.paused) {
                        this.pumpThread = null;
                        return;
                    }
                    continue;
                }
            }
        }
        catch (final IOException e) {
            this.processIOException(e);
            synchronized (this.lock) {
                this.pumpThread = null;
            }
        }
        finally {
            synchronized (this.lock) {
                this.pumpThread = null;
            }
        }
        try {
            this.slaveInput.close();
        }
        catch (final IOException ex) {}
    }
    
    @Override
    public Cursor getCursorPosition(final IntConsumer discarded) {
        return CursorSupport.getCursorPosition(this, discarded);
    }
    
    @Override
    public TerminalProvider getProvider() {
        return this.provider;
    }
}
