/*
 * 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.
 */
package org.apache.hive.beeline.common.log;

import static org.fusesource.jansi.Ansi.ansi;

import com.google.common.base.Function;
import com.google.common.collect.Lists;
import java.io.PrintStream;
import java.io.StringWriter;
import java.text.DecimalFormat;
import java.util.List;
import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
import org.fusesource.jansi.Ansi;

/** Renders information from ProgressMonitor to the stream provided. */
public class InPlaceUpdate {

  public static final int MIN_TERMINAL_WIDTH = 94;

  // keep this within 80 chars width. If more columns needs to be added then update min terminal
  // width requirement and SEPARATOR width accordingly
  private static final String HEADER_FORMAT = "%16s%10s %13s  %5s  %9s  %7s  %7s  %6s  %6s  ";
  private static final String VERTEX_FORMAT = "%-16s%10s %13s  %5s  %9s  %7s  %7s  %6s  %6s  ";
  private static final String FOOTER_FORMAT = "%-15s  %-30s %-4s  %-25s";

  private static final int PROGRESS_BAR_CHARS = 30;
  private static final String SEPARATOR =
      new String(new char[MIN_TERMINAL_WIDTH]).replace("\0", "-");

  /* Pretty print the values */
  private final DecimalFormat secondsFormatter = new DecimalFormat("#0.00");
  private int lines = 0;
  private PrintStream out;

  public InPlaceUpdate(PrintStream out) {
    this.out = out;
  }

  public InPlaceUpdate() {
    this(System.out);
  }

  public static void reprintLine(PrintStream out, String line) {
    out.print(ansi().eraseLine(Ansi.Erase.ALL).a(line).a('\n').toString());
    out.flush();
  }

  public static void rePositionCursor(PrintStream ps) {
    ps.print(ansi().cursorUp(0).toString());
    ps.flush();
  }

  /**
   * NOTE: Use this method only if isUnixTerminal is true. Erases the current line and prints the
   * given line.
   *
   * @param line - line to print
   */
  private void reprintLine(String line) {
    reprintLine(out, line);
    lines++;
  }

  /**
   * NOTE: Use this method only if isUnixTerminal is true. Erases the current line and prints the
   * given line with the specified color.
   *
   * @param line - line to print
   * @param color - color for the line
   */
  private void reprintLineWithColorAsBold(String line, Ansi.Color color) {
    out.print(
        ansi()
            .eraseLine(Ansi.Erase.ALL)
            .fg(color)
            .bold()
            .a(line)
            .a('\n')
            .boldOff()
            .reset()
            .toString());
    out.flush();
    lines++;
  }

  /**
   * NOTE: Use this method only if isUnixTerminal is true. Erases the current line and prints the
   * given multiline. Make sure the specified line is not terminated by linebreak.
   *
   * @param line - line to print
   */
  private void reprintMultiLine(String line) {
    int numLines = line.split("\r\n|\r|\n").length;
    out.print(ansi().eraseLine(Ansi.Erase.ALL).a(line).a('\n').toString());
    out.flush();
    lines += numLines;
  }

  /**
   * NOTE: Use this method only if isUnixTerminal is true. Repositions the cursor back to line 0.
   */
  private void repositionCursor() {
    if (lines > 0) {
      out.print(ansi().cursorUp(lines).toString());
      out.flush();
      lines = 0;
    }
  }

  // [==================>>-----]
  private String getInPlaceProgressBar(double percent) {
    StringWriter bar = new StringWriter();
    bar.append("[");
    int remainingChars = PROGRESS_BAR_CHARS - 4;
    int completed = (int) (remainingChars * percent);
    int pending = remainingChars - completed;
    for (int i = 0; i < completed; i++) {
      bar.append("=");
    }
    bar.append(">>");
    for (int i = 0; i < pending; i++) {
      bar.append("-");
    }
    bar.append("]");
    return bar.toString();
  }

  public void render(ProgressMonitor monitor) {
    if (monitor == null) return;
    // position the cursor to line 0
    repositionCursor();

    // print header
    // -------------------------------------------------------------------------------
    //         VERTICES     STATUS  TOTAL  COMPLETED  RUNNING  PENDING  FAILED  KILLED
    // -------------------------------------------------------------------------------
    reprintLine(SEPARATOR);
    reprintLineWithColorAsBold(
        String.format(HEADER_FORMAT, monitor.headers().toArray()), Ansi.Color.CYAN);
    reprintLine(SEPARATOR);

    // Map 1 .......... container  SUCCEEDED      7          7        0        0       0       0
    List<String> printReady =
        Lists.transform(
            monitor.rows(),
            new Function<List<String>, String>() {
              @Nullable
              @Override
              public String apply(@Nullable List<String> row) {
                return String.format(VERTEX_FORMAT, row.toArray());
              }
            });
    reprintMultiLine(StringUtils.join(printReady, "\n"));

    // -------------------------------------------------------------------------------
    // VERTICES: 03/04            [=================>>-----] 86%  ELAPSED TIME: 1.71 s
    // -------------------------------------------------------------------------------
    String progressStr = "" + (int) (monitor.progressedPercentage() * 100) + "%";
    float et = (float) (System.currentTimeMillis() - monitor.startTime()) / (float) 1000;
    String elapsedTime = "ELAPSED TIME: " + secondsFormatter.format(et) + " s";
    String footer =
        String.format(
            FOOTER_FORMAT,
            monitor.footerSummary(),
            getInPlaceProgressBar(monitor.progressedPercentage()),
            progressStr,
            elapsedTime);

    reprintLine(SEPARATOR);
    reprintLineWithColorAsBold(footer, Ansi.Color.RED);
    reprintLine(SEPARATOR);
  }
}
