package de.renew.unify;

import java.io.Serializable;

import de.renew.util.HashedRelation;

/**
 * This class is used as a node in the {@link TupleIndex} data structure.
 * An {@code ArityBranch}-instance stores the
 * {@link ComponentBranch}-instances that are used during traversal.
 * Traversal is performed based on the arity of the given pattern object
 * ({@link ArityBranch#traverse}).
 * This class is serializable.
 */
public class ArityBranch implements Serializable {
    /**
     * This relation maps hash codes to sets
     * of tokens that exhibit the hashcode at
     * the component currently under investigation.
     **/
    private final HashedRelation<Integer, Object> _hashCodeRelation = new HashedRelation<>();

    /**
     * This array stores the component branches in order of the arity of their tuples,
     * with the component branch with lists being stored at the first index:
     * 0: list pairs
     * 1: 1-tuples
     * 2: 2-tuples
     * 3: 3-tuples
     * and so on. This simplifies some procedures.
     */
    private ComponentBranch[] _branches = new ComponentBranch[0];

    /**
     * Constructor for the class ArityBranch with no arguments.
     */
    public ArityBranch() {}

    /**
     * Returns the relation that maps hash codes to sets of tokens.
     *
     * @return the hash code relation
     */
    public HashedRelation<Integer, Object> getHashCodeRelation() {
        return _hashCodeRelation;
    }

    /**
     * Sets the capacity of the branches to the given value.
     * Copies the content of the existing branches up to that capacity.
     *
     * @param cap the capacity the branches should have
     */
    private void setCapacity(int cap) {
        ComponentBranch[] newBranches = new ComponentBranch[cap];
        System.arraycopy(_branches, 0, newBranches, 0, Math.min(cap, _branches.length));
        _branches = newBranches;
    }

    /**
     * Sets the capacity of the branches to the given value
     * if the current capacity is less.
     *
     * @param cap the new/needed capacity
     */
    private void ensureCapacity(int cap) {
        if (_branches.length < cap) {
            setCapacity(cap);
        }
    }

    /**
     * Trims the branches to remove any <code>null</code> elements.
     */
    private void trim() {
        int cap = _branches.length;
        while (cap > 0 && _branches[cap - 1] == null) {
            cap--;
        }
        if (cap < _branches.length) {
            setCapacity(cap);
        }
    }

    /**
     * Traverses the index based on the given object's arity and calls the
     * given visitor for each component.
     * If the given object is a normal Java object, the traversal is not performed.
     *
     * @param visitor the visitor to call for each component
     * @param remainder the object that should be used for traversal
     */
    void traverse(TupleIndexVisitor visitor, Object remainder) {
        if (visitor.visitIndex(this, remainder)) {
            int arity;
            int index;

            if (remainder instanceof Aggregate aggregate) {
                arity = aggregate.getReferences().length;
                if (arity == 0) {
                    // No need to traverse deeper into the search tree.
                    // The object in question is a nullary tuple or an empty list.
                    // During the search it is either known and
                    // indexed in the relation or unknown and
                    // indexed in the parent component branch.
                    return;
                } else if (remainder instanceof Tuple) {
                    // It is a non-empty tuple.
                    // Non-empty tuples are stored in branch arity.
                    index = arity;
                } else {
                    // It is a list pair.
                    // List pairs are stored in branch 0.
                    index = 0;
                }
            } else {
                // No need to traverse deeper into the search tree.
                // The object in question is an ordinary Java object.
                // During the search it is either known and
                // indexed in the relation or unknown and
                // indexed in the parent component branch.
                return;
            }

            // Make sure to have something traversable.
            ensureCapacity(index + 1);
            if (_branches[index] == null) {
                _branches[index] = new ComponentBranch(arity);
            }

            // Traverse.
            _branches[index].traverse(visitor, aggregate);

            // Make sure to clean up, if nothing stored.
            if (_branches[index].size() == 0) {
                _branches[index] = null;
                trim();
            }
        }
    }
}