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

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

import org.jline.utils.Colors;
import com.sun.jna.ptr.IntByReference;
import java.io.IOException;
import java.io.Writer;
import com.sun.jna.Pointer;
import org.jline.utils.AnsiWriter;

public final class WindowsAnsiWriter extends AnsiWriter
{
    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 static final int MAX_ESCAPE_SEQUENCE_LENGTH = 100;
    private final Pointer console;
    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, final Pointer console) throws IOException {
        super(out);
        this.info = new Kernel32.CONSOLE_SCREEN_BUFFER_INFO();
        this.savedX = -1;
        this.savedY = -1;
        this.console = console;
        this.getConsoleInfo();
        this.originalColors = this.info.wAttributes;
    }
    
    private void getConsoleInfo() throws IOException {
        this.out.flush();
        Kernel32.INSTANCE.GetConsoleScreenBufferInfo(this.console, this.info);
        if (this.negative) {
            this.info.wAttributes = this.invertAttributeColors(this.info.wAttributes);
        }
    }
    
    private void applyAttribute() throws IOException {
        this.out.flush();
        short attributes = this.info.wAttributes;
        if (this.bold) {
            attributes |= 0x8;
        }
        if (this.underline) {
            attributes |= 0x80;
        }
        if (this.negative) {
            attributes = this.invertAttributeColors(attributes);
        }
        Kernel32.INSTANCE.SetConsoleTextAttribute(this.console, attributes);
    }
    
    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.dwCursorPosition.X = (short)Math.max(0, Math.min(this.info.dwSize.X - 1, this.info.dwCursorPosition.X));
        this.info.dwCursorPosition.Y = (short)Math.max(0, Math.min(this.info.dwSize.Y - 1, this.info.dwCursorPosition.Y));
        Kernel32.INSTANCE.SetConsoleCursorPosition(this.console, this.info.dwCursorPosition);
    }
    
    @Override
    protected void processEraseScreen(final int eraseOption) throws IOException {
        this.getConsoleInfo();
        final IntByReference written = new IntByReference();
        switch (eraseOption) {
            case 2: {
                final Kernel32.COORD topLeft = new Kernel32.COORD();
                topLeft.X = 0;
                topLeft.Y = this.info.srWindow.Top;
                final int screenLength = this.info.srWindow.height() * this.info.dwSize.X;
                Kernel32.INSTANCE.FillConsoleOutputCharacter(this.console, ' ', screenLength, topLeft, written);
                Kernel32.INSTANCE.FillConsoleOutputAttribute(this.console, this.info.wAttributes, screenLength, topLeft, written);
                break;
            }
            case 1: {
                final Kernel32.COORD topLeft2 = new Kernel32.COORD();
                topLeft2.X = 0;
                topLeft2.Y = this.info.srWindow.Top;
                final int lengthToCursor = (this.info.dwCursorPosition.Y - this.info.srWindow.Top) * this.info.dwSize.X + this.info.dwCursorPosition.X;
                Kernel32.INSTANCE.FillConsoleOutputCharacter(this.console, ' ', lengthToCursor, topLeft2, written);
                Kernel32.INSTANCE.FillConsoleOutputAttribute(this.console, this.info.wAttributes, lengthToCursor, topLeft2, written);
                break;
            }
            case 0: {
                final int lengthToEnd = (this.info.srWindow.Bottom - this.info.dwCursorPosition.Y) * this.info.dwSize.X + (this.info.dwSize.X - this.info.dwCursorPosition.X);
                Kernel32.INSTANCE.FillConsoleOutputCharacter(this.console, ' ', lengthToEnd, this.info.dwCursorPosition, written);
                Kernel32.INSTANCE.FillConsoleOutputAttribute(this.console, this.info.wAttributes, lengthToEnd, this.info.dwCursorPosition, written);
                break;
            }
        }
    }
    
    @Override
    protected void processEraseLine(final int eraseOption) throws IOException {
        this.getConsoleInfo();
        final IntByReference written = new IntByReference();
        switch (eraseOption) {
            case 2: {
                final Kernel32.COORD leftColCurrRow = new Kernel32.COORD((short)0, this.info.dwCursorPosition.Y);
                Kernel32.INSTANCE.FillConsoleOutputCharacter(this.console, ' ', this.info.dwSize.X, leftColCurrRow, written);
                Kernel32.INSTANCE.FillConsoleOutputAttribute(this.console, this.info.wAttributes, this.info.dwSize.X, leftColCurrRow, written);
                break;
            }
            case 1: {
                final Kernel32.COORD leftColCurrRow2 = new Kernel32.COORD((short)0, this.info.dwCursorPosition.Y);
                Kernel32.INSTANCE.FillConsoleOutputCharacter(this.console, ' ', this.info.dwCursorPosition.X, leftColCurrRow2, written);
                Kernel32.INSTANCE.FillConsoleOutputAttribute(this.console, this.info.wAttributes, this.info.dwCursorPosition.X, leftColCurrRow2, written);
                break;
            }
            case 0: {
                final int lengthToLastCol = this.info.dwSize.X - this.info.dwCursorPosition.X;
                Kernel32.INSTANCE.FillConsoleOutputCharacter(this.console, ' ', lengthToLastCol, this.info.dwCursorPosition, written);
                Kernel32.INSTANCE.FillConsoleOutputAttribute(this.console, this.info.wAttributes, lengthToLastCol, this.info.dwCursorPosition, written);
                break;
            }
        }
    }
    
    @Override
    protected void processCursorUpLine(final int count) throws IOException {
        this.getConsoleInfo();
        this.info.dwCursorPosition.X = 0;
        final Kernel32.COORD dwCursorPosition = this.info.dwCursorPosition;
        dwCursorPosition.Y -= (short)count;
        this.applyCursorPosition();
    }
    
    @Override
    protected void processCursorDownLine(final int count) throws IOException {
        this.getConsoleInfo();
        this.info.dwCursorPosition.X = 0;
        final Kernel32.COORD dwCursorPosition = this.info.dwCursorPosition;
        dwCursorPosition.Y += (short)count;
        this.applyCursorPosition();
    }
    
    @Override
    protected void processCursorLeft(final int count) throws IOException {
        this.getConsoleInfo();
        final Kernel32.COORD dwCursorPosition = this.info.dwCursorPosition;
        dwCursorPosition.X -= (short)count;
        this.applyCursorPosition();
    }
    
    @Override
    protected void processCursorRight(final int count) throws IOException {
        this.getConsoleInfo();
        final Kernel32.COORD dwCursorPosition = this.info.dwCursorPosition;
        dwCursorPosition.X += (short)count;
        this.applyCursorPosition();
    }
    
    @Override
    protected void processCursorDown(final int count) throws IOException {
        this.getConsoleInfo();
        final int nb = Math.max(0, this.info.dwCursorPosition.Y + count - this.info.dwSize.Y + 1);
        if (nb != count) {
            final Kernel32.COORD dwCursorPosition = this.info.dwCursorPosition;
            dwCursorPosition.Y += (short)count;
            this.applyCursorPosition();
        }
        if (nb > 0) {
            final Kernel32.SMALL_RECT scroll = new Kernel32.SMALL_RECT(this.info.srWindow);
            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(' ', this.originalColors);
            Kernel32.INSTANCE.ScrollConsoleScreenBuffer(this.console, scroll, scroll, org, info);
        }
    }
    
    @Override
    protected void processCursorUp(final int count) throws IOException {
        this.getConsoleInfo();
        final Kernel32.COORD dwCursorPosition = this.info.dwCursorPosition;
        dwCursorPosition.Y -= (short)count;
        this.applyCursorPosition();
    }
    
    @Override
    protected void processCursorTo(final int row, final int col) throws IOException {
        this.getConsoleInfo();
        this.info.dwCursorPosition.Y = (short)(this.info.srWindow.Top + row - 1);
        this.info.dwCursorPosition.X = (short)(col - 1);
        this.applyCursorPosition();
    }
    
    @Override
    protected void processCursorToColumn(final int x) throws IOException {
        this.getConsoleInfo();
        this.info.dwCursorPosition.X = (short)(x - 1);
        this.applyCursorPosition();
    }
    
    @Override
    protected void processSetForegroundColorExt(final int paletteIndex) throws IOException {
        final int color = Colors.roundColor(paletteIndex, 16);
        this.info.wAttributes = (short)((this.info.wAttributes & 0xFFFFFFF8) | WindowsAnsiWriter.ANSI_FOREGROUND_COLOR_MAP[color & 0x7]);
        this.info.wAttributes = (short)((this.info.wAttributes & 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.wAttributes = (short)((this.info.wAttributes & 0xFFFFFF8F) | WindowsAnsiWriter.ANSI_BACKGROUND_COLOR_MAP[color & 0x7]);
        this.info.wAttributes = (short)((this.info.wAttributes & 0xFFFFFF7F) | ((color >= 8) ? 128 : 0));
        this.applyAttribute();
    }
    
    @Override
    protected void processDefaultTextColor() throws IOException {
        this.info.wAttributes = (short)((this.info.wAttributes & 0xFFFFFFF0) | (this.originalColors & 0xF));
        this.applyAttribute();
    }
    
    @Override
    protected void processDefaultBackgroundColor() throws IOException {
        this.info.wAttributes = (short)((this.info.wAttributes & 0xFFFFFF0F) | (this.originalColors & 0xF0));
        this.applyAttribute();
    }
    
    @Override
    protected void processAttributeRest() throws IOException {
        this.info.wAttributes = (short)((this.info.wAttributes & 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.dwCursorPosition.X;
        this.savedY = this.info.dwCursorPosition.Y;
    }
    
    @Override
    protected void processRestoreCursorPosition() throws IOException {
        if (this.savedX != -1 && this.savedY != -1) {
            this.out.flush();
            this.info.dwCursorPosition.X = this.savedX;
            this.info.dwCursorPosition.Y = this.savedY;
            this.applyCursorPosition();
        }
    }
    
    @Override
    protected void processInsertLine(final int optionInt) throws IOException {
        this.getConsoleInfo();
        final Kernel32.SMALL_RECT scroll = new Kernel32.SMALL_RECT(this.info.srWindow);
        scroll.Top = this.info.dwCursorPosition.Y;
        final Kernel32.COORD org = new Kernel32.COORD();
        org.X = 0;
        org.Y = (short)(this.info.dwCursorPosition.Y + optionInt);
        final Kernel32.CHAR_INFO info = new Kernel32.CHAR_INFO(' ', this.originalColors);
        Kernel32.INSTANCE.ScrollConsoleScreenBuffer(this.console, scroll, scroll, org, info);
    }
    
    @Override
    protected void processDeleteLine(final int optionInt) throws IOException {
        this.getConsoleInfo();
        final Kernel32.SMALL_RECT scroll = new Kernel32.SMALL_RECT(this.info.srWindow);
        scroll.Top = this.info.dwCursorPosition.Y;
        final Kernel32.COORD org = new Kernel32.COORD();
        org.X = 0;
        org.Y = (short)(this.info.dwCursorPosition.Y - optionInt);
        final Kernel32.CHAR_INFO info = new Kernel32.CHAR_INFO(' ', this.originalColors);
        Kernel32.INSTANCE.ScrollConsoleScreenBuffer(this.console, scroll, scroll, org, info);
    }
    
    @Override
    protected void processChangeWindowTitle(final String label) {
        Kernel32.INSTANCE.SetConsoleTitle(label);
    }
    
    static {
        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 };
    }
}
