/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2003-2013, 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: scala.xml.Equality)`
 *
 *  This one tries to honor the little things like symmetry and hashCode
 *  contracts.  The `equals` method routes all comparisons through this.
 *
 *  1. `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._

trait Equality extends scala.Equals {
  protected def basisForHashCode: Seq[Any]

  def strict_==(other: Equality): Boolean
  def strict_!=(other: Equality) = !strict_==(other)

  /** 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.##
  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)))
  }
}
