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

package org.jline.terminal.impl.jansi.win;

import org.jline.utils.InfoCmp;
import java.io.IOError;
import org.jline.terminal.Cursor;
import java.util.function.IntConsumer;
import org.jline.terminal.Size;
import java.io.Writer;
import java.io.BufferedWriter;
import org.jline.utils.OSUtils;
import org.fusesource.jansi.internal.Kernel32;
import java.io.IOException;
import org.jline.terminal.Terminal;
import java.nio.charset.Charset;
import org.jline.terminal.spi.SystemStream;
import org.jline.terminal.spi.TerminalProvider;
import org.jline.terminal.impl.AbstractWindowsTerminal;

public class JansiWinSysTerminal extends AbstractWindowsTerminal<Long>
{
    private static final long consoleIn;
    private static final long consoleOut;
    private static final long consoleErr;
    private char[] focus;
    private char[] mouse;
    
    public static JansiWinSysTerminal createTerminal(final TerminalProvider provider, final SystemStream systemStream, final String name, final String type, final boolean ansiPassThrough, final Charset encoding, final boolean nativeSignals, final Terminal.SignalHandler signalHandler, final boolean paused) throws IOException {
        return createTerminal(provider, systemStream, name, type, ansiPassThrough, encoding, encoding, encoding, encoding, nativeSignals, signalHandler, paused);
    }
    
    public static JansiWinSysTerminal createTerminal(final TerminalProvider provider, final SystemStream systemStream, final String name, String type, final boolean ansiPassThrough, final Charset encoding, final Charset stdinEncoding, final Charset stdoutEncoding, final Charset stderrEncoding, final boolean nativeSignals, final Terminal.SignalHandler signalHandler, final boolean paused) throws IOException {
        final int[] inMode = { 0 };
        if (Kernel32.GetConsoleMode(JansiWinSysTerminal.consoleIn, inMode) == 0) {
            throw new IOException("Failed to get console mode: " + WindowsSupport.getLastErrorMessage());
        }
        final long console = getConsole(systemStream);
        final int[] outMode = { 0 };
        if (Kernel32.GetConsoleMode(console, outMode) == 0) {
            throw new IOException("Failed to get console mode: " + WindowsSupport.getLastErrorMessage());
        }
        Writer writer;
        if (ansiPassThrough) {
            type = ((type != null) ? type : (OSUtils.IS_CONEMU ? "windows-conemu" : "windows"));
            writer = newConsoleWriter(console);
        }
        else if (enableVtp(console, outMode[0])) {
            type = ((type != null) ? type : "windows-vtp");
            writer = newConsoleWriter(console);
        }
        else if (OSUtils.IS_CONEMU) {
            type = ((type != null) ? type : "windows-conemu");
            writer = newConsoleWriter(console);
        }
        else {
            type = ((type != null) ? type : "windows");
            writer = new WindowsAnsiWriter(new BufferedWriter(newConsoleWriter(console)));
        }
        final Charset outputEncoding = (systemStream == SystemStream.Error) ? stderrEncoding : stdoutEncoding;
        final JansiWinSysTerminal terminal = new JansiWinSysTerminal(provider, systemStream, writer, name, type, encoding, stdinEncoding, outputEncoding, nativeSignals, signalHandler, JansiWinSysTerminal.consoleIn, inMode[0], console, outMode[0]);
        if (!paused) {
            terminal.resume();
        }
        return terminal;
    }
    
    public static long getConsole(final SystemStream systemStream) {
        long console = 0L;
        switch (systemStream) {
            case Output: {
                console = JansiWinSysTerminal.consoleOut;
                break;
            }
            case Error: {
                console = JansiWinSysTerminal.consoleErr;
                break;
            }
            default: {
                throw new IllegalArgumentException("Unsupported stream for console: " + systemStream);
            }
        }
        return console;
    }
    
    private static boolean enableVtp(final long console, final int outMode) {
        return Kernel32.SetConsoleMode(console, outMode | 0x4) != 0;
    }
    
    private static Writer newConsoleWriter(final long console) {
        return new JansiWinConsoleWriter(console);
    }
    
    public static boolean isWindowsSystemStream(final SystemStream stream) {
        final int[] mode = { 0 };
        long console = 0L;
        switch (stream) {
            case Input: {
                console = JansiWinSysTerminal.consoleIn;
                break;
            }
            case Output: {
                console = JansiWinSysTerminal.consoleOut;
                break;
            }
            case Error: {
                console = JansiWinSysTerminal.consoleErr;
                break;
            }
            default: {
                return false;
            }
        }
        return Kernel32.GetConsoleMode(console, mode) != 0;
    }
    
    JansiWinSysTerminal(final TerminalProvider provider, final SystemStream systemStream, final Writer writer, final String name, final String type, final Charset encoding, final boolean nativeSignals, final Terminal.SignalHandler signalHandler, final long inConsole, final int inMode, final long outConsole, final int outMode) throws IOException {
        this(provider, systemStream, writer, name, type, encoding, encoding, encoding, nativeSignals, signalHandler, inConsole, inMode, outConsole, outMode);
    }
    
    JansiWinSysTerminal(final TerminalProvider provider, final SystemStream systemStream, final Writer writer, final String name, final String type, final Charset encoding, final Charset inputEncoding, final Charset outputEncoding, final boolean nativeSignals, final Terminal.SignalHandler signalHandler, final long inConsole, final int inMode, final long outConsole, final int outMode) throws IOException {
        super(provider, systemStream, writer, name, type, encoding, inputEncoding, outputEncoding, nativeSignals, signalHandler, inConsole, inMode, outConsole, outMode);
        this.focus = new char[] { '\u001b', '[', ' ' };
        this.mouse = new char[] { '\u001b', '[', 'M', ' ', ' ', ' ' };
    }
    
    @Override
    protected int getConsoleMode(final Long console) {
        final int[] mode = { 0 };
        if (Kernel32.GetConsoleMode(console, mode) == 0) {
            return -1;
        }
        return mode[0];
    }
    
    @Override
    protected void setConsoleMode(final Long console, final int mode) {
        Kernel32.SetConsoleMode(console, mode);
    }
    
    @Override
    public Size getSize() {
        final Kernel32.CONSOLE_SCREEN_BUFFER_INFO info = new Kernel32.CONSOLE_SCREEN_BUFFER_INFO();
        Kernel32.GetConsoleScreenBufferInfo((long)this.outConsole, info);
        return new Size(info.windowWidth(), info.windowHeight());
    }
    
    @Override
    public Size getBufferSize() {
        final Kernel32.CONSOLE_SCREEN_BUFFER_INFO info = new Kernel32.CONSOLE_SCREEN_BUFFER_INFO();
        Kernel32.GetConsoleScreenBufferInfo((long)this.outConsole, info);
        return new Size(info.size.x, info.size.y);
    }
    
    @Override
    protected boolean processConsoleInput() throws IOException {
        if ((long)this.inConsole != Kernel32.INVALID_HANDLE_VALUE && Kernel32.WaitForSingleObject((long)this.inConsole, 100) == 0) {
            final Kernel32.INPUT_RECORD[] events = Kernel32.readConsoleInputHelper((long)this.inConsole, 1, false);
            boolean flush = false;
            for (final Kernel32.INPUT_RECORD event : events) {
                if (event.eventType == Kernel32.INPUT_RECORD.KEY_EVENT) {
                    final Kernel32.KEY_EVENT_RECORD keyEvent = event.keyEvent;
                    this.processKeyEvent(keyEvent.keyDown, keyEvent.keyCode, keyEvent.uchar, keyEvent.controlKeyState);
                    flush = true;
                }
                else if (event.eventType == Kernel32.INPUT_RECORD.WINDOW_BUFFER_SIZE_EVENT) {
                    this.raise(Terminal.Signal.WINCH);
                }
                else if (event.eventType == Kernel32.INPUT_RECORD.MOUSE_EVENT) {
                    this.processMouseEvent(event.mouseEvent);
                    flush = true;
                }
                else if (event.eventType == Kernel32.INPUT_RECORD.FOCUS_EVENT) {
                    this.processFocusEvent(event.focusEvent.setFocus);
                }
            }
            return flush;
        }
        return false;
    }
    
    private void processFocusEvent(final boolean hasFocus) throws IOException {
        if (this.focusTracking) {
            this.focus[2] = (hasFocus ? 'I' : 'O');
            this.slaveInputPipe.write(this.focus);
        }
    }
    
    private void processMouseEvent(final Kernel32.MOUSE_EVENT_RECORD mouseEvent) throws IOException {
        int dwEventFlags = mouseEvent.eventFlags;
        final int dwButtonState = mouseEvent.buttonState;
        if (this.tracking == Terminal.MouseTracking.Off || (this.tracking == Terminal.MouseTracking.Normal && dwEventFlags == Kernel32.MOUSE_EVENT_RECORD.MOUSE_MOVED) || (this.tracking == Terminal.MouseTracking.Button && dwEventFlags == Kernel32.MOUSE_EVENT_RECORD.MOUSE_MOVED && dwButtonState == 0)) {
            return;
        }
        int cb = 0;
        dwEventFlags &= ~Kernel32.MOUSE_EVENT_RECORD.DOUBLE_CLICK;
        if (dwEventFlags == Kernel32.MOUSE_EVENT_RECORD.MOUSE_WHEELED) {
            cb |= 0x40;
            if (dwButtonState >> 16 < 0) {
                cb |= 0x1;
            }
        }
        else {
            if (dwEventFlags == Kernel32.MOUSE_EVENT_RECORD.MOUSE_HWHEELED) {
                return;
            }
            if ((dwButtonState & Kernel32.MOUSE_EVENT_RECORD.FROM_LEFT_1ST_BUTTON_PRESSED) != 0x0) {
                cb |= 0x0;
            }
            else if ((dwButtonState & Kernel32.MOUSE_EVENT_RECORD.RIGHTMOST_BUTTON_PRESSED) != 0x0) {
                cb |= 0x1;
            }
            else if ((dwButtonState & Kernel32.MOUSE_EVENT_RECORD.FROM_LEFT_2ND_BUTTON_PRESSED) != 0x0) {
                cb |= 0x2;
            }
            else {
                cb |= 0x3;
            }
        }
        final int cx = mouseEvent.mousePosition.x;
        final int cy = mouseEvent.mousePosition.y;
        this.mouse[3] = (char)(32 + cb);
        this.mouse[4] = (char)(32 + cx + 1);
        this.mouse[5] = (char)(32 + cy + 1);
        this.slaveInputPipe.write(this.mouse);
    }
    
    @Override
    public Cursor getCursorPosition(final IntConsumer discarded) {
        final Kernel32.CONSOLE_SCREEN_BUFFER_INFO info = new Kernel32.CONSOLE_SCREEN_BUFFER_INFO();
        if (Kernel32.GetConsoleScreenBufferInfo((long)this.outConsole, info) == 0) {
            throw new IOError(new IOException("Could not get the cursor position: " + WindowsSupport.getLastErrorMessage()));
        }
        return new Cursor(info.cursorPosition.x, info.cursorPosition.y);
    }
    
    public void disableScrolling() {
        this.strings.remove(InfoCmp.Capability.insert_line);
        this.strings.remove(InfoCmp.Capability.parm_insert_line);
        this.strings.remove(InfoCmp.Capability.delete_line);
        this.strings.remove(InfoCmp.Capability.parm_delete_line);
    }
    
    @Override
    public int getDefaultForegroundColor() {
        final Kernel32.CONSOLE_SCREEN_BUFFER_INFO info = new Kernel32.CONSOLE_SCREEN_BUFFER_INFO();
        if (Kernel32.GetConsoleScreenBufferInfo((long)this.outConsole, info) == 0) {
            return -1;
        }
        return this.convertAttributeToRgb(info.attributes & 0xF, true);
    }
    
    @Override
    public int getDefaultBackgroundColor() {
        final Kernel32.CONSOLE_SCREEN_BUFFER_INFO info = new Kernel32.CONSOLE_SCREEN_BUFFER_INFO();
        if (Kernel32.GetConsoleScreenBufferInfo((long)this.outConsole, info) == 0) {
            return -1;
        }
        return this.convertAttributeToRgb((info.attributes & 0xF0) >> 4, false);
    }
    
    static {
        consoleIn = Kernel32.GetStdHandle(Kernel32.STD_INPUT_HANDLE);
        consoleOut = Kernel32.GetStdHandle(Kernel32.STD_OUTPUT_HANDLE);
        consoleErr = Kernel32.GetStdHandle(Kernel32.STD_ERROR_HANDLE);
    }
}
