@SuppressWarnings("unused")
public class CLibrary {

    //
    // Initialization
    //

    public static final boolean LOADED;

    static {
        LOADED = JansiLoader.initialize();
        if (LOADED) {
            init();
        }
    }

    private static native void init();

    //
    // Constants
    //

    public static int STDOUT_FILENO = 1;

    public static int STDERR_FILENO = 2;

    public static boolean HAVE_ISATTY;

    public static boolean HAVE_TTYNAME;

    public static int TCSANOW;
    public static int TCSADRAIN;
    public static int TCSAFLUSH;
    public static long TIOCGETA;
    public static long TIOCSETA;
    public static long TIOCGETD;
    public static long TIOCSETD;
    public static long TIOCGWINSZ;
    public static long TIOCSWINSZ;

    /**
     * test whether a file descriptor refers to a terminal
     *
     * @param fd file descriptor
     * @return isatty() returns 1 if fd is an open file descriptor referring to a
     * terminal; otherwise 0 is returned, and errno is set to indicate the
     * error
     * @see <a href="http://man7.org/linux/man-pages/man3/isatty.3.html">ISATTY(3) man-page</a>
     * @see <a href="http://man7.org/linux/man-pages/man3/isatty.3p.html">ISATTY(3P) man-page</a>
     */
    public static native int isatty(int fd);

    public static native String ttyname(int filedes);

    /**
     * The openpty() function finds an available pseudoterminal and returns
     * file descriptors for the master and slave in amaster and aslave.
     *
     * @param amaster master return value
     * @param aslave  slave return value
     * @param name    filename return value
     * @param termios optional pty attributes
     * @param winsize optional size
     * @return 0 on success
     * @see <a href="http://man7.org/linux/man-pages/man3/openpty.3.html">OPENPTY(3) man-page</a>
     */
    public static native int openpty(
            int[] amaster,
            int[] aslave,
            byte[] name,
            Termios termios,
            WinSize winsize);

    public static native int tcgetattr(
            int filedes,
            Termios termios);

    public static native int tcsetattr(
            int filedes,
            int optional_actions,
            Termios termios);

    /**
     * Control a STREAMS device.
     *
     * @see <a href="http://man7.org/linux/man-pages/man3/ioctl.3p.html">IOCTL(3P) man-page</a>
     */
    public static native int ioctl(
            int filedes,
            long request,
            int[] params);

    public static native int ioctl(
            int filedes,
            long request,
            WinSize params);

    /**
     * Window sizes.
     *
     * @see <a href="http://man7.org/linux/man-pages/man4/tty_ioctl.4.html">IOCTL_TTY(2) man-page</a>
     */
    public static class WinSize {

        static {
            JansiLoader.initialize();
            init();
        }

        private static native void init();

        public static int SIZEOF;

        public short ws_row;
        public short ws_col;
        public short ws_xpixel;
        public short ws_ypixel;

        public WinSize() {
        }

        public WinSize(short ws_row, short ws_col) {
            this.ws_row = ws_row;
            this.ws_col = ws_col;
        }
    }

    /**
     * termios structure for termios functions, describing a general terminal interface that is
     * provided to control asynchronous communications ports
     *
     * @see <a href="http://man7.org/linux/man-pages/man3/termios.3.html">TERMIOS(3) man-page</a>
     */
    public static class Termios {

        static {
            JansiLoader.initialize();
            init();
        }

        private static native void init();

        public static int SIZEOF;

        public long c_iflag;
        public long c_oflag;
        public long c_cflag;
        public long c_lflag;
        public byte[] c_cc = new byte[32];
        public long c_ispeed;
        public long c_ospeed;
    }

} public static class WinSize {

    static {
        JansiLoader.initialize();
        init();
    }

    private static native void init();

    public static int SIZEOF;

    public short ws_row;
    public short ws_col;
    public short ws_xpixel;
    public short ws_ypixel;

    public WinSize() {
    }

    public WinSize(short ws_row, short ws_col) {
        this.ws_row = ws_row;
        this.ws_col = ws_col;
    }
} public class AnsiOutputStream extends FilterOutputStream {

    public static final byte[] RESET_CODE = "\033[0m".getBytes();

    public interface IoRunnable {
        void run() throws IOException;
    }

    public interface WidthSupplier {
        int getTerminalWidth();
    }

    public static class ZeroWidthSupplier implements WidthSupplier {
        @Override
        public int getTerminalWidth() {
            return 0;
        }
    }

    private static final int LOOKING_FOR_FIRST_ESC_CHAR = 0;
    private static final int LOOKING_FOR_SECOND_ESC_CHAR = 1;
    private static final int LOOKING_FOR_NEXT_ARG = 2;
    private static final int LOOKING_FOR_STR_ARG_END = 3;
    private static final int LOOKING_FOR_INT_ARG_END = 4;
    private static final int LOOKING_FOR_OSC_COMMAND = 5;
    private static final int LOOKING_FOR_OSC_COMMAND_END = 6;
    private static final int LOOKING_FOR_OSC_PARAM = 7;
    private static final int LOOKING_FOR_ST = 8;
    private static final int LOOKING_FOR_CHARSET = 9;

    private static final int FIRST_ESC_CHAR = 27;
    private static final int SECOND_ESC_CHAR = '[';
    private static final int SECOND_OSC_CHAR = ']';
    private static final int BEL = 7;
    private static final int SECOND_ST_CHAR = '\\';
    private static final int SECOND_CHARSET0_CHAR = '(';
    private static final int SECOND_CHARSET1_CHAR = ')';

    private AnsiProcessor ap;
    private final static int MAX_ESCAPE_SEQUENCE_LENGTH = 100;
    private final byte[] buffer = new byte[MAX_ESCAPE_SEQUENCE_LENGTH];
    private int pos = 0;
    private int startOfValue;
    private final ArrayList<Object> options = new ArrayList<Object>();
    private int state = LOOKING_FOR_FIRST_ESC_CHAR;
    private final Charset cs;

    private final WidthSupplier width;
    private final AnsiProcessor processor;
    private final AnsiType type;
    private final AnsiColors colors;
    private final IoRunnable installer;
    private final IoRunnable uninstaller;
    private AnsiMode mode;
    private boolean resetAtUninstall;

    public AnsiOutputStream(OutputStream os, WidthSupplier width, AnsiMode mode,
                            AnsiProcessor processor, AnsiType type, AnsiColors colors,
                            Charset cs, IoRunnable installer, IoRunnable uninstaller, boolean resetAtUninstall) {
        super(os);
        this.width = width;
        this.processor = processor;
        this.type = type;
        this.colors = colors;
        this.installer = installer;
        this.uninstaller = uninstaller;
        this.resetAtUninstall = resetAtUninstall;
        this.cs = cs;
        setMode(mode);
    }

    public int getTerminalWidth() {
        return width.getTerminalWidth();
    }

    public AnsiType getType() {
        return type;
    }

    public AnsiColors getColors() {
        return colors;
    }

    public AnsiMode getMode() {
        return mode;
    }

    public void setMode(AnsiMode mode) {
        ap = mode == AnsiMode.Strip
                ? new AnsiProcessor(out)
                : mode == AnsiMode.Force || processor == null ? new ColorsAnsiProcessor(out, colors) : processor;
        this.mode = mode;
    }

    public boolean isResetAtUninstall() {
        return resetAtUninstall;
    }

    public void setResetAtUninstall(boolean resetAtUninstall) {
        this.resetAtUninstall = resetAtUninstall;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void write(int data) throws IOException {
        switch (state) {
            case LOOKING_FOR_FIRST_ESC_CHAR:
                if (data == FIRST_ESC_CHAR) {
                    buffer[pos++] = (byte) data;
                    state = LOOKING_FOR_SECOND_ESC_CHAR;
                } else {
                    out.write(data);
                }
                break;

            case LOOKING_FOR_SECOND_ESC_CHAR:
                buffer[pos++] = (byte) data;
                if (data == SECOND_ESC_CHAR) {
                    state = LOOKING_FOR_NEXT_ARG;
                } else if (data == SECOND_OSC_CHAR) {
                    state = LOOKING_FOR_OSC_COMMAND;
                } else if (data == SECOND_CHARSET0_CHAR) {
                    options.add(0);
                    state = LOOKING_FOR_CHARSET;
                } else if (data == SECOND_CHARSET1_CHAR) {
                    options.add(1);
                    state = LOOKING_FOR_CHARSET;
                } else {
                    reset(false);
                }
                break;

            case LOOKING_FOR_NEXT_ARG:
                buffer[pos++] = (byte) data;
                if ('"' == data) {
                    startOfValue = pos - 1;
                    state = LOOKING_FOR_STR_ARG_END;
                } else if ('0' <= data && data <= '9') {
                    startOfValue = pos - 1;
                    state = LOOKING_FOR_INT_ARG_END;
                } else if (';' == data) {
                    options.add(null);
                } else if ('?' == data) {
                    options.add('?');
                } else if ('=' == data) {
                    options.add('=');
                } else {
                    processEscapeCommand(data);
                }
                break;
            default:
                break;

            case LOOKING_FOR_INT_ARG_END:
                buffer[pos++] = (byte) data;
                if (!('0' <= data && data <= '9')) {
                    String strValue = new String(buffer, startOfValue, (pos - 1) - startOfValue);
                    Integer value = Integer.valueOf(strValue);
                    options.add(value);
                    if (data == ';') {
                        state = LOOKING_FOR_NEXT_ARG;
                    } else {
                        processEscapeCommand(data);
                    }
                }
                break;

            case LOOKING_FOR_STR_ARG_END:
                buffer[pos++] = (byte) data;
                if ('"' != data) {
                    String value = new String(buffer, startOfValue, (pos - 1) - startOfValue, cs);
                    options.add(value);
                    if (data == ';') {
                        state = LOOKING_FOR_NEXT_ARG;
                    } else {
                        processEscapeCommand(data);
                    }
                }
                break;

            case LOOKING_FOR_OSC_COMMAND:
                buffer[pos++] = (byte) data;
                if ('0' <= data && data <= '9') {
                    startOfValue = pos - 1;
                    state = LOOKING_FOR_OSC_COMMAND_END;
                } else {
                    reset(false);
                }
                break;

            case LOOKING_FOR_OSC_COMMAND_END:
                buffer[pos++] = (byte) data;
                if (';' == data) {
                    String strValue = new String(buffer, startOfValue, (pos - 1) - startOfValue);
                    Integer value = Integer.valueOf(strValue);
                    options.add(value);
                    startOfValue = pos;
                    state = LOOKING_FOR_OSC_PARAM;
                } else if ('0' <= data && data <= '9') {
                    // already pushed digit to buffer, just keep looking
                } else {
                    // oops, did not expect this
                    reset(false);
                }
                break;

            case LOOKING_FOR_OSC_PARAM:
                buffer[pos++] = (byte) data;
                if (BEL == data) {
                    String value = new String(buffer, startOfValue, (pos - 1) - startOfValue, cs);
                    options.add(value);
                    processOperatingSystemCommand();
                } else if (FIRST_ESC_CHAR == data) {
                    state = LOOKING_FOR_ST;
                } else {
                    // just keep looking while adding text
                }
                break;

            case LOOKING_FOR_ST:
                buffer[pos++] = (byte) data;
                if (SECOND_ST_CHAR == data) {
                    String value = new String(buffer, startOfValue, (pos - 2) - startOfValue, cs);
                    options.add(value);
                    processOperatingSystemCommand();
                } else {
                    state = LOOKING_FOR_OSC_PARAM;
                }
                break;

            case LOOKING_FOR_CHARSET:
                options.add((char) data);
                processCharsetSelect();
                break;
        }

        // Is it just too long?
        if (pos >= buffer.length) {
            reset(false);
        }
    }

    private void processCharsetSelect() throws IOException {
        try {
            reset(ap != null && ap.processCharsetSelect(options));
        } catch (RuntimeException e) {
            reset(true);
            throw e;
        }
    }

    private void processOperatingSystemCommand() throws IOException {
        try {
            reset(ap != null && ap.processOperatingSystemCommand(options));
        } catch (RuntimeException e) {
            reset(true);
            throw e;
        }
    }

    private void processEscapeCommand(int data) throws IOException {
        try {
            reset(ap != null && ap.processEscapeCommand(options, data));
        } catch (RuntimeException e) {
            reset(true);
            throw e;
        }
    }

    /**
     * Resets all state to continue with regular parsing
     * @param skipBuffer if current buffer should be skipped or written to out
     * @throws IOException
     */
    private void reset(boolean skipBuffer) throws IOException {
        if (!skipBuffer) {
            out.write(buffer, 0, pos);
        }
        pos = 0;
        startOfValue = 0;
        options.clear();
        state = LOOKING_FOR_FIRST_ESC_CHAR;
    }

    public void install() throws IOException {
        if (installer != null) {
            installer.run();
        }
    }

    public void uninstall() throws IOException {
        if (resetAtUninstall
                && type != AnsiType.Redirected
                && type != AnsiType.Unsupported) {
            setMode(AnsiMode.Default);
            write(RESET_CODE);
            flush();
        }
        if (uninstaller != null) {
            uninstaller.run();
        }
    }

    @Override
    public void close() throws IOException {
        uninstall();
        super.close();
    }
} @SuppressWarnings("unused")
public class AnsiProcessor {
    protected final OutputStream os;

    public AnsiProcessor(OutputStream os) {
        this.os = os;
    }

    /**
     * Helper for processEscapeCommand() to iterate over integer options
     * @param  optionsIterator  the underlying iterator
     * @throws IOException      if no more non-null values left
     */
    protected int getNextOptionInt(Iterator<Object> optionsIterator) throws IOException {
        for (;;) {
            if (!optionsIterator.hasNext())
                throw new IllegalArgumentException();
            Object arg = optionsIterator.next();
            if (arg != null)
                return (Integer) arg;
        }
    }

    /**
     * @return true if the escape command was processed.
     */
    protected boolean processEscapeCommand(ArrayList<Object> options, int command) throws IOException {
        try {
            switch (command) {
                case 'A':
                    processCursorUp(optionInt(options, 0, 1));
                    return true;
                case 'B':
                    processCursorDown(optionInt(options, 0, 1));
                    return true;
                case 'C':
                    processCursorRight(optionInt(options, 0, 1));
                    return true;
                case 'D':
                    processCursorLeft(optionInt(options, 0, 1));
                    return true;
                case 'E':
                    processCursorDownLine(optionInt(options, 0, 1));
                    return true;
                case 'F':
                    processCursorUpLine(optionInt(options, 0, 1));
                    return true;
                case 'G':
                    processCursorToColumn(optionInt(options, 0));
                    return true;
                case 'H':
                case 'f':
                    processCursorTo(optionInt(options, 0, 1), optionInt(options, 1, 1));
                    return true;
                case 'J':
                    processEraseScreen(optionInt(options, 0, 0));
                    return true;
                case 'K':
                    processEraseLine(optionInt(options, 0, 0));
                    return true;
                case 'L':
                    processInsertLine(optionInt(options, 0, 1));
                    return true;
                case 'M':
                    processDeleteLine(optionInt(options, 0, 1));
                    return true;
                case 'S':
                    processScrollUp(optionInt(options, 0, 1));
                    return true;
                case 'T':
                    processScrollDown(optionInt(options, 0, 1));
                    return true;
                case 'm':
                    // Validate all options are ints...
                    for (Object next : options) {
                        if (next != null && next.getClass() != Integer.class) {
                            throw new IllegalArgumentException();
                        }
                    }

                    int count = 0;
                    Iterator<Object> optionsIterator = options.iterator();
                    while (optionsIterator.hasNext()) {
                        Object next = optionsIterator.next();
                        if (next != null) {
                            count++;
                            int value = (Integer) next;
                            if (30 <= value && value <= 37) {
                                processSetForegroundColor(value - 30);
                            } else if (40 <= value && value <= 47) {
                                processSetBackgroundColor(value - 40);
                            } else if (90 <= value && value <= 97) {
                                processSetForegroundColor(value - 90, true);
                            } else if (100 <= value && value <= 107) {
                                processSetBackgroundColor(value - 100, true);
                            } else if (value == 38 || value == 48) {
                                if (!optionsIterator.hasNext()) {
                                    continue;
                                }
                                // extended color like `esc[38;5;<index>m` or `esc[38;2;<r>;<g>;<b>m`
                                int arg2or5 = getNextOptionInt(optionsIterator);
                                if (arg2or5 == 2) {
                                    // 24 bit color style like `esc[38;2;<r>;<g>;<b>m`
                                    int r = getNextOptionInt(optionsIterator);
                                    int g = getNextOptionInt(optionsIterator);
                                    int b = getNextOptionInt(optionsIterator);
                                    if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) {
                                        if (value == 38)
                                            processSetForegroundColorExt(r, g, b);
                                        else
                                            processSetBackgroundColorExt(r, g, b);
                                    } else {
                                        throw new IllegalArgumentException();
                                    }
                                }
                                else if (arg2or5 == 5) {
                                    // 256 color style like `esc[38;5;<index>m`
                                    int paletteIndex = getNextOptionInt(optionsIterator);
                                    if (paletteIndex >= 0 && paletteIndex <= 255) {
                                        if (value == 38)
                                            processSetForegroundColorExt(paletteIndex);
                                        else
                                            processSetBackgroundColorExt(paletteIndex);
                                    } else {
                                        throw new IllegalArgumentException();
                                    }
                                }
                                else {
                                    throw new IllegalArgumentException();
                                }
                            } else {
                                switch (value) {
                                    case 39:
                                        processDefaultTextColor();
                                        break;
                                    case 49:
                                        processDefaultBackgroundColor();
                                        break;
                                    case 0:
                                        processAttributeReset();
                                        break;
                                    default:
                                        processSetAttribute(value);
                                }
                            }
                        }
                    }
                    if (count == 0) {
                        processAttributeReset();
                    }
                    return true;
                case 's':
                    processSaveCursorPosition();
                    return true;
                case 'u':
                    processRestoreCursorPosition();
                    return true;

                default:
                    if ('a' <= command && command <= 'z') {
                        processUnknownExtension(options, command);
                        return true;
                    }
                    if ('A' <= command && command <= 'Z') {
                        processUnknownExtension(options, command);
                        return true;
                    }
                    return false;
            }
        } catch (IllegalArgumentException ignore) {
        }
        return false;
    }

    /**
     * @return true if the operating system command was processed.
     */
    protected boolean processOperatingSystemCommand(ArrayList<Object> options) {
        int command = optionInt(options, 0);
        String label = (String) options.get(1);
        // for command > 2 label could be composed (i.e. contain ';'), but we'll leave
        // it to processUnknownOperatingSystemCommand implementations to handle that
        try {
            switch (command) {
                case 0:
                    processChangeIconNameAndWindowTitle(label);
                    return true;
                case 1:
                    processChangeIconName(label);
                    return true;
                case 2:
                    processChangeWindowTitle(label);
                    return true;

                default:
                    // not exactly unknown, but not supported through dedicated process methods:
                    processUnknownOperatingSystemCommand(command, label);
                    return true;
            }
        } catch (IllegalArgumentException ignore) {
        }
        return false;
    }

    /**
     * Process character set sequence.
     * @param options options
     * @return true if the charcter set select command was processed.
     */
    protected boolean processCharsetSelect(ArrayList<Object> options) {
        int set = optionInt(options, 0);
        char seq = (Character) options.get(1);
        processCharsetSelect(set, seq);
        return true;
    }

    private int optionInt(ArrayList<Object> options, int index) {
        if (options.size() <= index)
            throw new IllegalArgumentException();
        Object value = options.get(index);
        if (value == null)
            throw new IllegalArgumentException();
        if (!value.getClass().equals(Integer.class))
            throw new IllegalArgumentException();
        return (Integer) value;
    }

    private int optionInt(ArrayList<Object> options, int index, int defaultValue) {
        if (options.size() > index) {
            Object value = options.get(index);
            if (value == null) {
                return defaultValue;
            }
            return (Integer) value;
        }
        return defaultValue;
    }

    /**
     * Process <code>CSI u</code> ANSI code, corresponding to <code>RCP – Restore Cursor Position</code>
     * @throws IOException IOException
     */
    protected void processRestoreCursorPosition() throws IOException {
    }

    /**
     * Process <code>CSI s</code> ANSI code, corresponding to <code>SCP – Save Cursor Position</code>
     * @throws IOException IOException
     */
    protected void processSaveCursorPosition() throws IOException {
    }

    /**
     * Process <code>CSI L</code> ANSI code, corresponding to <code>IL – Insert Line</code>
     * @param optionInt option
     * @throws IOException IOException
     * @since 1.16
     */
    protected void processInsertLine(int optionInt) throws IOException {
    }

    /**
     * Process <code>CSI M</code> ANSI code, corresponding to <code>DL – Delete Line</code>
     * @param optionInt option
     * @throws IOException IOException
     * @since 1.16
     */
    protected void processDeleteLine(int optionInt) throws IOException {
    }

    /**
     * Process <code>CSI n T</code> ANSI code, corresponding to <code>SD – Scroll Down</code>
     * @param optionInt option
     * @throws IOException IOException
     */
    protected void processScrollDown(int optionInt) throws IOException {
    }

    /**
     * Process <code>CSI n U</code> ANSI code, corresponding to <code>SU – Scroll Up</code>
     * @param optionInt option
     * @throws IOException IOException
     */
    protected void processScrollUp(int optionInt) throws IOException {
    }

    protected static final int ERASE_SCREEN_TO_END = 0;
    protected static final int ERASE_SCREEN_TO_BEGINING = 1;
    protected static final int ERASE_SCREEN = 2;

    /**
     * Process <code>CSI n J</code> ANSI code, corresponding to <code>ED – Erase in Display</code>
     * @param eraseOption eraseOption
     * @throws IOException IOException
     */
    protected void processEraseScreen(int eraseOption) throws IOException {
    }

    protected static final int ERASE_LINE_TO_END = 0;
    protected static final int ERASE_LINE_TO_BEGINING = 1;
    protected static final int ERASE_LINE = 2;

    /**
     * Process <code>CSI n K</code> ANSI code, corresponding to <code>ED – Erase in Line</code>
     * @param eraseOption eraseOption
     * @throws IOException IOException
     */
    protected void processEraseLine(int eraseOption) throws IOException {
    }

    protected static final int ATTRIBUTE_INTENSITY_BOLD = 1; // 	Intensity: Bold
    protected static final int ATTRIBUTE_INTENSITY_FAINT = 2; // 	Intensity; Faint 	not widely supported
    protected static final int ATTRIBUTE_ITALIC = 3; // 	Italic; on 	not widely supported. Sometimes treated as inverse.
    protected static final int ATTRIBUTE_UNDERLINE = 4; // 	Underline; Single
    protected static final int ATTRIBUTE_BLINK_SLOW = 5; // 	Blink; Slow 	less than 150 per minute
    protected static final int ATTRIBUTE_BLINK_FAST = 6; // 	Blink; Rapid 	MS-DOS ANSI.SYS; 150 per minute or more
    protected static final int ATTRIBUTE_NEGATIVE_ON = 7; // 	Image; Negative 	inverse or reverse; swap foreground and background
    protected static final int ATTRIBUTE_CONCEAL_ON = 8; // 	Conceal on
    protected static final int ATTRIBUTE_UNDERLINE_DOUBLE = 21; // 	Underline; Double 	not widely supported
    protected static final int ATTRIBUTE_INTENSITY_NORMAL = 22; // 	Intensity; Normal 	not bold and not faint
    protected static final int ATTRIBUTE_UNDERLINE_OFF = 24; // 	Underline; None
    protected static final int ATTRIBUTE_BLINK_OFF = 25; // 	Blink; off
    protected static final int ATTRIBUTE_NEGATIVE_OFF = 27; // 	Image; Positive
    protected static final int ATTRIBUTE_CONCEAL_OFF = 28; // 	Reveal 	conceal off

    /**
     * process <code>SGR</code> other than <code>0</code> (reset), <code>30-39</code> (foreground),
     * <code>40-49</code> (background), <code>90-97</code> (foreground high intensity) or
     * <code>100-107</code> (background high intensity)
     * @param attribute attribute
     * @throws IOException IOException
     * @see #processAttributeReset()
     * @see #processSetForegroundColor(int)
     * @see #processSetForegroundColor(int, boolean)
     * @see #processSetForegroundColorExt(int)
     * @see #processSetForegroundColorExt(int, int, int)
     * @see #processDefaultTextColor()
     * @see #processDefaultBackgroundColor()
     */
    protected void processSetAttribute(int attribute) throws IOException {
    }

    protected static final int BLACK = 0;
    protected static final int RED = 1;
    protected static final int GREEN = 2;
    protected static final int YELLOW = 3;
    protected static final int BLUE = 4;
    protected static final int MAGENTA = 5;
    protected static final int CYAN = 6;
    protected static final int WHITE = 7;

    /**
     * process <code>SGR 30-37</code> corresponding to <code>Set text color (foreground)</code>.
     * @param color the text color
     * @throws IOException IOException
     */
    protected void processSetForegroundColor(int color) throws IOException {
        processSetForegroundColor(color, false);
    }

    /**
     * process <code>SGR 30-37</code> or <code>SGR 90-97</code> corresponding to
     * <code>Set text color (foreground)</code> either in normal mode or high intensity.
     * @param color the text color
     * @param bright is high intensity?
     * @throws IOException IOException
     */
    protected void processSetForegroundColor(int color, boolean bright) throws IOException {
    }

    /**
     * process <code>SGR 38</code> corresponding to <code>extended set text color (foreground)</code>
     * with a palette of 255 colors.
     * @param paletteIndex the text color in the palette
     * @throws IOException IOException
     */
    protected void processSetForegroundColorExt(int paletteIndex) throws IOException {
    }

    /**
     * process <code>SGR 38</code> corresponding to <code>extended set text color (foreground)</code>
     * with a 24 bits RGB definition of the color.
     * @param r red
     * @param g green
     * @param b blue
     * @throws IOException IOException
     */
    protected void processSetForegroundColorExt(int r, int g, int b) throws IOException {
    }

    /**
     * process <code>SGR 40-47</code> corresponding to <code>Set background color</code>.
     * @param color the background color
     * @throws IOException IOException
     */
    protected void processSetBackgroundColor(int color) throws IOException {
        processSetBackgroundColor(color, false);
    }

    /**
     * process <code>SGR 40-47</code> or <code>SGR 100-107</code> corresponding to
     * <code>Set background color</code> either in normal mode or high intensity.
     * @param color the background color
     * @param bright is high intensity?
     * @throws IOException IOException
     */
    protected void processSetBackgroundColor(int color, boolean bright) throws IOException {
    }

    /**
     * process <code>SGR 48</code> corresponding to <code>extended set background color</code>
     * with a palette of 255 colors.
     * @param paletteIndex the background color in the palette
     * @throws IOException IOException
     */
    protected void processSetBackgroundColorExt(int paletteIndex) throws IOException {
    }

    /**
     * process <code>SGR 48</code> corresponding to <code>extended set background color</code>
     * with a 24 bits RGB definition of the color.
     * @param r red
     * @param g green
     * @param b blue
     * @throws IOException IOException
     */
    protected void processSetBackgroundColorExt(int r, int g, int b) throws IOException {
    }

    /**
     * process <code>SGR 39</code> corresponding to <code>Default text color (foreground)</code>
     * @throws IOException IOException
     */
    protected void processDefaultTextColor() throws IOException {
    }

    /**
     * process <code>SGR 49</code> corresponding to <code>Default background color</code>
     * @throws IOException IOException
     */
    protected void processDefaultBackgroundColor() throws IOException {
    }

    /**
     * process <code>SGR 0</code> corresponding to <code>Reset / Normal</code>
     * @throws IOException IOException
     */
    protected void processAttributeReset() throws IOException {
    }

    /**
     * process <code>CSI n ; m H</code> corresponding to <code>CUP – Cursor Position</code> or
     * <code>CSI n ; m f</code> corresponding to <code>HVP – Horizontal and Vertical Position</code>
     * @param row row
     * @param col col
     * @throws IOException IOException
     */
    protected void processCursorTo(int row, int col) throws IOException {
    }

    /**
     * process <code>CSI n G</code> corresponding to <code>CHA – Cursor Horizontal Absolute</code>
     * @param x the column
     * @throws IOException IOException
     */
    protected void processCursorToColumn(int x) throws IOException {
    }

    /**
     * process <code>CSI n F</code> corresponding to <code>CPL – Cursor Previous Line</code>
     * @param count line count
     * @throws IOException IOException
     */
    protected void processCursorUpLine(int count) throws IOException {
    }

    /**
     * process <code>CSI n E</code> corresponding to <code>CNL – Cursor Next Line</code>
     * @param count line count
     * @throws IOException IOException
     */
    protected void processCursorDownLine(int count) throws IOException {
        // Poor mans impl..
        for (int i = 0; i < count; i++) {
            os.write('\n');
        }
    }

    /**
     * process <code>CSI n D</code> corresponding to <code>CUB – Cursor Back</code>
     * @param count count
     * @throws IOException IOException
     */
    protected void processCursorLeft(int count) throws IOException {
    }

    /**
     * process <code>CSI n C</code> corresponding to <code>CUF – Cursor Forward</code>
     * @param count count
     * @throws IOException IOException
     */
    protected void processCursorRight(int count) throws IOException {
        // Poor mans impl..
        for (int i = 0; i < count; i++) {
            os.write(' ');
        }
    }

    /**
     * process <code>CSI n B</code> corresponding to <code>CUD – Cursor Down</code>
     * @param count count
     * @throws IOException IOException
     */
    protected void processCursorDown(int count) throws IOException {
    }

    /**
     * process <code>CSI n A</code> corresponding to <code>CUU – Cursor Up</code>
     * @param count count
     * @throws IOException IOException
     */
    protected void processCursorUp(int count) throws IOException {
    }

    /**
     * Process Unknown Extension
     * @param options options
     * @param command command
     */
    protected void processUnknownExtension(ArrayList<Object> options, int command) {
    }

    /**
     * process <code>OSC 0;text BEL</code> corresponding to <code>Change Window and Icon label</code>
     * @param label window title name
     */
    protected void processChangeIconNameAndWindowTitle(String label) {
        processChangeIconName(label);
        processChangeWindowTitle(label);
    }

    /**
     * process <code>OSC 1;text BEL</code> corresponding to <code>Change Icon label</code>
     * @param label icon label
     */
    protected void processChangeIconName(String label) {
    }

    /**
     * process <code>OSC 2;text BEL</code> corresponding to <code>Change Window title</code>
     * @param label window title text
     */
    protected void processChangeWindowTitle(String label) {
    }

    /**
     * Process unknown <code>OSC</code> command.
     * @param command command
     * @param param param
     */
    protected void processUnknownOperatingSystemCommand(int command, String param) {
    }

    protected void processCharsetSelect(int set, char seq) {
    }
} public class FastBufferedOutputStream extends FilterOutputStream {

    protected final byte buf[] = new byte[8192];
    protected int count;

    public FastBufferedOutputStream(OutputStream out) {
        super(out);
    }

    @Override
    public void write(int b) throws IOException {
        if (count >= buf.length) {
            flushBuffer();
        }
        buf[count++] = (byte) b;
    }

    @Override
    public void write(byte b[], int off, int len) throws IOException {
        if (len >= buf.length) {
            flushBuffer();
            out.write(b, off, len);
            return;
        }
        if (len > buf.length - count) {
            flushBuffer();
        }
        System.arraycopy(b, off, buf, count, len);
        count += len;
    }

    private void flushBuffer() throws IOException {
        if (count > 0) {
            out.write(buf, 0, count);
            count = 0;
        }
    }

    @Override
    public void flush() throws IOException {
        flushBuffer();
        out.flush();
    }

} public final class WindowsAnsiProcessor extends AnsiProcessor {

    private final long console;

    private static final short FOREGROUND_BLACK = 0;
    private static final short FOREGROUND_YELLOW = (short) (FOREGROUND_RED | FOREGROUND_GREEN);
    private static final short FOREGROUND_MAGENTA = (short) (FOREGROUND_BLUE | FOREGROUND_RED);
    private static final short FOREGROUND_CYAN = (short) (FOREGROUND_BLUE | FOREGROUND_GREEN);
    private static final short FOREGROUND_WHITE = (short) (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);

    private static final short BACKGROUND_BLACK = 0;
    private static final short BACKGROUND_YELLOW = (short) (BACKGROUND_RED | BACKGROUND_GREEN);
    private static final short BACKGROUND_MAGENTA = (short) (BACKGROUND_BLUE | BACKGROUND_RED);
    private static final short BACKGROUND_CYAN = (short) (BACKGROUND_BLUE | BACKGROUND_GREEN);
    private static final short BACKGROUND_WHITE = (short) (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE);

    private static final short[] ANSI_FOREGROUND_COLOR_MAP = {
            FOREGROUND_BLACK,
            FOREGROUND_RED,
            FOREGROUND_GREEN,
            FOREGROUND_YELLOW,
            FOREGROUND_BLUE,
            FOREGROUND_MAGENTA,
            FOREGROUND_CYAN,
            FOREGROUND_WHITE,
    };

    private static final short[] ANSI_BACKGROUND_COLOR_MAP = {
            BACKGROUND_BLACK,
            BACKGROUND_RED,
            BACKGROUND_GREEN,
            BACKGROUND_YELLOW,
            BACKGROUND_BLUE,
            BACKGROUND_MAGENTA,
            BACKGROUND_CYAN,
            BACKGROUND_WHITE,
    };

    private final CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO();
    private final short originalColors;

    private boolean negative;
    private short savedX = -1;
    private short savedY = -1;

    public WindowsAnsiProcessor(OutputStream ps, long console) throws IOException {
        super(ps);
        this.console = console;
        getConsoleInfo();
        originalColors = info.attributes;
    }

    public WindowsAnsiProcessor(OutputStream ps, boolean stdout) throws IOException {
        this(ps, GetStdHandle(stdout ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE));
    }

    public WindowsAnsiProcessor(OutputStream ps) throws IOException {
        this(ps, true);
    }

    private void getConsoleInfo() throws IOException {
        os.flush();
        if (GetConsoleScreenBufferInfo(console, info) == 0) {
            throw new IOException("Could not get the screen info: " + WindowsSupport.getLastErrorMessage());
        }
        if (negative) {
            info.attributes = invertAttributeColors(info.attributes);
        }
    }

    private void applyAttribute() throws IOException {
        os.flush();
        short attributes = info.attributes;
        if (negative) {
            attributes = invertAttributeColors(attributes);
        }
        if (SetConsoleTextAttribute(console, attributes) == 0) {
            throw new IOException(WindowsSupport.getLastErrorMessage());
        }
    }

    private short invertAttributeColors(short attributes) {
        // Swap the the Foreground and Background bits.
        int fg = 0x000F & attributes;
        fg <<= 4;
        int bg = 0X00F0 & attributes;
        bg >>= 4;
        attributes = (short) ((attributes & 0xFF00) | fg | bg);
        return attributes;
    }

    private void applyCursorPosition() throws IOException {
        if (SetConsoleCursorPosition(console, info.cursorPosition.copy()) == 0) {
            throw new IOException(WindowsSupport.getLastErrorMessage());
        }
    }

    @Override
    protected void processEraseScreen(int eraseOption) throws IOException {
        getConsoleInfo();
        int[] written = new int[1];
        switch (eraseOption) {
            case ERASE_SCREEN:
                COORD topLeft = new COORD();
                topLeft.x = 0;
                topLeft.y = info.window.top;
                int screenLength = info.window.height() * info.size.x;
                FillConsoleOutputAttribute(console, info.attributes, screenLength, topLeft, written);
                FillConsoleOutputCharacterW(console, ' ', screenLength, topLeft, written);
                break;
            case ERASE_SCREEN_TO_BEGINING:
                COORD topLeft2 = new COORD();
                topLeft2.x = 0;
                topLeft2.y = info.window.top;
                int lengthToCursor = (info.cursorPosition.y - info.window.top) * info.size.x
                        + info.cursorPosition.x;
                FillConsoleOutputAttribute(console, info.attributes, lengthToCursor, topLeft2, written);
                FillConsoleOutputCharacterW(console, ' ', lengthToCursor, topLeft2, written);
                break;
            case ERASE_SCREEN_TO_END:
                int lengthToEnd = (info.window.bottom - info.cursorPosition.y) * info.size.x +
                        (info.size.x - info.cursorPosition.x);
                FillConsoleOutputAttribute(console, info.attributes, lengthToEnd, info.cursorPosition.copy(), written);
                FillConsoleOutputCharacterW(console, ' ', lengthToEnd, info.cursorPosition.copy(), written);
                break;
            default:
                break;
        }
    }

    @Override
    protected void processEraseLine(int eraseOption) throws IOException {
        getConsoleInfo();
        int[] written = new int[1];
        switch (eraseOption) {
            case ERASE_LINE:
                COORD leftColCurrRow = info.cursorPosition.copy();
                leftColCurrRow.x = 0;
                FillConsoleOutputAttribute(console, info.attributes, info.size.x, leftColCurrRow, written);
                FillConsoleOutputCharacterW(console, ' ', info.size.x, leftColCurrRow, written);
                break;
            case ERASE_LINE_TO_BEGINING:
                COORD leftColCurrRow2 = info.cursorPosition.copy();
                leftColCurrRow2.x = 0;
                FillConsoleOutputAttribute(console, info.attributes, info.cursorPosition.x, leftColCurrRow2, written);
                FillConsoleOutputCharacterW(console, ' ', info.cursorPosition.x, leftColCurrRow2, written);
                break;
            case ERASE_LINE_TO_END:
                int lengthToLastCol = info.size.x - info.cursorPosition.x;
                FillConsoleOutputAttribute(console, info.attributes, lengthToLastCol, info.cursorPosition.copy(), written);
                FillConsoleOutputCharacterW(console, ' ', lengthToLastCol, info.cursorPosition.copy(), written);
                break;
            default:
                break;
        }
    }

    @Override
    protected void processCursorLeft(int count) throws IOException {
        getConsoleInfo();
        info.cursorPosition.x = (short) Math.max(0, info.cursorPosition.x - count);
        applyCursorPosition();
    }

    @Override
    protected void processCursorRight(int count) throws IOException {
        getConsoleInfo();
        info.cursorPosition.x = (short) Math.min(info.window.width(), info.cursorPosition.x + count);
        applyCursorPosition();
    }

    @Override
    protected void processCursorDown(int count) throws IOException {
        getConsoleInfo();
        info.cursorPosition.y = (short) Math.min(Math.max(0, info.size.y - 1), info.cursorPosition.y + count);
        applyCursorPosition();
    }

    @Override
    protected void processCursorUp(int count) throws IOException {
        getConsoleInfo();
        info.cursorPosition.y = (short) Math.max(info.window.top, info.cursorPosition.y - count);
        applyCursorPosition();
    }

    @Override
    protected void processCursorTo(int row, int col) throws IOException {
        getConsoleInfo();
        info.cursorPosition.y = (short) Math.max(info.window.top, Math.min(info.size.y, info.window.top + row - 1));
        info.cursorPosition.x = (short) Math.max(0, Math.min(info.window.width(), col - 1));
        applyCursorPosition();
    }

    @Override
    protected void processCursorToColumn(int x) throws IOException {
        getConsoleInfo();
        info.cursorPosition.x = (short) Math.max(0, Math.min(info.window.width(), x - 1));
        applyCursorPosition();
    }

    @Override
    protected void processCursorUpLine(int count) throws IOException {
        getConsoleInfo();
        info.cursorPosition.x = 0;
        info.cursorPosition.y = (short) Math.max(info.window.top, info.cursorPosition.y - count);
        applyCursorPosition();
    }

    @Override
    protected void processCursorDownLine(int count) throws IOException {
        getConsoleInfo();
        info.cursorPosition.x = 0;
        info.cursorPosition.y = (short) Math.max(info.window.top, info.cursorPosition.y + count);
        applyCursorPosition();
    }

    @Override
    protected void processSetForegroundColor(int color, boolean bright) throws IOException {
        info.attributes = (short) ((info.attributes & ~0x0007) | ANSI_FOREGROUND_COLOR_MAP[color]);
        if (bright) {
            info.attributes |= FOREGROUND_INTENSITY;
        }
        applyAttribute();
    }

    @Override
    protected void processSetForegroundColorExt(int paletteIndex) throws IOException {
        int round = Colors.roundColor(paletteIndex, 16);
        processSetForegroundColor(round >= 8 ? round - 8 : round, round >= 8);
    }

    @Override
    protected void processSetForegroundColorExt(int r, int g, int b) throws IOException {
        int round = Colors.roundRgbColor(r, g, b, 16);
        processSetForegroundColor(round >= 8 ? round - 8 : round, round >= 8);
    }

    @Override
    protected void processSetBackgroundColor(int color, boolean bright) throws IOException {
        info.attributes = (short) ((info.attributes & ~0x0070) | ANSI_BACKGROUND_COLOR_MAP[color]);
        if (bright) {
            info.attributes |= BACKGROUND_INTENSITY;
        }
        applyAttribute();
    }

    @Override
    protected void processSetBackgroundColorExt(int paletteIndex) throws IOException {
        int round = Colors.roundColor(paletteIndex, 16);
        processSetBackgroundColor(round >= 8 ? round - 8 : round, round >= 8);
    }

    @Override
    protected void processSetBackgroundColorExt(int r, int g, int b) throws IOException {
        int round = Colors.roundRgbColor(r, g, b, 16);
        processSetBackgroundColor(round >= 8 ? round - 8 : round, round >= 8);
    }

    @Override
    protected void processDefaultTextColor() throws IOException {
        info.attributes = (short) ((info.attributes & ~0x000F) | (originalColors & 0xF));
        info.attributes = (short) (info.attributes & ~FOREGROUND_INTENSITY);
        applyAttribute();
    }

    @Override
    protected void processDefaultBackgroundColor() throws IOException {
        info.attributes = (short) ((info.attributes & ~0x00F0) | (originalColors & 0xF0));
        info.attributes = (short) (info.attributes & ~BACKGROUND_INTENSITY);
        applyAttribute();
    }

    @Override
    protected void processAttributeReset() throws IOException {
        info.attributes = (short) ((info.attributes & ~0x00FF) | originalColors);
        this.negative = false;
        applyAttribute();
    }

    @Override
    protected void processSetAttribute(int attribute) throws IOException {
        switch (attribute) {
            case ATTRIBUTE_INTENSITY_BOLD:
                info.attributes = (short) (info.attributes | FOREGROUND_INTENSITY);
                applyAttribute();
                break;
            case ATTRIBUTE_INTENSITY_NORMAL:
                info.attributes = (short) (info.attributes & ~FOREGROUND_INTENSITY);
                applyAttribute();
                break;

            // Yeah, setting the background intensity is not underlining.. but it's best we can do
            // using the Windows console API
            case ATTRIBUTE_UNDERLINE:
                info.attributes = (short) (info.attributes | BACKGROUND_INTENSITY);
                applyAttribute();
                break;
            case ATTRIBUTE_UNDERLINE_OFF:
                info.attributes = (short) (info.attributes & ~BACKGROUND_INTENSITY);
                applyAttribute();
                break;

            case ATTRIBUTE_NEGATIVE_ON:
                negative = true;
                applyAttribute();
                break;
            case ATTRIBUTE_NEGATIVE_OFF:
                negative = false;
                applyAttribute();
                break;
            default:
                break;
        }
    }

    @Override
    protected void processSaveCursorPosition() throws IOException {
        getConsoleInfo();
        savedX = info.cursorPosition.x;
        savedY = info.cursorPosition.y;
    }

    @Override
    protected void processRestoreCursorPosition() throws IOException {
        // restore only if there was a save operation first
        if (savedX != -1 && savedY != -1) {
            os.flush();
            info.cursorPosition.x = savedX;
            info.cursorPosition.y = savedY;
            applyCursorPosition();
        }
    }

    @Override
    protected void processInsertLine(int optionInt) throws IOException {
        getConsoleInfo();
        SMALL_RECT scroll = info.window.copy();
        scroll.top = info.cursorPosition.y;
        COORD org = new COORD();
        org.x = 0;
        org.y = (short)(info.cursorPosition.y + optionInt);
        CHAR_INFO info = new CHAR_INFO();
        info.attributes = originalColors;
        info.unicodeChar = ' ';
        if (ScrollConsoleScreenBuffer(console, scroll, scroll, org, info) == 0) {
            throw new IOException(WindowsSupport.getLastErrorMessage());
        }
    }

    @Override
    protected void processDeleteLine(int optionInt) throws IOException {
        getConsoleInfo();
        SMALL_RECT scroll = info.window.copy();
        scroll.top = info.cursorPosition.y;
        COORD org = new COORD();
        org.x = 0;
        org.y = (short)(info.cursorPosition.y - optionInt);
        CHAR_INFO info = new CHAR_INFO();
        info.attributes = originalColors;
        info.unicodeChar = ' ';
        if (ScrollConsoleScreenBuffer(console, scroll, scroll, org, info) == 0) {
            throw new IOException(WindowsSupport.getLastErrorMessage());
        }
    }

    @Override
    protected void processChangeWindowTitle(String label) {
        SetConsoleTitle(label);
    }
} public static class CONSOLE_SCREEN_BUFFER_INFO {

    static {
        JansiLoader.initialize();
        init();
    }

    private static native void init();

    public static int SIZEOF;

    public COORD size = new COORD();
    public COORD cursorPosition = new COORD();
    public short attributes;
    public SMALL_RECT window = new SMALL_RECT();
    public COORD maximumWindowSize = new COORD();

    public int windowWidth() {
        return window.width() + 1;
    }

    public int windowHeight() {
        return window.height() + 1;
    }
} public static native int ioctl(
        int filedes,
        long request,
        int[] params); public static native int isatty(int fd); public static native int GetConsoleMode(
        long handle,
        int[] mode); public static native long GetStdHandle(int stdHandle); public static int STD_ERROR_HANDLE; public static int STD_OUTPUT_HANDLE; public static native int SetConsoleMode(
        long handle,
        int mode); public static native int GetConsoleScreenBufferInfo(
        long consoleOutput,
        CONSOLE_SCREEN_BUFFER_INFO consoleScreenBufferInfo);
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOError;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Locale;
import org.fusesource.jansi.internal.CLibrary;
import org.fusesource.jansi.internal.CLibrary.WinSize;
import org.fusesource.jansi.io.AnsiOutputStream;
import org.fusesource.jansi.io.AnsiProcessor;
import org.fusesource.jansi.io.FastBufferedOutputStream;
import org.fusesource.jansi.io.WindowsAnsiProcessor;
import org.fusesource.jansi.internal.Kernel32.CONSOLE_SCREEN_BUFFER_INFO;
import static org.fusesource.jansi.internal.CLibrary.ioctl;
import static org.fusesource.jansi.internal.CLibrary.isatty;
import static org.fusesource.jansi.internal.Kernel32.GetConsoleMode;
import static org.fusesource.jansi.internal.Kernel32.GetStdHandle;
import static org.fusesource.jansi.internal.Kernel32.STD_ERROR_HANDLE;
import static org.fusesource.jansi.internal.Kernel32.STD_OUTPUT_HANDLE;
import static org.fusesource.jansi.internal.Kernel32.SetConsoleMode;
import static org.fusesource.jansi.internal.Kernel32.GetConsoleScreenBufferInfo;
/*
 * Copyright (C) 2009-2017 the original author(s).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.fusesource.jansi;




/**
 * Provides consistent access to an ANSI aware console PrintStream or an ANSI codes stripping PrintStream
 * if not on a terminal (see 
 * <a href="http://fusesource.github.io/jansi/documentation/native-api/index.html?org/fusesource/jansi/internal/CLibrary.html">Jansi native
 * CLibrary isatty(int)</a>).
 * <p>The native library used is named <code>jansi</code> and is loaded using <a href="http://fusesource.github.io/hawtjni/">HawtJNI</a> Runtime
 * <a href="http://fusesource.github.io/hawtjni/documentation/api/index.html?org/fusesource/hawtjni/runtime/Library.html"><code>Library</code></a>
 *
 * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
 * @since 1.0
 * @see #systemInstall()
 * @see #out()
 * @see #err()
 * @see #ansiStream(boolean) for more details on ANSI mode selection
 */
public class AnsiConsole {

    /**
     * The default mode which Jansi will use, can be either <code>force</code>, <code>strip</code>
     * or <code>default</code> (the default).
     * If this property is set, it will override <code>jansi.passthrough</code>,
     * <code>jansi.strip</code> and <code>jansi.force</code> properties.
     */
    public static final String JANSI_MODE = "jansi.mode";
    /**
     * Jansi mode specific to the standard output stream.
     */
    public static final String JANSI_OUT_MODE = "jansi.out.mode";
    /**
     * Jansi mode specific to the standard error stream.
     */
    public static final String JANSI_ERR_MODE = "jansi.err.mode";

    /**
     * Jansi mode value to strip all ansi sequences.
     */
    public static final String JANSI_MODE_STRIP = "strip";
    /**
     * Jansi mode value to force ansi sequences to the stream even if it's not a terminal.
     */
    public static final String JANSI_MODE_FORCE = "force";
    /**
     * Jansi mode value that output sequences if on a terminal, else strip them.
     */
    public static final String JANSI_MODE_DEFAULT = "default";

    /**
     * The default color support that Jansi will use, can be either <code>16</code>,
     * <code>256</code> or <code>truecolor</code>.  If not set, JANSI will try to
     * autodetect the number of colors supported by the terminal by checking the
     * <code>COLORTERM</code> and <code>TERM</code> variables.
     */
    public static final String JANSI_COLORS = "jansi.colors";
    /**
     * Jansi colors specific to the standard output stream.
     */
    public static final String JANSI_OUT_COLORS = "jansi.out.colors";
    /**
     * Jansi colors specific to the standard error stream.
     */
    public static final String JANSI_ERR_COLORS = "jansi.err.colors";

    /**
     * Force the use of 16 colors. When using a 256-indexed color, or an RGB
     * color, the color will be rounded to the nearest one from the 16 palette.
     */
    public static final String JANSI_COLORS_16 = "16";
    /**
     * Force the use of 256 colors. When using an RGB color, the color will be
     * rounded to the nearest one from the standard 256 palette.
     */
    public static final String JANSI_COLORS_256 = "256";
    /**
     * Force the use of 24-bit colors.
     */
    public static final String JANSI_COLORS_TRUECOLOR = "truecolor";

    /**
     * If the <code>jansi.passthrough</code> system property is set to true, will not perform any transformation
     * and any ansi sequence will be conveyed without any modification.
     *
     * @deprecated use {@link #JANSI_MODE} instead
     */
    @Deprecated
    public static final String JANSI_PASSTHROUGH = "jansi.passthrough";
    /**
     * If the <code>jansi.strip</code> system property is set to true, and <code>jansi.passthrough</code>
     * is not enabled, all ansi sequences will be stripped before characters are written to the output streams.
     *
     * @deprecated use {@link #JANSI_MODE} instead
     */
    @Deprecated
    public static final String JANSI_STRIP = "jansi.strip";
    /**
     * If the <code>jansi.force</code> system property is set to true, and neither <code>jansi.passthrough</code>
     * nor <code>jansi.strip</code> are set, then ansi sequences will be printed to the output stream.
     * This forces the behavior which is by default dependent on the output stream being a real console: if the
     * output stream is redirected to a file or through a system pipe, ansi sequences are disabled by default.
     *
     * @deprecated use {@link #JANSI_MODE} instead
     */
    @Deprecated
    public static final String JANSI_FORCE = "jansi.force";
    /**
     * If the <code>jansi.eager</code> system property is set to true, the system streams will be eagerly
     * initialized, else the initialization is delayed until {@link #out()}, {@link #err()} or {@link #systemInstall()}
     * is called.
     *
     * @deprecated this property has been added but only for backward compatibility.
     * @since 2.1
     */
    @Deprecated()
    public static final String JANSI_EAGER = "jansi.eager";
    /**
     * If the <code>jansi.noreset</code> system property is set to true, the attributes won't be
     * reset when the streams are uninstalled.
     */
    public static final String JANSI_NORESET = "jansi.noreset";
    /**
     * If the <code>jansi.graceful</code> system property is set to false, any exception that occurs
     * during the initialization will cause the library to report this exception and fail. The default
     * behavior is to behave gracefully and fall back to pure emulation on posix systems.
     */
    public static final String JANSI_GRACEFUL = "jansi.graceful";

    /**
     * @deprecated this field will be made private in a future release, use {@link #sysOut()} instead
     */
    @Deprecated
    public static PrintStream system_out = System.out;
    /**
     * @deprecated this field will be made private in a future release, use {@link #out()} instead
     */
    @Deprecated
    public static PrintStream out;
    /**
     * @deprecated this field will be made private in a future release, use {@link #sysErr()} instead
     */
    @Deprecated
    public static PrintStream system_err = System.err;
    /**
     * @deprecated this field will be made private in a future release, use {@link #err()} instead
     */
    @Deprecated
    public static PrintStream err;

    /**
     * Try to find the width of the console for this process.
     * Both output and error streams will be checked to determine the width.
     * A value of 0 is returned if the width can not be determined.
     * @since 2.2
     */
    public static int getTerminalWidth() {
        int w = out().getTerminalWidth();
        if (w <= 0) {
            w = err().getTerminalWidth();
        }
        return w;
    }

    static final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("win");

    static final boolean IS_CYGWIN = IS_WINDOWS
            && System.getenv("PWD") != null
            && System.getenv("PWD").startsWith("/");

    static final boolean IS_MSYSTEM = IS_WINDOWS
            && System.getenv("MSYSTEM") != null
            && (System.getenv("MSYSTEM").startsWith("MINGW")
                || System.getenv("MSYSTEM").equals("MSYS"));

    static final boolean IS_CONEMU = IS_WINDOWS
            && System.getenv("ConEmuPID") != null;

    static final int ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;

    static int STDOUT_FILENO = 1;

    static int STDERR_FILENO = 2;


    static {
        if (getBoolean(JANSI_EAGER)) {
            initStreams();
        }
    }

    private static boolean initialized;
    private static int installed;
    private static int virtualProcessing;

    private AnsiConsole() {
    }

    private static AnsiPrintStream ansiStream(boolean stdout) {
        FileDescriptor descriptor = stdout ? FileDescriptor.out : FileDescriptor.err;
        final OutputStream out = new FastBufferedOutputStream(new FileOutputStream(descriptor));

        String enc = System.getProperty(stdout ? "sun.stdout.encoding" : "sun.stderr.encoding");

        final boolean isatty;
        boolean isAtty;
        boolean withException;
        // Do not use the CLibrary.STDOUT_FILENO to avoid errors in case
        // the library can not be loaded on unsupported platforms
        final int fd = stdout ? STDOUT_FILENO : STDERR_FILENO;
        try {
            // If we can detect that stdout is not a tty.. then setup
            // to strip the ANSI sequences..
            isAtty = isatty(fd) != 0;
            String term = System.getenv("TERM");
            String emacs = System.getenv("INSIDE_EMACS");
            if (isAtty && "dumb".equals(term) && emacs != null && !emacs.contains("comint")) {
                isAtty = false;
            }
            withException = false;
        } catch (Throwable ignore) {
            // These errors happen if the JNI lib is not available for your platform.
            // But since we are on ANSI friendly platform, assume the user is on the console.
            isAtty = false;
            withException = true;
        }
        isatty = isAtty;

        final AnsiOutputStream.WidthSupplier width;
        final AnsiProcessor processor;
        final AnsiType type;
        final AnsiOutputStream.IoRunnable installer;
        final AnsiOutputStream.IoRunnable uninstaller;
        if (!isatty) {
            processor = null;
            type = withException ? AnsiType.Unsupported : AnsiType.Redirected;
            installer = uninstaller = null;
            width = new AnsiOutputStream.ZeroWidthSupplier();
        }
        else if (IS_WINDOWS) {
            final long console = GetStdHandle(stdout ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);