package org.dataone.cn.batch.synchronization;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;

import org.apache.commons.io.IOUtils;
import org.dataone.exceptions.MarshallingException;
import org.dataone.service.types.v1.Identifier;
import org.dataone.service.types.v1.NodeReference;
import org.dataone.service.types.v1.ObjectFormatIdentifier;
import org.dataone.service.types.v1.Subject;
import org.dataone.service.util.TypeMarshaller;

/**
 * This class offers direct and efficient equality comparison of DataONE types without having 
 * to do any null & empty-string checking prior to the comparison.  The 'equals' methods and 
 * 'serializedFormEquals' method follow the ObjectUtils and StringUtils pattern
 * of returning false if exactly 1 of the objects passed in is null.  
 * 
 * The 'valueEquals' methods will evaluate to true if a null Object is compared 
 * to a null-valued object  (like when identifier.getValue() == null).
 * 
 * The 'emptyEquals' methods considers null objects, null values, and empty strings as equivalent.
 * So will evaluate to true if a null Object is compared to an empty string'ed object (like when 
 * identifier.getValue() == "") 
 * 
 * 
 * @author rnahf
 *
 */
public class D1TypeUtils {

    public D1TypeUtils() {
        // TODO Auto-generated constructor stub
    }
    
    /**
     * A null-safe equality test for two Identifiers.  
     * (null object and null object.getValue() are considered the equal)
     * 
     * @see valueEquals and emptyEquals
     * 
     * @param id1
     * @param id2
     * @return
     */
    public static boolean equals(Identifier id1, Identifier id2) {
        
        if (id1 == id2)
            return true;
        
        if (id1 == null || id2 == null)
            return false;
        
        if (id1.getValue() == id2.getValue())
            return true;
        
        if (id1.getValue() == null || id2.getValue() == null)
            return false;
        
        return (id1.getValue().equals(id2.getValue()));
    }
    

    /**
     * A null-safe equality test for two NodeReferences
     * (null object and null object.getValue() are considered the equal)
     * 
     * @see valueEquals and emptyEquals
     * 
     * @param id1
     * @param id2
     * @return
     */
    public static boolean equals(NodeReference id1, NodeReference id2) {
        if (id1 == id2)
            return true;
        
        if (id1 == null || id2 == null)
            return false;
        
        if (id1.getValue() == id2.getValue())
            return true;
        
        if (id1.getValue() == null || id2.getValue() == null)
            return false;
        
        return (id1.getValue().equals(id2.getValue()));
    }
    
    /**
     * A null-safe equality test for two ObjectFormatIdentifiers
     *(null object and null object.getValue() are considered the equal)
     * 
     * @see valueEquals and emptyEquals
     * 
     * @param id1
     * @param id2
     * @return
     */
    public static boolean equals(ObjectFormatIdentifier id1, ObjectFormatIdentifier id2) {
        if (id1 == id2)
            return true;
        
        if (id1 == null || id2 == null)
            return false;
        
        if (id1.getValue() == id2.getValue())
            return true;
        
        if (id1.getValue() == null || id2.getValue() == null)
            return false;
        
        return (id1.getValue().equals(id2.getValue()));
    }
    
    /**
     * A null-safe equality test for two Subjects.
     * (null object and null object.getValue() are considered the equal)
     * 
     * @see valueEquals and emptyEquals
     * 
     * @param id1
     * @param id2
     * @return
     */
    public static boolean equals(Subject id1, Subject id2) {
        if (id1 == id2)
            return true;
        
        if (id1 == null || id2 == null)
            return false;
        
        if (id1.getValue() == id2.getValue())
            return true;
        
        if (id1.getValue() == null || id2.getValue() == null)
            return false;
        
        return (id1.getValue().equals(id2.getValue()));
    }
    
    /**
     * A null-safe equality test for two Dataone objects that bases the comparison
     * on the equality of their serialized representations.
     * 
     * Note: functionally equivalent objects (those that have unordered lists, for example)
     * may not evaluate as equal.
     *   
     * @param object1
     * @param object2
     * @return
     * @throws MarshallingException
     * @throws IOException
     */
    public static boolean serializedFormEquals(Object object1, Object object2) throws MarshallingException, IOException {
        
        if (object1 == object2)
            return true;
        
        if (object1 == null || object2 == null)
            return false;
        
        ByteArrayOutputStream obj1os = null;
        ByteArrayOutputStream obj2os = null;
        
        try {
            obj1os = new ByteArrayOutputStream();
            obj2os = new ByteArrayOutputStream();

            TypeMarshaller.marshalTypeToOutputStream(object1, obj1os);
            TypeMarshaller.marshalTypeToOutputStream(object2, obj2os);

            return Arrays.equals(obj1os.toByteArray(), obj2os.toByteArray());
        
        } finally {
            IOUtils.closeQuietly(obj1os);
            IOUtils.closeQuietly(obj2os);
        }
    }
 
    /**
     * Similar to equals method, but also returns true if one ID is null, and
     * the other ID's value property is null.
     * 
     * @see equals and emptyEquals
     * 
     * @param id1
     * @param id2
     * @return
     */
    public static boolean valueEquals(Identifier id1, Identifier id2) {
        
        if (id1 == id2)
            return true;
        
        if (id1 == null) 
            if (id2.getValue() == null)
                return true;
            else
                return false;
        
        if (id2 == null) 
            if (id1.getValue() == null)
                return true;
            else
                return false;
        
        if (id1.getValue() == id2.getValue())
            return true;
        
        if (id1.getValue() == null || id2.getValue() == null)
            return false;
        
        return (id1.getValue().equals(id2.getValue()));
    }

    /**
     * Similar to equals method, but also returns true if one ID is null, and
     * the other ID's value property is null.
     * 
     * @see equals and emptyEquals
     * 
     * @param id1
     * @param id2
     * @return
     */
    public static boolean valueEquals(NodeReference id1, NodeReference id2) {
        
        if (id1 == id2)
            return true;
        
        if (id1 == null) 
            if (id2.getValue() == null)
                return true;
            else
                return false;
        
        if (id2 == null) 
            if (id1.getValue() == null)
                return true;
            else
                return false;
        
        if (id1.getValue() == id2.getValue())
            return true;
        
        if (id1.getValue() == null || id2.getValue() == null)
            return false;
        
        return (id1.getValue().equals(id2.getValue()));
    }

    /**
     * Similar to equals method, but also returns true if one ID is null, and
     * the other ID's value property is null.
     * 
     * @see equals and emptyEquals
     * 
     * @param id1
     * @param id2
     * @return
     */
    public static boolean valueEquals(ObjectFormatIdentifier id1, ObjectFormatIdentifier id2) {
        
        if (id1 == id2)
            return true;
        
        if (id1 == null) 
            if (id2.getValue() == null)
                return true;
            else
                return false;
        
        if (id2 == null) 
            if (id1.getValue() == null)
                return true;
            else
                return false;
        
        if (id1.getValue() == id2.getValue())
            return true;
        
        if (id1.getValue() == null || id2.getValue() == null)
            return false;
        
        return (id1.getValue().equals(id2.getValue()));
    }

    /**
     * Similar to equals method, but also returns true if one ID is null, and
     * the other ID's value property is null.
     * @param id1
     * @param id2
     * 
     * @see equals and emptyEquals
     * 
     * @return
     */
    public static boolean valueEquals(Subject id1, Subject id2) {
        
        if (id1 == id2)
            return true;
        
        if (id1 == null) 
            if (id2.getValue() == null)
                return true;
            else
                return false;
        
        if (id2 == null) 
            if (id1.getValue() == null)
                return true;
            else
                return false;
        
        if (id1.getValue() == id2.getValue())
            return true;
        
        if (id1.getValue() == null || id2.getValue() == null)
            return false;
        
        return (id1.getValue().equals(id2.getValue()));
    }
    
    /**
     * Similar to equals, but treats a null object, null value-property, and empty string
     * value-property as equal to each other.
     * 
     * @see equals and valueEquals
     * 
     * @param id1
     * @param id2
     * @return
     */
    public static boolean emptyEquals(Identifier id1, Identifier id2) {

        // deal with all of the null & empty string cases first where:
        // null == null-value == empty
        if (id1 == null || id1.getValue() == null || id1.getValue().equals("")) 
            if (id2 == null || id2.getValue() == null || id2.getValue().equals(""))
                return true;
            else
                return false;

        else if (id2 == null || id2.getValue() == null || id2.getValue().equals(""))
            return false;

        // only non-null and non-empty cases

        if (id1 == id2)
            return true;
        
        return (id1.getValue().equals(id2.getValue()));
    }
    
 
    /**
     * Similar to equals, but treats a null object, null value-property, and empty string
     * value-property as equal to each other.
     * 
     * @see equals and valueEquals
     * 
     * @param id1
     * @param id2
     * @return
     */
    public static boolean emptyEquals(NodeReference id1, NodeReference id2) {

        // deal with all of the null & empty string cases first where:
        // null == null-value == empty
        if (id1 == null || id1.getValue() == null || id1.getValue().equals("")) 
            if (id2 == null || id2.getValue() == null || id2.getValue().equals(""))
                return true;
            else
                return false;

        else if (id2 == null || id2.getValue() == null || id2.getValue().equals(""))
            return false;

        // only non-null and non-empty cases

        if (id1 == id2)
            return true;
        
        return (id1.getValue().equals(id2.getValue()));
    }

    /**
     * Similar to equals, but treats a null object, null value-property, and empty string
     * value-property as equal to each other.
     * 
     * @see equals and valueEquals
     * 
     * @param id1
     * @param id2
     * @return
     */
    public static boolean emptyEquals(ObjectFormatIdentifier id1, ObjectFormatIdentifier id2) {

        // deal with all of the null & empty string cases first where:
        // null == null-value == empty
        if (id1 == null || id1.getValue() == null || id1.getValue().equals("")) 
            if (id2 == null || id2.getValue() == null || id2.getValue().equals(""))
                return true;
            else
                return false;

        else if (id2 == null || id2.getValue() == null || id2.getValue().equals(""))
            return false;

        // only non-null and non-empty cases

        if (id1 == id2)
            return true;
        
        return (id1.getValue().equals(id2.getValue()));
    }
    
    
    /**
     * Similar to equals, but treats a null object, null value-property, and empty string
     * value-property as equal to each other.
     * 
     * @see equals and valueEquals
     * 
     * @param id1
     * @param id2
     * @return
     */
    public static boolean emptyEquals(Subject id1, Subject id2) {

        // deal with all of the null & empty string cases first where:
        // null == null-value == empty
        if (id1 == null || id1.getValue() == null || id1.getValue().equals("")) 
            if (id2 == null || id2.getValue() == null || id2.getValue().equals(""))
                return true;
            else
                return false;

        else if (id2 == null || id2.getValue() == null || id2.getValue().equals(""))
            return false;

        // only non-null and non-empty cases

        if (id1 == id2)
            return true;
        
        return (id1.getValue().equals(id2.getValue()));
    }
}