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

package scala.xml

import scala.collection.{ mutable, immutable, generic, SeqLike, AbstractSeq }
import mutable.{ Builder, ListBuffer }
import generic.{ CanBuildFrom }
import scala.language.implicitConversions

/** This object ...
 *
 *  @author  Burak Emir
 *  @version 1.0
 */
object NodeSeq {
  final val Empty = fromSeq(Nil)
  def fromSeq(s: Seq[Node]): NodeSeq = new NodeSeq {
    def theSeq = s
  }
  type Coll = NodeSeq
  implicit def canBuildFrom: CanBuildFrom[Coll, Node, NodeSeq] =
    new CanBuildFrom[Coll, Node, NodeSeq] {
      def apply(from: Coll) = newBuilder
      def apply() = newBuilder
    }
  def newBuilder: Builder[Node, NodeSeq] = new ListBuffer[Node] mapResult fromSeq
  implicit def seqToNodeSeq(s: Seq[Node]): NodeSeq = fromSeq(s)
}

/** This class implements a wrapper around `Seq[Node]` that adds XPath
 *  and comprehension methods.
 *
 *  @author  Burak Emir
 *  @version 1.0
 */
abstract class NodeSeq extends AbstractSeq[Node] with immutable.Seq[Node] with SeqLike[Node, NodeSeq] with Equality {
  import NodeSeq.seqToNodeSeq // import view magic for NodeSeq wrappers

  /** Creates a list buffer as builder for this class */
  override protected[this] def newBuilder = NodeSeq.newBuilder

  def theSeq: Seq[Node]
  def length = theSeq.length
  override def iterator = theSeq.iterator

  def apply(i: Int): Node = theSeq(i)
  def apply(f: Node => Boolean): NodeSeq = filter(f)

  def xml_sameElements[A](that: Iterable[A]): Boolean = {
    val these = this.iterator
    val those = that.iterator
    while (these.hasNext && those.hasNext)
      if (these.next xml_!= those.next)
        return false

    !these.hasNext && !those.hasNext
  }

  protected def basisForHashCode: Seq[Any] = theSeq

  override def canEqual(other: Any) = other match {
    case _: NodeSeq   => true
    case _            => false
  }

  override def strict_==(other: Equality) = other match {
    case x: NodeSeq => (length == x.length) && (theSeq sameElements x.theSeq)
    case _          => false
  }

  /** Projection function, which returns  elements of `this` sequence based
   *  on the string `that`. Use:
   *   - `this \ "foo"` to get a list of all elements that are labelled with `"foo"`;
   *   - `\ "_"` to get a list of all elements (wildcard);
   *   - `ns \ "@foo"` to get the unprefixed attribute `"foo"`;
   *   - `ns \ "@{uri}foo"` to get the prefixed attribute `"pre:foo"` whose
   *     prefix `"pre"` is resolved to the namespace `"uri"`.
   *
   *  For attribute projections, the resulting [[scala.xml.NodeSeq]] attribute
   *  values are wrapped in a [[scala.xml.Group]].
   *
   *  There is no support for searching a prefixed attribute by its literal prefix.
   *
   *  The document order is preserved.
   */
  def \(that: String): NodeSeq = {
    def fail = throw new IllegalArgumentException(that)
    def atResult = {
      lazy val y = this(0)
      val attr =
        if (that.length == 1) fail
        else if (that(1) == '{') {
          val i = that indexOf '}'
          if (i == -1) fail
          val (uri, key) = (that.substring(2,i), that.substring(i+1, that.length()))
          if (uri == "" || key == "") fail
          else y.attribute(uri, key)
        }
        else y.attribute(that drop 1)

      attr match {
        case Some(x)  => Group(x)
        case _        => NodeSeq.Empty
      }
    }

    def makeSeq(cond: (Node) => Boolean) =
      NodeSeq fromSeq (this flatMap (_.child) filter cond)

    that match {
      case ""                                         => fail
      case "_"                                        => makeSeq(!_.isAtom)
      case _ if (that(0) == '@' && this.length == 1)  => atResult
      case _                                          => makeSeq(_.label == that)
    }
  }

  /** Projection function, which returns elements of `this` sequence and of
   *  all its subsequences, based on the string `that`. Use:
   *   - `this \\ 'foo` to get a list of all elements that are labelled with `"foo"`;
   *   - `\\ "_"` to get a list of all elements (wildcard);
   *   - `ns \\ "@foo"` to get the unprefixed attribute `"foo"`;
   *   - `ns \\ "@{uri}foo"` to get each prefixed attribute `"pre:foo"` whose
   *     prefix `"pre"` is resolved to the namespace `"uri"`.
   *
   *  For attribute projections, the resulting [[scala.xml.NodeSeq]] attribute
   *  values are wrapped in a [[scala.xml.Group]].
   *
   *  There is no support for searching a prefixed attribute by its literal prefix.
   *
   *  The document order is preserved.
   */
  def \\ (that: String): NodeSeq = {
    def filt(cond: (Node) => Boolean) = this flatMap (_.descendant_or_self) filter cond
    that match {
      case "_"                  => filt(!_.isAtom)
      case _ if that(0) == '@'  => filt(!_.isAtom) flatMap (_ \ that)
      case _                    => filt(x => !x.isAtom && x.label == that)
    }
  }

  override def toString(): String = theSeq.mkString

  def text: String = (this map (_.text)).mkString
}
