/**
 * Copyright 2009 Google Inc.
 *
 * 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.waveprotocol.wave.examples.client.console;

import com.google.common.collect.ImmutableList;

import jline.ANSIBuffer;

import org.waveprotocol.wave.examples.fedone.common.DocumentConstants;
import org.waveprotocol.wave.model.document.operation.AnnotationBoundaryMap;
import org.waveprotocol.wave.model.document.operation.Attributes;
import org.waveprotocol.wave.model.document.operation.BufferedDocOp;
import org.waveprotocol.wave.model.document.operation.DocInitializationCursor;
import org.waveprotocol.wave.model.document.operation.impl.DocOpBuilder;
import org.waveprotocol.wave.model.document.operation.impl.InitializationCursorAdapter;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Utility methods and constants for use with the console client.
 *
 *
 */
public class ConsoleUtils {

  /** ANSI code for text with no attributes. */
  public static final int ANSI_NO_ATTRS = 0;

  /** ANSI code for bold text. */
  public static final int ANSI_BOLD = 1;

  /** ANSI code for underlined text. */
  public static final int ANSI_UNDERLINE = 4;

  /** ANSI code for red foreground text. */
  public static final int ANSI_RED_FG = 31;

  /** ANSI code for green foreground text. */
  public static final int ANSI_GREEN_FG = 32;

  /** ANSI code for yellow foreground text. */
  public static final int ANSI_YELLOW_FG = 33;

  /** ANSI code for blue foreground text. */
  public static final int ANSI_BLUE_FG = 34;

  /** ANSI code for cyan foreground text. */
  public static final int ANSI_CYAN_FG = 36;

  /** ANSI code for white foreground text. */
  public static final int ANSI_WHITE_FG = 37;

  /** ANSI code for green background text. */
  public static final int ANSI_GREEN_BG = 42;

  /** ANSI code for blue background text. */
  public static final int ANSI_BLUE_BG = 44;

  /** ANSI code for cyan background text. */
  public static final int ANSI_CYAN_BG = 46;

  /** ANSI code for white background text. */
  public static final int ANSI_WHITE_BG = 47;

  private ConsoleUtils() {
  }

  /**
   * Ensure the width of a StringBuilder by dropping characters at the end or filling with spaces.
   *
   * @param width to set the string buffer to
   * @param builder to ensure length of
   */
  public static void ensureWidth(int width, StringBuilder builder) {
    if (builder.length() > width) {
      builder.delete(width, builder.length());
    } else {
      while (builder.length() < width) {
        builder.append(' ');
      }
    }
  }

  /**
   * Ensure the width of a String by dropping characters at the end or filling with spaces.
   *
   * @param width of the new String
   * @param string to ensure length of
   * @return String guaranteed to be of given width
   */
  public static String ensureWidth(int width, String string) {
    StringBuilder builder = new StringBuilder(string);
    ensureWidth(width, builder);
    return builder.toString();
  }

  /**
   * Create a blank line of a given width.
   *
   * @param width of the blank line
   * @return blank line of given width
   */
  public static String blankLine(int width) {
    return ensureWidth(width, "");
  }

  /**
   * Ensure the "height" of a number of lines by adding blank lines to the end.
   *
   * @param width each line should be (for filling in missing lines)
   * @param height
   * @param lines to ensure height of
   */
  public static void ensureHeight(int width, int height, List<String> lines) {
    while (lines.size() < height) {
      lines.add(blankLine(width));
    }
  }

  /**
   * Wrap a string in a list of ANSI escape codes, then reset at the end.
   *
   * @param ansiCodes to apply to the string
   * @param string to apply the codes to
   * @return string with applied ANSI codes
   */
  public static String ansiWrap(List<Integer> ansiCodes, String string) {
    StringBuilder builder = new StringBuilder(string);

    for (Integer code : ansiCodes) {
      builder.insert(0, ANSIBuffer.ANSICodes.attrib(code));
    }

    builder.append(ANSIBuffer.ANSICodes.attrib(ANSI_NO_ATTRS));
    return builder.toString();
  }

  /**
   * Wrap a string in a single ANSI escape code, then reset at the end.
   *
   * @param ansiCode to apply to the string
   * @param string to apply the code to
   * @return string with applied ANSI code
   */
  public static String ansiWrap(int ansiCode, String string) {
    return ansiWrap(ImmutableList.of(ansiCode), string);
  }

  /**
   * Render a String "nicely" by replacing new lines and tabs with spaces (etc).
   *
   * @param string to render nicely
   * @return nice version of string
   */
  public static String renderNice(String string) {
    string = string.replaceAll("\n", " ");
    string = string.replaceAll("\t", "        ");
    return string;
  }

  /**
   * Delete a line from a document.
   *
   * Each line is indicated by an start/end XML line tag, followed by text content until the next
   * start/end XML line tag or the end of the document.  The start/end tags as well as line contents
   * will be deleted by the generated operation.
   *
   * @param doc to delete line from
   * @param lineNumber of line (as an index) to delete
   * @return operation to delete a line from a document
   */
  public static BufferedDocOp createLineDeletion(final BufferedDocOp doc, final int lineNumber) {
    final DocOpBuilder lineDeletion = new DocOpBuilder();
    final AtomicInteger currentLine = new AtomicInteger(-1);

    doc.apply(InitializationCursorAdapter.adapt(new DocInitializationCursor() {
      @Override public void characters(String s) {
        if (currentLine.get() == lineNumber) {
          lineDeletion.deleteCharacters(s);
        } else {
          lineDeletion.retain(s.length());
        }
      }

      @Override public void elementStart(String key, Attributes attrs) {
        if (key.equals(DocumentConstants.LINE)) {
          currentLine.incrementAndGet();
        }

        if (currentLine.get() == lineNumber) {
          lineDeletion.deleteElementStart(key, attrs);
        } else {
          lineDeletion.retain(1);
        }
      }

      @Override public void elementEnd() {
        if (currentLine.get() == lineNumber) {
          lineDeletion.deleteElementEnd();
        } else {
          lineDeletion.retain(1);
        }
      }

      @Override public void annotationBoundary(AnnotationBoundaryMap map) {}
    }));

    return lineDeletion.build();
  }
}
