/*
 * #%L
 * Alfresco Data model classes
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.service.cmr.repository;

import java.io.Serializable;
import java.util.StringTokenizer;

import org.alfresco.api.AlfrescoPublicApi;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.EqualsHelper;

/**
 * This class represents a child relationship between two nodes. This
 * relationship is named.
 * <p>
 * So it requires the parent node ref, the child node ref and the name of the
 * child within the particular parent.
 * <p>
 * This combination is not a unique identifier for the relationship with regard
 * to structure. In use this does not matter as we have no concept of order,
 * particularly in the index.
 * 
 * @author andyh
 * 
 */
@AlfrescoPublicApi
public class ChildAssociationRef
        implements EntityRef, Comparable<ChildAssociationRef>, Serializable
{
    private static final long serialVersionUID = 4051322336257127729L;
    
    private static final String FILLER = "|";

    private QName assocTypeQName;
    private NodeRef parentRef;
    private QName childQName;
    private NodeRef childRef;
    private boolean isPrimary;
    private int nthSibling;
    

    /**
     * Construct a representation of a parent --- name ----> child relationship.
     * 
     * @param assocTypeQName
     *            the type of the association
     * @param parentRef
     *            the parent reference - may be null
     * @param childQName
     *            the qualified name of the association - may be null
     * @param childRef
     *            the child node reference. This must not be null.
     * @param isPrimary
     *            true if this represents the primary parent-child relationship
     * @param nthSibling
     *            the nth association with the same properties. Usually -1 to be
     *            ignored.
     */
    public ChildAssociationRef(
            QName assocTypeQName,
            NodeRef parentRef,
            QName childQName,
            NodeRef childRef,
            boolean isPrimary,
            int nthSibling)
    {
        this.assocTypeQName = assocTypeQName;
        this.parentRef = parentRef;
        this.childQName = childQName;
        this.childRef = childRef;
        this.isPrimary = isPrimary;
        this.nthSibling = nthSibling;

        // check
        if (childRef == null)
        {
            throw new IllegalArgumentException("Child reference may not be null");
        }
    }

    /**
     * Constructs a <b>non-primary</b>, -1th sibling parent-child association
     * reference.
     * 
     * @see ChildAssociationRef#ChildAssociationRef(org.alfresco.service.namespace.QName, NodeRef, org.alfresco.service.namespace.QName, NodeRef)
     */
    public ChildAssociationRef(QName assocTypeQName, NodeRef parentRef, QName childQName, NodeRef childRef)
    {
        this(assocTypeQName, parentRef, childQName, childRef, false, -1);
    }
    
    /**
     * @param childAssocRefStr a string of the form <b>parentNodeRef|childNodeRef|assocTypeQName|assocQName|isPrimary|nthSibling</b>
     */
    public ChildAssociationRef(String childAssocRefStr)
    {
        StringTokenizer tokenizer = new StringTokenizer(childAssocRefStr, FILLER);
        if (tokenizer.countTokens() != 6)
        {
            throw new AlfrescoRuntimeException("Unable to parse child association string: " + childAssocRefStr);
        }
        String parentNodeRefStr = tokenizer.nextToken();
        String childNodeRefStr = tokenizer.nextToken();
        String assocTypeQNameStr = tokenizer.nextToken();
        String assocQNameStr = tokenizer.nextToken();
        String isPrimaryStr = tokenizer.nextToken();
        String nthSiblingStr = tokenizer.nextToken();
        
        this.parentRef = new NodeRef(parentNodeRefStr);
        this.childRef = new NodeRef(childNodeRefStr);
        this.assocTypeQName = QName.createQName(assocTypeQNameStr);
        this.childQName = QName.createQName(assocQNameStr);
        this.isPrimary = Boolean.parseBoolean(isPrimaryStr);
        this.nthSibling = Integer.parseInt(nthSiblingStr);
    }

    /**
     * @return Returns a string of the form <b>parentNodeRef|childNodeRef|assocTypeQName|assocQName|isPrimary|nthSibling</b>
     */
    public String toString()
    {
        StringBuilder sb = new StringBuilder(250);
        sb.append(parentRef).append(FILLER)
          .append(childRef).append(FILLER)
          .append(assocTypeQName).append(FILLER)
          .append(childQName).append(FILLER)
          .append(isPrimary).append(FILLER)
          .append(nthSibling);
        return sb.toString();
    }
    
    /**
     * Compares:
     * <ul>
     * <li>{@link #assocTypeQName}</li>
     * <li>{@link #parentRef}</li>
     * <li>{@link #childRef}</li>
     * <li>{@link #childQName}</li>
     * </ul>
     */
    public boolean equals(Object o)
    {
        if (this == o)
        {
            return true;
        }
        if (!(o instanceof ChildAssociationRef))
        {
            return false;
        }
        ChildAssociationRef other = (ChildAssociationRef) o;

        return (EqualsHelper.nullSafeEquals(this.assocTypeQName, other.assocTypeQName)
                && EqualsHelper.nullSafeEquals(this.parentRef, other.parentRef)
                && EqualsHelper.nullSafeEquals(this.childQName, other.childQName)
                && EqualsHelper.nullSafeEquals(this.childRef, other.childRef));
    }

    public int hashCode()
    {
        int hashCode = ((getTypeQName() == null) ? 0 : getTypeQName().hashCode());
        hashCode = 37 * hashCode + ((getParentRef() == null) ? 0 : getParentRef().hashCode());
        hashCode = 37 * hashCode + ((getQName() == null) ? 0 : getQName().hashCode());
        hashCode = 37 * hashCode + getChildRef().hashCode();
        return hashCode;
    }

    /**
     * @see #setNthSibling(int)
     */
    public int compareTo(ChildAssociationRef another)
    {
        int thisVal = this.nthSibling;
        int anotherVal = another.nthSibling;
        return (thisVal < anotherVal ? -1 : (thisVal == anotherVal ? 0 : 1));
    }

    /**
     * Get the qualified name of the association type
     * 
     * @return Returns the qualified name of the parent-child association type
     *      as defined in the data dictionary.  It may be null if this is the
     *      imaginary association to the root node.
     */
    public QName getTypeQName()
    {
        return assocTypeQName;
    }

    /**
     * Get the qualified name of the parent-child association
     * 
     * @return Returns the qualified name of the parent-child association. It
     *         may be null if this is the imaginary association to a root node.
     */
    public QName getQName()
    {
        return childQName;
    }

    /**
     * @return Returns the child node reference - never null
     */
    public NodeRef getChildRef()
    {
        return childRef;
    }

    /**
     * @return Returns the parent node reference, which may be null if this
     *         represents the imaginary reference to the root node
     */
    public NodeRef getParentRef()
    {
        return parentRef;
    }

    /**
     * @return Returns true if this represents a primary association
     */
    public boolean isPrimary()
    {
        return isPrimary;
    }

    /**
     * @return Returns the nth sibling required
     */
    public int getNthSibling()
    {
        return nthSibling;
    }

    /**
     * Allows post-creation setting of the ordering index.  This is a helper
     * so that sorted sets and lists can be easily sorted.
     * <p>
     * This index is <b>in no way absolute</b> and should change depending on
     * the results that appear around this instance.  Therefore, the sibling
     * number cannot be used to construct, say, sibling number 5.  Sibling
     * number 5 will exist only in results where there are siblings 1 - 4.
     * 
     * @param nthSibling the sibling index
     */
    public void setNthSibling(int nthSibling)
    {
        this.nthSibling = nthSibling;
    }
}