/*
 * 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.jcr;

import javax.jcr.AccessDeniedException;
import javax.jcr.Item;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.observation.EventJournal;
import javax.jcr.version.Version;
import javax.jcr.version.VersionHistory;
import javax.servlet.http.HttpServletResponse;

import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.util.Text;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavMethods;
import org.apache.jackrabbit.webdav.DavResource;
import org.apache.jackrabbit.webdav.DavResourceFactory;
import org.apache.jackrabbit.webdav.DavResourceLocator;
import org.apache.jackrabbit.webdav.DavServletRequest;
import org.apache.jackrabbit.webdav.DavServletResponse;
import org.apache.jackrabbit.webdav.DavSession;
import org.apache.jackrabbit.webdav.jcr.transaction.TxLockManagerImpl;
import org.apache.jackrabbit.webdav.jcr.version.VersionHistoryItemCollection;
import org.apache.jackrabbit.webdav.jcr.version.VersionItemCollection;
import org.apache.jackrabbit.webdav.observation.ObservationResource;
import org.apache.jackrabbit.webdav.observation.SubscriptionManager;
import org.apache.jackrabbit.webdav.transaction.TransactionDavServletRequest;
import org.apache.jackrabbit.webdav.transaction.TransactionResource;
import org.apache.jackrabbit.webdav.version.DeltaVServletRequest;
import org.apache.jackrabbit.webdav.version.VersionControlledResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <code>DavResourceFactoryImpl</code>...
 */
public class DavResourceFactoryImpl implements DavResourceFactory {

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

    private final TxLockManagerImpl txMgr;
    private final SubscriptionManager subsMgr;

    /**
     * Create a new <code>DavResourceFactoryImpl</code>.
     *
     * @param txMgr
     * @param subsMgr
     */
    public DavResourceFactoryImpl(TxLockManagerImpl txMgr, SubscriptionManager subsMgr) {
        this.txMgr = txMgr;
        this.subsMgr = subsMgr;
    }

    /**
     * Create a new <code>DavResource</code> from the specified locator and request
     * objects. Note, that in contrast to
     * {@link #createResource(DavResourceLocator, DavSession)} the locator may
     * point to a non-existing resource.
     * <p/>
     * If the request contains a {@link org.apache.jackrabbit.webdav.version.DeltaVServletRequest#getLabel()
     * Label header}, the resource is build from the indicated
     * {@link org.apache.jackrabbit.webdav.version.VersionResource version} instead.
     *
     * @param locator
     * @param request
     * @param response
     * @return
     * @see DavResourceFactory#createResource(org.apache.jackrabbit.webdav.DavResourceLocator, org.apache.jackrabbit.webdav.DavServletRequest, org.apache.jackrabbit.webdav.DavServletResponse)
     */
    public DavResource createResource(DavResourceLocator locator,
                                      DavServletRequest request,
                                      DavServletResponse response) throws DavException {
        JcrDavSession.checkImplementation(request.getDavSession());
        JcrDavSession session = (JcrDavSession)request.getDavSession();

        DavResource resource;
        String type = request.getParameter("type");

        if (locator.isRootLocation()) {
            // root
            resource = new RootCollection(locator, session, this);
        } else if ("journal".equals(type) && locator.getResourcePath().equals(locator.getWorkspacePath())) {
            // feed/event journal resource
            try {
                EventJournal ej = session.getRepositorySession().getWorkspace().getObservationManager()
                        .getEventJournal();
                if (ej == null) {
                    throw new DavException(HttpServletResponse.SC_NOT_IMPLEMENTED, "event journal not supported");
                }
                resource = new EventJournalResourceImpl(ej, locator, session, request, this);
            } catch (AccessDeniedException ex) {
                // EventJournal only allowed for admin?
                throw new DavException(HttpServletResponse.SC_UNAUTHORIZED, ex);
            } catch (RepositoryException ex) {
                throw new DavException(HttpServletResponse.SC_BAD_REQUEST, ex);
            }
        } else if (locator.getResourcePath().equals(locator.getWorkspacePath())) {
            // workspace resource
            resource = new WorkspaceResourceImpl(locator, session, this);
        } else {
            // resource corresponds to a repository item
            try {
                resource = createResourceForItem(locator, session);

                Item item = getItem(session, locator);
                boolean versionable = item.isNode() && ((Node) item).isNodeType(JcrConstants.MIX_VERSIONABLE);

                /* if the created resource is version-controlled and the request
                contains a Label header, the corresponding Version must be used
                instead.*/
                if (request instanceof DeltaVServletRequest && versionable) {
                    String labelHeader = ((DeltaVServletRequest)request).getLabel();
                    if (labelHeader != null && DavMethods.isMethodAffectedByLabel(request) && isVersionControlled(resource)) {
                        Version v = ((Node)item).getVersionHistory().getVersionByLabel(labelHeader);
                        DavResourceLocator vloc = locator.getFactory().createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(), v.getPath(), false);
                        resource =  new VersionItemCollection(vloc, session, this, v);
                    }
                }
            } catch (PathNotFoundException e) {
                /* item does not exist yet: create the default resources
                Note: MKCOL request forces a collection-resource even if there already
                exists a repository-property with the given path. the MKCOL will
                in that particular case fail with a 405 (method not allowed).*/
                if (DavMethods.getMethodCode(request.getMethod()) == DavMethods.DAV_MKCOL) {
                    resource = new VersionControlledItemCollection(locator, session, this, null);
                } else {
                    resource = new DefaultItemResource(locator, session, this, null);
                }
            } catch (RepositoryException e) {
                log.error("Failed to build resource from item '"+ locator.getRepositoryPath() + "'");
                throw new JcrDavException(e);
            }
        }

        if (request instanceof TransactionDavServletRequest && resource instanceof TransactionResource) {
            ((TransactionResource)resource).init(txMgr, ((TransactionDavServletRequest)request).getTransactionId());
        }
        if (resource instanceof ObservationResource) {
            ((ObservationResource)resource).init(subsMgr);
        }
        return resource;
    }

    /**
     * Create a new <code>DavResource</code> from the given locator and session.
     *
     * @param locator
     * @param session
     * @return DavResource representing either a repository item or the {@link RootCollection}.
     * @throws DavException if the given locator does neither refer to a repository item
     * nor does represent the {@link org.apache.jackrabbit.webdav.DavResourceLocator#isRootLocation()
     * root location}.
     */
    public DavResource createResource(DavResourceLocator locator, DavSession session) throws DavException {
        JcrDavSession.checkImplementation(session);
        JcrDavSession sessionImpl = (JcrDavSession)session;

        DavResource resource;
        if (locator.isRootLocation()) {
            resource =  new RootCollection(locator, sessionImpl, this);
        } else if (locator.getResourcePath().equals(locator.getWorkspacePath())) {
            resource = new WorkspaceResourceImpl(locator, sessionImpl, this);
        } else {
            try {
                resource = createResourceForItem(locator, sessionImpl);
            } catch (RepositoryException e) {
                log.debug("Creating resource for non-existing repository item: " + locator.getRepositoryPath());
                // todo: is this correct?
                resource = new VersionControlledItemCollection(locator, sessionImpl, this, null);
            }
        }

        // todo: currently transactionId is set manually after creation > to be improved.
        resource.addLockManager(txMgr);
        if (resource instanceof ObservationResource) {
            ((ObservationResource)resource).init(subsMgr);
        }
        return resource;
    }

    /**
     * Tries to retrieve the repository item defined by the locator's resource
     * path and build the corresponding WebDAV resource. The following distinction
     * is made between items: Version nodes, VersionHistory nodes, root node,
     * unspecified nodes and finally property items.
     *
     * @param locator
     * @param sessionImpl
     * @return DavResource representing a repository item.
     * @throws RepositoryException if {@link javax.jcr.Session#getItem(String)} fails.
     */
    private DavResource createResourceForItem(DavResourceLocator locator, JcrDavSession sessionImpl) throws RepositoryException, DavException {
        DavResource resource;
        Item item = getItem(sessionImpl, locator);
        if (item.isNode()) {
            // create special resources for Version and VersionHistory
            if (item instanceof Version) {
                resource = new VersionItemCollection(locator, sessionImpl, this, item);
            } else if (item instanceof VersionHistory) {
                resource = new VersionHistoryItemCollection(locator, sessionImpl, this, item);
            } else{
                resource = new VersionControlledItemCollection(locator, sessionImpl, this, item);
            }
        } else {
            resource = new DefaultItemResource(locator, sessionImpl, this, item);
        }
        return resource;
    }

    protected Item getItem(JcrDavSession sessionImpl, DavResourceLocator locator)
        throws PathNotFoundException, RepositoryException {
        return sessionImpl.getRepositorySession().getItem(locator.getRepositoryPath());
    }

    /**
     * Returns true, if the specified resource is a {@link VersionControlledResource}
     * and has a version history.
     *
     * @param resource
     * @return true if the specified resource is version-controlled.
     */
    private boolean isVersionControlled(DavResource resource) {
        boolean vc = false;
        if (resource instanceof VersionControlledResource) {
            try {
                vc = ((VersionControlledResource)resource).getVersionHistory() != null;
            } catch (DavException e) {
                log.debug("Resource '" + resource.getHref() + "' is not version-controlled.");
            }
        }
        return vc;
    }
}
