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.EventType;
import org.xbib.io.webdav.api.Namespace;
import org.xbib.io.webdav.api.XMLizable;
import java.util.ArrayList;
import java.util.List;

/**
 * <code>SubscriptionInfo</code> class encapsulates the subscription info
 * that forms the request body of a SUBSCRIBE request.<br>
 * The following xml layout is defined for the subscription info:
 * <pre>
 * &lt;!ELEMENT subscriptioninfo ( eventtype, nolocal?, filter? ) &gt;
 * &lt;!ELEMENT eventtype ANY &gt;
 *
 * ANY defines any sequence of elements where at least one defines a valid
 * eventtype. Note that a single eventtype must not occur multiple times.
 *
 * &lt;!ELEMENT nolocal EMPTY &gt;
 * &lt;!ELEMENT filter ANY &gt;
 *
 * ANY: any sequence of elements identifying a filter for event listening but
 * at least a single element.
 * </pre>
 */
public class SubscriptionInfo implements XMLizable {

    private static final Namespace DCR_NAMESPACE = Namespace.getNamespace("dcr", "http://www.day.com/jcr/webdav/1.0");

    private final EventType[] eventTypes;
    private final Filter[] filters;
    private final boolean noLocal;
    private final boolean isDeep;
    private final long timeout;

    /**
     * Create a new <code>SubscriptionInfo</code>
     *
     * @param eventTypes
     * @param isDeep
     * @param timeout
     */
    public SubscriptionInfo(EventType[] eventTypes, boolean isDeep, long timeout) {
        this(eventTypes, null, false, isDeep, timeout);
    }

    /**
     * Create a new <code>SubscriptionInfo</code>
     *
     * @param eventTypes
     * @param filters
     * @param noLocal
     * @param isDeep
     * @param timeout
     */
    public SubscriptionInfo(EventType[] eventTypes, Filter[] filters, boolean noLocal, boolean isDeep, long timeout) {
        if (eventTypes == null || eventTypes.length == 0) {
            throw new IllegalArgumentException("'subscriptioninfo' must at least indicate a single event type.");
        }

        this.eventTypes = eventTypes;
        this.noLocal = noLocal;

        if (filters != null) {
            this.filters = filters;
        } else {
            this.filters = new Filter[0];
        }

        this.isDeep = isDeep;
        this.timeout = timeout;
    }

    /**
     * Create a new <code>SubscriptionInfo</code> from the given Xml element
     * and from additional information that is transported within the request
     * header:
     * <ul>
     * <li>{@link TimeoutHeader timeout},</li>
     * <li>{@link DepthHeader isDeep}</li>
     * </ul>
     *
     * @param reqInfo Xml element present in the request body.
     * @param timeout as defined in the {@link DavConstants#HEADER_TIMEOUT timeout header}.
     * @param isDeep  as defined in the {@link DavConstants#HEADER_DEPTH depth header}.
     * @throws IllegalArgumentException if the reqInfo element does not contain the mandatory elements.
     */
    public SubscriptionInfo(Element reqInfo, long timeout, boolean isDeep) throws DavException {
        if (!DomUtil.matches(reqInfo, "subscriptioninfo", DCR_NAMESPACE)) {
            //LOG.log(Level.WARNING, "Element with name 'subscriptioninfo' expected");
            throw new DavException(400);
        }
        Element el = DomUtil.getChildElement(reqInfo, "eventtype", DCR_NAMESPACE);
        if (el != null) {
            eventTypes = DefaultEventType.createFromXml(el);
            if (eventTypes.length == 0) {
                //LOG.log(Level.WARNING, "'subscriptioninfo' must at least indicate a single, valid event type.");
                throw new DavException(400);
            }
        } else {
            //LOG.log(Level.WARNING, "'subscriptioninfo' must contain an 'eventtype' child element.");
            throw new DavException(400);
        }

        List<Filter> filters = new ArrayList<Filter>();
        el = DomUtil.getChildElement(reqInfo, "filter", DCR_NAMESPACE);
        if (el != null) {
            ElementIterator it = DomUtil.getChildren(el);
            while (it.hasNext()) {
                Filter f = new Filter(it.nextElement());
                filters.add(f);
            }
        }
        this.filters = filters.toArray(new Filter[filters.size()]);

        this.noLocal = DomUtil.hasChildElement(reqInfo, "nolocal", DCR_NAMESPACE);
        this.isDeep = isDeep;
        this.timeout = timeout;
    }

    /**
     * Return array of event type names present in the subscription info.
     *
     * @return array of String defining the names of the events this subscription
     * should listen to.
     */
    public EventType[] getEventTypes() {
        return eventTypes;
    }

    /**
     * Return all filters defined for this <code>SubscriptionInfo</code>
     *
     * @return all filters or an empty Filter array.
     */
    public Filter[] getFilters() {
        return filters;
    }

    /**
     * Return array of filters with the specified name.
     *
     * @param localName the filter elements must provide.
     * @param namespace
     * @return array containing the text of the filter elements with the given
     * name.
     */
    public Filter[] getFilters(String localName, Namespace namespace) {
        List<Filter> l = new ArrayList<Filter>();
        for (Filter filter : filters) {
            if (filter.isMatchingFilter(localName, namespace)) {
                l.add(filter);
            }
        }
        return l.toArray(new Filter[l.size()]);
    }

    /**
     * Returns true if the nolocal element is present in this
     * subscription info.
     *
     * @return nolocal element is present.
     */
    public boolean isNoLocal() {
        return noLocal;
    }

    /**
     * Returns true if the {@link DavConstants#HEADER_DEPTH
     * depths header} defined a depth other than '0'.
     *
     * @return true if this subscription info was created with <code>isDeep</code>
     * true.
     */
    public boolean isDeep() {
        return isDeep;
    }

    /**
     * Return the timeout as retrieved from the request.
     *
     * @return timeout.
     */
    public long getTimeOut() {
        return timeout;
    }

    /**
     * Xml representation of this <code>SubscriptionInfo</code>.
     *
     * @param document
     * @return Xml representation
     * @see XMLizable#toXml(Document)
     */
    public Element toXml(Document document) {
        Element subscrInfo = DomUtil.createElement(document, "subscriptioninfo", DCR_NAMESPACE);
        Element eventType = DomUtil.addChildElement(subscrInfo, "eventtype", DCR_NAMESPACE);
        for (EventType et : eventTypes) {
            eventType.appendChild(et.toXml(document));
        }

        if (filters.length > 0) {
            Element filter = DomUtil.addChildElement(subscrInfo, "filter", DCR_NAMESPACE);
            for (Filter f : filters) {
                filter.appendChild(f.toXml(document));
            }
        }

        if (noLocal) {
            DomUtil.addChildElement(subscrInfo, "nolocal", DCR_NAMESPACE);
        }
        return subscrInfo;
    }
}
