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

package org.jline.terminal.impl;

import org.jline.terminal.spi.SystemStream;
import org.jline.terminal.spi.TerminalProvider;
import java.util.Objects;
import java.util.EnumSet;
import java.io.Writer;
import java.io.OutputStreamWriter;
import java.io.InputStream;
import org.jline.utils.NonBlocking;
import java.io.IOException;
import org.jline.terminal.Terminal;
import java.nio.charset.Charset;
import org.jline.terminal.Size;
import org.jline.terminal.Attributes;
import java.io.PrintWriter;
import org.jline.utils.NonBlockingReader;
import org.jline.utils.NonBlockingPumpInputStream;
import java.io.OutputStream;

public class LineDisciplineTerminal extends AbstractTerminal
{
    private static final int PIPE_SIZE = 1024;
    protected final OutputStream masterOutput;
    protected final OutputStream slaveInputPipe;
    protected final NonBlockingPumpInputStream slaveInput;
    protected final NonBlockingReader slaveReader;
    protected final PrintWriter slaveWriter;
    protected final OutputStream slaveOutput;
    protected final Attributes attributes;
    protected final Size size;
    protected boolean skipNextLf;
    
    public LineDisciplineTerminal(final String name, final String type, final OutputStream masterOutput, final Charset encoding) throws IOException {
        this(name, type, masterOutput, encoding, Terminal.SignalHandler.SIG_DFL);
    }
    
    public LineDisciplineTerminal(final String name, final String type, final OutputStream masterOutput, final Charset encoding, final Terminal.SignalHandler signalHandler) throws IOException {
        this(name, type, masterOutput, encoding, encoding, encoding, signalHandler);
    }
    
    public LineDisciplineTerminal(final String name, final String type, final OutputStream masterOutput, final Charset encoding, final Charset inputEncoding, final Charset outputEncoding, final Terminal.SignalHandler signalHandler) throws IOException {
        super(name, type, encoding, inputEncoding, outputEncoding, signalHandler);
        final NonBlockingPumpInputStream input = NonBlocking.nonBlockingPumpInputStream(1024);
        this.slaveInputPipe = input.getOutputStream();
        this.slaveInput = input;
        this.slaveReader = NonBlocking.nonBlocking(this.getName(), this.slaveInput, this.inputEncoding());
        this.slaveOutput = new FilteringOutputStream();
        this.slaveWriter = new PrintWriter(new OutputStreamWriter(this.slaveOutput, this.outputEncoding()));
        this.masterOutput = masterOutput;
        this.attributes = getDefaultTerminalAttributes();
        this.size = new Size(160, 50);
        this.parseInfoCmp();
    }
    
    private static Attributes getDefaultTerminalAttributes() {
        final Attributes attr = new Attributes();
        attr.setLocalFlags(EnumSet.of(Attributes.LocalFlag.ICANON, new Attributes.LocalFlag[] { Attributes.LocalFlag.ISIG, Attributes.LocalFlag.IEXTEN, Attributes.LocalFlag.ECHO, Attributes.LocalFlag.ECHOE, Attributes.LocalFlag.ECHOKE, Attributes.LocalFlag.ECHOCTL, Attributes.LocalFlag.PENDIN }));
        attr.setInputFlags(EnumSet.of(Attributes.InputFlag.ICRNL, new Attributes.InputFlag[] { Attributes.InputFlag.IXON, Attributes.InputFlag.IXANY, Attributes.InputFlag.IMAXBEL, Attributes.InputFlag.IUTF8, Attributes.InputFlag.BRKINT }));
        attr.setOutputFlags(EnumSet.of(Attributes.OutputFlag.OPOST, Attributes.OutputFlag.ONLCR));
        attr.setControlChar(Attributes.ControlChar.VDISCARD, ctrl('O'));
        attr.setControlChar(Attributes.ControlChar.VDSUSP, ctrl('Y'));
        attr.setControlChar(Attributes.ControlChar.VEOF, ctrl('D'));
        attr.setControlChar(Attributes.ControlChar.VERASE, ctrl('?'));
        attr.setControlChar(Attributes.ControlChar.VINTR, ctrl('C'));
        attr.setControlChar(Attributes.ControlChar.VKILL, ctrl('U'));
        attr.setControlChar(Attributes.ControlChar.VLNEXT, ctrl('V'));
        attr.setControlChar(Attributes.ControlChar.VMIN, 1);
        attr.setControlChar(Attributes.ControlChar.VQUIT, ctrl('\\'));
        attr.setControlChar(Attributes.ControlChar.VREPRINT, ctrl('R'));
        attr.setControlChar(Attributes.ControlChar.VSTART, ctrl('Q'));
        attr.setControlChar(Attributes.ControlChar.VSTATUS, ctrl('T'));
        attr.setControlChar(Attributes.ControlChar.VSTOP, ctrl('S'));
        attr.setControlChar(Attributes.ControlChar.VSUSP, ctrl('Z'));
        attr.setControlChar(Attributes.ControlChar.VTIME, 0);
        attr.setControlChar(Attributes.ControlChar.VWERASE, ctrl('W'));
        return attr;
    }
    
    private static int ctrl(final char c) {
        return (c == '?') ? 177 : (c - '@');
    }
    
    @Override
    public NonBlockingReader reader() {
        return this.slaveReader;
    }
    
    @Override
    public PrintWriter writer() {
        return this.slaveWriter;
    }
    
    @Override
    public InputStream input() {
        return this.slaveInput;
    }
    
    @Override
    public OutputStream output() {
        return this.slaveOutput;
    }
    
    @Override
    public Attributes getAttributes() {
        return new Attributes(this.attributes);
    }
    
    @Override
    public void setAttributes(final Attributes attr) {
        this.attributes.copy(attr);
    }
    
    @Override
    public Size getSize() {
        final Size sz = new Size();
        sz.copy(this.size);
        return sz;
    }
    
    @Override
    public void setSize(final Size sz) {
        this.size.copy(sz);
    }
    
    @Override
    public void raise(final Terminal.Signal signal) {
        Objects.requireNonNull(signal);
        this.echoSignal(signal);
        super.raise(signal);
    }
    
    public void processInputByte(final int c) throws IOException {
        final boolean flushOut = this.doProcessInputByte(c);
        this.slaveInputPipe.flush();
        if (flushOut) {
            this.masterOutput.flush();
        }
    }
    
    public void processInputBytes(final byte[] input) throws IOException {
        this.processInputBytes(input, 0, input.length);
    }
    
    public void processInputBytes(final byte[] input, final int offset, final int length) throws IOException {
        boolean flushOut = false;
        for (int i = 0; i < length; ++i) {
            flushOut |= this.doProcessInputByte(input[offset + i]);
        }
        this.slaveInputPipe.flush();
        if (flushOut) {
            this.masterOutput.flush();
        }
    }
    
    protected boolean doProcessInputByte(int c) throws IOException {
        if (this.attributes.getLocalFlag(Attributes.LocalFlag.ISIG)) {
            if (c == this.attributes.getControlChar(Attributes.ControlChar.VINTR)) {
                this.raise(Terminal.Signal.INT);
                return false;
            }
            if (c == this.attributes.getControlChar(Attributes.ControlChar.VQUIT)) {
                this.raise(Terminal.Signal.QUIT);
                return false;
            }
            if (c == this.attributes.getControlChar(Attributes.ControlChar.VSUSP)) {
                this.raise(Terminal.Signal.TSTP);
                return false;
            }
            if (c == this.attributes.getControlChar(Attributes.ControlChar.VSTATUS)) {
                this.raise(Terminal.Signal.INFO);
            }
        }
        if (this.attributes.getInputFlag(Attributes.InputFlag.INORMEOL)) {
            if (c == 13) {
                this.skipNextLf = true;
                c = 10;
            }
            else if (c == 10) {
                if (this.skipNextLf) {
                    return this.skipNextLf = false;
                }
            }
            else {
                this.skipNextLf = false;
            }
        }
        else if (c == 13) {
            if (this.attributes.getInputFlag(Attributes.InputFlag.IGNCR)) {
                return false;
            }
            if (this.attributes.getInputFlag(Attributes.InputFlag.ICRNL)) {
                c = 10;
            }
        }
        else if (c == 10 && this.attributes.getInputFlag(Attributes.InputFlag.INLCR)) {
            c = 13;
        }
        boolean flushOut = false;
        if (this.attributes.getLocalFlag(Attributes.LocalFlag.ECHO)) {
            this.processOutputByte(c);
            flushOut = true;
        }
        this.slaveInputPipe.write(c);
        return flushOut;
    }
    
    protected void processOutputByte(final int c) throws IOException {
        if (this.attributes.getOutputFlag(Attributes.OutputFlag.OPOST) && c == 10 && this.attributes.getOutputFlag(Attributes.OutputFlag.ONLCR)) {
            this.masterOutput.write(13);
            this.masterOutput.write(10);
            return;
        }
        this.masterOutput.write(c);
    }
    
    protected void processIOException(final IOException ioException) {
        this.slaveInput.setIoException(ioException);
    }
    
    @Override
    protected void doClose() throws IOException {
        super.doClose();
        try {
            this.slaveReader.close();
        }
        finally {
            try {
                this.slaveInputPipe.close();
            }
            finally {
                this.slaveWriter.close();
            }
        }
    }
    
    @Override
    public TerminalProvider getProvider() {
        return null;
    }
    
    @Override
    public SystemStream getSystemStream() {
        return null;
    }
    
    private class FilteringOutputStream extends OutputStream
    {
        @Override
        public void write(final int b) throws IOException {
            LineDisciplineTerminal.this.processOutputByte(b);
            this.flush();
        }
        
        @Override
        public void write(final byte[] b, final int off, final int len) throws IOException {
            if (b == null) {
                throw new NullPointerException();
            }
            if (off < 0 || off > b.length || len < 0 || off + len > b.length || off + len < 0) {
                throw new IndexOutOfBoundsException();
            }
            if (len == 0) {
                return;
            }
            for (int i = 0; i < len; ++i) {
                LineDisciplineTerminal.this.processOutputByte(b[off + i]);
            }
            this.flush();
        }
        
        @Override
        public void flush() throws IOException {
            LineDisciplineTerminal.this.masterOutput.flush();
        }
        
        @Override
        public void close() throws IOException {
            LineDisciplineTerminal.this.masterOutput.close();
        }
    }
}
