/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 */
package org.opends.server.util.table;



import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.opends.messages.Message;



/**
 * A class which can be used to construct tables of information to be
 * displayed in a terminal. Once built the table can be output using a
 * {@link TableSerializer}.
 */
public final class TableBuilder {

  // The current column number in the current row where 0 represents
  // the left-most column in the table.
  private int column = 0;

  // The current with of each column.
  private List<Integer> columnWidths = new ArrayList<Integer>();

  // The list of column headings.
  private List<Message> header = new ArrayList<Message>();

  // The current number of rows in the table.
  private int height = 0;

  // The list of table rows.
  private List<List<String>> rows = new ArrayList<List<String>>();

  // The linked list of sort keys comparators.
  private List<Comparator<String>> sortComparators =
    new ArrayList<Comparator<String>>();

  // The linked list of sort keys.
  private List<Integer> sortKeys = new ArrayList<Integer>();

  // The current number of columns in the table.
  private int width = 0;



  /**
   * Creates a new table printer.
   */
  public TableBuilder() {
    // No implementation required.
  }



  /**
   * Adds a table sort key. The table will be sorted according to the
   * case-insensitive string ordering of the cells in the specified
   * column.
   *
   * @param column
   *          The column which will be used as a sort key.
   */
  public void addSortKey(int column) {
    addSortKey(column, String.CASE_INSENSITIVE_ORDER);
  }



  /**
   * Adds a table sort key. The table will be sorted according to the
   * provided string comparator.
   *
   * @param column
   *          The column which will be used as a sort key.
   * @param comparator
   *          The string comparator.
   */
  public void addSortKey(int column, Comparator<String> comparator) {
    sortKeys.add(column);
    sortComparators.add(comparator);
  }



  /**
   * Appends a new blank cell to the current row.
   */
  public void appendCell() {
    appendCell("");
  }



  /**
   * Appends a new cell to the current row containing the provided
   * boolean value.
   *
   * @param value
   *          The boolean value.
   */
  public void appendCell(boolean value) {
    appendCell(String.valueOf(value));
  }



  /**
   * Appends a new cell to the current row containing the provided
   * byte value.
   *
   * @param value
   *          The byte value.
   */
  public void appendCell(byte value) {
    appendCell(String.valueOf(value));
  }



  /**
   * Appends a new cell to the current row containing the provided
   * char value.
   *
   * @param value
   *          The char value.
   */
  public void appendCell(char value) {
    appendCell(String.valueOf(value));
  }



  /**
   * Appends a new cell to the current row containing the provided
   * double value.
   *
   * @param value
   *          The double value.
   */
  public void appendCell(double value) {
    appendCell(String.valueOf(value));
  }



  /**
   * Appends a new cell to the current row containing the provided
   * float value.
   *
   * @param value
   *          The float value.
   */
  public void appendCell(float value) {
    appendCell(String.valueOf(value));
  }



  /**
   * Appends a new cell to the current row containing the provided
   * integer value.
   *
   * @param value
   *          The boolean value.
   */
  public void appendCell(int value) {
    appendCell(String.valueOf(value));
  }



  /**
   * Appends a new cell to the current row containing the provided
   * long value.
   *
   * @param value
   *          The long value.
   */
  public void appendCell(long value) {
    appendCell(String.valueOf(value));
  }



  /**
   * Appends a new cell to the current row containing the provided
   * object value.
   *
   * @param value
   *          The object value.
   */
  public void appendCell(Object value) {
    // Make sure that the first row has been created.
    if (height == 0) {
      startRow();
    }

    // Create the cell.
    String s = String.valueOf(value);
    rows.get(height - 1).add(s);
    column++;

    // Update statistics.
    if (column > width) {
      width = column;
      columnWidths.add(s.length());
    } else if (columnWidths.get(column - 1) < s.length()) {
      columnWidths.set(column - 1, s.length());
    }
  }



  /**
   * Appends a new blank column heading to the header row.
   */
  public void appendHeading() {
    appendHeading(Message.EMPTY);
  }



  /**
   * Appends a new column heading to the header row.
   *
   * @param value
   *          The column heading value.
   */
  public void appendHeading(Message value) {
    header.add(value);

    // Update statistics.
    if (header.size() > width) {
      width = header.size();
      columnWidths.add(value.length());
    } else if (columnWidths.get(header.size() - 1) < value.length()) {
      columnWidths.set(header.size() - 1, value.length());
    }
  }



  /**
   * Gets the width of the current row.
   *
   * @return Returns the width of the current row.
   */
  public int getRowWidth() {
    return column;
  }



  /**
   * Gets the number of rows in table.
   *
   * @return Returns the number of rows in table.
   */
  public int getTableHeight() {
    return height;
  }



  /**
   * Gets the number of columns in table.
   *
   * @return Returns the number of columns in table.
   */
  public int getTableWidth() {
    return width;
  }



  /**
   * Prints the table in its current state using the provided table
   * printer.
   *
   * @param printer
   *          The table printer.
   */
  public void print(TablePrinter printer) {
    // Create a new printer instance.
    TableSerializer serializer = printer.getSerializer();

    // First sort the table.
    List<List<String>> sortedRows = new ArrayList<List<String>>(rows);

    Comparator<List<String>> comparator = new Comparator<List<String>>() {

      public int compare(List<String> row1, List<String> row2) {
        for (int i = 0; i < sortKeys.size(); i++) {
          String cell1 = row1.get(sortKeys.get(i));
          String cell2 = row2.get(sortKeys.get(i));

          int rc = sortComparators.get(i).compare(cell1, cell2);
          if (rc != 0) {
            return rc;
          }
        }

        // Both rows are equal.
        return 0;
      }

    };

    Collections.sort(sortedRows, comparator);

    // Now output the table.
    serializer.startTable(height, width);
    for (int i = 0; i < width; i++) {
      serializer.addColumn(columnWidths.get(i));
    }

    // Column headings.
    serializer.startHeader();
    for (Message s : header) {
      serializer.addHeading(s.toString());
    }
    serializer.endHeader();

    // Table contents.
    serializer.startContent();
    for (List<String> row : sortedRows) {
      serializer.startRow();

      // Print each cell in the row, padding missing trailing cells.
      for (int i = 0; i < width; i++) {
        if (i < row.size()) {
          serializer.addCell(row.get(i));
        } else {
          serializer.addCell("");
        }
      }

      serializer.endRow();
    }
    serializer.endContent();
    serializer.endTable();
  }



  /**
   * Appends a new row to the table.
   */
  public void startRow() {
    rows.add(new ArrayList<String>());
    height++;
    column = 0;
  }
}
