/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.jackrabbit.test.api.version;

import org.apache.jackrabbit.test.NotExecutableException;

import javax.jcr.RepositoryException;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.Node;
import javax.jcr.ReferentialIntegrityException;
import javax.jcr.Value;
import javax.jcr.version.Version;
import javax.jcr.version.VersionException;
import javax.jcr.version.VersionHistory;
import javax.jcr.version.VersionIterator;
import java.util.ArrayList;
import java.util.List;

/**
 * <code>RemoveVersionTest</code> provides test methods covering {@link VersionHistory#removeVersion(String)}.
 * Please note, that removing versions is defined to be an optional feature in
 * the JSR 170 specification. The setup therefore includes a initial removal,
 * in order to test, whether removing versions is supported.
 *
 */
public class RemoveVersionTest extends AbstractVersionTest {

    protected Node versionableNode2;
    protected Version version;
    protected Version version2;

    protected VersionHistory vHistory;

    protected void setUp() throws Exception {
        super.setUp();

        Version testV = versionableNode.checkin(); // create 1.0
        versionableNode.checkout();
        versionableNode.checkin(); // create 1.1
        versionableNode.checkout();
        versionableNode.checkin(); // create 1.2
        try {
            versionableNode.getVersionHistory().removeVersion(testV.getName());
        } catch (UnsupportedRepositoryOperationException e) {
            throw new NotExecutableException("Removing version is not supported: " + e.getMessage());
        }

        versionableNode.checkout();
        version = versionableNode.checkin();
        // create a second version
        versionableNode.checkout();
        version2 = versionableNode.checkin();

        vHistory = versionableNode.getVersionHistory();

        // build a second versionable node below the testroot
        try {
            versionableNode2 = createVersionableNode(testRootNode, nodeName2, versionableNodeType);
        } catch (RepositoryException e) {
            fail("Failed to create a second versionable node: " + e.getMessage());
        }
    }

    protected void tearDown() throws Exception {
        try {
            versionableNode2.remove();
        } finally {
            versionableNode2 = null;
            version = null;
            version2 = null;
            vHistory = null;
            super.tearDown();
        }
    }

    /**
     * Test removed version gets invalid
     */
    public void testRemovedInvalid() throws Exception {
        versionableNode.getVersionHistory().removeVersion(version.getName());
        // assert: version has become invalid
        try {
            version.getPredecessors();
            fail("Removed version still operational.");
        } catch (RepositoryException e) {
            // expected
        }
    }

    /**
     * Test if the predecessors of the removed version are made predecessor of
     * its original successor version.
     *
     * @throws RepositoryException
     */
    public void testRemoveVersionAdjustPredecessorSet() throws RepositoryException {

        // retrieve predecessors to test and remove the version
        List<String> predecPaths = new ArrayList<String>();
        Version[] predec = version.getPredecessors();
        for (int i = 0; i < predec.length; i++) {
            predecPaths.add(predec[i].getPath());
        }
        vHistory.removeVersion(version.getName());

        // new predecessors of the additional version
        Version[] predec2 = version2.getPredecessors();
        for (int i = 0; i < predec2.length; i++) {
            if (!predecPaths.remove(predec2[i].getPath())) {
                fail("All predecessors of the removed version must be made predecessors of it's original successor version.");
            }
        }

        if (!predecPaths.isEmpty()) {
            fail("All predecessors of the removed version must be made predecessors of it's original successor version.");
        }
    }

    /**
     * Test if the successors of the removed version are made successors of
     * all predecessors of the the removed version.
     *
     * @throws RepositoryException
     */
    public void testRemoveVersionAdjustSucessorSet() throws RepositoryException {

        // retrieve predecessors to test and remove the version
        Version[] predec = version.getPredecessors();
        vHistory.removeVersion(version.getName());

        for (int i = 0; i < predec.length; i++) {
            boolean isContained = false;
            Version[] succ = predec[i].getSuccessors();
            for (int j = 0; j < succ.length; j++) {
                isContained |= succ[j].isSame(version2);
            }
            if (!isContained) {
                fail("Removing a version must make all it's successor version to successors of the removed version's predecessors.");
            }
        }
    }

    /**
     * Test if removing a version from the version history throws a VersionException
     * if the specified version does not exist.
     *
     * @throws RepositoryException
     * @throws NotExecutableException
     */
    public void testRemoveInvalidVersion() throws RepositoryException, NotExecutableException {
        Version invalidV = versionableNode2.checkin();
        String invalidName = invalidV.getName();

        // build a version name that is not present in the current history
        boolean found = false;
        for (int i = 0; i < 10 && !found; i++) {
            try {
                vHistory.getVersion(invalidName);
                invalidName += i;
            } catch (VersionException e) {
                // ok > found a name that is invalid.
                found = true;
            }
        }

        if (!found) {
            throw new NotExecutableException("Failed to create an invalid name in order to test the removal of versions.");
        }

        try {
            vHistory.removeVersion(invalidName);
            fail("Removing a version that does not exist must fail with a VersionException.");
        } catch (VersionException e) {
            // success
        }
    }

    /**
     * Checks if {@link javax.jcr.version.VersionHistory#removeVersion(String)}
     * throws a {@link javax.jcr.ReferentialIntegrityException} if the named
     * version is still referenced by another node.
     * <ul>
     * <li>{@code nodetype} name of a node type that supports a reference
     *  property.
     * <li>{@code nodename4} name of the node created with <code>nodetype</code>.
     * <li>{@code propertyname1} a single value reference property available
     *  in <code>nodetype</code>.
     *  </ul>
     */
    public void testReferentialIntegrityException() throws RepositoryException, NotExecutableException {
        // create reference: n1.p1 -> version
        Node n1 = testRootNode.addNode(nodeName4, testNodeType);
        Value refValue = superuser.getValueFactory().createValue(version);
        ensureCanSetProperty(n1, propertyName1, refValue);
        n1.setProperty(propertyName1, refValue);
        testRootNode.getSession().save();

        try {
            vHistory.removeVersion(version.getName());
            fail("Method removeVersion() must throw a ReferentialIntegrityException " +
                 "if the version is the target of a REFERENCE property and the current " +
                 "Session has read access to that REFERENCE property");
        }
        catch (ReferentialIntegrityException e) {
            // success
        }
    }

    /**
     * Checks if all versions but the base and root one can be removed.
     */
    public void testRemoveAllBut2() throws RepositoryException {
        String baseVersion = versionableNode.getBaseVersion().getName();
        VersionHistory vh = versionableNode.getVersionHistory();
        VersionIterator vi = vh.getAllVersions();
        while (vi.hasNext()) {
            Version currenVersion = vi.nextVersion();
            String versionName = currenVersion.getName();
            if (!versionName.equals("jcr:rootVersion") && !versionName.equals(baseVersion)) {
                vh.removeVersion(versionName);
            }
        }
    }

    /**
     * Checks if all versions by the base and root one can be removed.
     */
    public void testRemoveRootVersion() throws RepositoryException {
        try {
            versionableNode.getVersionHistory().getRootVersion().remove();
            fail("Removal of root version should throw an exception.");
        } catch (RepositoryException e) {
            // ignore
        }
    }

    /**
     * Checks if all versions by the base and root one can be removed.
     */
    public void testRemoveBaseVersion() throws RepositoryException {
        try {
            versionableNode.getBaseVersion().remove();
            fail("Removal of base version should throw an exception.");
        } catch (RepositoryException e) {
            // ignore
        }
    }
}
