/**
 * 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.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

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

import org.apache.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.AuthorizeManager;
import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.Collection;
import org.dspace.content.DCValue;
import org.dspace.content.Item;
import org.dspace.content.crosswalk.CrosswalkException;
import org.dspace.content.packager.PackageDisseminator;
import org.dspace.content.packager.PackageException;
import org.dspace.content.packager.PackageParameters;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.core.PluginManager;
import org.dspace.core.Utils;
import org.dspace.eperson.EPerson;
import org.dspace.license.CreativeCommons;
import org.jdom.Element;
import org.jdom.Namespace;


/**
 * This defines the behavior of DSpace "resources" in the WebDAV interface; it
 * maps DAV operations onto DSpace object.s
 */
class DAVItem extends DAVDSpaceObject
{
    
    /** log4j category. */
    private static Logger log = Logger.getLogger(DAVItem.class);

    /** The item. */
    private Item item = null;

    /** The Constant submitterProperty. */
    private static final Element submitterProperty = new Element("submitter",
            DAV.NS_DSPACE);

    /** The Constant getlastmodifiedProperty. */
    private static final Element getlastmodifiedProperty = new Element(
            "getlastmodified", DAV.NS_DAV);

    /** The Constant licenseProperty. */
    private static final Element licenseProperty = new Element("license",
            DAV.NS_DSPACE);

    /** The Constant cc_license_textProperty. */
    private static final Element cc_license_textProperty = new Element(
            "cc_license_text", DAV.NS_DSPACE);

    /** The Constant cc_license_rdfProperty. */
    private static final Element cc_license_rdfProperty = new Element(
            "cc_license_rdf", DAV.NS_DSPACE);

    /** The Constant cc_license_urlProperty. */
    private static final Element cc_license_urlProperty = new Element(
            "cc_license_url", DAV.NS_DSPACE);

    /** The Constant owning_collectionProperty. */
    private static final Element owning_collectionProperty = new Element(
            "owning_collection", DAV.NS_DSPACE);

    /** The Constant withdrawnProperty. */
    private static final Element withdrawnProperty = new Element("withdrawn",
            DAV.NS_DSPACE);

    /** The all props. */
    private static List<Element> allProps = new ArrayList<Element>(commonProps);
    static
    {
        allProps.add(submitterProperty);
        allProps.add(getlastmodifiedProperty);
        allProps.add(licenseProperty);
        allProps.add(cc_license_textProperty);
        allProps.add(cc_license_rdfProperty);
        allProps.add(cc_license_urlProperty);
        allProps.add(owning_collectionProperty);
        allProps.add(handleProperty);
        allProps.add(withdrawnProperty);
    }

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

    /**
     * Instantiates a new DAV item.
     * 
     * @param context the context
     * @param request the request
     * @param response the response
     * @param pathElt the path elt
     * @param item the item
     */
    protected DAVItem(Context context, HttpServletRequest request,
            HttpServletResponse response, String pathElt[], Item item)
    {
        super(context, request, response, pathElt, item);
        this.type = TYPE_ITEM;
        this.item = item;
    }

    /**
     * Match Item URIs that identify the item by a database ID. Handle URIs are
     * matched by DAVDSpaceObject.
     * 
     * @param context the context
     * @param request the request
     * @param response the response
     * @param pathElt the path elt
     * 
     * @return the DAV resource
     * 
     * @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
    {
        int id = -1;
        String bsElt = null;

        try
        {
            // The "/item_db_" element in last or next-to-last element
            if (pathElt[pathElt.length - 1].startsWith("item_db_"))
            {
                id = Integer.parseInt(pathElt[pathElt.length - 1].substring(8));
            }
            else if (pathElt[pathElt.length - 1].startsWith("bitstream_")
                    && pathElt.length > 1
                    && pathElt[pathElt.length - 2].startsWith("item_db_"))
            {
                id = Integer.parseInt(pathElt[pathElt.length - 2].substring(8));
                bsElt = pathElt[pathElt.length - 1];
            }
            if (id >= 0)
            {
                Item item = Item.find(context, id);
                if (item == null)
                {
                    throw new DAVStatusException(
                            HttpServletResponse.SC_NOT_FOUND, "Item with ID="
                                    + String.valueOf(id) + " not found.");
                }
                if (bsElt != null)
                {
                    Bitstream bs = DAVBitstream.findBitstream(context, item,
                            bsElt);
                    if (bs == null)
                    {
                        throw new DAVStatusException(
                                HttpServletResponse.SC_NOT_FOUND,
                                "Bitstream not found.");
                    }
                    return new DAVBitstream(context, request, response,
                            pathElt, item, bs);
                }
                else
                {
                    return new DAVItem(context, request, response, pathElt,
                            item);
                }
            }
            return null;
        }
        catch (NumberFormatException ne)
        {
            throw new DAVStatusException(HttpServletResponse.SC_BAD_REQUEST,
                    "Error parsing number in request URI.", ne);
        }
    }

    /**
     * Return pathname element in db-id format.
     * 
     * @param dbid the dbid
     * 
     * @return the path elt
     */
    protected static String getPathElt(int dbid)
    {
        return "item_db_" + String.valueOf(dbid);
    }

    /**
     * Return pathname element to this Item. Use db-id format if no handle,
     * otherwise the DSpace object format.
     * 
     * @param item the item
     * 
     * @return the path elt
     */
    protected static String getPathElt(Item item)
    {
        String handle = item.getHandle();
        if (handle == null)
        {
            return getPathElt(item.getID());
        }
        else
        {
            return DAVDSpaceObject.getPathElt(item);
        }
    }

    /**
     * Return this resource's children. Item's children are its bitstreams.
     * 
     * @return the DAV resource[]
     * 
     * @throws SQLException the SQL exception
     */
    @Override
    protected DAVResource[] children() throws SQLException
    {
        // Check for overall read permission on Item
        if (!AuthorizeManager.authorizeActionBoolean(this.context, this.item,
                Constants.READ))
        {
            return new DAVResource[0];
        }

        Vector result = new Vector();
        Bundle[] bundles = this.item.getBundles();
        for (Bundle element : bundles)
        {
            // check read permission on this Bundle
            if (!AuthorizeManager.authorizeActionBoolean(this.context, element,
                    Constants.READ))
            {
                continue;
            }

            Bitstream[] bitstreams = element.getBitstreams();
            for (Bitstream element0 : bitstreams)
            {
                String ext[] = element0.getFormat().getExtensions();
                result.add(new DAVBitstream(this.context, this.request, this.response,
                        makeChildPath(DAVBitstream.getPathElt(element0
                                .getSequenceID(), ext.length < 1 ? null
                                : ext[0])), this.item, element0));
            }
        }
        return (DAVResource[]) result.toArray(new DAVResource[result.size()]);
    }

    /* (non-Javadoc)
     * @see org.dspace.app.dav.DAVDSpaceObject#propfindInternal(org.jdom.Element)
     */
    @Override
    protected Element propfindInternal(Element property) throws SQLException,
            AuthorizeException, IOException, DAVStatusException
    {
        String value = null;

        /*
         * FIXME: This implements permission check that really belongs in
         * business logic. Although communities and collections don't check for
         * read auth, Item may contain sensitive data and should always check
         * for READ permission. Exception: allow "withdrawn" property to be
         * checked regardless of authorization since all permissions are removed
         * when item is withdrawn.
         */
        if (!elementsEqualIsh(property, withdrawnProperty))
        {
            AuthorizeManager.authorizeAction(this.context, this.item, Constants.READ);
        }

        if (elementsEqualIsh(property, withdrawnProperty))
        {
            value = String.valueOf(this.item.isWithdrawn());
        }
        else if (elementsEqualIsh(property, displaynameProperty))
        {
            // displayname - title or handle.
            DCValue titleDc[] = this.item.getDC("title", Item.ANY, Item.ANY);
            value = titleDc.length > 0 ? titleDc[0].value : this.item.getHandle();
        }
        else if (elementsEqualIsh(property, handleProperty))
        {
            value = canonicalizeHandle(this.item.getHandle());
        }
        else if (elementsEqualIsh(property, submitterProperty))
        {
            EPerson ep = this.item.getSubmitter();
            if (ep != null)
            {
                value = hrefToEPerson(ep);
            }
        }
        else if (elementsEqualIsh(property, owning_collectionProperty))
        {
            Collection owner = this.item.getOwningCollection();
            if (owner != null)
            {
                value = canonicalizeHandle(owner.getHandle());
            }
        }
        else if (elementsEqualIsh(property, getlastmodifiedProperty))
        {
            value = DAV.applyHttpDateFormat(this.item.getLastModified());
        }
        else if (elementsEqualIsh(property, licenseProperty))
        {
            value = getLicenseAsString();
        }
        else if (elementsEqualIsh(property, cc_license_textProperty))
        {
            value = CreativeCommons.getLicenseText(this.item);
        }
        else if (elementsEqualIsh(property, cc_license_rdfProperty))
        {
            value = CreativeCommons.getLicenseRDF(this.item);
        }
        else if (elementsEqualIsh(property, cc_license_urlProperty))
        {
            value = CreativeCommons.getLicenseURL(this.item);
        }
        else
        {
            return super.propfindInternal(property);
        }

        // value was set up by "if" clause:
        if (value == null)
        {
            throw new DAVStatusException(HttpServletResponse.SC_NOT_FOUND,
                    "Not found.");
        }
        Element p = new Element(property.getName(), property.getNamespace());
        p.setText(filterForXML(value));
        return p;
    }

    /**
     * Get the license for this Item as String. License is the first bitstream
     * named "license.txt" in a LICENSE bundle, apparently?
     * <p>
     * FIXME: is this correct? there's no counterexample..
     * 
     * @return license string, or null if none found.
     * 
     * @throws SQLException the SQL exception
     * @throws AuthorizeException the authorize exception
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private String getLicenseAsString() throws SQLException,
            AuthorizeException, IOException
    {
        Bundle lb[] = this.item.getBundles(Constants.LICENSE_BUNDLE_NAME);
        for (Bundle element : lb)
        {
            Bitstream lbs = element.getBitstreamByName("license.txt");
            if (lbs != null)
            {
                ByteArrayOutputStream baos = new ByteArrayOutputStream(
                        (int) lbs.getSize());
                Utils.copy(lbs.retrieve(), baos);
                return baos.toString();
            }
        }
        return null;
    }

    /* (non-Javadoc)
     * @see org.dspace.app.dav.DAVResource#proppatchInternal(int, org.jdom.Element)
     */
    @Override
    protected int proppatchInternal(int action, Element prop)
            throws SQLException, AuthorizeException, IOException,
            DAVStatusException
    {
        // Don't need any authorization checks since the Item layer
        // checks authorization for write and delete.

        Namespace ns = prop.getNamespace();
        String propName = prop.getName();

        // "submitter" is the only Item-specific mutable property, rest are
        // live and unchangeable.
        if (ns != null && ns.equals(DAV.NS_DSPACE)
                && propName.equals("submitter"))
        {
            if (action == DAV.PROPPATCH_REMOVE)
            {
                throw new DAVStatusException(DAV.SC_CONFLICT,
                        "The submitter property cannot be removed.");
            }
            String newName = prop.getText();
            EPerson ep = EPerson.findByEmail(this.context, newName);
            if (ep == null)
            {
                throw new DAVStatusException(DAV.SC_CONFLICT,
                        "Cannot set submitter, no EPerson found for email address: "
                                + newName);
            }
            this.item.setSubmitter(ep);
            this.item.update();
            return HttpServletResponse.SC_OK;
        }
        throw new DAVStatusException(DAV.SC_CONFLICT, "The " + prop.getName()
                + " property cannot be changed.");
    }

    /**
     * GET implementation returns the contents of the Item as a package. The
     * query arg "package" must be specified.
     * 
     * @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 get() throws SQLException, AuthorizeException,
            IOException, DAVStatusException
    {
        // Check for overall read permission on Item, because nothing else will
        AuthorizeManager.authorizeAction(this.context, this.item, Constants.READ);

        String packageType = this.request.getParameter("package");

        if (packageType == null)
        {
            packageType = "default";
        }
        PackageDisseminator dip = (PackageDisseminator) PluginManager
                .getNamedPlugin(PackageDisseminator.class, packageType);
        if (dip == null)
        {
            throw new DAVStatusException(HttpServletResponse.SC_BAD_REQUEST,
                    "Cannot find a disseminate plugin for package="
                            + packageType);
        }
        else
        {
            try
            {
                // Create a temporary file to disseminate into
                String tempDirectory = ConfigurationManager.getProperty("upload.temp.dir");
                File tempFile = File.createTempFile("DAVItemGet" + this.item.hashCode(), null, new File(tempDirectory));
                tempFile.deleteOnExit();

                // Disseminate item to temporary file
                PackageParameters pparams = PackageParameters.create(this.request);
                this.response.setContentType(dip.getMIMEType(pparams));
                dip.disseminate(this.context, this.item, pparams, tempFile);

                // Copy temporary file contents to response stream
                FileInputStream fileIn = null;
                try
                {
                    fileIn = new FileInputStream(tempFile);
                    Utils.copy(fileIn, this.response.getOutputStream());
                }
                finally
                {
                    if (fileIn != null)
                    {
                        fileIn.close();
                    }
                }
            }
            catch (CrosswalkException pe)
            {
                throw new DAVStatusException(
                        HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                        "Failed in crosswalk of metadata: " + pe.toString(), pe);
            }
            catch (PackageException pe)
            {
                pe.log(log);
                throw new DAVStatusException(
                        HttpServletResponse.SC_INTERNAL_SERVER_ERROR, pe
                                .toString(), pe);
            }
        }
    }

    /* (non-Javadoc)
     * @see org.dspace.app.dav.DAVResource#put()
     */
    @Override
    protected void put() throws SQLException, AuthorizeException,
            IOException, DAVStatusException
    {
        throw new DAVStatusException(HttpServletResponse.SC_NOT_IMPLEMENTED,
                "PUT is not implemented for Item.");
    }

    /**
     * COPY is implemented by an "add", which is the closest we can get in
     * DSpace semantics.
     * 
     * @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
    {
        return addItemToCollection(this.context, this.item, destination, overwrite);
    }

    /**
     * Do the work of "copy" method; this code is shared with e.g.
     * DAVInProgressSubmission.
     * 
     * @param context the context
     * @param item the item
     * @param destination the destination
     * @param overwrite the overwrite
     * 
     * @return HTTP status code.
     * 
     * @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.
     */
    protected static int addItemToCollection(Context context, Item item,
            DAVResource destination, boolean overwrite)
            throws DAVStatusException, SQLException, AuthorizeException,
            IOException
    {
        // sanity checks
        if (!(destination instanceof DAVCollection))
        {
            throw new DAVStatusException(
                    HttpServletResponse.SC_METHOD_NOT_ALLOWED,
                    "COPY of Item is only allowed when destination is a DSpace Collection.");
        }

        // access check
        AuthorizeManager.authorizeAction(context, item, Constants.READ);

        // make sure item doesn't belong to this collection
        Collection destColl = ((DAVCollection) destination).getCollection();

        log.debug("COPY from=" + item.toString() + " (" + item.getHandle()
                + "), to=" + destColl.toString() + " (" + destColl.getHandle()
                + ")");

        // check if it's already a member
        Collection refs[] = item.getCollections();
        for (Collection element : refs)
        {
            if (destColl.equals(element))
            {
                log.debug("COPY - item @ " + item.getHandle()
                        + " is already a member of collection @ "
                        + destColl.getHandle());
                if (overwrite)
                {
                    return DAV.SC_NO_CONTENT;
                }
                else
                {
                    throw new DAVStatusException(DAV.SC_CONFLICT,
                            "This Item is already a member of collection handle="
                                    + destColl.getHandle());
                }
            }
        }

        destColl.addItem(item);
        return DAV.SC_NO_CONTENT;
    }

    /* (non-Javadoc)
     * @see org.dspace.app.dav.DAVResource#deleteInternal()
     */
    @Override
    protected int deleteInternal() throws DAVStatusException, SQLException,
            AuthorizeException, IOException
    {
        this.item.withdraw();
        return HttpServletResponse.SC_OK; // HTTP OK
    }

    /* (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 Item.");
    }
}
