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

package scala.xml

/** In an attempt to contain the damage being inflicted on
 *  consistency by the ad hoc equals methods spread around
 *  xml, the logic is centralized and all the xml classes
 *  go through the xml.Equality trait.  There are two forms
 *  of xml comparison.
 *
 *  1) def strict_==(other: xml.Equality)
 *
 *  This one tries to honor the little things like symmetry
 *  and hashCode contracts.  The equals method routes all
 *  comparisons through this.
 *
 *  2) xml_==(other: Any)
 *
 *  This one picks up where strict_== leaves off.  It might
 *  declare any two things equal.
 *
 *  As things stood, the logic not only made a mockery of
 *  the collections equals contract, but also laid waste to
 *  that of case classes.
 *
 *  Among the obstacles to sanity are/were:
 *
 *    Node extends NodeSeq extends Seq[Node]
 *    MetaData extends Iterable[MetaData]
 *    The hacky "Group" xml node which throws exceptions
 *      with wild abandon, so don't get too close
 *    Rampant asymmetry and impossible hashCodes
 *    Most classes claiming to be equal to "String" if
 *      some specific stringification of it was the same.
 *      String was never going to return the favor.
 */

object Equality {
  def asRef(x: Any): AnyRef = x.asInstanceOf[AnyRef]
  
  /** Note - these functions assume strict equality has already failed.
   */
  def compareBlithely(x1: AnyRef, x2: String): Boolean = x1 match {
    case x: Atom[_]   => x.data == x2
    case x: NodeSeq   => x.text == x2
    case _            => false
  }
  def compareBlithely(x1: AnyRef, x2: Node): Boolean = x1 match {
    case x: NodeSeq if x.length == 1  => x2 == x(0)
    case _                            => false
  }
  def compareBlithely(x1: AnyRef, x2: AnyRef): Boolean = {
    if (x1 == null || x2 == null)
      return (x1 eq x2)

    x2 match {
      case s: String  => compareBlithely(x1, s)
      case n: Node    => compareBlithely(x1, n)
      case _          => false
    }
  }
}
import Equality._

private[xml]
trait Equality extends scala.Equals {
  def basisForHashCode: Seq[Any]
  def strict_==(other: Equality): Boolean
  def strict_!=(other: Equality) = !strict_==(other)

  private def hashOf(x: Any) = if (x == null) 1 else x.##
  
  /** We insist we're only equal to other xml.Equality implementors,
   *  which heads off a lot of inconsistency up front.
   */
  override def canEqual(other: Any): Boolean = other match {
    case x: Equality    => true
    case _              => false
  }

  /** It's be nice to make these final, but there are probably
   *  people out there subclassing the XML types, especially when
   *  it comes to equals.  However WE at least can pretend they
   *  are final since clearly individual classes cannot be trusted
   *  to maintain a semblance of order.
   */
  override def hashCode() = basisForHashCode match {
    case Nil      => 0
    case x :: xs  => hashOf(x) * 41 + (xs map hashOf).foldLeft(0)(_ * 7 + _)
  }
  override def equals(other: Any) = doComparison(other, false)
  final def xml_==(other: Any)    = doComparison(other, true)
  final def xml_!=(other: Any)    = !xml_==(other)

  /** The "blithe" parameter expresses the caller's unconcerned attitude
   *  regarding the usual constraints on equals.  The method is thereby
   *  given carte blanche to declare any two things equal.
   */
  private def doComparison(other: Any, blithe: Boolean) = {
    val strictlyEqual = other match {
      case x: AnyRef if this eq x => true
      case x: Equality            => (x canEqual this) && (this strict_== x)
      case _                      => false
    }
    
    strictlyEqual || (blithe && compareBlithely(this, asRef(other)))
  }
}

