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

package org.fusesource.jansi.io;

import java.io.IOException;
import java.io.OutputStream;
import org.fusesource.jansi.internal.Kernel32;

public final class WindowsAnsiProcessor extends AnsiProcessor
{
    private 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 short savedX;
    private short savedY;
    
    public WindowsAnsiProcessor(final OutputStream ps, final long console) throws IOException {
        super(ps);
        this.info = new Kernel32.CONSOLE_SCREEN_BUFFER_INFO();
        this.savedX = -1;
        this.savedY = -1;
        this.console = console;
        this.getConsoleInfo();
        this.originalColors = this.info.attributes;
    }
    
    public WindowsAnsiProcessor(final OutputStream ps, final boolean stdout) throws IOException {
        this(ps, Kernel32.GetStdHandle(stdout ? Kernel32.STD_OUTPUT_HANDLE : Kernel32.STD_ERROR_HANDLE));
    }
    
    public WindowsAnsiProcessor(final OutputStream ps) throws IOException {
        this(ps, true);
    }
    
    private void getConsoleInfo() throws IOException {
        this.os.flush();
        if (Kernel32.GetConsoleScreenBufferInfo(this.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.os.flush();
        short attributes = this.info.attributes;
        if (this.negative) {
            attributes = this.invertAttributeColors(attributes);
        }
        if (Kernel32.SetConsoleTextAttribute(this.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 {
        if (Kernel32.SetConsoleCursorPosition(this.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(this.console, this.info.attributes, screenLength, topLeft, written);
                Kernel32.FillConsoleOutputCharacterW(this.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(this.console, this.info.attributes, lengthToCursor, topLeft2, written);
                Kernel32.FillConsoleOutputCharacterW(this.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(this.console, this.info.attributes, lengthToEnd, this.info.cursorPosition.copy(), written);
                Kernel32.FillConsoleOutputCharacterW(this.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(this.console, this.info.attributes, this.info.size.x, leftColCurrRow, written);
                Kernel32.FillConsoleOutputCharacterW(this.console, ' ', this.info.size.x, leftColCurrRow, written);
                break;
            }
            case 1: {
                final Kernel32.COORD leftColCurrRow2 = this.info.cursorPosition.copy();
                leftColCurrRow2.x = 0;
                Kernel32.FillConsoleOutputAttribute(this.console, this.info.attributes, this.info.cursorPosition.x, leftColCurrRow2, written);
                Kernel32.FillConsoleOutputCharacterW(this.console, ' ', this.info.cursorPosition.x, leftColCurrRow2, written);
                break;
            }
            case 0: {
                final int lengthToLastCol = this.info.size.x - this.info.cursorPosition.x;
                Kernel32.FillConsoleOutputAttribute(this.console, this.info.attributes, lengthToLastCol, this.info.cursorPosition.copy(), written);
                Kernel32.FillConsoleOutputCharacterW(this.console, ' ', lengthToLastCol, this.info.cursorPosition.copy(), written);
                break;
            }
        }
    }
    
    @Override
    protected void processCursorLeft(final int count) throws IOException {
        this.getConsoleInfo();
        this.info.cursorPosition.x = (short)Math.max(0, this.info.cursorPosition.x - count);
        this.applyCursorPosition();
    }
    
    @Override
    protected void processCursorRight(final int count) throws IOException {
        this.getConsoleInfo();
        this.info.cursorPosition.x = (short)Math.min(this.info.window.width(), this.info.cursorPosition.x + count);
        this.applyCursorPosition();
    }
    
    @Override
    protected void processCursorDown(final int count) throws IOException {
        this.getConsoleInfo();
        this.info.cursorPosition.y = (short)Math.min(Math.max(0, this.info.size.y - 1), this.info.cursorPosition.y + count);
        this.applyCursorPosition();
    }
    
    @Override
    protected void processCursorUp(final int count) throws IOException {
        this.getConsoleInfo();
        this.info.cursorPosition.y = (short)Math.max(this.info.window.top, 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)Math.max(this.info.window.top, Math.min(this.info.size.y, this.info.window.top + row - 1));
        this.info.cursorPosition.x = (short)Math.max(0, Math.min(this.info.window.width(), col - 1));
        this.applyCursorPosition();
    }
    
    @Override
    protected void processCursorToColumn(final int x) throws IOException {
        this.getConsoleInfo();
        this.info.cursorPosition.x = (short)Math.max(0, Math.min(this.info.window.width(), x - 1));
        this.applyCursorPosition();
    }
    
    @Override
    protected void processCursorUpLine(final int count) throws IOException {
        this.getConsoleInfo();
        this.info.cursorPosition.x = 0;
        this.info.cursorPosition.y = (short)Math.max(this.info.window.top, this.info.cursorPosition.y - count);
        this.applyCursorPosition();
    }
    
    @Override
    protected void processCursorDownLine(final int count) throws IOException {
        this.getConsoleInfo();
        this.info.cursorPosition.x = 0;
        this.info.cursorPosition.y = (short)Math.max(this.info.window.top, this.info.cursorPosition.y + count);
        this.applyCursorPosition();
    }
    
    @Override
    protected void processSetForegroundColor(final int color, final boolean bright) throws IOException {
        this.info.attributes = (short)((this.info.attributes & 0xFFFFFFF8) | WindowsAnsiProcessor.ANSI_FOREGROUND_COLOR_MAP[color]);
        if (bright) {
            final Kernel32.CONSOLE_SCREEN_BUFFER_INFO info = this.info;
            info.attributes |= Kernel32.FOREGROUND_INTENSITY;
        }
        this.applyAttribute();
    }
    
    @Override
    protected void processSetForegroundColorExt(final int paletteIndex) throws IOException {
        final int round = Colors.roundColor(paletteIndex, 16);
        this.processSetForegroundColor((round >= 8) ? (round - 8) : round, round >= 8);
    }
    
    @Override
    protected void processSetForegroundColorExt(final int r, final int g, final int b) throws IOException {
        final int round = Colors.roundRgbColor(r, g, b, 16);
        this.processSetForegroundColor((round >= 8) ? (round - 8) : round, round >= 8);
    }
    
    @Override
    protected void processSetBackgroundColor(final int color, final boolean bright) throws IOException {
        this.info.attributes = (short)((this.info.attributes & 0xFFFFFF8F) | WindowsAnsiProcessor.ANSI_BACKGROUND_COLOR_MAP[color]);
        if (bright) {
            final Kernel32.CONSOLE_SCREEN_BUFFER_INFO info = this.info;
            info.attributes |= Kernel32.BACKGROUND_INTENSITY;
        }
        this.applyAttribute();
    }
    
    @Override
    protected void processSetBackgroundColorExt(final int paletteIndex) throws IOException {
        final int round = Colors.roundColor(paletteIndex, 16);
        this.processSetBackgroundColor((round >= 8) ? (round - 8) : round, round >= 8);
    }
    
    @Override
    protected void processSetBackgroundColorExt(final int r, final int g, final int b) throws IOException {
        final int round = Colors.roundRgbColor(r, g, b, 16);
        this.processSetBackgroundColor((round >= 8) ? (round - 8) : round, round >= 8);
    }
    
    @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 processAttributeReset() throws IOException {
        this.info.attributes = (short)((this.info.attributes & 0xFFFFFF00) | this.originalColors);
        this.negative = false;
        this.applyAttribute();
    }
    
    @Override
    protected void processSetAttribute(final int attribute) throws IOException {
        switch (attribute) {
            case 1: {
                this.info.attributes |= Kernel32.FOREGROUND_INTENSITY;
                this.applyAttribute();
                break;
            }
            case 22: {
                this.info.attributes &= (short)~Kernel32.FOREGROUND_INTENSITY;
                this.applyAttribute();
                break;
            }
            case 4: {
                this.info.attributes |= Kernel32.BACKGROUND_INTENSITY;
                this.applyAttribute();
                break;
            }
            case 24: {
                this.info.attributes &= (short)~Kernel32.BACKGROUND_INTENSITY;
                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.os.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(this.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(this.console, scroll, scroll, org, info) == 0) {
            throw new IOException(Kernel32.getLastErrorMessage());
        }
    }
    
    @Override
    protected void processChangeWindowTitle(final String label) {
        Kernel32.SetConsoleTitle(label);
    }
    
    static {
        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, WindowsAnsiProcessor.FOREGROUND_YELLOW, Kernel32.FOREGROUND_BLUE, WindowsAnsiProcessor.FOREGROUND_MAGENTA, WindowsAnsiProcessor.FOREGROUND_CYAN, WindowsAnsiProcessor.FOREGROUND_WHITE };
        ANSI_BACKGROUND_COLOR_MAP = new short[] { 0, Kernel32.BACKGROUND_RED, Kernel32.BACKGROUND_GREEN, WindowsAnsiProcessor.BACKGROUND_YELLOW, Kernel32.BACKGROUND_BLUE, WindowsAnsiProcessor.BACKGROUND_MAGENTA, WindowsAnsiProcessor.BACKGROUND_CYAN, WindowsAnsiProcessor.BACKGROUND_WHITE };
    }
}
