import java.util.Date

sealed trait DataFrame[A] {
  import DataFrame._

  def size: Int =
    this.foldLeft(0)( (s, _) => s + 1 )

  def foldLeft[B](z: B)(f: (B, A) => B): B =
    this match {
      case Apply(data) => data.foldLeft(z)(f)
      case Filter(s, filter) => s.foldLeft(z){ (b, a) =>
        if(filter(a)) f(b, a) else b
      }
      case Project(s, project) => s.foldLeft(z){ (b, a) =>
        f(b, project(a))
      }
    }

  def filter(f: A => Boolean): DataFrame[A] =
    Filter(this, f)

  def project[B](f: A => B): DataFrame[B] =
    Project(this, f)
}
object DataFrame {
  // Allows us to write DataFrame(1, 2, 3, 4)
  def apply[A](as: A*): DataFrame[A] =
    Apply(as.toList)

  def apply[A](data: List[A]): DataFrame[A] =
    Apply(data)

  // DataFrame algebraic data type
  final case class Apply[A](data: List[A]) extends DataFrame[A]
  final case class Filter[A](source: DataFrame[A], f: A => Boolean) extends DataFrame[A]
  final case class Project[A,B](source: DataFrame[A], f: A => B) extends DataFrame[B]
}

object DataFrameSyntax {
  implicit class DoubleSyntax(data: DataFrame[Double]) {
    def sum: Double =
      data.foldLeft(0.0)(_ + _)
    def average: Double =
      data.sum / data.size
  }
}