/*
 * Copyright 2010 WorldWide Conferencing, LLC
 *
 * 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.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.liftweb {
package util {

import _root_.net.liftweb.common._
  /**
   * A wiring Cell.  A Cell can be a ValueCell which holds
   * a value which can be set (and thus update the dependencies),
   * a FuncCell (a cell that is a function that depends on other cells),
   * or a DynamicCell which has a value that updates each time the cell is
   * accessed.
   */
  trait Cell[T] {
    /**
     * The cell's value and most recent change time
     */
    def currentValue: (T, Long)

    /**
     * Get the cell's value
     */
    def get: T = currentValue._1

    /**
     * Create a new Cell by applying the function to this cell
     */
    def lift[A](f: T => A): Cell[A] = FuncCell(this)(f)

    def lift[A,B](cell: Cell[B])(f: (T, B) => A) = FuncCell(this, cell)(f)
  }

  object Cell {
    def apply[T](v: T): ValueCell[T] = new ValueCell[T](v)
  }

  /**
   * A cell that changes value on each access.  This kind of cell
   * could be used to access an external resource.  <b>Warning</b>
   * the function may be accessed many times during a single wiring
   * recalculation, so it's best to use this as a front to a value
   * that's memoized for some duration.
   */
  final case class DynamicCell[T](f: () => T) extends Cell[T] {
    /**
     * The cell's value and most recent change time
     */
    def currentValue: (T, Long) = 
      f() -> System.nanoTime()
  }

  /**
   * The companion object that has a helpful constructor
   */
  object ValueCell {
    def apply[A](value: A): ValueCell[A] = new ValueCell(value)

    implicit def vcToT[T](in: ValueCell[T]): T = in.get
  }

  /**
   * A ValueCell holds a value that can be mutated.
   */
  final class ValueCell[A](initialValue: A) extends Cell[A] with LiftValue[A] {
    private var value: A = initialValue
    private var ct: Long = System.nanoTime()

    /**
     * The cell's value and most recent change time
     */
    def currentValue: (A, Long) = synchronized {
      (value, ct)
    }

    /**
     * Get the cell's value
     */
    def set(v: A): A = synchronized {
      value = v
      ct = System.nanoTime()
      value
    }

    override def toString(): String = synchronized {
      "ValueCell("+value+")"
    }

    override def hashCode(): Int = synchronized {
      if (null.asInstanceOf[Object] eq value.asInstanceOf[Object]) 0
      else value.hashCode()
    }

    override def equals(other: Any): Boolean = synchronized {
      other match {
        case vc: ValueCell[_] => value == vc.get
        case _ => false
      }
    }
  }

  /**
   * A collection of Cells og a given type
   */
  final case class SeqCell[T](cells: Cell[T]*) extends Cell[Seq[T]] {
    /**
     * The cell's value and most recent change time
     */
    def currentValue: (Seq[T], Long) = {
      val tcv = cells.map(_.currentValue)
      tcv.map(_._1) -> tcv.foldLeft(0L)((max, c) => if (max > c._2) max else c._2)
    }
  }

  /**
   * The companion object for FuncCell (function cells)
   */
  object FuncCell {
    /**
     * Construct a function cell based on a single parameter
     */
    def apply[A, Z](a: Cell[A])(f: A => Z): Cell[Z] = FuncCell1(a, f)

    /**
     * Construct a function cell based on two parameters
     */
    def apply[A, B, Z](a: Cell[A], b: Cell[B])(f: (A, B) => Z): Cell[Z] = FuncCell2(a, b, f)

    /**
     * Construct a function cell based on three parameters
     */
    def apply[A, B, C, Z](a: Cell[A], b: Cell[B], c: Cell[C])(f: (A, B, C) => Z): Cell[Z] = FuncCell3(a, b, c, f)

    /**
     * Construct a function cell based on four parameters
     */
    def apply[A, B, C, D, Z](a: Cell[A], b: Cell[B], c: Cell[C], d: Cell[D])(f: (A, B, C, D) => Z): Cell[Z] = FuncCell4(a, b, c, d, f)

    /**
     * Construct a function cell based on five parameters
     */
    def apply[A, B, C, D, E, Z](a: Cell[A], b: Cell[B], c: Cell[C], d: Cell[D], e: Cell[E])(f: (A, B, C, D, E) => Z): Cell[Z] = FuncCell5(a, b, c, d, e, f)
  }

  final case class FuncCell1[A, Z](a: Cell[A], f: A => Z) extends Cell[Z] {
    private var value: Z = _
    private var ct: Long = 0

    def currentValue: (Z, Long) = synchronized {
      val (v, t) = a.currentValue
      if (t > ct) {
        value = f(v)
        ct = t
      }
      (value -> ct)
    }
  }

  final case class FuncCell2[A, B, Z](a: Cell[A], b: Cell[B], f: (A, B) => Z) extends Cell[Z] {
    private var value: Z = _
    private var ct: Long = 0

    def currentValue: (Z, Long) = synchronized {
      val (v1, t1) = a.currentValue
      val (v2, t2) = b.currentValue
      val t = WiringHelper.max(t1, t2)
      if (t > ct) {
        value = f(v1, v2)
        ct = t
      }
      (value -> ct)
    }
  }
  
  final case class FuncCell3[A, B, C, Z](a: Cell[A], b: Cell[B], c: Cell[C], f: (A, B, C) => Z) extends Cell[Z] {
    private var value: Z = _
    private var ct: Long = 0

    def currentValue: (Z, Long) = synchronized {
      val (v1, t1) = a.currentValue
      val (v2, t2) = b.currentValue
      val (v3, t3) = c.currentValue
      val t = WiringHelper.max(t1, t2, t3)
      if (t > ct) {
        value = f(v1, v2, v3)
        ct = t
      }
      (value -> ct)
    }
  }
  
  final case class FuncCell4[A, B, C, D, Z](a: Cell[A], b: Cell[B], c: Cell[C], d: Cell[D], f: (A, B, C, D) => Z) extends Cell[Z] {
    private var value: Z = _
    private var ct: Long = 0

    def currentValue: (Z, Long) = synchronized {
      val (v1, t1) = a.currentValue
      val (v2, t2) = b.currentValue
      val (v3, t3) = c.currentValue
      val (v4, t4) = d.currentValue
      val t = WiringHelper.max(t1, t2, t3, t4)
      if (t > ct) {
        value = f(v1, v2, v3, v4)
        ct = t
      }
      (value -> ct)
    }
  }
  
  final case class FuncCell5[A, B, C, D, E, Z](a: Cell[A], b: Cell[B], c: Cell[C], d: Cell[D], e: Cell[E], f: (A, B, C, D, E) => Z) extends Cell[Z] {
    private var value: Z = _
    private var ct: Long = 0

    def currentValue: (Z, Long) = synchronized {
      val (v1, t1) = a.currentValue
      val (v2, t2) = b.currentValue
      val (v3, t3) = c.currentValue
      val (v4, t4) = d.currentValue
      val (v5, t5) = e.currentValue
      val t = WiringHelper.max(t1, t2, t3, t4, t5)
      if (t > ct) {
        value = f(v1, v2, v3, v4, v5)
        ct = t
      }
      (value -> ct)
    }
  }
  
  private object WiringHelper {
    def max(a: Long, b: Long*): Long = b.foldLeft(a){
      (v1, v2) =>
        if (v1 > v2) v1 else v2
    }
  }
}
}