/*
 * Copyright 2008-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 common._

/**
 * This trait is used to represent a PartialFunction with additional
 * associated metadata, a name that allows the NamedPartialFunction
 * to be looked up dynamically.
 */
trait NamedPartialFunction[-A, +B] extends PartialFunction[A, B] {
  def functionName: String
}

/**
 * This class is the base implementation of the NamedPartialFunction trait.
 */
class NamedPF[-A, +B](name: String, f: PartialFunction[A, B]) extends NamedPartialFunction[A, B] {
  override def isDefinedAt(x: A): Boolean = f.isDefinedAt(x)
  override def apply(x: A): B = f(x)
  val functionName = name
}

object NamedPF {
  /**
   * Curried constructor for NamedPF
   */
  def apply[A, B](name: String)(f: PartialFunction[A,B]):
  NamedPartialFunction[A,B] = new NamedPF(name, f)

  /**
   * Find the first partial function in the specified sequence that
   * is defined at the given value.
   *
   * @param value the value to use to test each PartialFunction
   * @param lst the sequence to search for a PartialFunction defined at <code>value</code>
   * @return a Full Box containing the PartialFunction if found,
   * or Empty othewise.
   */
  def find[A, B](value: A, lst: Seq[PartialFunction[A, B]]):
  Box[PartialFunction[A, B]] = lst.find(_.isDefinedAt(value))

  /**
   * Determine whether any PartialFunction in the specified sequence
   * is defined at the specified value.
   *
   * @param value the value to use to test each PartialFunction
   * @param lst the sequence to search for a PartialFunction defined at <code>value</code>
   * @return whether such a PartialFunction is found
   */
  def isDefinedAt[A, B](value: A, lst: Seq[PartialFunction[A, B]]): Boolean =
  find(value, lst).isDefined

  /**
   * Find the first PartialFunction in the specified sequence that is defined
   * at the specified value, apply it to that value and return the result
   * or throw a MatchError on failure to find such a function.
   *
   * @param value the value to use to test each PartialFunction
   * @param lst the sequence to search for a PartialFunction defined at <code>value</code>
   * @return the result of applying any such PartialFunction to the specified value.
   * @throws MatchError on failure to find such a PartialFunction
   */
  def apply[A, B](value: A, lst: Seq[PartialFunction[A, B]]): B =
  find(value, lst) match {
    case Full(pf) => pf.apply(value)
    case _ => throw new MatchError(value)
  }

  /**
   * Find the first PartialFunction in the specified sequence that is defined
   * at the specified value, apply it to that value and return the result
   * in a Full Box if found; return Empty otherwise
   *
   * @param value the value to use to test each PartialFunction
   * @param lst the sequence to search for a PartialFunction defined at <code>value</code>
   * @return a Full Box containing the result of applying the first PartialFunction which is
   * defined at the specified value to that value, or Empty if no such PartialFunction is found
   */
  def applyBox[A, B](value: A, lst: Seq[PartialFunction[A, B]]): Box[B] =
  find(value, lst).map(_.apply(value))
}

}
}