/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2010-2013, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |    http://scala-lang.org/               **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
\*                                                                      */

package scala.util.continuations

import scala.annotation.{ Annotation, StaticAnnotation, TypeConstraint }

/** This annotation is used to mark a parameter as part of a continuation
 * context.
 *
 * The type `A @cpsParam[B,C]` is desugared to `ControlContext[A,B,C]` at compile
 * time.
 *
 * @tparam B  The type of computation state after computation has executed, and
 *   before control is returned to the shift.
 * @tparam C  The eventual return type of this delimited compuation.
 * @see scala.util.continuations.ControlContext
 */
class cpsParam[-B,+C] extends StaticAnnotation with TypeConstraint

private class cpsSym[B] extends Annotation // implementation detail

private class cpsSynth extends Annotation // implementation detail

private class cpsPlus extends StaticAnnotation with TypeConstraint // implementation detail
private class cpsMinus extends Annotation // implementation detail


/**
 * This class represent a portion of computation that has a 'hole' in it.  The
 * class has the ability to compute state up until a certain point where the
 * state has the `A` type.  If this context is given a function of type
 * `A => B` to move the state to the `B` type, then the entire computation can
 * be completed resulting in a value of type `C`.
 *
 * An Example: {{{
 *   val cc = new ControlContext[String, String, String](
 *      fun = { (f: String=>String, err: Exception => String) =>
 *        val updatedState =
 *          try f("State")
 *          catch {
 *            case e: Exception => err(e)
 *          }
 *        updatedState + "-Complete!"
 *      },
 *      x = null.asIntanceOf[String]
 *  }
 *  cc.foreach(_ + "-Continued")  // Results in "State-Continued-Complete!"
 * }}}
 *
 * This class is used to transform calls to `shift` in the `continuations`
 * package.  Direct use and instantiation is possible, but usually reserved
 * for advanced cases.
 *
 *
 * A context may either be ''trivial'' or ''non-trivial''.   A ''trivial''
 * context '''just''' has a state of type `A`.  When completing the computation,
 * it's only necessary to use the function of type `A => B` directly against
 * the trivial value. A ''non-trivial'' value stores a computation '''around'''
 * the state transformation of type `A => B` and cannot be short-circuited.
 *
 * @param fun The captured computation so far.  The type
 *   `(A => B, Exception => B) => C` is a function where:
 *   - The first parameter `A=>B` represents the computation defined against
 *       the current state held in the ControlContext.
 *   - The second parameter `Exception => B` represents a computation to
 *       perform if an exception is thrown from the first parameter's computation.
 *   - The return value is the result of the entire computation contained in this
 *       `ControlContext`.
 * @param x  The current state stored in this context.  Allowed to be null if
 *   the context is non-trivial.
 * @tparam A  The type of the state currently held in the context.
 * @tparam B  The type of the transformed state needed to complete this computation.
 * @tparam C  The return type of the entire computation stored in this context.
 * @note `fun` and `x` are allowed to be `null`.
 * @see scala.util.continutations.shiftR
 */
final class ControlContext[+A,-B,+C](val fun: (A => B, Exception => B) => C, val x: A) extends Serializable {

  /*
    final def map[A1](f: A => A1): ControlContext[A1,B,C] = {
      new ControlContext((k:(A1 => B)) => fun((x:A) => k(f(x))), null.asInstanceOf[A1])
    }

    final def flatMap[A1,B1<:B](f: (A => ControlContext[A1,B1,B])): ControlContext[A1,B1,C] = {
      new ControlContext((k:(A1 => B1)) => fun((x:A) => f(x).fun(k)))
    }
  */

  /**
   * Modifies the currently captured state in this `ControlContext`.
   * @tparam A1 The new type of state in this context.
   * @param f A transformation function on the current state of the `ControlContext`.
   * @return The new `ControlContext`.
   */
  @noinline final def map[A1](f: A => A1): ControlContext[A1,B,C] = {
    if (fun eq null)
      try {
        new ControlContext[A1,B,C](null, f(x)) // TODO: only alloc if f(x) != x
      } catch {
        case ex: Exception =>
          new ControlContext((k: A1 => B, thr: Exception => B) => thr(ex).asInstanceOf[C], null.asInstanceOf[A1])
      }
    else
      new ControlContext({ (k: A1 => B, thr: Exception => B) =>
        fun( { (x:A) =>
          var done = false
          try {
            val res = f(x)
            done = true
            k(res)
          } catch {
            case ex: Exception if !done =>
              thr(ex)
          }
        }, thr)
      }, null.asInstanceOf[A1])
  }


  // it would be nice if @inline would turn the trivial path into a tail call.
  // unfortunately it doesn't, so we do it ourselves in SelectiveCPSTransform

  /**
   * Maps and flattens this `ControlContext` with another `ControlContext` generated from the current state.
   * @note   The resulting comuptation is still the type `C`.
   * @tparam A1 The new type of the contained state.
   * @tparam B1 The new type of the state after the stored continuation has executed.
   * @tparam C1 The result type of the nested `ControlContext`.  Because the nested `ControlContext` is executed within
   *   the outer `ControlContext`, this type must `>: B` so that the resulting nested computation can be fed through
   *   the current continuation.
   * @param f A transformation function from the current state to a nested `ControlContext`.
   * @return The transformed `ControlContext`.
   */
  @noinline final def flatMap[A1,B1,C1<:B](f: (A => ControlContext[A1,B1,C1])): ControlContext[A1,B1,C] = {
    if (fun eq null)
      try {
        f(x).asInstanceOf[ControlContext[A1,B1,C]]
      } catch {
        case ex: Exception =>
          new ControlContext((k: A1 => B1, thr: Exception => B1) => thr(ex).asInstanceOf[C], null.asInstanceOf[A1])
      }
    else
      new ControlContext({ (k: A1 => B1, thr: Exception => B1) =>
        fun( { (x:A) =>
          var done = false
          try {
            val ctxR = f(x)
            done = true
            val res: C1 = ctxR.foreachFull(k, thr) // => B1
            res
          } catch {
            case ex: Exception if !done =>
              thr(ex).asInstanceOf[B] // => B NOTE: in general this is unsafe!
          }                           // However, the plugin will not generate offending code
        }, thr.asInstanceOf[Exception=>B]) // => B
      }, null.asInstanceOf[A1])
  }

  /**
   * Runs the computation against the state stored in this `ControlContext`.
   * @param f the computation that modifies the current state of the context.
   * @note This method could throw exceptions from the computations.
   */
  final def foreach(f: A => B) = foreachFull(f, throw _)

  def foreachFull(f: A => B, g: Exception => B): C = {
    if (fun eq null)
      f(x).asInstanceOf[C]
    else
      fun(f, g)
  }

  /** @return true if this context only stores a state value and not any deferred computation. */
  final def isTrivial = fun eq null
  /** @return The current state value. */
  final def getTrivialValue = x.asInstanceOf[A]

  // need filter or other functions?

  final def flatMapCatch[A1>:A,B1<:B,C1>:C<:B1](pf: PartialFunction[Exception, ControlContext[A1,B1,C1]]): ControlContext[A1,B1,C1] = {
    if (fun eq null)
      this
    else {
      val fun1 = (ret1: A1 => B1, thr1: Exception => B1) => {
        val thr: Exception => B1 = { t: Exception =>
          var captureExceptions = true
          try {
            if (pf.isDefinedAt(t)) {
              val cc1 = pf(t)
              captureExceptions = false
              cc1.foreachFull(ret1, thr1) // Throw => B
            } else {
              captureExceptions = false
              thr1(t) // Throw => B1
            }
          } catch {
            case t1: Exception if captureExceptions => thr1(t1) // => E2
          }
        }
        fun(ret1, thr)// fun(ret1, thr)  // => B
      }
      new ControlContext(fun1, null.asInstanceOf[A1])
    }
  }

  final def mapFinally(f: () => Unit): ControlContext[A,B,C] = {
    if (fun eq null) {
      try {
        f()
        this
      } catch {
        case ex: Exception =>
          new ControlContext((k: A => B, thr: Exception => B) => thr(ex).asInstanceOf[C], null.asInstanceOf[A])
      }
    } else {
      val fun1 = (ret1: A => B, thr1: Exception => B) => {
        val ret: A => B = { x: A =>
          var captureExceptions = true
          try {
            f()
            captureExceptions = false
            ret1(x)
          } catch {
            case t1: Exception if captureExceptions => thr1(t1)
          }
        }
        val thr: Exception => B = { t: Exception =>
          var captureExceptions = true
          try {
            f()
            captureExceptions = false
            thr1(t)
          } catch {
            case t1: Exception if captureExceptions => thr1(t1)
          }
        }
        fun(ret, thr1)
      }
      new ControlContext(fun1, null.asInstanceOf[A])
    }
  }

}
