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

import java.io.IOException;
import java.sql.SQLException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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

import org.dspace.app.util.SubmissionInfo;
import org.dspace.authorize.AuthorizeException;
import org.dspace.core.Context;

/**
 * Abstract processing class for DSpace Submission Steps. This defines the base
 * methods which are required for any Step processing class.
 * <P>
 * This abstract class defines the base methods which are used by both the
 * Manakin XML UI and the JSP UI to perform submission step processing.
 * <P>
 * This includes the following methods:
 * <ul>
 * <li>doProcessing() method - called to perform any step processing</li>
 * <li>getErrorFields() method - called to determine the fields which errored
 * out during processing</li>
 * <li>getErrorMessage() method - called to determine any error message
 * returned after processing</li>
 * </ul>
 * <P>
 * If you are using the JSP UI (with the SubmissionController servlet) you
 * should extend the org.dspace.submit.SubmissionStep class, which defines
 * additional methods used to maintain the context of the submission within a
 * JSP environment!
 * 
 * @see org.dspace.app.webui.submit.JSPStepManager
 * @see org.dspace.app.webui.servlet.SubmissionController
 * @see org.dspace.app.util.SubmissionConfig
 * @see org.dspace.app.util.SubmissionStepConfig
 * 
 * @author Tim Donohue
 * @version $Revision: 5844 $
 */
public abstract class AbstractProcessingStep
{
    /***************************************************************************
     * Constant - Name of the "<-Previous" button
     **************************************************************************/
    public static final String PREVIOUS_BUTTON = "submit_prev";

    /***************************************************************************
     * Constant - Name of the "Next->" button
     **************************************************************************/
    public static final String NEXT_BUTTON = "submit_next";

    /***************************************************************************
     * Constant - Name of the "Cancel/Save" button
     **************************************************************************/
    public static final String CANCEL_BUTTON = "submit_cancel";

    /***************************************************************************
     * Constant - Prefix of all buttons in the Progress Bar
     **************************************************************************/
    public static final String PROGRESS_BAR_PREFIX = "submit_jump_";

    /***************************************************************************
     * Flag which specifies that the LAST PAGE of a step has been reached. This
     * flag is used when a Workflow Item is rejected (and returned to the
     * workspace) to specify that the LAST PAGE of the LAST STEP has already
     * been reached
     **************************************************************************/
    public static final int LAST_PAGE_REACHED = Integer.MAX_VALUE;

    /***************************************************************************
     * STATUS / ERROR FLAGS (returned by doProcessing() if an error occurs or
     * additional user interaction may be required)
     **************************************************************************/
    public static final int STATUS_COMPLETE = 0;

    /** Maps each status/error flag to a textual, human understandable message * */
    private Map<Integer, String> errorMessages = null;

    private static final String ERROR_FIELDS_ATTRIBUTE = "dspace.submit.error_fields";
    

    /**
     * Do any processing of the information input by the user, and/or perform
     * step processing (if no user interaction required)
     * <P>
     * It is this method's job to save any data to the underlying database, as
     * necessary, and return error messages (if any) which can then be processed
     * by the doPostProcessing() method.
     * <P>
     * NOTE: If this step is a non-interactive step (i.e. requires no UI), then
     * it should perform *all* of its processing in this method!
     * 
     * @param context
     *            current DSpace context
     * @param request
     *            current servlet request object
     * @param response
     *            current servlet response object
     * @param subInfo
     *            submission info object
     * @return Status or error flag which will be processed by
     *         doPostProcessing() below! (if STATUS_COMPLETE or 0 is returned,
     *         no errors occurred!)
     */
    public abstract int doProcessing(Context context,
            HttpServletRequest request, HttpServletResponse response,
            SubmissionInfo subInfo) throws ServletException, IOException,
            SQLException, AuthorizeException;

    /**
     * Return a list of all UI fields which had errors that occurred during the
     * step processing. This list is for usage in generating the appropriate
     * error message(s) in the UI.
     * <P>
     * The list of fields which had errors should be set by the AbstractProcessingStep's
     * doProcessing() method, so that it can be accessed later by whatever UI is
     * generated.
     * 
     * @param request
     *            current servlet request object
     * @return List of error fields (as Strings)
     */
    public static final List<String> getErrorFields(HttpServletRequest request)
    {
        return (List<String>) request.getAttribute(ERROR_FIELDS_ATTRIBUTE);
    }
    
    /**
     * Sets the list of all UI fields which had errors that occurred during the
     * step processing. This list is for usage in generating the appropriate
     * error message(s) in the UI.
     * <P>
     * The list of fields which had errors should be set by the AbstractProcessingStep's
     * doProcessing() method, so that it can be accessed later by whatever UI is
     * generated.
     * 
     * @param request
     *            current servlet request object
     * @param errorFields
     *            List of all fields (as Strings) which had errors
     */
    private static final void setErrorFields(HttpServletRequest request, List<String> errorFields)
    {
        if(errorFields==null)
        {
            request.removeAttribute(ERROR_FIELDS_ATTRIBUTE);
        }
        else
        {
            request.setAttribute(ERROR_FIELDS_ATTRIBUTE, errorFields);
        }
    }

    /**
     * Add a single UI field to the list of all error fields (which can
     * later be retrieved using getErrorFields())
     * <P>
     * The list of fields which had errors should be set by the AbstractProcessingStep's
     * doProcessing() method, so that it can be accessed later by whatever UI is
     * generated.
     * 
     * @param fieldName
     *            the name of the field which had an error
     */
    protected static final void addErrorField(HttpServletRequest request, String fieldName)
    {
        //get current list
        List<String> errorFields = getErrorFields(request);
        
        if (errorFields == null)
        {
            errorFields = new ArrayList<String>();
        }

        //add this field
        errorFields.add(fieldName);
        
        //save updated list
        setErrorFields(request, errorFields);
    }

    /**
     * Clears the list of all fields that errored out during the previous step's
     * processing.
     * 
     * @param request
     *        current servlet request object
     * 
     */
    protected static final void clearErrorFields(HttpServletRequest request)
    {
        //get current list
        List<String> errorFields = getErrorFields(request);
        
        if (errorFields != null)
        {
            setErrorFields(request, null);
        }
    }

    /**
     * Return the text of an error message based on the passed in error flag.
     * These error messages are used for non-interactive steps (so that they can
     * log something more specific than just an error flag)
     * <P>
     * Since each step can define its own error messages and flags, this method
     * depends on all the error messages being initialized by using the
     * "addErrorMessage()" method within the constructor for the step class!
     * 
     * @param errorFlag
     *            The error flag defined in this step which represents an error
     *            message.
     * @return String which contains the text of the error message, or null if
     *         error message not found
     */
    public final String getErrorMessage(int errorFlag)
    {
        if (this.errorMessages == null || this.errorMessages.size() == 0)
        {
            return null;
        }
        else
        {
            return this.errorMessages.get(Integer.valueOf(errorFlag));
        }
    }

    /**
     * Add an error message to the internal map for this step.
     * <P>
     * This method associates a specific error message with an error flag
     * defined in this step.
     * <P>
     * This is extremely useful to define the error message which will be logged
     * for a non-interactive step.
     * 
     * @param errorFlag
     *            the status value indicating the type of error
     * @param errorMessage
     *            text of the message to be added
     */
    protected final void addErrorMessage(int errorFlag, String errorMessage)
    {
        if (this.errorMessages == null)
        {
            this.errorMessages = new HashMap<Integer, String>();
        }

        errorMessages.put(Integer.valueOf(errorFlag), errorMessage);
    }

    /**
     * Retrieves the number of pages that this "step" extends over. This method
     * is used by the SubmissionController to build the progress bar.
     * <P>
     * This method may just return 1 for most steps (since most steps consist of
     * a single page). But, it should return a number greater than 1 for any
     * "step" which spans across a number of HTML pages. For example, the
     * configurable "Describe" step (configured using input-forms.xml) overrides
     * this method to return the number of pages that are defined by its
     * configuration file.
     * <P>
     * Steps which are non-interactive (i.e. they do not display an interface to
     * the user) should return a value of 1, so that they are only processed
     * once!
     * 
     * @param request
     *            The HTTP Request
     * @param subInfo
     *            The current submission information object
     * 
     * @return the number of pages in this step
     */
    public abstract int getNumberOfPages(HttpServletRequest request,
            SubmissionInfo subInfo) throws ServletException;

    /**
     * Find out which page a user is currently viewing
     * 
     * @param request
     *            HTTP request
     * 
     * @return current page
     */
    public static final int getCurrentPage(HttpServletRequest request)
    {
        int pageNum = -1;

        // try to retrieve cached page from request attribute
        Integer currentPage = (Integer) request.getAttribute("submission.page");

        if (currentPage == null)
        {
            // try and get it as a 'page' parameter
            String val = request.getParameter("page");

            try
            {
                pageNum =  Integer.parseInt(val.trim());
            }
            catch (Exception e)
            {
                // Problem with parameter
                pageNum = -1;
            }
            
            // if couldn't find page in request parameter
            if (pageNum < 0)
            {
                // default to page #1, since no other optionsc
                pageNum = 1;

                setCurrentPage(request, pageNum);
            }
            else
            {
                // save to request attribute
                setCurrentPage(request, pageNum);
            }
        }
        else
        {
            pageNum = currentPage.intValue();
        }

        return pageNum;
    }

    /**
     * Set which page a user is currently viewing
     * 
     * @param request
     *            HTTP request
     * @param pageNumber
     *            new current page
     */
    public static final void setCurrentPage(HttpServletRequest request,
            int pageNumber)
    {
        // set info to request
        request.setAttribute("submission.page", Integer.valueOf(pageNumber));
    }

}
