/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

/*
 * This source file is based on code taken from SQLLine 1.0.2
 * See SQLLine notice in LICENSE
 */
package org.apache.hive.beeline;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/** A buffer that can output segments using ANSI color. */
final class ColorBuffer implements Comparable<Object> {
  private static final ColorBuffer.ColorAttr BOLD = new ColorAttr("\033[1m");
  private static final ColorBuffer.ColorAttr NORMAL = new ColorAttr("\033[m");
  private static final ColorBuffer.ColorAttr REVERS = new ColorAttr("\033[7m");
  private static final ColorBuffer.ColorAttr LINED = new ColorAttr("\033[4m");
  private static final ColorBuffer.ColorAttr GREY = new ColorAttr("\033[1;30m");
  private static final ColorBuffer.ColorAttr RED = new ColorAttr("\033[1;31m");
  private static final ColorBuffer.ColorAttr GREEN = new ColorAttr("\033[1;32m");
  private static final ColorBuffer.ColorAttr BLUE = new ColorAttr("\033[1;34m");
  private static final ColorBuffer.ColorAttr CYAN = new ColorAttr("\033[1;36m");
  private static final ColorBuffer.ColorAttr YELLOW = new ColorAttr("\033[1;33m");
  private static final ColorBuffer.ColorAttr MAGENTA = new ColorAttr("\033[1;35m");
  private static final ColorBuffer.ColorAttr INVISIBLE = new ColorAttr("\033[8m");

  private final List<Object> parts = new LinkedList<Object>();
  private int visibleLength = 0;

  private final boolean useColor;

  public ColorBuffer(boolean useColor) {
    this.useColor = useColor;
    append("");
  }

  public ColorBuffer(String str, boolean useColor) {
    this.useColor = useColor;
    append(str);
  }

  /**
   * Pad the specified String with spaces to the indicated length
   *
   * @param str the String to pad
   * @param len the length we want the return String to be
   * @return the passed in String with spaces appended until the length matches the specified
   *     length.
   */
  ColorBuffer pad(ColorBuffer str, int len) {
    while (str.getVisibleLength() < len) {
      str.append(" ");
    }
    return append(str);
  }

  ColorBuffer center(String str, int len) {
    StringBuilder buf = new StringBuilder(str);
    while (buf.length() < len) {
      buf.append(" ");
      if (buf.length() < len) {
        buf.insert(0, " ");
      }
    }
    return append(buf.toString());
  }

  ColorBuffer pad(String str, int len) {
    if (str == null) {
      str = "";
    }
    return pad(new ColorBuffer(str, false), len);
  }

  public String getColor() {
    return getBuffer(useColor);
  }

  public String getMono() {
    return getBuffer(false);
  }

  String getBuffer(boolean color) {
    StringBuilder buf = new StringBuilder();
    for (Object part : parts) {
      if (!color && part instanceof ColorBuffer.ColorAttr) {
        continue;
      }
      buf.append(part.toString());
    }
    return buf.toString();
  }

  /**
   * Truncate the ColorBuffer to the specified length and return the new ColorBuffer. Any open color
   * tags will be closed. Do nothing if the specified length is <= 0.
   */
  public ColorBuffer truncate(int len) {
    if (len <= 0) {
      return this;
    }
    ColorBuffer cbuff = new ColorBuffer(useColor);
    ColorBuffer.ColorAttr lastAttr = null;
    for (Iterator<Object> i = parts.iterator(); cbuff.getVisibleLength() < len && i.hasNext(); ) {
      Object next = i.next();
      if (next instanceof ColorBuffer.ColorAttr) {
        lastAttr = (ColorBuffer.ColorAttr) next;
        cbuff.append((ColorBuffer.ColorAttr) next);
        continue;
      }
      String val = next.toString();
      if (cbuff.getVisibleLength() + val.length() > len) {
        int partLen = len - cbuff.getVisibleLength();
        val = val.substring(0, partLen);
      }
      cbuff.append(val);
    }

    // close off the buffer with a normal tag
    if (lastAttr != null && lastAttr != NORMAL) {
      cbuff.append(NORMAL);
    }

    return cbuff;
  }

  @Override
  public String toString() {
    return getColor();
  }

  public ColorBuffer append(String str) {
    parts.add(str);
    visibleLength += str.length();
    return this;
  }

  public ColorBuffer append(ColorBuffer buf) {
    parts.addAll(buf.parts);
    visibleLength += buf.getVisibleLength();
    return this;
  }

  private ColorBuffer append(ColorBuffer.ColorAttr attr) {
    parts.add(attr);
    return this;
  }

  public int getVisibleLength() {
    return visibleLength;
  }

  private ColorBuffer append(ColorBuffer.ColorAttr attr, String val) {
    parts.add(attr);
    parts.add(val);
    parts.add(NORMAL);
    visibleLength += val.length();
    return this;
  }

  public ColorBuffer bold(String str) {
    return append(BOLD, str);
  }

  public ColorBuffer lined(String str) {
    return append(LINED, str);
  }

  public ColorBuffer grey(String str) {
    return append(GREY, str);
  }

  public ColorBuffer red(String str) {
    return append(RED, str);
  }

  public ColorBuffer blue(String str) {
    return append(BLUE, str);
  }

  public ColorBuffer green(String str) {
    return append(GREEN, str);
  }

  public ColorBuffer cyan(String str) {
    return append(CYAN, str);
  }

  public ColorBuffer yellow(String str) {
    return append(YELLOW, str);
  }

  public ColorBuffer magenta(String str) {
    return append(MAGENTA, str);
  }

  private static class ColorAttr {
    private final String attr;

    public ColorAttr(String attr) {
      this.attr = attr;
    }

    @Override
    public String toString() {
      return attr;
    }
  }

  public int compareTo(Object other) {
    return getMono().compareTo(((ColorBuffer) other).getMono());
  }
}
