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

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

import org.jline.terminal.Cursor;
import java.util.function.IntConsumer;
import org.jline.terminal.Size;
import org.jline.utils.InfoCmp;
import com.sun.jna.LastErrorException;
import java.io.Writer;
import java.io.BufferedWriter;
import org.jline.utils.OSUtils;
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 com.sun.jna.ptr.IntByReference;
import com.sun.jna.Pointer;
import org.jline.terminal.impl.AbstractWindowsTerminal;

public class JnaWinSysTerminal extends AbstractWindowsTerminal<Pointer>
{
    private static final Pointer consoleIn;
    private static final Pointer consoleOut;
    private static final Pointer consoleErr;
    private char[] focus;
    private char[] mouse;
    private final Kernel32.INPUT_RECORD[] inputEvents;
    private final IntByReference eventsRead;
    
    public static JnaWinSysTerminal 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 JnaWinSysTerminal 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 IntByReference inMode = new IntByReference();
        Kernel32.INSTANCE.GetConsoleMode(JnaWinSysTerminal.consoleIn, inMode);
        Pointer console = null;
        switch (systemStream) {
            case Output: {
                console = JnaWinSysTerminal.consoleOut;
                break;
            }
            case Error: {
                console = JnaWinSysTerminal.consoleErr;
                break;
            }
            default: {
                throw new IllegalArgumentException("Unsupported stream for console: " + systemStream);
            }
        }
        final IntByReference outMode = new IntByReference();
        Kernel32.INSTANCE.GetConsoleMode(console, outMode);
        Writer writer;
        if (ansiPassThrough) {
            type = ((type != null) ? type : (OSUtils.IS_CONEMU ? "windows-conemu" : "windows"));
            writer = new JnaWinConsoleWriter(console);
        }
        else if (enableVtp(console, outMode.getValue())) {
            type = ((type != null) ? type : "windows-vtp");
            writer = new JnaWinConsoleWriter(console);
        }
        else if (OSUtils.IS_CONEMU) {
            type = ((type != null) ? type : "windows-conemu");
            writer = new JnaWinConsoleWriter(console);
        }
        else {
            type = ((type != null) ? type : "windows");
            writer = new WindowsAnsiWriter(new BufferedWriter(new JnaWinConsoleWriter(console)), console);
        }
        final Charset outputEncoding = (systemStream == SystemStream.Error) ? stderrEncoding : stdoutEncoding;
        final JnaWinSysTerminal terminal = new JnaWinSysTerminal(provider, systemStream, writer, name, type, encoding, stdinEncoding, outputEncoding, nativeSignals, signalHandler, JnaWinSysTerminal.consoleIn, inMode.getValue(), console, outMode.getValue());
        if (!paused) {
            terminal.resume();
        }
        return terminal;
    }
    
    private static boolean enableVtp(final Pointer console, final int outMode) {
        try {
            Kernel32.INSTANCE.SetConsoleMode(console, outMode | 0x4);
            return true;
        }
        catch (final LastErrorException e) {
            return false;
        }
    }
    
    public static boolean isWindowsSystemStream(final SystemStream stream) {
        try {
            final IntByReference mode = new IntByReference();
            Pointer console = null;
            switch (stream) {
                case Input: {
                    console = JnaWinSysTerminal.consoleIn;
                    break;
                }
                case Output: {
                    console = JnaWinSysTerminal.consoleOut;
                    break;
                }
                case Error: {
                    console = JnaWinSysTerminal.consoleErr;
                    break;
                }
                default: {
                    return false;
                }
            }
            Kernel32.INSTANCE.GetConsoleMode(console, mode);
            return true;
        }
        catch (final LastErrorException e) {
            return false;
        }
    }
    
    JnaWinSysTerminal(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 Pointer inConsole, final int inConsoleMode, final Pointer outConsole, final int outConsoleMode) throws IOException {
        this(provider, systemStream, writer, name, type, encoding, encoding, encoding, nativeSignals, signalHandler, inConsole, inConsoleMode, outConsole, outConsoleMode);
    }
    
    JnaWinSysTerminal(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 Pointer inConsole, final int inConsoleMode, final Pointer outConsole, final int outConsoleMode) throws IOException {
        super(provider, systemStream, writer, name, type, encoding, inputEncoding, outputEncoding, nativeSignals, signalHandler, inConsole, inConsoleMode, outConsole, outConsoleMode);
        this.focus = new char[] { '\u001b', '[', ' ' };
        this.mouse = new char[] { '\u001b', '[', 'M', ' ', ' ', ' ' };
        this.inputEvents = new Kernel32.INPUT_RECORD[1];
        this.eventsRead = new IntByReference();
        this.strings.put(InfoCmp.Capability.key_mouse, "\\E[M");
    }
    
    @Override
    protected int getConsoleMode(final Pointer console) {
        final IntByReference mode = new IntByReference();
        Kernel32.INSTANCE.GetConsoleMode(console, mode);
        return mode.getValue();
    }
    
    @Override
    protected void setConsoleMode(final Pointer console, final int mode) {
        Kernel32.INSTANCE.SetConsoleMode(console, mode);
    }
    
    @Override
    public Size getSize() {
        final Kernel32.CONSOLE_SCREEN_BUFFER_INFO info = new Kernel32.CONSOLE_SCREEN_BUFFER_INFO();
        Kernel32.INSTANCE.GetConsoleScreenBufferInfo((Pointer)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.INSTANCE.GetConsoleScreenBufferInfo(JnaWinSysTerminal.consoleOut, info);
        return new Size(info.dwSize.X, info.dwSize.Y);
    }
    
    @Override
    protected boolean processConsoleInput() throws IOException {
        final Kernel32.INPUT_RECORD event = this.readConsoleInput(100);
        if (event == null) {
            return false;
        }
        switch (event.EventType) {
            case 1: {
                this.processKeyEvent(event.Event.KeyEvent);
                return true;
            }
            case 4: {
                this.raise(Terminal.Signal.WINCH);
                return false;
            }
            case 2: {
                this.processMouseEvent(event.Event.MouseEvent);
                return true;
            }
            case 16: {
                this.processFocusEvent(event.Event.FocusEvent.bSetFocus);
                return true;
            }
            default: {
                return false;
            }
        }
    }
    
    private void processKeyEvent(final Kernel32.KEY_EVENT_RECORD keyEvent) throws IOException {
        this.processKeyEvent(keyEvent.bKeyDown, keyEvent.wVirtualKeyCode, keyEvent.uChar.UnicodeChar, keyEvent.dwControlKeyState);
    }
    
    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.dwEventFlags;
        final int dwButtonState = mouseEvent.dwButtonState;
        if (this.tracking == Terminal.MouseTracking.Off || (this.tracking == Terminal.MouseTracking.Normal && dwEventFlags == 1) || (this.tracking == Terminal.MouseTracking.Button && dwEventFlags == 1 && dwButtonState == 0)) {
            return;
        }
        int cb = 0;
        dwEventFlags &= 0xFFFFFFFD;
        if (dwEventFlags == 4) {
            cb |= 0x40;
            if (dwButtonState >> 16 < 0) {
                cb |= 0x1;
            }
        }
        else {
            if (dwEventFlags == 8) {
                return;
            }
            if ((dwButtonState & 0x1) != 0x0) {
                cb |= 0x0;
            }
            else if ((dwButtonState & 0x2) != 0x0) {
                cb |= 0x1;
            }
            else if ((dwButtonState & 0x4) != 0x0) {
                cb |= 0x2;
            }
            else {
                cb |= 0x3;
            }
        }
        final int cx = mouseEvent.dwMousePosition.X;
        final int cy = mouseEvent.dwMousePosition.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);
    }
    
    private Kernel32.INPUT_RECORD readConsoleInput(final int dwMilliseconds) throws IOException {
        if (Kernel32.INSTANCE.WaitForSingleObject(JnaWinSysTerminal.consoleIn, dwMilliseconds) != 0) {
            return null;
        }
        Kernel32.INSTANCE.ReadConsoleInput(JnaWinSysTerminal.consoleIn, this.inputEvents, 1, this.eventsRead);
        if (this.eventsRead.getValue() == 1) {
            return this.inputEvents[0];
        }
        return null;
    }
    
    @Override
    public Cursor getCursorPosition(final IntConsumer discarded) {
        final Kernel32.CONSOLE_SCREEN_BUFFER_INFO info = new Kernel32.CONSOLE_SCREEN_BUFFER_INFO();
        Kernel32.INSTANCE.GetConsoleScreenBufferInfo(JnaWinSysTerminal.consoleOut, info);
        return new Cursor(info.dwCursorPosition.X, info.dwCursorPosition.Y);
    }
    
    @Override
    public int getDefaultForegroundColor() {
        final Kernel32.CONSOLE_SCREEN_BUFFER_INFO info = new Kernel32.CONSOLE_SCREEN_BUFFER_INFO();
        Kernel32.INSTANCE.GetConsoleScreenBufferInfo(JnaWinSysTerminal.consoleOut, info);
        return this.convertAttributeToRgb(info.wAttributes & 0xF, true);
    }
    
    @Override
    public int getDefaultBackgroundColor() {
        final Kernel32.CONSOLE_SCREEN_BUFFER_INFO info = new Kernel32.CONSOLE_SCREEN_BUFFER_INFO();
        Kernel32.INSTANCE.GetConsoleScreenBufferInfo(JnaWinSysTerminal.consoleOut, info);
        return this.convertAttributeToRgb((info.wAttributes & 0xF0) >> 4, false);
    }
    
    static {
        consoleIn = Kernel32.INSTANCE.GetStdHandle(-10);
        consoleOut = Kernel32.INSTANCE.GetStdHandle(-11);
        consoleErr = Kernel32.INSTANCE.GetStdHandle(-12);
    }
}
