/*
 * Copyright 2015 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * 
 *      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.eclipse.webdav.dom;

import java.util.Enumeration;
import java.util.NoSuchElementException;
import org.eclipse.webdav.Policy;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * An element editor for sets of hrefs, for example, the WebDAV
 * predecessor-set element.
 * <p>
 * <b>Note:</b> This class/interface is part of an interim API that is still under 
 * development and expected to change significantly before reaching stability. 
 * It is being made available at this early stage to solicit feedback from pioneering 
 * adopters on the understanding that any code that uses this API will almost 
 * certainly be broken (repeatedly) as the API evolves.
 * </p>
 */
public class HrefSet extends ElementEditor {
    /**
     * Creates a new editor on the given href set element with the given
     * name. The element is assumed to be well formed.
     *
     * @param root    an href element
     * @param name    the name of the element that this editor is expected
     *                to manipulate.
     * @throws        MalformedElementException if there is reason to
     *                believe that the element is not well formed
     */
    public HrefSet(Element root, QualifiedName name) throws MalformedElementException {
        super(root, name.getLocalName());
        ensure(DAV_NS.equals(name.getQualifier()), Policy.bind("ensure.mustHaveDAVQualifier")); //$NON-NLS-1$
    }

    /**
     * Adds the given href to the end of the set of hrefs. If the href
     * already exists it is not added.
     *
     * @param href    the href to add to the end of the set of hrefs
     */
    public void addHref(String href) {
        String encodedHref = encodeHref(href);
        if (isDuplicate(encodedHref))
            return;
        appendChild(root, "href", encodedHref); //$NON-NLS-1$
    }

    /**
     * Creates a new href set element with the given name and sets it as
     * the root of the given document. Returns an editor on the new href set
     * element. The document must not be <code>null</code>, and must not
     * already have a root element.
     *
     * @param document the document that will become the root of a new
     *                 href set element
     * @return         an element editor on a href set element
     */
    public static HrefSet create(Document document, QualifiedName name) {
        Assert.isNotNull(document);
        Assert.isTrue(document.getOwnerDocument() == null);
        Assert.isNotNull(name);
        Assert.isTrue(DAV_NS.equals(name.getQualifier()));
        Element element = create(document, name.getLocalName());
        try {
            return new HrefSet(element, name);
        } catch (MalformedElementException e) {
            Assert.isTrue(false, Policy.bind("assert.internalError")); //$NON-NLS-1$
            return null; // Never reached.
        }
    }

    /**
     * Returns an <code>Enumeration</code> over the set of hrefs.
     *
     * @return an <code>Enumeration</code> of href <code>String</code>s
     */
    public Enumeration getHrefs() {
        final Element firstHref = getFirstChild(root, "href"); //$NON-NLS-1$
        Enumeration e = new Enumeration() {
            Element currentHref = firstHref;

            public boolean hasMoreElements() {
                return currentHref != null;
            }

            public Object nextElement() {
                if (!hasMoreElements())
                    throw new NoSuchElementException();
                String href = getFirstText(currentHref);
                currentHref = getNextSibling(currentHref, "href"); //$NON-NLS-1$
                return decodeHref(href);
            }
        };
        return e;
    }

    /**
     * Inserts the given newHref after the given refHref in the set of
     * hrefs. If newHref already exists it is not inserted.
     *
     * @param refHref the existing href
     * @param newHref the new href to be inserted after the existing href
     */
    public void insertHrefAfter(String refHref, String newHref) {
        String refHrefEncoded = encodeHref(refHref);
        String newHrefEncoded = encodeHref(newHref);
        if (isDuplicate(newHrefEncoded))
            return;
        Element child = getFirstChild(root, "href"); //$NON-NLS-1$
        while (child != null) {
            if (refHrefEncoded.equals(getFirstText(child))) {
                Element nextSibling = getNextSibling(child, "href"); //$NON-NLS-1$
                if (nextSibling == null)
                    appendChild(root, "href", newHrefEncoded); //$NON-NLS-1$
                else
                    insertBefore(nextSibling, "href", newHrefEncoded); //$NON-NLS-1$
                return;
            }
            child = getNextSibling(child, "href"); //$NON-NLS-1$
        }
        Assert.isTrue(false, Policy.bind("assert.noHrefRef")); //$NON-NLS-1$
    }

    /**
     * Inserts the given newHref before the given refHref in the set of
     * hrefs. If newHref already exists it is not inserted.
     *
     * @param refHref the existing href
     * @param newHref the new href to be inserted before the existing href
     */
    public void insertHrefBefore(String newHref, String refHref) {
        String refHrefEncoded = encodeHref(refHref);
        String newHrefEncoded = encodeHref(newHref);
        if (isDuplicate(newHrefEncoded))
            return;
        Element child = getFirstChild(root, "href"); //$NON-NLS-1$
        while (child != null) {
            if (refHrefEncoded.equals(getFirstText(child))) {
                insertBefore(child, "href", newHrefEncoded); //$NON-NLS-1$
                return;
            }
            child = getNextSibling(child, "href"); //$NON-NLS-1$
        }
        Assert.isTrue(false, Policy.bind("assert.noHrefRef")); //$NON-NLS-1$
    }

    protected boolean isDuplicate(String encodedHref) {
        Element child = getFirstChild(root, "href"); //$NON-NLS-1$
        while (child != null) {
            if (encodedHref.equals(getFirstText(child)))
                return true;
            child = getNextSibling(child, "href"); //$NON-NLS-1$
        }
        return false;
    }

    /**
     * Remove the given href from the set of hrefs.
     *
     * @param href    the href to remove from the set of hrefs
     */
    public void removeHref(String href) {
        String encodedHref = encodeHref(href);
        Element child = getFirstChild(root, "href"); //$NON-NLS-1$
        while (child != null) {
            if (encodedHref.equals(getFirstText(child))) {
                root.removeChild(child);
                return;
            }
            child = getNextSibling(child, "href"); //$NON-NLS-1$
        }
    }
}
