/* (c) 2015 NerdWallet All rights reserved.
 *
 * 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.
 */
package gobblin.cli;

import java.util.ArrayList;
import java.util.List;

import com.google.common.base.Preconditions;
import com.google.common.primitives.Ints;


/**
 * A format helper for CLI output. Unfortunately it only supports strings, so
 * values need to be converted previous to passing in. This is done in order to
 * support table-like formatting.
 * <p/>
 * It's recommended that this class is built using the inner {@link Builder} class.
 *
 * @author ahollenbach@nerdwallet.com
 */
public class CliTablePrinter {
  /**
   * Labels for each columns
   */
  private List<String> labels;

  /**
   * A list of flag strings (corresponding to each column)
   */
  private List<String> flags;

  /**
   * Overall indentation of a table
   */
  private int indentation;

  /**
   * Number of spaces to place between columns
   */
  private int delimiterWidth;

  /**
   * Table of data to print
   */
  private List<List<String>> data;

  /**
   * The row format (generated by the constructor).
   */
  private String rowFormat;


  public CliTablePrinter(List<String> labels, List<String> flags, int indentation, int delimiterWidth,
      List<List<String>> data) {
    Preconditions.checkArgument(data.size() > 0);
    Preconditions.checkArgument(data.get(0).size() > 0);

    if (labels != null) {
      Preconditions.checkArgument(data.get(0).size() == labels.size());
    }
    if (flags != null) {
      Preconditions.checkArgument(data.get(0).size() == flags.size());
    }

    this.labels = labels;
    this.flags = flags;
    this.indentation = indentation;
    this.delimiterWidth = delimiterWidth;
    this.data = data;

    this.rowFormat = getRowFormat(getColumnMaxWidths());
  }

  /**
   * Used to build a {@link CliTablePrinter} object.
   */
  public static final class Builder {
    private List<String> labels;
    private List<String> flags;
    private int indentation;
    private int delimiterWidth;
    private List<List<String>> data;

    public Builder() {
      // Set defaults
      this.delimiterWidth = 1;
    }

    public Builder labels(List<String> labels) {
      this.labels = labels;
      return this;
    }

    public Builder data(List<List<String>> data) {
      this.data = data;
      return this;
    }

    public Builder indentation(int indentation) {
      this.indentation = indentation;
      return this;
    }

    public Builder delimiterWidth(int delimiterWidth) {
      this.delimiterWidth = delimiterWidth;
      return this;
    }

    public Builder flags(List<String> flags) {
      this.flags = flags;
      return this;
    }

    public CliTablePrinter build() {
      return new CliTablePrinter(this.labels, this.flags, this.indentation, this.delimiterWidth, this.data);
    }
  }

  /**
   * Prints the table of data
   */
  public void printTable() {
    if (this.labels != null) {
      System.out.printf(this.rowFormat, this.labels.toArray());
    }
    for (List<String> row : this.data) {
      System.out.printf(this.rowFormat, row.toArray());
    }
  }

  /**
   * A function for determining the max widths of columns, accounting for labels and data.
   *
   * @return An array of maximum widths for the strings in each column
   */
  private List<Integer> getColumnMaxWidths() {
    int numCols = data.get(0).size();
    int[] widths = new int[numCols];

    if (this.labels != null) {
      for (int i=0; i<numCols; i++) {
        widths[i] = this.labels.get(i).length();
      }
    }

    for (List<String> row : this.data) {
      for (int i=0;i<row.size(); i++) {
        if (row.get(i) == null) {
          widths[i] = Math.max(widths[i], 4);
        } else {
          widths[i] = Math.max(widths[i], row.get(i).length());
        }
      }
    }

    return Ints.asList(widths);
  }

  /**
   * Generates a simple row format string given a set of widths
   *
   * @param widths A list of widths for each column in the table
   * @return A row format for each row in the table
   */
  private String getRowFormat(List<Integer> widths) {
    String rowFormat = spaces(this.indentation);
    for (int i=0; i< widths.size(); i++) {
      rowFormat += "%";
      rowFormat += this.flags != null ? this.flags.get(i) : "";
      rowFormat += widths.get(i).toString();
      rowFormat += "s";
      rowFormat += spaces(this.delimiterWidth);
    }
    rowFormat += "\n";

    return rowFormat;
  }

  private static String spaces(int numSpaces) {
    StringBuilder sb = new StringBuilder();
    for (int i=0; i<numSpaces; i++) {
      sb.append(" ");
    }
    return sb.toString();
  }
}

@@ -1106,16 +1106,30 @@
 list of 
+sprintf-style 
 flag str

tweak comment slightly