/*
******************************************************************
Copyright (c) 2001-2007,2015,2022 Jeff Martin, Tim Bacon
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above
      copyright notice, this list of conditions and the following
      disclaimer in the documentation and/or other materials provided
      with the distribution.
    * Neither the name of the XMLUnit nor the names
      of its contributors may be used to endorse or promote products
      derived from this software without specific prior written
      permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

******************************************************************
*/

package org.custommonkey.xmlunit;


/**
 * Value object that describes a difference between DOM Nodes using one of
 * the DifferenceConstants ID values and a NodeDetail instance.
 * @see NodeDetail
 */
public class Difference {
    /** Simple unique identifier */
    private final int id;
    /** Description of the difference */
    private final String description;
    /** TRUE if the difference represents a similarity, FALSE otherwise */
    private boolean recoverable;

    private NodeDetail controlNodeDetail = null;
    private NodeDetail testNodeDetail = null;

    /**
     * Constructor for non-similar Difference instances
     * @param id type of difference
     * @param description description of the difference
     */
    protected Difference(int id, String description) {
        this(id, description, false);
    }

    /**
     * Constructor for similar or non-similar Difference instances
     * @param id type of difference
     * @param description description of the difference
     * @param recoverable whether the difference represents a similarity
     */
    protected Difference(int id, String description, boolean recoverable) {
        this.id = id;
        this.description = description;
        this.recoverable = recoverable;
    }

    /**
     * Copy constructor using prototype Difference and
     * encountered NodeDetails
     * @param prototype the difference to copy from
     * @param controlNodeDetail details of the control node
     * @param testNodeDetail details of the test node
     */
    protected Difference(Difference prototype, NodeDetail controlNodeDetail,
                         NodeDetail testNodeDetail) {
        this(prototype.getId(), prototype.getDescription(), prototype.isRecoverable());
        this.controlNodeDetail = controlNodeDetail;
        this.testNodeDetail = testNodeDetail;
    }

    /**
     * @return the id
     */
    public int getId() {
        return id;
    }

    /**
     * @return the description
     */
    public String getDescription() {
        return description;
    }

    /**
     * @return TRUE if the difference represents a similarity, FALSE otherwise
     */
    public boolean isRecoverable() {
        return recoverable;
    }

    /**
     * Allow the recoverable field value to be overridden.
     * Used when an override DifferenceListener is used in conjunction with
     * a DetailedDiff.
     * @param overrideValue whether the difference represents a similarity
     */
    protected void setRecoverable(boolean overrideValue) {
        recoverable = overrideValue;
    }

    /**
     * @return the NodeDetail from the piece of XML used as the control
     * at the Node where this difference was encountered
     */
    public NodeDetail getControlNodeDetail() {
        return controlNodeDetail;
    }

    /**
     * @return the NodeDetail from the piece of XML used as the test
     * at the Node where this difference was encountered
     */
    public NodeDetail getTestNodeDetail() {
        return testNodeDetail;
    }

    /**
     * Now that Differences can be constructed from prototypes
     * we need to be able to compare them to those in DifferenceConstants
     */
    @Override
    public boolean equals(Object other) {
        if (other == null) {
            return false;
        } else if (other instanceof Difference) {
            Difference otherDifference = (Difference) other;
            return id == otherDifference.getId();
        } else {
            return false;
        }
    }

    /**
     * hashcode implementation to go with equals.
     */
    @Override
    public int hashCode() {
        return id;
    }

    /**
     * @return a basic representation of the object state and identity
     * and if <code>NodeDetail</code> instances are populated append
     * their details also
     */
    @Override
    public String toString() {
        StringBuffer buf = new StringBuffer();
        if (controlNodeDetail == null || testNodeDetail == null) {
            appendBasicRepresentation(buf);
        } else {
            appendDetailedRepresentation(buf);
        }
        return buf.toString();
    }

    private void appendBasicRepresentation(StringBuffer buf) {
        buf.append("Difference (#").append(id).
            append(") ").append(description);
    }

    private void appendDetailedRepresentation(StringBuffer buf) {
        buf.append("Expected ").append(getDescription())
            .append(" '").append(controlNodeDetail.getValue())
            .append("' but was '").append(testNodeDetail.getValue())
            .append("' - comparing ");
        NodeDescriptor.appendNodeDetail(buf, controlNodeDetail);
        buf.append(" to ");
        NodeDescriptor.appendNodeDetail(buf, testNodeDetail);
    }

}
