/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
package org.dspace.app.dav;

import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.handle.HandleManager;
import org.jdom.Element;


/**
 * The Lookup resource translates a DSpace persistent object identifier (i.e. an
 * Item or Bitstream Handle) into a DAV resource URI for the LNI. It accepts two
 * simple, flexible formats: one for Item handles and one for a bitstream within
 * an Item.
 * <p>
 * Any GET, PUT, PROPFIND, etc response gets a "Temporarily Moved" status and
 * the DAV URL in the "Location:" header of the response.
 * <p>
 * The "lookup" URI format:
 * 
 * <pre>
 * {prefix}/lookup/handle/{hdl-prefix}/{hdl-suffix}   ... item Handle
 * e.g.
 * {prefix}/lookup/handle/1234.56/99   ... item Handle
 * {prefix}/lookup/handle/1234.56%2f99   ... item Handle
 * {prefix}/lookup/bitstream-handle/{seq-id}/{hdl-prefix}/{hdl-suffix}
 * e.g.
 * {prefix}/lookup/bitstream-handle/13/1234.56/99   ... bitstream Handle
 * {prefix}/lookup/bitstream-handle/13/1234.56%2f99   ... bitstream Handle
 * </pre>
 */
class DAVLookup extends DAVResource
{
    
    /** log4j category. */
    private static Logger log = Logger.getLogger(DAVLookup.class);

    /**
     * Instantiates a new DAV lookup.
     * 
     * @param context the context
     * @param request the request
     * @param response the response
     * @param pathElt the path elt
     */
    protected DAVLookup(Context context, HttpServletRequest request,
            HttpServletResponse response, String pathElt[])
    {
        super(context, request, response, pathElt);
    }

    // empty property list, this class doesn't implement propfind.
    /** The Constant allProps. */
    private static final List<Element> allProps = new ArrayList<Element>();

    /* (non-Javadoc)
     * @see org.dspace.app.dav.DAVResource#getAllProperties()
     */
    @Override
    protected List<Element> getAllProperties()
    {
        return allProps;
    }

    /**
     * Match the URIs this subclass understands and return the corresponding
     * resource.
     * 
     * @param context the context
     * @param request the request
     * @param response the response
     * @param pathElt the path elt
     * 
     * @return a DAVLookup resource if we can parse this URI, or null.
     * 
     * @throws DAVStatusException the DAV status exception
     * @throws SQLException the SQL exception
     */
    protected static DAVResource matchResourceURI(Context context,
            HttpServletRequest request, HttpServletResponse response,
            String pathElt[]) throws DAVStatusException, SQLException
    {
        // The "/lookup" request:
        if (pathElt[0].equals("lookup"))
        {
            return new DAVLookup(context, request, response, pathElt);
        }

        return null;
    }

    /**
     * Send a redirect (302) response to client with DAV URL of the resource for
     * this handle and/or bitstream. Puts URL in the <code>Location:</code>
     * header.
     * 
     * @return URL in string form for desired DAV resource.
     * 
     * @throws IOException Signals that an I/O exception has occurred.
     * @throws SQLException the SQL exception
     * @throws DAVStatusException the DAV status exception
     * 
     * @throw IOException
     * @throw SQLException
     */
    private void doRedirect() throws IOException, SQLException,
            DAVStatusException
    {
        DSpaceObject dso = null;
        String bsPid = null;

        /*
         * FIXME: (maybe?) NOTE: This is currently hard-wired to accomodate the
         * syntax of Handles, with "prefix/suffix" separated by the slash --
         * that means the Handle probably takes up multiple path elements,
         * unless the client escaped the '/'. This code *might* need adjusting
         * if we allow other kinds of persistent identifiers for DSpace objects.
         */
        int hdlStart = -1;
        if (this.pathElt.length > 2 && this.pathElt[1].equals("handle"))
        {
            hdlStart = 2;
        }
        else if (this.pathElt.length > 3 && this.pathElt[1].equals("bitstream-handle"))
        {
            bsPid = this.pathElt[2];
            hdlStart = 3;
        }
        else
        {
            throw new DAVStatusException(HttpServletResponse.SC_BAD_REQUEST,
                    "Unrecognized 'lookup' request format.");
        }
        String prefix = decodeHandle(this.pathElt[hdlStart]);
        String handle = null;

        // if "prefix" contains a slash, then it's the whole handle:
        if (prefix.indexOf('/') >= 0)
        {
            handle = prefix;
            log.debug("Lookup: resolving escaped handle \"" + handle + "\"");
        }
        else if (this.pathElt.length >= hdlStart + 2)
        {
            StringBuffer hdl = new StringBuffer(prefix);
            for (int i = hdlStart + 1; i < this.pathElt.length; ++i)
            {
                hdl.append("/");
                hdl.append(this.pathElt[i]);
            }
            handle = hdl.toString();
            log.debug("Lookup: resolving multielement handle \"" + handle
                    + "\"");
        }
        else
        {
            throw new DAVStatusException(HttpServletResponse.SC_BAD_REQUEST,
                    "Incomplete handle in lookup request.");
        }

        // did handle lookup fail?
        dso = HandleManager.resolveToObject(this.context, handle);
        if (dso == null)
        {
            throw new DAVStatusException(HttpServletResponse.SC_NOT_FOUND,
                    "Cannot resolve handle \"" + handle + "\"");
        }

        // bitstream must exist too
        String location = makeLocation(dso, bsPid);
        if (location == null)
        {
            throw new DAVStatusException(HttpServletResponse.SC_NOT_FOUND,
                    "Bitstream \"" + bsPid + "\" does not exist in \"" + handle
                            + "\"");
        }

        // add query string -- unnecessary, but it helps naive clients that
        // use GET with "package" query arg to download an Item.
        String qs = this.request.getQueryString();
        if (qs != null)
        {
            location += "?" + qs;
        }

        log.debug("Lookup returning redirect to: " + location);
        this.response.setHeader("Location", location);
        this.response.sendError(HttpServletResponse.SC_MOVED_TEMPORARILY,
                "These are not the droids you are looking for.");
    }

    /**
     * Create URI as string for a given handle and optional bitstream. URI is
     * relative to top of DAV hierarchy, but starts with '/'.
     * 
     * @param handle handle of a DSpace object (Item, Collection, etc)
     * @param bsPid bitstream persistent identifier.
     * 
     * @return "absolute" URI from top of DAV hierarchy
     * 
     * @throws IOException Signals that an I/O exception has occurred.
     * @throws SQLException the SQL exception
     */
    protected String makeURI(String handle, String bsPid) throws IOException,
            SQLException
    {
        DSpaceObject dso = HandleManager.resolveToObject(this.context, handle);
        if (dso == null)
        {
            return null;
        }
        return makeURI(dso, bsPid);
    }

    /**
     * Create URI as string for a given handle and optional bitstream. URI is
     * relative to top of DAV hierarchy, but starts with '/'.
     * 
     * @param dso a DSpace object (Item, Collection, etc)
     * @param bsPid bitstream persistent identifier.
     * 
     * @return "absolute" URI from top of DAV hierarchy
     * 
     * @throws IOException Signals that an I/O exception has occurred.
     * @throws SQLException the SQL exception
     */
    private String makeURI(DSpaceObject dso, String bsPid) throws IOException,
            SQLException
    {
        // make sure that bitstream actually exists:
        if (bsPid != null)
        {
            if (dso.getType() != Constants.ITEM)
            {
                log.warn("Non-Item with Bitstream Sequence ID in DAV Lookup.");
                return null;
            }
            try
            {
                int pid = Integer.parseInt(bsPid);
                if (DAVBitstream.getBitstreamBySequenceID((Item) dso, pid) == null)
                {
                    log
                            .warn("Bitstream Sequence ID Not Found in DAV Lookup: \""
                                    + bsPid + "\"");
                    return null;
                }
            }
            catch (NumberFormatException nfe)
            {
                log.warn("Invalid Bitstream Sequence ID in DAV Lookup: \""
                        + bsPid + "\"");
                return null;
            }
        }
        String base = "/" + DAVDSpaceObject.getPathElt(dso);
        if (bsPid != null)
        {
            return base + "/bitstream_" + bsPid;
        }
        else
        {
            return base;
        }

    }

    // returns fully-qualified URL or null upon error.
    /**
     * Make location.
     * 
     * @param dso the dso
     * @param bsPid the bs pid
     * 
     * @return the string
     * 
     * @throws IOException Signals that an I/O exception has occurred.
     * @throws SQLException the SQL exception
     */
    private String makeLocation(DSpaceObject dso, String bsPid)
            throws IOException, SQLException
    {
        String prefix = hrefPrefix();

        String rest = makeURI(dso, bsPid);
        if (rest == null)
        {
            return null;
        }

        // delete leading '/' from URI since prefix has trailing one
        return prefix + rest.substring(1);
    }

    /**
     * placeholder that does nothing since propfind() is overridden.
     * 
     * @param property the property
     * 
     * @return the element
     * 
     * @throws SQLException the SQL exception
     * @throws AuthorizeException the authorize exception
     * @throws IOException Signals that an I/O exception has occurred.
     * @throws DAVStatusException the DAV status exception
     */

    @Override
    protected Element propfindInternal(Element property) throws SQLException,
            AuthorizeException, IOException, DAVStatusException
    {
        return null;
    }

    /**
     * Override propfind() to make sure it always returns a redirect.
     * 
     * @throws SQLException the SQL exception
     * @throws AuthorizeException the authorize exception
     * @throws IOException Signals that an I/O exception has occurred.
     * @throws DAVStatusException the DAV status exception
     */
    @Override
    protected void propfind() throws SQLException,
            AuthorizeException, IOException, DAVStatusException
    {
        this.doRedirect();
    }

    /* (non-Javadoc)
     * @see org.dspace.app.dav.DAVResource#proppatchInternal(int, org.jdom.Element)
     */
    @Override
    protected int proppatchInternal(int mode, Element prop)
            throws SQLException, AuthorizeException, IOException,
            DAVStatusException
    {
        return HttpServletResponse.SC_METHOD_NOT_ALLOWED;
    }

    /* (non-Javadoc)
     * @see org.dspace.app.dav.DAVResource#get()
     */
    @Override
    protected void get() throws SQLException, AuthorizeException,
            IOException, DAVStatusException
    {
        this.doRedirect();
    }

    /* (non-Javadoc)
     * @see org.dspace.app.dav.DAVResource#put()
     */
    @Override
    protected void put() throws SQLException, AuthorizeException,
            IOException, DAVStatusException
    {
        this.doRedirect();
    }

    /**
     * Reject copy. Client should get resource URL first.
     * 
     * @param destination the destination
     * @param depth the depth
     * @param overwrite the overwrite
     * @param keepProperties the keep properties
     * 
     * @return the int
     * 
     * @throws DAVStatusException the DAV status exception
     * @throws SQLException the SQL exception
     * @throws AuthorizeException the authorize exception
     * @throws IOException Signals that an I/O exception has occurred.
     */
    @Override
    protected int copyInternal(DAVResource destination, int depth,
            boolean overwrite, boolean keepProperties)
            throws DAVStatusException, SQLException, AuthorizeException,
            IOException
    {
        throw new DAVStatusException(HttpServletResponse.SC_METHOD_NOT_ALLOWED,
                "COPY method not allowed on lookup resource.");
    }

    /**
     * This should never get called.
     * 
     * @return the DAV resource[]
     * 
     * @throws SQLException the SQL exception
     */
    @Override
    protected DAVResource[] children() throws SQLException
    {
        return new DAVResource[0];
    }

    /* (non-Javadoc)
     * @see org.dspace.app.dav.DAVResource#typeValue()
     */
    @Override
    protected Element typeValue()
    {
        return null;
    }

    /* (non-Javadoc)
     * @see org.dspace.app.dav.DAVResource#deleteInternal()
     */
    @Override
    protected int deleteInternal() throws DAVStatusException, SQLException,
            AuthorizeException, IOException
    {
        throw new DAVStatusException(HttpServletResponse.SC_NOT_IMPLEMENTED,
                "DELETE method not implemented for Lookup.");
    }

    /* (non-Javadoc)
     * @see org.dspace.app.dav.DAVResource#mkcolInternal(java.lang.String)
     */
    @Override
    protected int mkcolInternal(String waste) throws DAVStatusException,
            SQLException, AuthorizeException, IOException
    {
        throw new DAVStatusException(HttpServletResponse.SC_METHOD_NOT_ALLOWED,
                "MKCOL method not allowed for Lookup.");
    }
}
