package org.basex.util;

import static org.basex.core.Text.*;
import static org.basex.util.Token.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Scanner;

import org.basex.util.list.BoolList;
import org.basex.util.list.IntList;
import org.basex.util.list.TokenList;

/**
 * This is a table representation for textual table output.
 * It should be guaranteed that the {@link #header} object has the
 * same number of entries as all {@link #contents} string arrays.
 *
 * @author BaseX Team 2005-11, BSD License
 * @author Christian Gruen
 */
public final class Table {
  /** Distance between table columns. */
  private static final int DIST = 2;

  /** Table header. */
  public final TokenList header = new TokenList();
  /** Alignment (false: left, true: right alignment). */
  public final BoolList align = new BoolList();
  /** Table contents. */
  public final ArrayList<TokenList> contents = new ArrayList<TokenList>();
  /** Data description; if available, will be added as footer. */
  public String description;

  /**
   * Default constructor.
   */
  public Table() { }

  /**
   * Constructor with table input.
   * @param in textual table input
   */
  public Table(final String in) {
    if(in.isEmpty()) return;

    // parse table header
    final Scanner scan = new Scanner(in);
    byte[] line = token(scan.nextLine());
    final IntList il = new IntList();
    int i = 0;
    while(i < line.length) {
      il.add(i);
      // find next two spaces
      while(++i + 1 < line.length && (line[i] != ' ' || line[i + 1] != ' '));
      header.add(substring(line, il.get(il.size() - 1), i));
      while(++i < line.length && line[i] == ' ');
    }
    il.add(i);

    // skip delimiter
    scan.nextLine();

    // parse table entries
    final int s = il.size() - 1;
    while((line = token(scan.nextLine())).length != 0) {
      final TokenList entry = new TokenList();
      for(int e = 0; e < s; ++e) {
        entry.add(trim(substring(line, il.get(e), il.get(e + 1))));
      }
      contents.add(entry);
    }
  }

  /**
   * Sorts the table by the first column.
   */
  public void sort() {
    Collections.sort(contents, new Comparator<TokenList>() {
      @Override
      public int compare(final TokenList tl1, final TokenList tl2) {
        return diff(lc(tl1.get(0)), lc(tl2.get(0)));
      }
    });
  }

  /**
   * Returns the value for the specified table position.
   * @param r row
   * @param c column
   * @return value
   */
  public String value(final int r, final int c) {
    return Token.string(contents.get(r).get(c));
  }

  /**
   * Returns the number of rows.
   * @return number of rows
   */
  public int rows() {
    return contents.size();
  }

  /**
   * Returns the number of columns.
   * @return number of columns
   */
  public int cols() {
    return header.size();
  }

  /**
   * Moves the specified string to top.
   * @param top entry to be moved to the top
   */
  public void toTop(final byte[] top) {
    for(int i = 0; i < contents.size(); ++i) {
      if(eq(top, contents.get(i).get(0))) {
        contents.add(0, contents.remove(i));
        return;
      }
    }
  }

  /**
   * Returns a textual representation of the table.
   * @return text
   */
  public byte[] finish() {
    final int[] ind = new int[header.size()];
    final int sz = header.size();
    for(int s = 0; s < sz; ++s) {
      for(final TokenList e : contents) {
        ind[s] = Math.max(ind[s], e.get(s).length);
      }
      ind[s] = Math.max(ind[s], header.get(s).length);
    }

    final TokenBuilder tb = new TokenBuilder();
    for(int u = 0; u < sz; ++u) {
      final byte[] s = header.get(u);
      final int is = ind[u] - s.length + DIST;
      tb.add(s);
      for(int i = 0; i < is; ++i) tb.add(' ');
    }
    tb.add(NL);
    for(int u = 0; u < sz; ++u) {
      for(int i = 0; i < ind[u] + (u + 1 == sz ? 0 : DIST); ++i) tb.add('-');
    }
    tb.add(NL);
    for(final TokenList e : contents) {
      for(int u = 0; u < sz; ++u) {
        final byte[] s = e.get(u);
        final int is = ind[u] - s.length;
        if(u < align.size() && align.get(u)) {
          for(int i = 0; i < is; ++i) tb.add(' ');
          tb.add(s);
        } else {
          tb.add(s);
          for(int i = 0; i < is; ++i) tb.add(' ');
        }
        for(int i = 0; i < DIST; ++i) tb.add(' ');
      }
      tb.add(NL);
    }
    if(description != null) {
      tb.add(NL).add(contents.size() + " " + description + DOT);
    }
    return tb.finish();
  }

  @Override
  public String toString() {
    final TokenBuilder tb = new TokenBuilder();
    for(final byte[] b : header) {
      tb.add(b);
      tb.add('\t');
    }
    tb.add(NL);
    for(final TokenList e : contents) {
      for(final byte[] b : e) {
        tb.add(b);
        tb.add('\t');
      }
    }
    return tb.toString();
  }
}
