/*
 * Scala (https://www.scala-lang.org)
 *
 * Copyright EPFL and Lightbend, Inc.
 *
 * Licensed under Apache License 2.0
 * (http://www.apache.org/licenses/LICENSE-2.0).
 *
 * See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 */

package scala
package reflect.internal.util

import TableDef._
import scala.collection.immutable.AbstractSeq

/** A class for representing tabular data in a way that preserves
 *  its inner beauty.
 *  One creates an instance of TableDef by defining the columns of
 *  the table, then uses that to create an instance of Table by
 *  passing in a sequence of rows.
 */
class TableDef[T](_cols: Column[T]*) {
  // These operators are about all there is to it.
  /** Appends a column to the table. */
  def ~(next: Column[T])            = retThis(cols :+= next)

  // Below this point should all be considered private/internal.
  private[this] var cols: List[Column[T]] = _cols.toList

  def defaultSep(index: Int)   = if (index > (cols.size - 2)) "" else " "
  def sepAfter(i: Int): String = defaultSep(i)
  def sepWidths                = cols.indices map (i => sepAfter(i).length)

  def colNames = cols map (_.name)
  def colFunctions = cols map (_.f)
  def colApply(el: T) = colFunctions map (f => f(el))
  def retThis(body: => Unit): this.type = { body ; this }

  class Table(val rows: Seq[T]) extends AbstractSeq[T] {
    def iterator          = rows.iterator
    def apply(index: Int) = rows(index)
    def length            = rows.length

    def maxColWidth(col: Column[T]) = (col.name +: rows.map(col.f)).map(_.toString.length).max
    def specs = cols map (_ formatSpec rows)

    val colWidths   = cols map maxColWidth
    val rowFormat   = mkFormatString(sepAfter)
    val headFormat  = mkFormatString(i => " " * sepWidths(i))
    val argLists    = rows map colApply

    val headers = List(
      headFormat.format(colNames: _*),
      colWidths.lazyZip(sepWidths).map((w1, w2) => "-" * w1 + " " * w2).mkString
    )

    def mkFormatString(sepf: Int => String): String =
      specs.zipWithIndex.map { case (c, i) => c + sepf(i) }.mkString

    def toFormattedSeq = argLists map (xs => rowFormat.format(xs: _*))
    def allToSeq = headers ++ toFormattedSeq

    override def toString = allToSeq mkString "\n"
  }

  def table(rows: Seq[T]) = new Table(rows)

  override def toString = cols.mkString("TableDef(", ", ", ")")
}

object TableDef {
  case class Column[-T](name: String, f: T => Any, left: Boolean) {
    def maxWidth(elems: Seq[T]): Int = (name +: elems.map(f)).map(_.toString.length).max
    def formatSpec(elems: Seq[T]): String = {
      val justify = if (left) "-" else ""
      "%" + justify + maxWidth(elems) + "s"
    }
    override def toString = {
      val justify = if (left) "<<" else ">>"
      justify + "(" + name + ")"
    }
  }

  def apply[T](cols: Column[T]*) = new TableDef[T](cols: _*)
}
