/**
 * 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.webui.servlet.admin;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException;

import org.apache.log4j.Logger;
import org.dspace.app.util.AuthorizeUtil;
import org.dspace.app.webui.servlet.DSpaceServlet;
import org.dspace.app.webui.util.FileUploadRequest;
import org.dspace.app.webui.util.JSPManager;
import org.dspace.app.webui.util.UIUtil;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.AuthorizeManager;
import org.dspace.content.Bitstream;
import org.dspace.content.BitstreamFormat;
import org.dspace.content.Bundle;
import org.dspace.content.Collection;
import org.dspace.content.DSpaceObject;
import org.dspace.content.FormatIdentifier;
import org.dspace.content.Item;
import org.dspace.content.MetadataField;
import org.dspace.content.MetadataSchema;
import org.dspace.content.authority.Choices;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.core.LogManager;
import org.dspace.handle.HandleManager;
import org.dspace.license.CreativeCommons;

/**
 * Servlet for editing and deleting (expunging) items
 *
 * @author Robert Tansley
 * @version $Revision: 6158 $
 */
public class EditItemServlet extends DSpaceServlet
{
    /** User wants to delete (expunge) an item */
    public static final int START_DELETE = 1;

    /** User confirms delete (expunge) of item */
    public static final int CONFIRM_DELETE = 2;

    /** User updates item */
    public static final int UPDATE_ITEM = 3;

    /** User starts withdrawal of item */
    public static final int START_WITHDRAW = 4;

    /** User confirms withdrawal of item */
    public static final int CONFIRM_WITHDRAW = 5;

    /** User reinstates a withdrawn item */
    public static final int REINSTATE = 6;

    /** User starts the movement of an item */
    public static final int START_MOVE_ITEM = 7;

    /** User confirms the movement of the item */
    public static final int CONFIRM_MOVE_ITEM = 8;

    /** Logger */
    private static Logger log = Logger.getLogger(EditCommunitiesServlet.class);

    protected void doDSGet(Context context, HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException,
            SQLException, AuthorizeException
    {
        /*
         * GET with no parameters displays "find by handle/id" form parameter
         * item_id -> find and edit item with internal ID item_id parameter
         * handle -> find and edit corresponding item if internal ID or Handle
         * are invalid, "find by handle/id" form is displayed again with error
         * message
         */
        int internalID = UIUtil.getIntParameter(request, "item_id");
        String handle = request.getParameter("handle");
        boolean showError = false;

        // See if an item ID or Handle was passed in
        Item itemToEdit = null;

        if (internalID > 0)
        {
            itemToEdit = Item.find(context, internalID);

            showError = (itemToEdit == null);
        }
        else if ((handle != null) && !handle.equals(""))
        {
            // resolve handle
            DSpaceObject dso = HandleManager.resolveToObject(context, handle.trim());

            // make sure it's an ITEM
            if ((dso != null) && (dso.getType() == Constants.ITEM))
            {
                itemToEdit = (Item) dso;
                showError = false;
            }
            else
            {
                showError = true;
            }
        }

        // Show edit form if appropriate
        if (itemToEdit != null)
        {
            // now check to see if person can edit item
            checkEditAuthorization(context, itemToEdit);
            showEditForm(context, request, response, itemToEdit);
        }
        else
        {
            if (showError)
            {
                request.setAttribute("invalid.id", Boolean.TRUE);
            }

            JSPManager.showJSP(request, response, "/tools/get-item-id.jsp");
        }
    }

    protected void doDSPost(Context context, HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException,
            SQLException, AuthorizeException
    {
        // First, see if we have a multipart request (uploading a new bitstream)
        String contentType = request.getContentType();

        if ((contentType != null)
                && (contentType.indexOf("multipart/form-data") != -1))
        {
            // This is a multipart request, so it's a file upload
            processUploadBitstream(context, request, response);

            return;
        }

        /*
         * Then we check for a "cancel" button - if it's been pressed, we simply
         * return to the "find by handle/id" page
         */
        if (request.getParameter("submit_cancel") != null)
        {
            JSPManager.showJSP(request, response, "/tools/get-item-id.jsp");

            return;
        }

        /*
         * Respond to submitted forms. Each form includes an "action" parameter
         * indicating what needs to be done (from the constants above.)
         */
        int action = UIUtil.getIntParameter(request, "action");

        Item item = Item.find(context, UIUtil.getIntParameter(request,
                "item_id"));
 
        String handle = HandleManager.findHandle(context, item);

        // now check to see if person can edit item
        checkEditAuthorization(context, item);

        request.setAttribute("item", item);
        request.setAttribute("handle", handle);

        switch (action)
        {
        case START_DELETE:

            // Show "delete item" confirmation page
            JSPManager.showJSP(request, response,
                    "/tools/confirm-delete-item.jsp");

            break;

        case CONFIRM_DELETE:

            // Delete the item - if "cancel" was pressed this would be
            // picked up above
            // FIXME: Don't know if this does all it should - remove Handle?
            Collection[] collections = item.getCollections();

            // Remove item from all the collections it's in
            for (int i = 0; i < collections.length; i++)
            {
                collections[i].removeItem(item);
            }

            JSPManager.showJSP(request, response, "/tools/get-item-id.jsp");
            context.complete();

            break;

        case UPDATE_ITEM:
            processUpdateItem(context, request, response, item);

            break;

        case START_WITHDRAW:

            // Show "withdraw item" confirmation page
            JSPManager.showJSP(request, response,
                    "/tools/confirm-withdraw-item.jsp");

            break;

        case CONFIRM_WITHDRAW:

            // Withdraw the item
            item.withdraw();
            JSPManager.showJSP(request, response, "/tools/get-item-id.jsp");
            context.complete();

            break;

        case REINSTATE:
            item.reinstate();
            JSPManager.showJSP(request, response, "/tools/get-item-id.jsp");
            context.complete();

            break;

        case START_MOVE_ITEM:
                if (AuthorizeManager.isAdmin(context,item))
                {
                        // Display move collection page with fields of collections and communities
                        Collection[] allNotLinkedCollections = item.getCollectionsNotLinked();
                        Collection[] allLinkedCollections = item.getCollections();
                    
                        // get only the collection where the current user has the right permission
                        List<Collection> authNotLinkedCollections = new ArrayList<Collection>();
                        for (Collection c : allNotLinkedCollections)
                        {
                            if (AuthorizeManager.authorizeActionBoolean(context, c, Constants.ADD))
                            {
                                authNotLinkedCollections.add(c);
                            }
                        }

                List<Collection> authLinkedCollections = new ArrayList<Collection>();
                for (Collection c : allLinkedCollections)
                {
                    if (AuthorizeManager.authorizeActionBoolean(context, c, Constants.REMOVE))
                    {
                        authLinkedCollections.add(c);
                    }
                }
                        
                Collection[] notLinkedCollections = new Collection[authNotLinkedCollections.size()];
                notLinkedCollections = authNotLinkedCollections.toArray(notLinkedCollections);
                Collection[] linkedCollections = new Collection[authLinkedCollections.size()];
                linkedCollections = authLinkedCollections.toArray(linkedCollections);
                
                        request.setAttribute("linkedCollections", linkedCollections);
                        request.setAttribute("notLinkedCollections", notLinkedCollections);
                                    
                        JSPManager.showJSP(request, response, "/tools/move-item.jsp");
                } else
                {
                        throw new ServletException("You must be an administrator to move an item");
                }
                
                break;
                        
        case CONFIRM_MOVE_ITEM:
                if (AuthorizeManager.isAdmin(context,item))
                {
                        Collection fromCollection = Collection.find(context, UIUtil.getIntParameter(request, "collection_from_id"));
                        Collection toCollection = Collection.find(context, UIUtil.getIntParameter(request, "collection_to_id"));

                        Boolean inheritPolicies = false;
                        if (request.getParameter("inheritpolicies") != null)
                        {
                            inheritPolicies = true;
                        }

                        if (fromCollection == null || toCollection == null)
                        {
                                throw new ServletException("Missing or incorrect collection IDs for moving item");
                        }
                                    
                        item.move(fromCollection, toCollection, inheritPolicies);
                    
                    showEditForm(context, request, response, item);
        
                    context.complete();
                } else
                {
                        throw new ServletException("You must be an administrator to move an item");
                }
                
                break;
                
        default:

            // Erm... weird action value received.
            log.warn(LogManager.getHeader(context, "integrity_error", UIUtil
                    .getRequestLogInfo(request)));
            JSPManager.showIntegrityError(request, response);
        }
    }

    /**
     * Throw an exception if user isn't authorized to edit this item
     *
     * @param c
     * @param item
     */
    private void checkEditAuthorization(Context c, Item item)
            throws AuthorizeException, java.sql.SQLException
    {
        if (!item.canEdit())
        {
            int userID = 0;

            // first, check if userid is set
            if (c.getCurrentUser() != null)
            {
                userID = c.getCurrentUser().getID();
            }

            // show an error or throw an authorization exception
            throw new AuthorizeException("EditItemServlet: User " + userID
                    + " not authorized to edit item " + item.getID());
        }
    }

    /**
     * Show the item edit form for a particular item
     *
     * @param context
     *            DSpace context
     * @param request
     *            the HTTP request containing posted info
     * @param response
     *            the HTTP response
     * @param item
     *            the item
     */
    private void showEditForm(Context context, HttpServletRequest request,
            HttpServletResponse response, Item item) throws ServletException,
            IOException, SQLException, AuthorizeException
    {
        if ( request.getParameter("cc_license_url") != null )
        {
            // check authorization
            AuthorizeUtil.authorizeManageCCLicense(context, item);
            
            // turn off auth system to allow replace also to user that can't
            // remove/add bitstream to the item
            context.turnOffAuthorisationSystem();
                // set or replace existing CC license
                CreativeCommons.setLicense( context, item,
                   request.getParameter("cc_license_url") );
                context.restoreAuthSystemState();
                context.commit();
        }
  
        // Get the handle, if any
        String handle = HandleManager.findHandle(context, item);

        // Collections
        Collection[] collections = item.getCollections();

        // All DC types in the registry
        MetadataField[] types = MetadataField.findAll(context);
        
        // Get a HashMap of metadata field ids and a field name to display
        Map<Integer, String> metadataFields = new HashMap<Integer, String>();
        
        // Get all existing Schemas
        MetadataSchema[] schemas = MetadataSchema.findAll(context);
        for (int i = 0; i < schemas.length; i++)
        {
            String schemaName = schemas[i].getName();
            // Get all fields for the given schema
            MetadataField[] fields = MetadataField.findAllInSchema(context, schemas[i].getSchemaID());
            for (int j = 0; j < fields.length; j++)
            {
                Integer fieldID = Integer.valueOf(fields[j].getFieldID());
                String displayName = "";
                displayName = schemaName + "." + fields[j].getElement() + (fields[j].getQualifier() == null ? "" : "." + fields[j].getQualifier());
                metadataFields.put(fieldID, displayName);
            }
        }

        request.setAttribute("admin_button", AuthorizeManager.authorizeActionBoolean(context, item, Constants.ADMIN));
        try
        {
            AuthorizeUtil.authorizeManageItemPolicy(context, item);
            request.setAttribute("policy_button", Boolean.TRUE);
        }
        catch (AuthorizeException authex)
        {
            request.setAttribute("policy_button", Boolean.FALSE);
        }
        
        if (AuthorizeManager.authorizeActionBoolean(context, item
                .getParentObject(), Constants.REMOVE))
        {
            request.setAttribute("delete_button", Boolean.TRUE);
        }
        else
        {
            request.setAttribute("delete_button", Boolean.FALSE);
        }
        
        try
        {
            AuthorizeManager.authorizeAction(context, item, Constants.ADD);
            request.setAttribute("create_bitstream_button", Boolean.TRUE);
        }
        catch (AuthorizeException authex)
        {
            request.setAttribute("create_bitstream_button", Boolean.FALSE);
        }
        
        try
        {
            AuthorizeManager.authorizeAction(context, item, Constants.REMOVE);
            request.setAttribute("remove_bitstream_button", Boolean.TRUE);
        }
        catch (AuthorizeException authex)
        {
            request.setAttribute("remove_bitstream_button", Boolean.FALSE);
        }
        
        try
        {
            AuthorizeUtil.authorizeManageCCLicense(context, item);
            request.setAttribute("cclicense_button", Boolean.TRUE);
        }
        catch (AuthorizeException authex)
        {
            request.setAttribute("cclicense_button", Boolean.FALSE);
        }
        
        if (!item.isWithdrawn())
        {
            try
            {
                AuthorizeUtil.authorizeWithdrawItem(context, item);
                request.setAttribute("withdraw_button", Boolean.TRUE);
            }
            catch (AuthorizeException authex)
            {
                request.setAttribute("withdraw_button", Boolean.FALSE);
            }
        }
        else
        {
            try
            {
                AuthorizeUtil.authorizeReinstateItem(context, item);
                request.setAttribute("reinstate_button", Boolean.TRUE);
            }
            catch (AuthorizeException authex)
            {
                request.setAttribute("reinstate_button", Boolean.FALSE);
            }
        }
        
        
        request.setAttribute("item", item);
        request.setAttribute("handle", handle);
        request.setAttribute("collections", collections);
        request.setAttribute("dc.types", types);
        request.setAttribute("metadataFields", metadataFields);

        JSPManager.showJSP(request, response, "/tools/edit-item-form.jsp");
    }

    /**
     * Process input from the edit item form
     *
     * @param context
     *            DSpace context
     * @param request
     *            the HTTP request containing posted info
     * @param response
     *            the HTTP response
     * @param item
     *            the item
     */
    private void processUpdateItem(Context context, HttpServletRequest request,
            HttpServletResponse response, Item item) throws ServletException,
            IOException, SQLException, AuthorizeException
    {
        String button = UIUtil.getSubmitButton(request, "submit");
        /*
         * "Cancel" handled above, so whatever happens, we need to update the
         * item metadata. First, we remove it all, then build it back up again.
         */
        item.clearMetadata(Item.ANY, Item.ANY, Item.ANY, Item.ANY);

        // We'll sort the parameters by name. This ensures that DC fields
        // of the same element/qualifier are added in the correct sequence.
        // Get the parameters names
        Enumeration unsortedParamNames = request.getParameterNames();

        // Put them in a list
        List<String> sortedParamNames = new LinkedList<String>();

        while (unsortedParamNames.hasMoreElements())
        {
            sortedParamNames.add((String)unsortedParamNames.nextElement());
        }

        // Sort the list
        Collections.sort(sortedParamNames);

        for (String p : sortedParamNames)
        {
            if (p.startsWith("value"))
            {
                /*
                 * It's a metadata value - it will be of the form
                 * value_element_1 OR value_element_qualifier_2 (the number
                 * being the sequence number) We use a StringTokenizer to
                 * extract these values
                 */
                StringTokenizer st = new StringTokenizer(p, "_");

                st.nextToken(); // Skip "value"

                String schema = st.nextToken();

                String element = st.nextToken();

                String qualifier = null;

                if (st.countTokens() == 2)
                {
                    qualifier = st.nextToken();
                }

                String sequenceNumber = st.nextToken();

                // Get a string with "element" for unqualified or
                // "element_qualifier"
                String key = MetadataField.formKey(schema,element,qualifier);

                // Get the language
                String language = request.getParameter("language_" + key + "_"
                        + sequenceNumber);

                // trim language and set empty string language = null
                if (language != null)
                {
                    language = language.trim();
                    if (language.equals(""))
                    {
                        language = null;
                    }
                }

                // Get the authority key if any
                String authority = request.getParameter("choice_" + key + "_authority_"
                        + sequenceNumber);

                // Empty string authority = null
                if ((authority != null) && authority.equals(""))
                {
                    authority = null;
                }

                // Get the authority confidence value, passed as symbolic name
                String sconfidence = request.getParameter("choice_" + key + "_confidence_" + sequenceNumber);
                int confidence = (sconfidence == null || sconfidence.equals("")) ?
                                 Choices.CF_NOVALUE : Choices.getConfidenceValue(sconfidence);

                // Get the value
                String value = request.getParameter(p).trim();

                // If remove button pressed for this value, we don't add it
                // back to the item. We also don't add empty values
                // (if no authority is specified).
                if (!((value.equals("") && authority == null) || button.equals("submit_remove_" + key
                        + "_" + sequenceNumber)))
                {
                    // Value is empty, or remove button for this wasn't pressed
                    item.addMetadata(schema, element, qualifier, language, value,
                            authority, confidence);
                }
            }
            else if (p.startsWith("bitstream_name"))
            {
                // We have bitstream metadata
                // First, get the bundle and bitstream ID
                // Parameter name is bitstream_name_(bundleID)_(bitstreamID)
                StringTokenizer st = new StringTokenizer(p, "_");

                // Ignore "bitstream" and "name"
                st.nextToken();
                st.nextToken();

                // Bundle ID and bitstream ID next
                int bundleID = Integer.parseInt(st.nextToken());
                int bitstreamID = Integer.parseInt(st.nextToken());

                Bundle bundle = Bundle.find(context, bundleID);
                Bitstream bitstream = Bitstream.find(context, bitstreamID);

                // Get the string "(bundleID)_(bitstreamID)" for finding other
                // parameters related to this bitstream
                String key = String.valueOf(bundleID) + "_" + bitstreamID;

                // Update bitstream metadata, or delete?
                if (button.equals("submit_delete_bitstream_" + key))
                {
                    // "delete" button pressed
                    bundle.removeBitstream(bitstream);

                    // Delete bundle too, if empty
                    if (bundle.getBitstreams().length == 0)
                    {
                        item.removeBundle(bundle);
                    }
                }
                else
                {
                    // Update the bitstream metadata
                    String name = request.getParameter(p);
                    String source = request.getParameter("bitstream_source_"
                            + key);
                    String desc = request.getParameter("bitstream_description_"
                            + key);
                    int formatID = UIUtil.getIntParameter(request,
                            "bitstream_format_id_" + key);
                    String userFormatDesc = request
                            .getParameter("bitstream_user_format_description_"
                                    + key);
                    int primaryBitstreamID = UIUtil.getIntParameter(request,
                            bundleID + "_primary_bitstream_id");

                    // Empty strings become non-null
                    if (source.equals(""))
                    {
                        source = null;
                    }

                    if (desc.equals(""))
                    {
                        desc = null;
                    }

                    if (userFormatDesc.equals(""))
                    {
                        userFormatDesc = null;
                    }

                    bitstream.setName(name);
                    bitstream.setSource(source);
                    bitstream.setDescription(desc);
                    bitstream
                            .setFormat(BitstreamFormat.find(context, formatID));

                    if (primaryBitstreamID > 0)
                    {
                        bundle.setPrimaryBitstreamID(primaryBitstreamID);
                    }

                    if (userFormatDesc != null)
                    {
                        bitstream.setUserFormatDescription(userFormatDesc);
                    }

                    bitstream.update();
                    bundle.update();
                }
            }
        }

        /*
         * Now respond to button presses, other than "Remove" or "Delete" button
         * presses which were dealt with in the above loop.
         */
        if (button.equals("submit_addfield"))
        {
            // Adding a metadata field
            int dcTypeID = UIUtil.getIntParameter(request, "addfield_dctype");
            String value = request.getParameter("addfield_value").trim();
            String lang = request.getParameter("addfield_language");

            // trim language and set empty string language = null
            if (lang != null)
            {
                lang = lang.trim();
                if (lang.equals(""))
                {
                    lang = null;
                }
            }

            MetadataField field = MetadataField.find(context, dcTypeID);
            MetadataSchema schema = MetadataSchema.find(context, field
                    .getSchemaID());
            item.addMetadata(schema.getName(), field.getElement(), field
                    .getQualifier(), lang, value);
        }

        item.update();

        if (button.equals("submit_addcc"))
        {
            // Show cc-edit page
            request.setAttribute("item", item);
            JSPManager
                    .showJSP(request, response, "/tools/creative-commons-edit.jsp");
        }
        
        if (button.equals("submit_addbitstream"))
        {
            // Show upload bitstream page
            request.setAttribute("item", item);
            JSPManager
                    .showJSP(request, response, "/tools/upload-bitstream.jsp");
        }
        else
        {
            // Show edit page again
            showEditForm(context, request, response, item);
        }
        
        // Complete transaction
        context.complete();
    }

    /**
     * Process the input from the upload bitstream page
     *
     * @param context
     *            current DSpace context
     * @param request
     *            current servlet request object
     * @param response
     *            current servlet response object
     */
    private void processUploadBitstream(Context context,
            HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException, SQLException,
            AuthorizeException
    {
        try {
            // Wrap multipart request to get the submission info
            FileUploadRequest wrapper = new FileUploadRequest(request);
            Bitstream b = null;
            Item item = Item.find(context, UIUtil.getIntParameter(wrapper, "item_id"));
            File temp = wrapper.getFile("file");

            // Read the temp file as logo
            InputStream is = new BufferedInputStream(new FileInputStream(temp));

            // now check to see if person can edit item
            checkEditAuthorization(context, item);

            // do we already have an ORIGINAL bundle?
            Bundle[] bundles = item.getBundles("ORIGINAL");

            if (bundles.length < 1)
            {
                // set bundle's name to ORIGINAL
                b = item.createSingleBitstream(is, "ORIGINAL");

                // set the permission as defined in the owning collection
                Collection owningCollection = item.getOwningCollection();
                if (owningCollection != null)
                {
                    Bundle bnd = b.getBundles()[0];
                    bnd.inheritCollectionDefaultPolicies(owningCollection);
                }
            } 
            else
            {
                // we have a bundle already, just add bitstream
                b = bundles[0].createBitstream(is);
            }

            // Strip all but the last filename. It would be nice
            // to know which OS the file came from.
            String noPath = wrapper.getFilesystemName("file");

            while (noPath.indexOf('/') > -1)
            {
                noPath = noPath.substring(noPath.indexOf('/') + 1);
            }

            while (noPath.indexOf('\\') > -1)
            {
                noPath = noPath.substring(noPath.indexOf('\\') + 1);
            }

            b.setName(noPath);
            b.setSource(wrapper.getFilesystemName("file"));

            // Identify the format
            BitstreamFormat bf = FormatIdentifier.guessFormat(context, b);
            b.setFormat(bf);
            b.update();

            item.update();

            // Back to edit form
            showEditForm(context, request, response, item);

            // Remove temp file
            if (!temp.delete())
            {
                log.error("Unable to delete temporary file");
            }

            // Update DB
            context.complete();
        } catch (FileSizeLimitExceededException ex)
        {
            log.warn("Upload exceeded upload.max");
            JSPManager.showFileSizeLimitExceededError(request, response, ex.getMessage(), ex.getActualSize(), ex.getPermittedSize());
        }
    }
}
