/*
 * 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.webdav.version.report;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.jackrabbit.webdav.DavResource;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavConstants;
import org.apache.jackrabbit.webdav.DavServletResponse;
import org.apache.jackrabbit.webdav.DavResourceLocator;
import org.apache.jackrabbit.webdav.DavResourceIterator;
import org.apache.jackrabbit.webdav.property.HrefProperty;
import org.apache.jackrabbit.webdav.xml.DomUtil;
import org.apache.jackrabbit.webdav.version.DeltaVConstants;
import org.apache.jackrabbit.webdav.version.BaselineResource;
import org.apache.jackrabbit.webdav.version.VersionResource;
import org.apache.jackrabbit.webdav.version.VersionControlledResource;
import org.w3c.dom.Element;
import org.w3c.dom.Document;

import java.util.List;
import java.util.ArrayList;

/**
 * <code>CompareBaselineReport</code>...
 */
public class CompareBaselineReport implements Report {

    private static Logger log = LoggerFactory.getLogger(CompareBaselineReport.class);

    private static final String XML_COMPARE_BASELINE = "compare-baseline";
    private static final String XML_COMPARE_BASELINE_REPORT = "compare-baseline-report";
    private static final String XML_ADDED_VERSION = "added-version";
    private static final String XML_DELETED_VERSION = "deleted-version";
    private static final String XML_CHANGED_VERSION = "changed-version";

    public static final ReportType COMPARE_BASELINE = ReportType.register(XML_COMPARE_BASELINE, DeltaVConstants.NAMESPACE, CompareBaselineReport.class);

    private BaselineResource requestBaseline;
    private BaselineResource compareBaseline;

    /**
     * Returns {@link #COMPARE_BASELINE}.
     *
     * @see Report#getType()
     */
    public ReportType getType() {
        return COMPARE_BASELINE;
    }

    /**
     * Always returns <code>false</code>.
     *
     * @return false
     * @see Report#isMultiStatusReport()
     */
    public boolean isMultiStatusReport() {
        return false;
    }

    /**
     *
     * @param resource
     * @param info
     * @throws DavException
     * @see Report#init(DavResource, ReportInfo)
     */
    public void init(DavResource resource, ReportInfo info) throws DavException {
        // validate info
        if (!getType().isRequestedReportType(info)) {
            throw new DavException(DavServletResponse.SC_BAD_REQUEST, "DAV:compare-baseline element expected.");
        }

        // make sure the report is applied to a version history resource
        if (resource != null && (resource instanceof BaselineResource)) {
            this.requestBaseline = (BaselineResource) resource;
        } else {
            throw new DavException(DavServletResponse.SC_BAD_REQUEST, "DAV:compare-baseline report can only be created for a baseline resource.");
        }

        // make sure the DAV:href element inside the request body points to
        // an baseline resource (precondition for this report).
        String compareHref = DomUtil.getText(info.getContentElement(DavConstants.XML_HREF, DavConstants.NAMESPACE));
        DavResourceLocator locator = resource.getLocator();
        DavResourceLocator compareLocator = locator.getFactory().createResourceLocator(locator.getPrefix(), compareHref);

        DavResource compRes = resource.getFactory().createResource(compareLocator, resource.getSession());
        if (compRes instanceof BaselineResource) {
            compareBaseline = (BaselineResource) compRes;
        } else {
            throw new DavException(DavServletResponse.SC_BAD_REQUEST, "DAV:latest-activity-version report: The DAV:href in the request body MUST identify an activity.");
        }

        // TODO: eventually add check for 'same-baseline-history' (RFC: "A server MAY require that the baselines being compared be from the same baseline history.")
    }

    /**
     * @see org.apache.jackrabbit.webdav.xml.XmlSerializable#toXml(Document)
     */
    public Element toXml(Document document) {
        Element el = DomUtil.createElement(document, XML_COMPARE_BASELINE_REPORT, DeltaVConstants.NAMESPACE);
        try {
            // TODO: check if correct
            List<VersionResource> requestVs = new ArrayList<VersionResource>();
            getVersions(requestBaseline.getBaselineCollection(), requestVs);

            List<VersionResource> compareVs = new ArrayList<VersionResource>();
            getVersions(compareBaseline.getBaselineCollection(), compareVs);

            for (VersionResource requestV : requestVs) {
                if (!compareVs.remove(requestV)) {
                    // check if another version of the same vh is present (change)
                    VersionResource changedV = findChangedVersion(requestV, compareVs);
                    if (changedV != null) {
                        // found a 'changed-version' entry
                        Element cv = DomUtil.addChildElement(el, XML_CHANGED_VERSION, DeltaVConstants.NAMESPACE);
                        cv.appendChild(DomUtil.hrefToXml(requestV.getHref(), document));
                        cv.appendChild(DomUtil.hrefToXml(changedV.getHref(), document));
                    } else {
                        // no corresponding version => 'deleted-version'
                        Element cv = DomUtil.addChildElement(el, XML_DELETED_VERSION, DeltaVConstants.NAMESPACE);
                        cv.appendChild(DomUtil.hrefToXml(requestV.getHref(), document));
                    }

                } // else: both baseline contain a vc-resource with the same checked-in version
            }

            // all remaining versions from the 'compare-baseline' can be considered
            // to be added-versions.
            for (VersionResource addedV : compareVs) {
                Element cv = DomUtil.addChildElement(el, XML_ADDED_VERSION, DeltaVConstants.NAMESPACE);
                cv.appendChild(DomUtil.hrefToXml(addedV.getHref(), document));
            }
        } catch (DavException e) {
            log.error("Internal error while building report", e);
        }
        return el;
    }

    private void getVersions(DavResource collection, List<VersionResource> vList) throws DavException {
        DavResourceIterator it = collection.getMembers();
        while (it.hasNext()) {
            DavResource member = it.nextResource();
            if (member instanceof VersionControlledResource) {
                String href = new HrefProperty(member.getProperty(VersionControlledResource.CHECKED_IN)).getHrefs().get(0);
                DavResourceLocator locator = member.getLocator();
                DavResourceLocator vLocator = locator.getFactory().createResourceLocator(locator.getPrefix(), href);

                DavResource v = member.getFactory().createResource(vLocator, member.getSession());
                if (v instanceof VersionResource) {
                    vList.add((VersionResource) v);
                } else {
                    log.error("Internal error: DAV:checked-in property must point to a VersionResource.");
                }
            }
            if (member.isCollection()) {
                getVersions(member, vList);
            }
        }
    }

    private VersionResource findChangedVersion(VersionResource requestV, List<VersionResource> compareVs) throws DavException {
        VersionResource[] vs = requestV.getVersionHistory().getVersions();
        for (VersionResource v : vs) {
            if (compareVs.remove(v)) {
                // another version of the same versionhistory is present among
                // the compare-baseline versions.
                return v;
            }
        }
        return null;
    }
}
