/*
 * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved.
 *
 * This software is distributable under the BSD license. See the terms of the BSD license in the
 * documentation provided with this software.
 */
package org.apache.geode.management.internal.cli.shell.jline;

import org.springframework.shell.support.util.OsUtils;

import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * A buffer that can contain ANSI text.
 *
 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
 */
public class ANSIBuffer {
  private boolean ansiEnabled = true;
  private final StringBuffer ansiBuffer = new StringBuffer();
  private final StringBuffer plainBuffer = new StringBuffer();

  public ANSIBuffer() {}

  public ANSIBuffer(final String str) {
    append(str);
  }

  public void setAnsiEnabled(final boolean ansi) {
    this.ansiEnabled = ansi;
  }

  public boolean getAnsiEnabled() {
    return this.ansiEnabled;
  }

  public String getAnsiBuffer() {
    return ansiBuffer.toString();
  }

  public String getPlainBuffer() {
    return plainBuffer.toString();
  }

  public String toString(final boolean ansi) {
    return ansi ? getAnsiBuffer() : getPlainBuffer();
  }

  public String toString() {
    return toString(ansiEnabled);
  }

  public ANSIBuffer append(final String str) {
    ansiBuffer.append(str);
    plainBuffer.append(str);

    return this;
  }

  public ANSIBuffer attrib(final String str, final int code) {
    ansiBuffer.append(ANSICodes.attrib(code)).append(str).append(ANSICodes.attrib(ANSICodes.OFF));
    plainBuffer.append(str);

    return this;
  }

  public ANSIBuffer red(final String str) {
    return attrib(str, ANSICodes.FG_RED);
  }

  public ANSIBuffer blue(final String str) {
    return attrib(str, ANSICodes.FG_BLUE);
  }

  public ANSIBuffer green(final String str) {
    return attrib(str, ANSICodes.FG_GREEN);
  }

  public ANSIBuffer black(final String str) {
    return attrib(str, ANSICodes.FG_BLACK);
  }

  public ANSIBuffer yellow(final String str) {
    return attrib(str, ANSICodes.FG_YELLOW);
  }

  public ANSIBuffer magenta(final String str) {
    return attrib(str, ANSICodes.FG_MAGENTA);
  }

  public ANSIBuffer cyan(final String str) {
    return attrib(str, ANSICodes.FG_CYAN);
  }

  public ANSIBuffer bold(final String str) {
    return attrib(str, ANSICodes.BOLD);
  }

  public ANSIBuffer underscore(final String str) {
    return attrib(str, ANSICodes.UNDERSCORE);
  }

  public ANSIBuffer blink(final String str) {
    return attrib(str, ANSICodes.BLINK);
  }

  public ANSIBuffer reverse(final String str) {
    return attrib(str, ANSICodes.REVERSE);
  }

  public static class ANSICodes {
    static final int OFF = 0;
    static final int BOLD = 1;
    static final int UNDERSCORE = 4;
    static final int BLINK = 5;
    static final int REVERSE = 7;
    static final int CONCEALED = 8;
    static final int FG_BLACK = 30;
    static final int FG_RED = 31;
    static final int FG_GREEN = 32;
    static final int FG_YELLOW = 33;
    static final int FG_BLUE = 34;
    static final int FG_MAGENTA = 35;
    static final int FG_CYAN = 36;
    static final int FG_WHITE = 37;
    static final char ESC = 27;

    /**
     * Constructor is private since this is a utility class.
     */
    private ANSICodes() {}

    /**
     * Sets the screen mode. The mode will be one of the following values:
     * 
     * <pre>
     * mode     description
     * ----------------------------------------
     *   0      40 x 148 x 25 monochrome (text)
     *   1      40 x 148 x 25 color (text)
     *   2      80 x 148 x 25 monochrome (text)
     *   3      80 x 148 x 25 color (text)
     *   4      320 x 148 x 200 4-color (graphics)
     *   5      320 x 148 x 200 monochrome (graphics)
     *   6      640 x 148 x 200 monochrome (graphics)
     *   7      Enables line wrapping
     *  13      320 x 148 x 200 color (graphics)
     *  14      640 x 148 x 200 color (16-color graphics)
     *  15      640 x 148 x 350 monochrome (2-color graphics)
     *  16      640 x 148 x 350 color (16-color graphics)
     *  17      640 x 148 x 480 monochrome (2-color graphics)
     *  18      640 x 148 x 480 color (16-color graphics)
     *  19      320 x 148 x 200 color (256-color graphics)
     * </pre>
     */
    public static String setmode(final int mode) {
      return ESC + "[=" + mode + "h";
    }

    /**
     * Same as setmode () except for mode = 7, which disables line wrapping (useful for writing the
     * right-most column without scrolling to the next line).
     */
    public static String resetmode(final int mode) {
      return ESC + "[=" + mode + "l";
    }

    /**
     * Clears the screen and moves the cursor to the home postition.
     */
    public static String clrscr() {
      return ESC + "[2J";
    }

    /**
     * Removes all characters from the current cursor position until the end of the line.
     */
    public static String clreol() {
      return ESC + "[K";
    }

    /**
     * Moves the cursor n positions to the left. If n is greater or equal to the current cursor
     * column, the cursor is moved to the first column.
     */
    public static String left(final int n) {
      return ESC + "[" + n + "D";
    }

    /**
     * Moves the cursor n positions to the right. If n plus the current cursor column is greater
     * than the rightmost column, the cursor is moved to the rightmost column.
     */
    public static String right(final int n) {
      return ESC + "[" + n + "C";
    }

    /**
     * Moves the cursor n rows up without changing the current column. If n is greater than or equal
     * to the current row, the cursor is placed in the first row.
     */
    public static String up(final int n) {
      return ESC + "[" + n + "A";
    }

    /**
     * Moves the cursor n rows down. If n plus the current row is greater than the bottom row, the
     * cursor is moved to the bottom row.
     */
    public static String down(final int n) {
      return ESC + "[" + n + "B";
    }

    /*
     * Moves the cursor to the given row and column. (1,1) represents the upper left corner. The
     * lower right corner of a usual DOS screen is (25, 80).
     */
    public static String gotoxy(final int row, final int column) {
      return ESC + "[" + row + ";" + column + "H";
    }

    /**
     * Saves the current cursor position.
     */
    public static String save() {
      return ESC + "[s";
    }

    /**
     * Restores the saved cursor position.
     */
    public static String restore() {
      return ESC + "[u";
    }

    /**
     * Sets the character attribute. It will be one of the following character attributes:
     *
     * <pre>
     * Text attributes
     *    0    All attributes off
     *    1    Bold on
     *    4    Underscore (on monochrome display adapter only)
     *    5    Blink on
     *    7    Reverse video on
     *    8    Concealed on
     *
     *   Foreground colors
     *    30    Black
     *    31    Red
     *    32    Green
     *    33    Yellow
     *    34    Blue
     *    35    Magenta
     *    36    Cyan
     *    37    White
     *
     *   Background colors
     *    40    Black
     *    41    Red
     *    42    Green
     *    43    Yellow
     *    44    Blue
     *    45    Magenta
     *    46    Cyan
     *    47    White
     * </pre>
     *
     * The attributes remain in effect until the next attribute command is sent.
     */
    public static String attrib(final int attr) {
      return ESC + "[" + attr + "m";
    }

    /**
     * Sets the key with the given code to the given value. code must be derived from the following
     * table, value must be any semicolon-separated combination of String (enclosed in double
     * quotes) and numeric values. For example, to set F1 to the String "Hello F1", followed by a
     * CRLF sequence, one can use: ANSI.setkey ("0;59", "\"Hello F1\";13;10"). Heres's the table of
     * key values:
     * 
     * <pre>
     * Key Code SHIFT+code CTRL+code ALT+code
     * --------------------------------------------------------------- F1 0;59 0;84 0;94 0;104 F2
     * 0;60 0;85 0;95 0;105 F3 0;61 0;86 0;96 0;106 F4 0;62 0;87 0;97 0;107 F5 0;63 0;88 0;98 0;108
     * F6 0;64 0;89 0;99 0;109 F7 0;65 0;90 0;100 0;110 F8 0;66 0;91 0;101 0;111 F9 0;67 0;92 0;102
     * 0;112 F10 0;68 0;93 0;103 0;113 F11 0;133 0;135 0;137 0;139 F12 0;134 0;136 0;138 0;140 HOME
     * (num keypad) 0;71 55 0;119 -- UP ARROW (num keypad) 0;72 56 (0;141) -- PAGE UP (num keypad)
     * 0;73 57 0;132 -- LEFT ARROW (num keypad) 0;75 52 0;115 -- RIGHT ARROW (num keypad) 0;77 54
     * 0;116 -- END (num keypad) 0;79 49 0;117 -- DOWN ARROW (num keypad) 0;80 50 (0;145) -- PAGE
     * DOWN (num keypad) 0;81 51 0;118 -- INSERT (num keypad) 0;82 48 (0;146) -- DELETE (num keypad)
     * 0;83 46 (0;147) -- HOME (224;71) (224;71) (224;119) (224;151) UP ARROW (224;72) (224;72)
     * (224;141) (224;152) PAGE UP (224;73) (224;73) (224;132) (224;153) LEFT ARROW (224;75)
     * (224;75) (224;115) (224;155) RIGHT ARROW (224;77) (224;77) (224;116) (224;157) END (224;79)
     * (224;79) (224;117) (224;159) DOWN ARROW (224;80) (224;80) (224;145) (224;154) PAGE DOWN
     * (224;81) (224;81) (224;118) (224;161) INSERT (224;82) (224;82) (224;146) (224;162) DELETE
     * (224;83) (224;83) (224;147) (224;163) PRINT SCREEN -- -- 0;114 -- PAUSE/BREAK -- -- 0;0 --
     * BACKSPACE 8 8 127 (0) ENTER 13 -- 10 (0 TAB 9 0;15 (0;148) (0;165) NULL 0;3 -- -- -- A 97 65
     * 1 0;30 B 98 66 2 0;48 C 99 66 3 0;46 D 100 68 4 0;32 E 101 69 5 0;18 F 102 70 6 0;33 G 103 71
     * 7 0;34 H 104 72 8 0;35 I 105 73 9 0;23 J 106 74 10 0;36 K 107 75 11 0;37 L 108 76 12 0;38 M
     * 109 77 13 0;50 N 110 78 14 0;49 O 111 79 15 0;24 P 112 80 16 0;25 Q 113 81 17 0;16 R 114 82
     * 18 0;19 S 115 83 19 0;31 T 116 84 20 0;20 U 117 85 21 0;22 V 118 86 22 0;47 W 119 87 23 0;17
     * X 120 88 24 0;45 Y 121 89 25 0;21 Z 122 90 26 0;44 1 49 33 -- 0;120 2 50 64 0 0;121 3 51 35
     * -- 0;122 4 52 36 -- 0;123 5 53 37 -- 0;124 6 54 94 30 0;125 7 55 38 -- 0;126 8 56 42 -- 0;126
     * 9 57 40 -- 0;127 0 48 41 -- 0;129 - 45 95 31 0;130 = 61 43 --- 0;131 [ 91 123 27 0;26 ] 93
     * 125 29 0;27 92 124 28 0;43 ; 59 58 -- 0;39 ' 39 34 -- 0;40 , 44 60 -- 0;51 . 46 62 -- 0;52 /
     * 47 63 -- 0;53 ` 96 126 -- (0;41) ENTER (keypad) 13 -- 10 (0;166) / (keypad) 47 47 (0;142)
     * (0;74) * (keypad) 42 (0;144) (0;78) -- - (keypad) 45 45 (0;149) (0;164) + (keypad) 43 43
     * (0;150) (0;55) 5 (keypad) (0;76) 53 (0;143) --
     */
    public static String setkey(final String code, final String value) {
      return ESC + "[" + code + ";" + value + "p";
    }
  }

  public static void main(final String[] args) throws Exception {
    // sequence, one can use: ANSI.setkey ("0;59", "\"Hello F1\";13;10").
    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    System.out.print(ANSICodes.setkey("97", "97;98;99;13") + ANSICodes.attrib(ANSICodes.OFF));
    System.out.flush();

    String line;

    while ((line = reader.readLine()) != null) {
      System.out.println("GOT: " + line);
    }
  }

  private static final boolean ROO_BRIGHT_COLORS = Boolean.getBoolean("roo.bright");
  private static final boolean SHELL_BRIGHT_COLORS = Boolean.getBoolean("spring.shell.bright");
  private static final boolean BRIGHT_COLORS = ROO_BRIGHT_COLORS || SHELL_BRIGHT_COLORS;

  public static ANSIBuffer getANSIBuffer() {
    final char esc = (char) 27;
    return new ANSIBuffer() {
      @Override
      public ANSIBuffer reverse(final String str) {
        if (OsUtils.isWindows()) {
          return super.reverse(str).append(ANSICodes.attrib(esc));
        }
        return super.reverse(str);
      };

      @Override
      public ANSIBuffer attrib(final String str, final int code) {
        if (BRIGHT_COLORS && 30 <= code && code <= 37) {
          // This is a color code: add a 'bright' code
          return append(esc + "[" + code + ";1m").append(str).append(ANSICodes.attrib(0));
        }
        return super.attrib(str, code);
      }
    };
  }
}
