/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2008 jOpenDocument, by ILM Informatique. All rights reserved.
 * 
 * The contents of this file are subject to the terms of the GNU
 * General Public License Version 3 only ("GPL").  
 * You may not use this file except in compliance with the License. 
 * You can obtain a copy of the License at http://www.gnu.org/licenses/gpl-3.0.html
 * See the License for the specific language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each file.
 * 
 */

package org.jopendocument.dom;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;

import org.jdom.Element;
import org.jdom.Namespace;

/**
 * A helper to create children in the schema order.
 * 
 * @author Sylvain CUAZ
 */
public class ChildCreator {

    private final Element content;
    private final List<Element> elemsOrder;

    protected ChildCreator(final Element content, final List<Element> children) {
        if (content == null)
            throw new NullPointerException("null content");
        this.content = content;
        this.elemsOrder = new ArrayList<Element>(children);
    }

    public ChildCreator(Element content, final Element... children) {
        this(content, Arrays.asList(children));
    }

    public final Element getElement() {
        return this.content;
    }

    public final List<Element> getChildren() {
        return this.elemsOrder;
    }

    // *** children

    public final Element getChild(Namespace childNS, String childName) {
        return this.getChild(childNS, childName, false);
    }

    private final int indexOf(Namespace childNS, String childName) {
        for (int i = 0; i < this.elemsOrder.size(); i++) {
            final Element elem = this.elemsOrder.get(i);
            if (elem.getNamespace().equals(childNS) && elem.getName().equals(childName))
                return i;
        }
        return -1;
    }

    private final int indexOf(final Element elem) {
        return this.indexOf(elem.getNamespace(), elem.getName());
    }

    /**
     * Trouve l'index ou il faut insérer le fils dans ce document.
     * 
     * @param childName le nom du fils que l'on veut insérer.
     * @return l'index ou il faut l'insérer (s'il est déjà présent son index actuel +1).
     * @throws IllegalArgumentException if childName is not in {@link #getChildren()}.
     */
    @SuppressWarnings("unchecked")
    private final int findInsertIndex(Namespace childNS, String childName) {
        // eg 6, for "master-styles"
        final int idealIndex = indexOf(childNS, childName);
        if (idealIndex == -1)
            throw new IllegalArgumentException(childName + " is unknown.");
        final Element existingChild = this.getChild(childNS, childName);
        if (existingChild != null)
            return this.getElement().getChildren().indexOf(existingChild) + 1;
        // eg [scripts, font-decls, styles, font-face-decls, automatic-styles, body]
        final List<Element> children = this.getElement().getChildren();
        final ListIterator<Element> iter = children.listIterator();
        while (iter.hasNext()) {
            final Element elem = iter.next();
            if (indexOf(elem) > idealIndex)
                // eg indexOf("body") == 7 > 6
                // eg return 5
                return iter.previousIndex();
        }
        return children.size();
    }

    /**
     * Insère cet élément à la bonne place. The child should not be already present.
     * 
     * @param child l'élément à insérer, doit être dans TOP_ELEMENTS.
     */
    @SuppressWarnings("unchecked")
    private final void insertChild(Element child) {
        // on ajoute au bon endroit
        this.getElement().getChildren().add(this.findInsertIndex(child.getNamespace(), child.getName()), child);
    }

    /**
     * Return the asked child, optionally creating it.
     * 
     * @param childNS the namespace of the child.
     * @param childName the name of the child.
     * @param create whether it should be created in case it doesn't exist.
     * @return the asked child or <code>null</code> if it doesn't exist and create is
     *         <code>false</code>
     */
    public final Element getChild(Namespace childNS, String childName, boolean create) {
        Element child = this.getElement().getChild(childName, childNS);
        if (create && child == null) {
            child = new Element(childName, childNS);
            this.insertChild(child);
        }
        return child;
    }

    public final Element addChild(Namespace childNS, String childName) {
        final Element child = new Element(childName, childNS);
        this.insertChild(child);
        return child;
    }

    public final void setChild(Element elem) {
        this.getElement().removeChildren(elem.getName(), elem.getNamespace());
        this.insertChild(elem);
    }

}