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

package org.jline.terminal.impl.ffm;

import java.io.IOError;
import org.jline.terminal.Cursor;
import java.util.function.IntConsumer;
import org.jline.terminal.Size;
import java.lang.foreign.MemoryLayout;
import java.io.Writer;
import java.io.BufferedWriter;
import java.lang.foreign.ValueLayout;
import org.jline.utils.OSUtils;
import java.lang.foreign.Arena;
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 java.lang.foreign.MemorySegment;
import org.jline.terminal.impl.AbstractWindowsTerminal;

public class NativeWinSysTerminal extends AbstractWindowsTerminal<MemorySegment>
{
    private final char[] focus;
    private final char[] mouse;
    
    public static NativeWinSysTerminal 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, nativeSignals, signalHandler, paused);
    }
    
    public static NativeWinSysTerminal createTerminal(final TerminalProvider provider, final SystemStream systemStream, final String name, String type, final boolean ansiPassThrough, final Charset encoding, final Charset inputEncoding, final Charset outputEncoding, final boolean nativeSignals, final Terminal.SignalHandler signalHandler, final boolean paused) throws IOException {
        try (final Arena arena = Arena.ofConfined()) {
            final MemorySegment consoleIn = Kernel32.GetStdHandle(-10);
            final MemorySegment inMode = allocateInt(arena);
            if (Kernel32.GetConsoleMode(consoleIn, inMode) == 0) {
                throw new IOException("Failed to get console mode: " + Kernel32.getLastErrorMessage());
            }
            MemorySegment console = null;
            switch (systemStream) {
                case Output: {
                    console = Kernel32.GetStdHandle(-11);
                    break;
                }
                case Error: {
                    console = Kernel32.GetStdHandle(-12);
                    break;
                }
                default: {
                    throw new IllegalArgumentException("Unsupported stream for console: " + String.valueOf(systemStream));
                }
            }
            final MemorySegment outMode = allocateInt(arena);
            if (Kernel32.GetConsoleMode(console, outMode) == 0) {
                throw new IOException("Failed to get console mode: " + Kernel32.getLastErrorMessage());
            }
            Writer writer;
            if (ansiPassThrough) {
                type = ((type != null) ? type : (OSUtils.IS_CONEMU ? "windows-conemu" : "windows"));
                writer = new NativeWinConsoleWriter();
            }
            else {
                final int m = outMode.get(ValueLayout.JAVA_INT, 0L);
                if (enableVtp(console, m)) {
                    type = ((type != null) ? type : "windows-vtp");
                    writer = new NativeWinConsoleWriter();
                }
                else if (OSUtils.IS_CONEMU) {
                    type = ((type != null) ? type : "windows-conemu");
                    writer = new NativeWinConsoleWriter();
                }
                else {
                    type = ((type != null) ? type : "windows");
                    writer = new WindowsAnsiWriter(new BufferedWriter(new NativeWinConsoleWriter()));
                }
            }
            final NativeWinSysTerminal terminal = new NativeWinSysTerminal(provider, systemStream, writer, name, type, encoding, inputEncoding, outputEncoding, nativeSignals, signalHandler, consoleIn, inMode.get(ValueLayout.JAVA_INT, 0L), console, outMode.get(ValueLayout.JAVA_INT, 0L));
            if (!paused) {
                terminal.resume();
            }
            return terminal;
        }
    }
    
    private static boolean enableVtp(final MemorySegment console, final int m) {
        return Kernel32.SetConsoleMode(console, m | 0x4) != 0;
    }
    
    public static boolean isWindowsSystemStream(final SystemStream stream) {
        try (final Arena arena = Arena.ofConfined()) {
            final MemorySegment mode = allocateInt(arena);
            MemorySegment console = null;
            switch (stream) {
                case Input: {
                    console = Kernel32.GetStdHandle(-10);
                    break;
                }
                case Output: {
                    console = Kernel32.GetStdHandle(-11);
                    break;
                }
                case Error: {
                    console = Kernel32.GetStdHandle(-12);
                    break;
                }
                default: {
                    final boolean b = false;
                    if (arena != null) {
                        arena.close();
                    }
                    return b;
                }
            }
            return Kernel32.GetConsoleMode(console, mode) != 0;
        }
    }
    
    private static MemorySegment allocateInt(final Arena arena) {
        return arena.allocate(ValueLayout.JAVA_INT);
    }
    
    NativeWinSysTerminal(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 MemorySegment inConsole, final int inConsoleMode, final MemorySegment outConsole, final int outConsoleMode) throws IOException {
        this(provider, systemStream, writer, name, type, encoding, encoding, encoding, nativeSignals, signalHandler, inConsole, inConsoleMode, outConsole, outConsoleMode);
    }
    
    NativeWinSysTerminal(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 MemorySegment inConsole, final int inConsoleMode, final MemorySegment 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', ' ', ' ', ' ' };
    }
    
    @Override
    protected int getConsoleMode(final MemorySegment console) {
        try (final Arena arena = Arena.ofConfined()) {
            final MemorySegment mode = arena.allocate(ValueLayout.JAVA_INT);
            if (Kernel32.GetConsoleMode(console, mode) == 0) {
                final int n = -1;
                if (arena != null) {
                    arena.close();
                }
                return n;
            }
            return mode.get(ValueLayout.JAVA_INT, 0L);
        }
    }
    
    @Override
    protected void setConsoleMode(final MemorySegment console, final int mode) {
        Kernel32.SetConsoleMode(console, mode);
    }
    
    @Override
    public Size getSize() {
        try (final Arena arena = Arena.ofConfined()) {
            final Kernel32.CONSOLE_SCREEN_BUFFER_INFO info = new Kernel32.CONSOLE_SCREEN_BUFFER_INFO(arena);
            Kernel32.GetConsoleScreenBufferInfo((MemorySegment)this.outConsole, info);
            return new Size(info.windowWidth(), info.windowHeight());
        }
    }
    
    @Override
    public Size getBufferSize() {
        try (final Arena arena = Arena.ofConfined()) {
            final Kernel32.CONSOLE_SCREEN_BUFFER_INFO info = new Kernel32.CONSOLE_SCREEN_BUFFER_INFO(arena);
            Kernel32.GetConsoleScreenBufferInfo((MemorySegment)this.outConsole, info);
            return new Size(info.size().x(), info.size().y());
        }
    }
    
    @Override
    protected boolean processConsoleInput() throws IOException {
        try (final Arena arena = Arena.ofConfined()) {
            if (this.inConsole != null && ((MemorySegment)this.inConsole).address() != -1L && Kernel32.WaitForSingleObject((MemorySegment)this.inConsole, 100) == 0) {
                final Kernel32.INPUT_RECORD[] events = Kernel32.readConsoleInputHelper(arena, (MemorySegment)this.inConsole, 1, false);
                boolean flush = false;
                for (final Kernel32.INPUT_RECORD event : events) {
                    final int eventType = event.eventType();
                    if (eventType == 1) {
                        final Kernel32.KEY_EVENT_RECORD keyEvent = event.keyEvent();
                        this.processKeyEvent(keyEvent.keyDown(), keyEvent.keyCode(), keyEvent.uchar(), keyEvent.controlKeyState());
                        flush = true;
                    }
                    else if (eventType == 4) {
                        this.raise(Terminal.Signal.WINCH);
                    }
                    else if (eventType == 2) {
                        this.processMouseEvent(event.mouseEvent());
                        flush = true;
                    }
                    else if (eventType == 16) {
                        this.processFocusEvent(event.focusEvent().setFocus());
                    }
                }
                final boolean b = flush;
                if (arena != null) {
                    arena.close();
                }
                return b;
            }
            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 == 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.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) {
        try (final Arena arena = Arena.ofConfined()) {
            final Kernel32.CONSOLE_SCREEN_BUFFER_INFO info = new Kernel32.CONSOLE_SCREEN_BUFFER_INFO(arena);
            if (Kernel32.GetConsoleScreenBufferInfo((MemorySegment)this.outConsole, info) == 0) {
                throw new IOError(new IOException("Could not get the cursor position: " + Kernel32.getLastErrorMessage()));
            }
            return new Cursor(info.cursorPosition().x(), info.cursorPosition().y());
        }
    }
    
    @Override
    public int getDefaultForegroundColor() {
        try (final Arena arena = Arena.ofConfined()) {
            final Kernel32.CONSOLE_SCREEN_BUFFER_INFO info = new Kernel32.CONSOLE_SCREEN_BUFFER_INFO(arena);
            if (Kernel32.GetConsoleScreenBufferInfo((MemorySegment)this.outConsole, info) == 0) {
                final int n = -1;
                if (arena != null) {
                    arena.close();
                }
                return n;
            }
            return this.convertAttributeToRgb(info.attributes() & 0xF, true);
        }
    }
    
    @Override
    public int getDefaultBackgroundColor() {
        try (final Arena arena = Arena.ofConfined()) {
            final Kernel32.CONSOLE_SCREEN_BUFFER_INFO info = new Kernel32.CONSOLE_SCREEN_BUFFER_INFO(arena);
            if (Kernel32.GetConsoleScreenBufferInfo((MemorySegment)this.outConsole, info) == 0) {
                final int n = -1;
                if (arena != null) {
                    arena.close();
                }
                return n;
            }
            return this.convertAttributeToRgb((info.attributes() & 0xF0) >> 4, false);
        }
    }
}
