// -----------------------------------------------------------------------------
//
//  Scalax - The Scala Community Library
//  Copyright (c) 2005-8 The Scalax Project. All rights reserved.
//
//  The primary distribution site is http://scalax.scalaforge.org/
//
//  This software is released under the terms of the Revised BSD License.
//  There is NO WARRANTY.  See the file LICENSE for the full text.
//
// -----------------------------------------------------------------------------

package scala.tools.scalap
package scalax
package rules

import language.postfixOps

trait Name {
  def name: String
  override def toString = name
}

/** A factory for rules.
  *
  * @author Andrew Foggin
  *
  * Inspired by the Scala parser combinator.
  */
trait Rules {

  import scala.language.implicitConversions
  implicit def rule[In, Out, A, X](f: In => Result[Out, A, X]): Rule[In, Out, A, X] = new DefaultRule(f)
  implicit def inRule[In, Out, A, X](rule: Rule[In, Out, A, X]): InRule[In, Out, A, X] = new InRule(rule)
  implicit def seqRule[In, A, X](rule: Rule[In, In, A, X]): SeqRule[In, A, X] = new SeqRule(rule)

  trait FromRule[In] {
    def apply[Out, A, X](f: In => Result[Out, A, X]): Rule[In, Out, A, X]
  }

  def from[In] = new FromRule[In] {
    def apply[Out, A, X](f: In => Result[Out, A, X]) = rule(f)
  }

  def state[s] = new StateRules {
    type S = s
    val factory = Rules.this
  }

  def success[Out, A](out: Out, a: A) = rule { in: Any => Success(out, a) }

  def failure = rule { in: Any => Failure }

  def error[In] = rule { in: In => Error(in) }
  def error[X](err: X) = rule { in: Any => Error(err) }

  def oneOf[In, Out, A, X](rules: Rule[In, Out, A, X] *): Rule[In, Out, A, X] = new Choice[In, Out, A, X] {
    val factory = Rules.this
    val choices = rules.toList
  }

  def ruleWithName[In, Out, A, X](_name: String, f: In => Result[Out, A, X]): Rule[In, Out, A, X] with Name =
    new DefaultRule(f) with Name {
      val name = _name
    }

  class DefaultRule[In, Out, A, X](f: In => Result[Out, A, X]) extends Rule[In, Out, A, X] {
    val factory = Rules.this
    def apply(in: In) = f(in)
  }

 /** Converts a rule into a function that throws an Exception on failure. */
  def expect[In, Out, A, Any](rule: Rule[In, Out, A, Any]): In => A = (in) => rule(in) match {
    case Success(_, a) => a
    case Failure => throw new ScalaSigParserError("Unexpected failure")
    case Error(x) => throw new ScalaSigParserError("Unexpected error: " + x)
  }
}

/** A factory for rules that apply to a particular context.
  *
  * @tparam S the context to which rules apply.
  *
  * @author Andrew Foggin
  *
  * Inspired by the Scala parser combinator.
  */
trait StateRules {
  type S
  type Rule[+A, +X] = rules.Rule[S, S, A, X]

  val factory: Rules
  import factory._

  def apply[A, X](f: S => Result[S, A, X]) = rule(f)

  def unit[A](a: => A) = apply { s => Success(s, a) }
  def read[A](f: S => A) = apply { s => Success(s, f(s)) }

  def get = apply { s => Success(s, s) }
  def set(s: => S) = apply { oldS => Success(s, oldS) }

  def update(f: S => S) = apply { s => Success(s, f(s)) }

  def nil = unit(Nil)
  def none = unit(None)

  /** Create a rule that identities if f(in) is true. */
  def cond(f: S => Boolean) = get filter f

  /** Create a rule that succeeds if all of the given rules succeed.
      @param rules the rules to apply in sequence.
  */
  def allOf[A, X](rules: Seq[Rule[A, X]]) = {
    def rep(in: S, rules: List[Rule[A, X]], results: List[A]): Result[S, List[A], X] = {
      rules match {
        case Nil => Success(in, results.reverse)
        case rule::tl => rule(in) match {
          case Failure => Failure
          case Error(x) => Error(x)
          case Success(out, v) => rep(out, tl, v::results)
        }
      }
    }
    in: S => rep(in, rules.toList, Nil)
  }


  /** Create a rule that succeeds with a list of all the provided rules that succeed.
      @param rules the rules to apply in sequence.
  */
  def anyOf[A, X](rules: Seq[Rule[A, X]]) = allOf(rules.map(_ ?)) ^^ { opts => opts.flatMap(x => x) }

  /** Repeatedly apply a rule from initial value until finished condition is met. */
  def repeatUntil[T, X](rule: Rule[T => T, X])(finished: T => Boolean)(initial: T) = apply {
    // more compact using HoF but written this way so it's tail-recursive
    def rep(in: S, t: T): Result[S, T, X] = {
      if (finished(t)) Success(in, t)
      else rule(in) match {
        case Success(out, f) => rep(out, f(t)) // scala/bug#5189 f.asInstanceOf[T => T]
        case Failure => Failure
        case Error(x) => Error(x)
      }
    }
    in => rep(in, initial)
  }


}

trait RulesWithState extends Rules with StateRules {
  val factory = this
}
