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

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

import org.jline.utils.Colors;
import java.io.IOException;
import java.io.Writer;
import org.jline.nativ.Kernel32;
import org.jline.utils.AnsiWriter;

public final class WindowsAnsiWriter extends AnsiWriter
{
    private static final long console;
    private static final short FOREGROUND_BLACK = 0;
    private static final short FOREGROUND_YELLOW;
    private static final short FOREGROUND_MAGENTA;
    private static final short FOREGROUND_CYAN;
    private static final short FOREGROUND_WHITE;
    private static final short BACKGROUND_BLACK = 0;
    private static final short BACKGROUND_YELLOW;
    private static final short BACKGROUND_MAGENTA;
    private static final short BACKGROUND_CYAN;
    private static final short BACKGROUND_WHITE;
    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();
        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 |= Kernel32.FOREGROUND_INTENSITY;
        }
        if (this.underline) {
            attributes |= Kernel32.BACKGROUND_INTENSITY;
        }
        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.copy()) == 0) {
            throw new IOException(Kernel32.getLastErrorMessage());
        }
    }
    
    @Override
    protected void processEraseScreen(final int eraseOption) throws IOException {
        this.getConsoleInfo();
        final int[] written = { 0 };
        switch (eraseOption) {
            case 2: {
                final Kernel32.COORD topLeft = new Kernel32.COORD();
                topLeft.x = 0;
                topLeft.y = 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();
                topLeft2.x = 0;
                topLeft2.y = 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.copy(), written);
                Kernel32.FillConsoleOutputCharacterW(WindowsAnsiWriter.console, ' ', lengthToEnd, this.info.cursorPosition.copy(), written);
                break;
            }
        }
    }
    
    @Override
    protected void processEraseLine(final int eraseOption) throws IOException {
        this.getConsoleInfo();
        final int[] written = { 0 };
        switch (eraseOption) {
            case 2: {
                final Kernel32.COORD leftColCurrRow = this.info.cursorPosition.copy();
                leftColCurrRow.x = 0;
                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 = this.info.cursorPosition.copy();
                leftColCurrRow2.x = 0;
                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.copy(), written);
                Kernel32.FillConsoleOutputCharacterW(WindowsAnsiWriter.console, ' ', lengthToLastCol, this.info.cursorPosition.copy(), written);
                break;
            }
        }
    }
    
    @Override
    protected void processCursorUpLine(final int count) throws IOException {
        this.getConsoleInfo();
        this.info.cursorPosition.x = 0;
        final Kernel32.COORD cursorPosition = this.info.cursorPosition;
        cursorPosition.y -= (short)count;
        this.applyCursorPosition();
    }
    
    @Override
    protected void processCursorDownLine(final int count) throws IOException {
        this.getConsoleInfo();
        this.info.cursorPosition.x = 0;
        final Kernel32.COORD cursorPosition = this.info.cursorPosition;
        cursorPosition.y += (short)count;
        this.applyCursorPosition();
    }
    
    @Override
    protected void processCursorLeft(final int count) throws IOException {
        this.getConsoleInfo();
        final Kernel32.COORD cursorPosition = this.info.cursorPosition;
        cursorPosition.x -= (short)count;
        this.applyCursorPosition();
    }
    
    @Override
    protected void processCursorRight(final int count) throws IOException {
        this.getConsoleInfo();
        final Kernel32.COORD cursorPosition = this.info.cursorPosition;
        cursorPosition.x += (short)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) {
            final Kernel32.COORD cursorPosition = this.info.cursorPosition;
            cursorPosition.y += (short)count;
            this.applyCursorPosition();
        }
        if (nb > 0) {
            final Kernel32.SMALL_RECT scroll = this.info.window.copy();
            scroll.top = 0;
            final Kernel32.COORD org = new Kernel32.COORD();
            org.x = 0;
            org.y = (short)(-nb);
            final Kernel32.CHAR_INFO info = new Kernel32.CHAR_INFO();
            info.unicodeChar = ' ';
            info.attributes = this.originalColors;
            Kernel32.ScrollConsoleScreenBuffer(WindowsAnsiWriter.console, scroll, scroll, org, info);
        }
    }
    
    @Override
    protected void processCursorUp(final int count) throws IOException {
        this.getConsoleInfo();
        final Kernel32.COORD cursorPosition = this.info.cursorPosition;
        cursorPosition.y -= (short)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 & ~Kernel32.FOREGROUND_INTENSITY) | ((color >= 8) ? Kernel32.FOREGROUND_INTENSITY : 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 & ~Kernel32.BACKGROUND_INTENSITY) | ((color >= 8) ? Kernel32.BACKGROUND_INTENSITY : 0));
        this.applyAttribute();
    }
    
    @Override
    protected void processDefaultTextColor() throws IOException {
        this.info.attributes = (short)((this.info.attributes & 0xFFFFFFF0) | (this.originalColors & 0xF));
        this.info.attributes &= (short)~Kernel32.FOREGROUND_INTENSITY;
        this.applyAttribute();
    }
    
    @Override
    protected void processDefaultBackgroundColor() throws IOException {
        this.info.attributes = (short)((this.info.attributes & 0xFFFFFF0F) | (this.originalColors & 0xF0));
        this.info.attributes &= (short)~Kernel32.BACKGROUND_INTENSITY;
        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 {
        this.getConsoleInfo();
        final Kernel32.SMALL_RECT scroll = this.info.window.copy();
        scroll.top = this.info.cursorPosition.y;
        final Kernel32.COORD org = new Kernel32.COORD();
        org.x = 0;
        org.y = (short)(this.info.cursorPosition.y + optionInt);
        final Kernel32.CHAR_INFO info = new Kernel32.CHAR_INFO();
        info.attributes = this.originalColors;
        info.unicodeChar = ' ';
        if (Kernel32.ScrollConsoleScreenBuffer(WindowsAnsiWriter.console, scroll, scroll, org, info) == 0) {
            throw new IOException(Kernel32.getLastErrorMessage());
        }
    }
    
    @Override
    protected void processDeleteLine(final int optionInt) throws IOException {
        this.getConsoleInfo();
        final Kernel32.SMALL_RECT scroll = this.info.window.copy();
        scroll.top = this.info.cursorPosition.y;
        final Kernel32.COORD org = new Kernel32.COORD();
        org.x = 0;
        org.y = (short)(this.info.cursorPosition.y - optionInt);
        final Kernel32.CHAR_INFO info = new Kernel32.CHAR_INFO();
        info.attributes = this.originalColors;
        info.unicodeChar = ' ';
        if (Kernel32.ScrollConsoleScreenBuffer(WindowsAnsiWriter.console, scroll, scroll, org, info) == 0) {
            throw new IOException(Kernel32.getLastErrorMessage());
        }
    }
    
    @Override
    protected void processChangeWindowTitle(final String title) {
        Kernel32.SetConsoleTitle(title);
    }
    
    static {
        console = Kernel32.GetStdHandle(Kernel32.STD_OUTPUT_HANDLE);
        FOREGROUND_YELLOW = (short)(Kernel32.FOREGROUND_RED | Kernel32.FOREGROUND_GREEN);
        FOREGROUND_MAGENTA = (short)(Kernel32.FOREGROUND_BLUE | Kernel32.FOREGROUND_RED);
        FOREGROUND_CYAN = (short)(Kernel32.FOREGROUND_BLUE | Kernel32.FOREGROUND_GREEN);
        FOREGROUND_WHITE = (short)(Kernel32.FOREGROUND_RED | Kernel32.FOREGROUND_GREEN | Kernel32.FOREGROUND_BLUE);
        BACKGROUND_YELLOW = (short)(Kernel32.BACKGROUND_RED | Kernel32.BACKGROUND_GREEN);
        BACKGROUND_MAGENTA = (short)(Kernel32.BACKGROUND_BLUE | Kernel32.BACKGROUND_RED);
        BACKGROUND_CYAN = (short)(Kernel32.BACKGROUND_BLUE | Kernel32.BACKGROUND_GREEN);
        BACKGROUND_WHITE = (short)(Kernel32.BACKGROUND_RED | Kernel32.BACKGROUND_GREEN | Kernel32.BACKGROUND_BLUE);
        ANSI_FOREGROUND_COLOR_MAP = new short[] { 0, Kernel32.FOREGROUND_RED, Kernel32.FOREGROUND_GREEN, WindowsAnsiWriter.FOREGROUND_YELLOW, Kernel32.FOREGROUND_BLUE, WindowsAnsiWriter.FOREGROUND_MAGENTA, WindowsAnsiWriter.FOREGROUND_CYAN, WindowsAnsiWriter.FOREGROUND_WHITE };
        ANSI_BACKGROUND_COLOR_MAP = new short[] { 0, Kernel32.BACKGROUND_RED, Kernel32.BACKGROUND_GREEN, WindowsAnsiWriter.BACKGROUND_YELLOW, Kernel32.BACKGROUND_BLUE, WindowsAnsiWriter.BACKGROUND_MAGENTA, WindowsAnsiWriter.BACKGROUND_CYAN, WindowsAnsiWriter.BACKGROUND_WHITE };
    }
}
