package org.xbib.io.webdav.common;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xbib.io.webdav.api.DavConstants;
import org.xbib.io.webdav.api.XMLizable;
import java.util.HashSet;
import java.util.Set;

/**
 * <code>Position</code> encapsulates the position in ordering information
 * contained in a Webdav request. This includes both the
 * header and the position Xml element present in the request body of an ORDERPATCH request.
 */
public class Position implements XMLizable {

    private static final Set<String> VALID_TYPES = new HashSet<String>();

    static {
        VALID_TYPES.add("first");
        VALID_TYPES.add("last");
        VALID_TYPES.add("after");
        VALID_TYPES.add("before");
    }

    private final String type;
    private final String segment;

    /**
     * Create a new <code>Position</code> object with the specified type.
     * Since any type except for first and last
     * must be combined with a segment, only the mentioned types are valid
     * arguments.
     *
     * @param type first or last
     * @throws IllegalArgumentException if the given type is other than first
     *                                  or last
     */
    public Position(String type) {
        if (!VALID_TYPES.contains(type)) {
            throw new IllegalArgumentException("Invalid type: " + type);
        }
        if (!("first".equals(type) || "last".equals(type))) {
            throw new IllegalArgumentException("If type is other than 'first' or 'last' a segment must be specified");
        }
        this.type = type;
        this.segment = null;
    }

    /**
     * Create a new <code>Position</code> object with the specified type and
     * segment.
     *
     * @param type
     * @param segment
     * @throws IllegalArgumentException if the specified type and segment do not
     *                                  form a valid pair.
     */
    public Position(String type, String segment) {
        if (!VALID_TYPES.contains(type)) {
            throw new IllegalArgumentException("Invalid type: " + type);
        }
        if (("after".equals(type) || "before".equals(type)) && (segment == null || "".equals(segment))) {
            throw new IllegalArgumentException("If type is other than 'first' or 'last' a segment must be specified");
        }
        this.type = type;
        this.segment = segment;
    }

    /**
     * Return the type of this <code>Position</code> object
     *
     * @return type
     */
    public String getType() {
        return type;
    }

    /**
     * Returns the segment used to create this <code>Position</code> object or
     * <code>null</code> if no segment is present with the type.
     *
     * @return segment or <code>null</code>
     * @see #getType()
     */
    public String getSegment() {
        return segment;
    }

    @Override
    public Element toXml(Document document) {
        Element positionElement = DomUtil.createElement(document, "position", DavConstants.NAMESPACE);
        Element typeElement = DomUtil.addChildElement(positionElement, type, DavConstants.NAMESPACE);
        if (segment != null) {
            DomUtil.addChildElement(typeElement, "segment", DavConstants.NAMESPACE, segment);
        }
        return positionElement;
    }

    /**
     * Create a new <code>Position</code> object from the specified position
     * element. The element must fulfill the following structure:<br>
     * <pre>
     * &lt;!ELEMENT position (first | last | before | after) &gt;
     * &lt;!ELEMENT segment (#PCDATA) &gt;
     * &lt;!ELEMENT first EMPTY &gt;
     * &lt;!ELEMENT last EMPTY &gt;
     * &lt;!ELEMENT before segment &gt;
     * &lt;!ELEMENT after segment &gt;
     * </pre>
     *
     * @param positionElement Xml element defining the position.
     * @throws IllegalArgumentException if the given Xml element is not valid.
     */
    public static Position createFromXml(Element positionElement) {
        if (!DomUtil.matches(positionElement, "position", DavConstants.NAMESPACE)) {
            throw new IllegalArgumentException("The 'DAV:position' element required.");
        }
        ElementIterator it = DomUtil.getChildren(positionElement);
        if (it.hasNext()) {
            Element el = it.nextElement();
            String type = el.getLocalName();
            // read the text of DAV:segment child element inside the type
            String segmentText = DomUtil.getChildText(el, "segment",DavConstants.NAMESPACE);
            // stop after the first iteration
            return new Position(type, segmentText);
        } else {
            throw new IllegalArgumentException("The 'DAV:position' element required with exact one child indicating the type.");
        }
    }
}
