/*
 * ModeShape (http://www.modeshape.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.modeshape.jcr.cache;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.modeshape.jcr.Connectors;
import org.modeshape.jcr.value.Name;
import org.modeshape.jcr.value.Property;

/**
 * 
 */
public interface MutableCachedNode extends CachedNode {

    /**
     * Return whether this node was created since last saved.
     * 
     * @return true if this node was created since the session was last saved, or false otherwise
     */
    boolean isNew();

    /**
     * Return whether the property with the supplied name was created since the session was last saved. This method will always
     * return {@code true} if {@link #isNew()} returns {@code true}.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @param propertyName the name of the property; may not be null
     * @return true if this node contains a property that was created since the session was last saved, or false otherwise
     * @see #isPropertyModified
     * @see #isNew
     */
    boolean isPropertyNew( SessionCache cache,
                           Name propertyName );

    /**
     * Return whether the property with the supplied name was modified since the session was last saved.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @param propertyName the name of the property; may not be null
     * @return true if this node contains a property that was created since the session was last saved, or false otherwise
     * @see #isPropertyNew
     */
    boolean isPropertyModified( SessionCache cache,
                                Name propertyName );

    /**
     * Return whether this node has changes, including property-related changes and other changes not related to properties. This
     * is equivalent to calling <code>hasNonPropertyChanges() || hasPropertyChanges()</code>.
     * 
     * @return true if this node has changes, or false otherwise
     * @see #hasNonPropertyChanges()
     * @see #hasPropertyChanges()
     */
    boolean hasChanges();

    /**
     * Return whether this node has changes unrelated to properties.
     * 
     * @return true if this node has changes unrelated to properties, or false otherwise
     * @see #hasChanges()
     * @see #hasPropertyChanges()
     */
    boolean hasNonPropertyChanges();

    /**
     * Return whether this node has changes in the properties.
     * 
     * @return true if this node has added, removed, or changed properties, or false otherwise
     * @see #hasChanges()
     * @see #hasNonPropertyChanges()
     */
    boolean hasPropertyChanges();

    /**
     * Lock this node.
     * 
     * @param sessionScoped true if the lock should be limited in scope to the lifetime of the session, or false otherwise
     */
    void lock( boolean sessionScoped );

    /**
     * Unlock this node.
     */
    void unlock();

    /**
     * Set the property with the given name.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @param property the property; may not be null
     * @throws NodeNotFoundException if this node no longer exists
     */
    void setProperty( SessionCache cache,
                      Property property );

    /**
     * Sets a property of type reference in the case when there's an active system cache and the property is a reference towards a
     * transient node from the system cache.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @param property the property; may not be null
     * @param systemCache an existing system cache which contains transient nodes towards which the property points.
     * @throws NodeNotFoundException if this node no longer exists
     */
    void setReference( SessionCache cache,
                       Property property,
                       SessionCache systemCache );

    /**
     * Set the given property only if it has not been set previously and therefore appear as changed.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @param property the property; may not be null
     * @throws NodeNotFoundException if this node no longer exists
     */
    void setPropertyIfUnchanged( SessionCache cache,
                                 Property property );

    /**
     * Set the properties on this node.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @param properties the properties to be set; may not be null
     * @throws NodeNotFoundException if this node no longer exists
     */
    void setProperties( SessionCache cache,
                        Iterable<Property> properties );

    /**
     * Set the properties on this node.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @param properties the iterator over the properties to be set; may not be null
     * @throws NodeNotFoundException if this node no longer exists
     */
    void setProperties( SessionCache cache,
                        Iterator<Property> properties );

    /**
     * Remove all of the properties from this node.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @throws NodeNotFoundException if this node no longer exists
     */
    void removeAllProperties( SessionCache cache );

    /**
     * Remove the property with the given name.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @param name the name of the property to be removed; may not be null
     * @throws NodeNotFoundException if this node no longer exists
     */
    void removeProperty( SessionCache cache,
                         Name name );

    /**
     * Add the supplied mixin type if not already an explicitly referenced.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @param mixinName the name of the mixin to be removed; may not be null
     */
    void addMixin( SessionCache cache,
                   Name mixinName );

    /**
     * Remove the supplied mixin type if already an explicitly referenced.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @param mixinName the name of the mixin to be removed; may not be null
     */
    void removeMixin( SessionCache cache,
                      Name mixinName );

    /**
     * Get the set of mixin names that were added to this node but not yet saved.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @return the newly-added mixin type names; never null but possibly empty
     */
    Set<Name> getAddedMixins( SessionCache cache );

    /**
     * Return whether the primary type for the node has changed.
     * 
     * @return true if the primary type for the node has changed, or false otherwise
     */
    boolean hasChangedPrimaryType();

    /**
     * Adds a new federated segment with the given name and key to this node.
     * 
     * @param segmentName the name of the segment (i.e. the name of the alias under which an external child is linked); may not be
     *        null
     * @param externalNodeKey the key of the external node which should be linked under this name; may not be null
     */
    void addFederatedSegment( String externalNodeKey,
                              String segmentName );

    /**
     * Removes the federated segment towards an external node.
     * 
     * @param externalNodeKey the key of the external node which should be linked under this name; may not be null
     */
    void removeFederatedSegment( String externalNodeKey );

    /**
     * Create a new node as a child of this node with the supplied name and properties.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @param key the key for the new node; may be null
     * @param name the name for the new node; may not be null
     * @param firstProperty the first property; may not be null
     * @param additionalProperties the properties that should be set on the node; may be null or empty, and any null property
     *        references will be ignored
     * @return the new child node
     */
    MutableCachedNode createChild( SessionCache cache,
                                   NodeKey key,
                                   Name name,
                                   Property firstProperty,
                                   Property... additionalProperties );

    /**
     * Create a new node as a child of this node with the supplied name and properties.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @param key the key for the new node; may be null
     * @param name the name for the new node; may not be null
     * @param properties the properties that should be set on the node; may be null or empty, and any null property references
     *        will be ignored
     * @return the new child node
     */
    MutableCachedNode createChild( SessionCache cache,
                                   NodeKey key,
                                   Name name,
                                   Iterable<Property> properties );

    /**
     * Remove the node from being a child of this node. <strong>NOTE: THIS METHOD DOES NOT DELETE THE NODE</strong>
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @param key the key for the child that is to be removed; may not be null
     * @throws NodeNotFoundException if the node does not exist as a child of this node
     */
    void removeChild( SessionCache cache,
                      NodeKey key );

    /**
     * Remove the node from being a child of this node and append it as a child of the supplied node.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @param key the key for the child that is to be removed; may not be null
     * @param newParent the new parent for the node; may not be null and may not be this node
     * @param newName the new name for the node, or null if the existing name is to be used
     * @throws NodeNotFoundException if the node does not exist as a child of this node
     */
    void moveChild( SessionCache cache,
                    NodeKey key,
                    MutableCachedNode newParent,
                    Name newName );

    /**
     * Link the existing node with the supplied key to be appended as a child of this node. After this method, the referenced node
     * is considered a child of this node as well as a child of its original parent(s).
     * <p>
     * The link can be removed by simply {@link #removeChild(SessionCache, NodeKey) removing} the linked child from the parent,
     * and this works whether or not the parent is the original parent or an additional parent.
     * </p>
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @param childKey the key for the child that is to be removed; may not be null
     * @param name the name for the (linked) node, or null if the existing name is to be used
     * @return true if the link was created, or false if the link already existed as a child of this node
     * @throws NodeNotFoundException if the node does not exist
     */
    boolean linkChild( SessionCache cache,
                       NodeKey childKey,
                       Name name );

    /**
     * Remove the node from being a child of this node and append it as a child before the supplied node.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @param key the key for the child that is to be removed; may not be null
     * @param nextNode the key for the node before which the node should be moved; may be null if the node should be moved to the
     *        end of the parents
     * @throws NodeNotFoundException if the node does not exist as a child of this node
     */
    void reorderChild( SessionCache cache,
                       NodeKey key,
                       NodeKey nextNode );

    /**
     * Renames the child node.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @param key the key for the child that is to be removed; may not be null
     * @param newName the new name for the node; may not be null
     * @throws NodeNotFoundException if the node does not exist as a child of this node
     */
    void renameChild( SessionCache cache,
                      NodeKey key,
                      Name newName );

    /**
     * Adds to this node a reference with the given type from the node with the supplied key to this node.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @param property the {@link org.modeshape.jcr.value.Property} of the referrer node; may not be null
     * @param referrerKey the key for the node that has a new reference to this node; may not be null
     * @param type the reference type; may not be null
     */
    void addReferrer( SessionCache cache,
                      Property property,
                      NodeKey referrerKey,
                      ReferenceType type );

    /**
     * Remove from this node a reference with the given type from the node with the supplied key to this node.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @param property the {@link org.modeshape.jcr.value.Property} of the referrer node; may not be null
     * @param referrerKey the key for the node that no longer has a reference to this node; may not be null
     * @param type the reference type; may not be null
     */
    void removeReferrer( SessionCache cache,
                         Property property,
                         NodeKey referrerKey,
                         ReferenceType type );

    /**
     * Compute an ETag value for this node.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @return an ETag value; never null but possibly empty
     */
    String getEtag( SessionCache cache );

    /**
     * Copies into this node all the properties and children (deep copy) from the given source node.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @param sourceNode the node from which to copy the properties and children; may not be null
     * @param sourceCache the cache in which the source node belongs; may not be null
     * @param systemWorkspaceKey the key of the system workspace; may not be null
     * @param connectors a {@link Connectors} instance which used for processing external nodes.
     * @return a [source key -> target key] which represents the node correspondence after the copy operation.
     */
    public Map<NodeKey, NodeKey> deepCopy( SessionCache cache,
                                           CachedNode sourceNode,
                                           SessionCache sourceCache,
                                           String systemWorkspaceKey,
                                           Connectors connectors );

    /**
     * Clones into this node all the properties and children (deep clone) from the given source node. Each cloned node will have
     * the same identifier as the source node.
     * 
     * @param cache the cache to which this node belongs; may not be null
     * @param sourceNode the node from which to copy the properties and children; may not be null
     * @param sourceCache the cache in which the source node belongs; may not be null
     * @param systemWorkspaceKey the key of the system workspace; may not be null
     * @param connectors a {@link Connectors} instance which used for processing external nodes.
     */
    public void deepClone( SessionCache cache,
                           CachedNode sourceNode,
                           SessionCache sourceCache,
                           String systemWorkspaceKey,
                           Connectors connectors );

    /**
     * Returns a set with all the referencing nodes (nodes which are referring this node) which have changed.
     * 
     * @return the set of {@link NodeKey} instances, never null.
     */
    public Set<NodeKey> getChangedReferrerNodes();

    /**
     * Return whether this node contains only changes to the additional parents.
     * 
     * @return true if this node contains only added or removed additional parents.
     */
    public boolean hasOnlyChangesToAdditionalParents();

    /**
     * Sets a flag indicating if this node and anything below it should be excluded from all searches and indexing operations.
     */
    public void excludeFromSearch();

    /**
     * Returns an object encapsulating all the different changes that this session node contains.
     * 
     * @return a {@code non-null} {@link NodeChanges} object.
     */
    public NodeChanges getNodeChanges();

    /**
     * Sets permissions for this node, creating in effect an ACL.
     *
     * @param cache the cache to which this node belongs; may not be null
     * @param permissions a set of privileges enqueued by principal name; may not be null
     * @return a {@link org.modeshape.jcr.cache.MutableCachedNode.PermissionChanges} instance which reflects how the persistent
     * state has been affected by the change; never null
     */
    public PermissionChanges setPermissions(SessionCache cache, Map<String, Set<String>> permissions);

    /**
     * Removes any potential existing ACL for this node.
     *
     * @param cache the cache to which this node belongs; may not be null
     * @return a {@link org.modeshape.jcr.cache.MutableCachedNode.PermissionChanges} instance which reflects which principal
     * have been removed; never null
     */
    public PermissionChanges removeACL(SessionCache cache);

    /**
     * Adds an internal property to this cached node. Internal properties are stored only in the internal document and have no
     * JCR relevance.
     *
     * @param name a {@code String} the name of the property; may not be null.
     * @param value a {@code String} the value of the property; may not be null.
     */
    public void addInternalProperty(String name, Object value);

    /**
     * Returns a map of all the internal properties added for this node. Internal properties are stored as-is in the document and have no
     * JCR relevance.
     * 
     * @return a {@link Map} of (propertyName, propertyValue) pairs. never {@code null} but possibly empty.
     */
    public Map<String, Object> getAddedInternalProperties();

    /**
     * Removes an internal property from this cached node. Internal properties are stored as-is in the document and have no
     * JCR relevance.
     *
     * @param name a {@code String} the name of the property; may not be null.
     * @return {@code true} if the property was removed, {@code false} otherwise.
     */
    public boolean removeInternalProperty(String name);

    /**
     * Returns a set of all the internal properties which have been removed from this node. Internal properties are stored 
     * as-is the document and have no JCR relevance.
     * 
     * @return a {@link Set} of property names; never {@code null} but possibly empty.
     */
    public Set<String> getRemovedInternalProperties();

    /**
     * Interface which exposes all the changes that have occurred on a {@link MutableCachedNode} instance
     */
    public interface NodeChanges {
        /**
         * Returns a set with the names of the properties that have changed. This includes new/modified properties.
         * 
         * @return a {@code non-null} Set
         */
        Set<Name> changedPropertyNames();

        /**
         * Returns a set with the names of the properties that have been removed.
         * 
         * @return a {@code non-null} Set
         */
        public Set<Name> removedPropertyNames();

        /**
         * Returns a set with the names of the mixins that have been added.
         * 
         * @return a {@code non-null} Set
         */
        public Set<Name> addedMixins();

        /**
         * Returns a set with the names of the mixins that have been removed.
         * 
         * @return a {@code non-null} Set
         */
        public Set<Name> removedMixins();

        /**
         * Returns the [childKey, childName] pairs of the children that have been appended (at the end).
         * 
         * @return a {@code non-null} Map
         */
        public LinkedHashMap<NodeKey, Name> appendedChildren();

        /**
         * Returns the set of children that have been removed
         * 
         * @return a {@code non-null} Set
         */
        public Set<NodeKey> removedChildren();

        /**
         * Returns the [childKey, childName] pairs of the children that have been renamed, where "childName" represents the new
         * name after the rename.
         * 
         * @return a {@code non-null} Map
         */
        public Map<NodeKey, Name> renamedChildren();

        /**
         * Returns the [insertBeforeChildKey, [childKey, childName]] structure of the children that been inserted before another
         * existing child. This is normally caused due to reorderings
         * 
         * @return a {@code non-null} Map
         */
        public Map<NodeKey, LinkedHashMap<NodeKey, Name>> childrenInsertedBefore();

        /**
         * Returns the set of parents that have been added
         * 
         * @return a {@code non-null} Set
         */
        public Set<NodeKey> addedParents();

        /**
         * Returns the set of parents that have been removed
         * 
         * @return a {@code non-null} Set
         */
        public Set<NodeKey> removedParents();

        /**
         * Returns the node key of the new primary parent, in case it has changed.
         * 
         * @return either the {@link NodeKey} of the new primary parent or {@code null}
         */
        public NodeKey newPrimaryParent();

        /**
         * Returns a set of node keys with the weak referrers that have been added.
         * 
         * @return a {@code non-null} Set
         */
        public Set<NodeKey> addedWeakReferrers();

        /**
         * Returns a set of node keys with the weak referrers that have been removed.
         * 
         * @return a {@code non-null} Set
         */
        public Set<NodeKey> removedWeakReferrers();

        /**
         * Returns a set of node keys with the strong referrers that have been added.
         * 
         * @return a {@code non-null} Set
         */
        public Set<NodeKey> addedStrongReferrers();

        /**
         * Returns a set of node keys with the strong referrers that have been removed.
         * 
         * @return a {@code non-null} Set
         */
        public Set<NodeKey> removedStrongReferrers();
    }

    /**
     * Interface which exposes the ACL-related changes for mutable cached nodes.
     */
    public interface PermissionChanges {
        /**
         * Returns the number of new principals for which privileges have been added in the node's ACL.
         *
         * @return the number of principals
         */
        public long addedPrincipalsCount();

        /**
         * Returns the number of principals which have been removed from this node's ACL.
         *
         * @return the number of principals
         */
        public long removedPrincipalsCount();
    }
}
