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

package org.jline.terminal.impl;

import org.jline.utils.ClosedException;
import java.io.Writer;
import java.io.OutputStreamWriter;
import org.jline.utils.NonBlocking;
import java.util.Objects;
import java.io.IOException;
import org.jline.terminal.Terminal;
import java.nio.charset.Charset;
import org.jline.terminal.spi.Pty;
import java.io.PrintWriter;
import org.jline.utils.NonBlockingReader;
import org.jline.utils.NonBlockingInputStream;
import java.io.OutputStream;
import java.io.InputStream;

public class PosixPtyTerminal extends AbstractPosixTerminal
{
    private final InputStream in;
    private final OutputStream out;
    private final InputStream masterInput;
    private final OutputStream masterOutput;
    private final NonBlockingInputStream input;
    private final OutputStream output;
    private final NonBlockingReader reader;
    private final PrintWriter writer;
    private final Object lock;
    private Thread inputPumpThread;
    private Thread outputPumpThread;
    private boolean paused;
    
    public PosixPtyTerminal(final String name, final String type, final Pty pty, final InputStream in, final OutputStream out, final Charset encoding) throws IOException {
        this(name, type, pty, in, out, encoding, Terminal.SignalHandler.SIG_DFL);
    }
    
    public PosixPtyTerminal(final String name, final String type, final Pty pty, final InputStream in, final OutputStream out, final Charset encoding, final Terminal.SignalHandler signalHandler) throws IOException {
        this(name, type, pty, in, out, encoding, signalHandler, false);
    }
    
    public PosixPtyTerminal(final String name, final String type, final Pty pty, final InputStream in, final OutputStream out, final Charset encoding, final Terminal.SignalHandler signalHandler, final boolean paused) throws IOException {
        this(name, type, pty, in, out, encoding, encoding, encoding, signalHandler, paused);
    }
    
    public PosixPtyTerminal(final String name, final String type, final Pty pty, final InputStream in, final OutputStream out, final Charset encoding, final Charset inputEncoding, final Charset outputEncoding, final Terminal.SignalHandler signalHandler, final boolean paused) throws IOException {
        super(name, type, pty, encoding, inputEncoding, outputEncoding, signalHandler);
        this.lock = new Object();
        this.paused = true;
        this.in = Objects.requireNonNull(in);
        this.out = Objects.requireNonNull(out);
        this.masterInput = pty.getMasterInput();
        this.masterOutput = pty.getMasterOutput();
        this.input = new InputStreamWrapper(NonBlocking.nonBlocking(name, pty.getSlaveInput()));
        this.output = pty.getSlaveOutput();
        this.reader = NonBlocking.nonBlocking(name, this.input, this.inputEncoding());
        this.writer = new PrintWriter(new OutputStreamWriter(this.output, this.outputEncoding()));
        this.parseInfoCmp();
        if (!paused) {
            this.resume();
        }
    }
    
    @Override
    public InputStream input() {
        return this.input;
    }
    
    @Override
    public NonBlockingReader reader() {
        return this.reader;
    }
    
    @Override
    public OutputStream output() {
        return this.output;
    }
    
    @Override
    public PrintWriter writer() {
        return this.writer;
    }
    
    @Override
    protected void doClose() throws IOException {
        super.doClose();
        this.reader.close();
    }
    
    @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 p1;
        final Thread p2;
        synchronized (this.lock) {
            this.paused = true;
            p1 = this.inputPumpThread;
            p2 = this.outputPumpThread;
        }
        if (p1 != null) {
            p1.interrupt();
        }
        if (p2 != null) {
            p2.interrupt();
        }
        if (wait) {
            if (p1 != null) {
                p1.join();
            }
            if (p2 != null) {
                p2.join();
            }
        }
    }
    
    @Override
    public void resume() {
        synchronized (this.lock) {
            this.paused = false;
            if (this.inputPumpThread == null) {
                (this.inputPumpThread = new Thread(this::pumpIn, this.toString() + " input pump thread")).setDaemon(true);
                this.inputPumpThread.start();
            }
            if (this.outputPumpThread == null) {
                (this.outputPumpThread = new Thread(this::pumpOut, this.toString() + " output pump thread")).setDaemon(true);
                this.outputPumpThread.start();
            }
        }
    }
    
    @Override
    public boolean paused() {
        synchronized (this.lock) {
            return this.paused;
        }
    }
    
    private void pumpIn() {
        try {
            while (true) {
                synchronized (this.lock) {
                    if (this.paused) {
                        this.inputPumpThread = null;
                        return;
                    }
                }
                final int b = this.in.read();
                if (b < 0) {
                    break;
                }
                this.masterOutput.write(b);
                this.masterOutput.flush();
            }
            this.input.close();
        }
        catch (final IOException e) {
            e.printStackTrace();
            synchronized (this.lock) {
                this.inputPumpThread = null;
            }
        }
        finally {
            synchronized (this.lock) {
                this.inputPumpThread = null;
            }
        }
    }
    
    private void pumpOut() {
        try {
            while (true) {
                synchronized (this.lock) {
                    if (this.paused) {
                        this.outputPumpThread = null;
                        return;
                    }
                }
                final int b = this.masterInput.read();
                if (b < 0) {
                    break;
                }
                this.out.write(b);
                this.out.flush();
            }
            this.input.close();
        }
        catch (final IOException e) {
            e.printStackTrace();
            synchronized (this.lock) {
                this.outputPumpThread = null;
            }
        }
        finally {
            synchronized (this.lock) {
                this.outputPumpThread = null;
            }
        }
        try {
            this.close();
        }
        catch (final Throwable t) {}
    }
    
    private static class InputStreamWrapper extends NonBlockingInputStream
    {
        private final NonBlockingInputStream in;
        private volatile boolean closed;
        
        protected InputStreamWrapper(final NonBlockingInputStream in) {
            this.in = in;
        }
        
        @Override
        public int read(final long timeout, final boolean isPeek) throws IOException {
            if (this.closed) {
                throw new ClosedException();
            }
            return this.in.read(timeout, isPeek);
        }
        
        @Override
        public void close() throws IOException {
            this.closed = true;
        }
    }
}
