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

package org.jline.terminal.impl.ffm;

import org.jline.utils.Colors;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.ValueLayout;
import java.io.IOException;
import java.lang.foreign.Arena;
import java.io.Writer;
import java.lang.foreign.MemorySegment;
import org.jline.utils.AnsiWriter;

class WindowsAnsiWriter extends AnsiWriter
{
    private static final MemorySegment console;
    private static final short FOREGROUND_BLACK = 0;
    private static final short FOREGROUND_YELLOW = 6;
    private static final short FOREGROUND_MAGENTA = 5;
    private static final short FOREGROUND_CYAN = 3;
    private static final short FOREGROUND_WHITE = 7;
    private static final short BACKGROUND_BLACK = 0;
    private static final short BACKGROUND_YELLOW = 96;
    private static final short BACKGROUND_MAGENTA = 80;
    private static final short BACKGROUND_CYAN = 48;
    private static final short BACKGROUND_WHITE = 112;
    private static final short[] ANSI_FOREGROUND_COLOR_MAP;
    private static final short[] ANSI_BACKGROUND_COLOR_MAP;
    private final Kernel32.CONSOLE_SCREEN_BUFFER_INFO info;
    private final short originalColors;
    private boolean negative;
    private boolean bold;
    private boolean underline;
    private short savedX;
    private short savedY;
    
    public WindowsAnsiWriter(final Writer out) throws IOException {
        super(out);
        this.info = new Kernel32.CONSOLE_SCREEN_BUFFER_INFO(Arena.ofAuto());
        this.savedX = -1;
        this.savedY = -1;
        this.getConsoleInfo();
        this.originalColors = this.info.attributes();
    }
    
    private void getConsoleInfo() throws IOException {
        this.out.flush();
        if (Kernel32.GetConsoleScreenBufferInfo(WindowsAnsiWriter.console, this.info) == 0) {
            throw new IOException("Could not get the screen info: " + Kernel32.getLastErrorMessage());
        }
        if (this.negative) {
            this.info.attributes(this.invertAttributeColors(this.info.attributes()));
        }
    }
    
    private void applyAttribute() throws IOException {
        this.out.flush();
        short attributes = this.info.attributes();
        if (this.bold) {
            attributes |= 0x8;
        }
        if (this.underline) {
            attributes |= 0x80;
        }
        if (this.negative) {
            attributes = this.invertAttributeColors(attributes);
        }
        if (Kernel32.SetConsoleTextAttribute(WindowsAnsiWriter.console, attributes) == 0) {
            throw new IOException(Kernel32.getLastErrorMessage());
        }
    }
    
    private short invertAttributeColors(short attributes) {
        int fg = 0xF & attributes;
        fg <<= 4;
        int bg = 0xF0 & attributes;
        bg >>= 4;
        attributes = (short)((attributes & 0xFF00) | fg | bg);
        return attributes;
    }
    
    private void applyCursorPosition() throws IOException {
        this.info.cursorPosition().x((short)Math.max(0, Math.min(this.info.size().x() - 1, this.info.cursorPosition().x())));
        this.info.cursorPosition().y((short)Math.max(0, Math.min(this.info.size().y() - 1, this.info.cursorPosition().y())));
        if (Kernel32.SetConsoleCursorPosition(WindowsAnsiWriter.console, this.info.cursorPosition()) == 0) {
            throw new IOException(Kernel32.getLastErrorMessage());
        }
    }
    
    @Override
    protected void processEraseScreen(final int eraseOption) throws IOException {
        this.getConsoleInfo();
        try (final Arena arena = Arena.ofConfined()) {
            final MemorySegment written = arena.allocate(ValueLayout.JAVA_INT);
            switch (eraseOption) {
                case 2: {
                    final Kernel32.COORD topLeft = new Kernel32.COORD(arena, (short)0, this.info.window().top());
                    final int screenLength = this.info.window().height() * this.info.size().x();
                    Kernel32.FillConsoleOutputAttribute(WindowsAnsiWriter.console, this.originalColors, screenLength, topLeft, written);
                    Kernel32.FillConsoleOutputCharacterW(WindowsAnsiWriter.console, ' ', screenLength, topLeft, written);
                    break;
                }
                case 1: {
                    final Kernel32.COORD topLeft2 = new Kernel32.COORD(arena, (short)0, this.info.window().top());
                    final int lengthToCursor = (this.info.cursorPosition().y() - this.info.window().top()) * this.info.size().x() + this.info.cursorPosition().x();
                    Kernel32.FillConsoleOutputAttribute(WindowsAnsiWriter.console, this.originalColors, lengthToCursor, topLeft2, written);
                    Kernel32.FillConsoleOutputCharacterW(WindowsAnsiWriter.console, ' ', lengthToCursor, topLeft2, written);
                    break;
                }
                case 0: {
                    final int lengthToEnd = (this.info.window().bottom() - this.info.cursorPosition().y()) * this.info.size().x() + (this.info.size().x() - this.info.cursorPosition().x());
                    Kernel32.FillConsoleOutputAttribute(WindowsAnsiWriter.console, this.originalColors, lengthToEnd, this.info.cursorPosition(), written);
                    Kernel32.FillConsoleOutputCharacterW(WindowsAnsiWriter.console, ' ', lengthToEnd, this.info.cursorPosition(), written);
                    break;
                }
            }
        }
    }
    
    @Override
    protected void processEraseLine(final int eraseOption) throws IOException {
        this.getConsoleInfo();
        try (final Arena arena = Arena.ofConfined()) {
            final MemorySegment written = arena.allocate(ValueLayout.JAVA_INT);
            switch (eraseOption) {
                case 2: {
                    final Kernel32.COORD leftColCurrRow = new Kernel32.COORD(arena, (short)0, this.info.cursorPosition().y());
                    Kernel32.FillConsoleOutputAttribute(WindowsAnsiWriter.console, this.originalColors, this.info.size().x(), leftColCurrRow, written);
                    Kernel32.FillConsoleOutputCharacterW(WindowsAnsiWriter.console, ' ', this.info.size().x(), leftColCurrRow, written);
                    break;
                }
                case 1: {
                    final Kernel32.COORD leftColCurrRow2 = new Kernel32.COORD(arena, (short)0, this.info.cursorPosition().y());
                    Kernel32.FillConsoleOutputAttribute(WindowsAnsiWriter.console, this.originalColors, this.info.cursorPosition().x(), leftColCurrRow2, written);
                    Kernel32.FillConsoleOutputCharacterW(WindowsAnsiWriter.console, ' ', this.info.cursorPosition().x(), leftColCurrRow2, written);
                    break;
                }
                case 0: {
                    final int lengthToLastCol = this.info.size().x() - this.info.cursorPosition().x();
                    Kernel32.FillConsoleOutputAttribute(WindowsAnsiWriter.console, this.originalColors, lengthToLastCol, this.info.cursorPosition(), written);
                    Kernel32.FillConsoleOutputCharacterW(WindowsAnsiWriter.console, ' ', lengthToLastCol, this.info.cursorPosition(), written);
                    break;
                }
            }
        }
    }
    
    @Override
    protected void processCursorUpLine(final int count) throws IOException {
        this.getConsoleInfo();
        this.info.cursorPosition().x((short)0);
        this.info.cursorPosition().y((short)(this.info.cursorPosition().y() - count));
        this.applyCursorPosition();
    }
    
    @Override
    protected void processCursorDownLine(final int count) throws IOException {
        this.getConsoleInfo();
        this.info.cursorPosition().x((short)0);
        this.info.cursorPosition().y((short)(this.info.cursorPosition().y() + count));
        this.applyCursorPosition();
    }
    
    @Override
    protected void processCursorLeft(final int count) throws IOException {
        this.getConsoleInfo();
        this.info.cursorPosition().x((short)(this.info.cursorPosition().x() - count));
        this.applyCursorPosition();
    }
    
    @Override
    protected void processCursorRight(final int count) throws IOException {
        this.getConsoleInfo();
        this.info.cursorPosition().x((short)(this.info.cursorPosition().x() + count));
        this.applyCursorPosition();
    }
    
    @Override
    protected void processCursorDown(final int count) throws IOException {
        this.getConsoleInfo();
        final int nb = Math.max(0, this.info.cursorPosition().y() + count - this.info.size().y() + 1);
        if (nb != count) {
            this.info.cursorPosition().y((short)(this.info.cursorPosition().y() + count));
            this.applyCursorPosition();
        }
        if (nb > 0) {
            try (final Arena arena = Arena.ofConfined()) {
                final Kernel32.SMALL_RECT scroll = new Kernel32.SMALL_RECT(arena, this.info.window());
                scroll.top((short)0);
                final Kernel32.COORD org = new Kernel32.COORD(arena);
                org.x((short)0);
                org.y((short)(-nb));
                final Kernel32.CHAR_INFO info = new Kernel32.CHAR_INFO(arena, ' ', this.originalColors);
                Kernel32.ScrollConsoleScreenBuffer(WindowsAnsiWriter.console, scroll, scroll, org, info);
            }
        }
    }
    
    @Override
    protected void processCursorUp(final int count) throws IOException {
        this.getConsoleInfo();
        this.info.cursorPosition().y((short)(this.info.cursorPosition().y() - count));
        this.applyCursorPosition();
    }
    
    @Override
    protected void processCursorTo(final int row, final int col) throws IOException {
        this.getConsoleInfo();
        this.info.cursorPosition().y((short)(this.info.window().top() + row - 1));
        this.info.cursorPosition().x((short)(col - 1));
        this.applyCursorPosition();
    }
    
    @Override
    protected void processCursorToColumn(final int x) throws IOException {
        this.getConsoleInfo();
        this.info.cursorPosition().x((short)(x - 1));
        this.applyCursorPosition();
    }
    
    @Override
    protected void processSetForegroundColorExt(final int paletteIndex) throws IOException {
        final int color = Colors.roundColor(paletteIndex, 16);
        this.info.attributes((short)((this.info.attributes() & 0xFFFFFFF8) | WindowsAnsiWriter.ANSI_FOREGROUND_COLOR_MAP[color & 0x7]));
        this.info.attributes((short)((this.info.attributes() & 0xFFFFFFF7) | ((color >= 8) ? 8 : 0)));
        this.applyAttribute();
    }
    
    @Override
    protected void processSetBackgroundColorExt(final int paletteIndex) throws IOException {
        final int color = Colors.roundColor(paletteIndex, 16);
        this.info.attributes((short)((this.info.attributes() & 0xFFFFFF8F) | WindowsAnsiWriter.ANSI_BACKGROUND_COLOR_MAP[color & 0x7]));
        this.info.attributes((short)((this.info.attributes() & 0xFFFFFF7F) | ((color >= 8) ? 128 : 0)));
        this.applyAttribute();
    }
    
    @Override
    protected void processDefaultTextColor() throws IOException {
        this.info.attributes((short)((this.info.attributes() & 0xFFFFFFF0) | (this.originalColors & 0xF)));
        this.info.attributes((short)(this.info.attributes() & 0xFFFFFFF7));
        this.applyAttribute();
    }
    
    @Override
    protected void processDefaultBackgroundColor() throws IOException {
        this.info.attributes((short)((this.info.attributes() & 0xFFFFFF0F) | (this.originalColors & 0xF0)));
        this.info.attributes((short)(this.info.attributes() & 0xFFFFFF7F));
        this.applyAttribute();
    }
    
    @Override
    protected void processAttributeRest() throws IOException {
        this.info.attributes((short)((this.info.attributes() & 0xFFFFFF00) | this.originalColors));
        this.negative = false;
        this.bold = false;
        this.underline = false;
        this.applyAttribute();
    }
    
    @Override
    protected void processSetAttribute(final int attribute) throws IOException {
        switch (attribute) {
            case 1: {
                this.bold = true;
                this.applyAttribute();
                break;
            }
            case 22: {
                this.bold = false;
                this.applyAttribute();
                break;
            }
            case 4: {
                this.underline = true;
                this.applyAttribute();
                break;
            }
            case 24: {
                this.underline = false;
                this.applyAttribute();
                break;
            }
            case 7: {
                this.negative = true;
                this.applyAttribute();
                break;
            }
            case 27: {
                this.negative = false;
                this.applyAttribute();
                break;
            }
        }
    }
    
    @Override
    protected void processSaveCursorPosition() throws IOException {
        this.getConsoleInfo();
        this.savedX = this.info.cursorPosition().x();
        this.savedY = this.info.cursorPosition().y();
    }
    
    @Override
    protected void processRestoreCursorPosition() throws IOException {
        if (this.savedX != -1 && this.savedY != -1) {
            this.out.flush();
            this.info.cursorPosition().x(this.savedX);
            this.info.cursorPosition().y(this.savedY);
            this.applyCursorPosition();
        }
    }
    
    @Override
    protected void processInsertLine(final int optionInt) throws IOException {
        try (final Arena arena = Arena.ofConfined()) {
            this.getConsoleInfo();
            final Kernel32.SMALL_RECT scroll = this.info.window().copy(arena);
            scroll.top(this.info.cursorPosition().y());
            final Kernel32.COORD org = new Kernel32.COORD(arena, (short)0, (short)(this.info.cursorPosition().y() + optionInt));
            final Kernel32.CHAR_INFO info = new Kernel32.CHAR_INFO(arena, ' ', this.originalColors);
            if (Kernel32.ScrollConsoleScreenBuffer(WindowsAnsiWriter.console, scroll, scroll, org, info) == 0) {
                throw new IOException(Kernel32.getLastErrorMessage());
            }
        }
    }
    
    @Override
    protected void processDeleteLine(final int optionInt) throws IOException {
        try (final Arena arena = Arena.ofConfined()) {
            this.getConsoleInfo();
            final Kernel32.SMALL_RECT scroll = this.info.window().copy(arena);
            scroll.top(this.info.cursorPosition().y());
            final Kernel32.COORD org = new Kernel32.COORD(arena, (short)0, (short)(this.info.cursorPosition().y() - optionInt));
            final Kernel32.CHAR_INFO info = new Kernel32.CHAR_INFO(arena, ' ', this.originalColors);
            if (Kernel32.ScrollConsoleScreenBuffer(WindowsAnsiWriter.console, scroll, scroll, org, info) == 0) {
                throw new IOException(Kernel32.getLastErrorMessage());
            }
        }
    }
    
    @Override
    protected void processChangeWindowTitle(final String title) {
        try (final Arena session = Arena.ofConfined()) {
            final MemorySegment str = session.allocateFrom(title);
            Kernel32.SetConsoleTitleW(str);
        }
    }
    
    static {
        console = Kernel32.GetStdHandle(-11);
        ANSI_FOREGROUND_COLOR_MAP = new short[] { 0, 4, 2, 6, 1, 5, 3, 7 };
        ANSI_BACKGROUND_COLOR_MAP = new short[] { 0, 64, 32, 96, 16, 80, 48, 112 };
    }
}
