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

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

import java.util.Iterator;

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

import org.apache.log4j.Logger;

import org.dspace.app.webui.servlet.SubmissionController;
import org.dspace.app.webui.util.JSPManager;
import org.dspace.app.util.SubmissionInfo;
import org.dspace.app.util.SubmissionStepConfig;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.WorkspaceItem;
import org.dspace.core.Context;
import org.dspace.submit.AbstractProcessingStep;

/**
 * Manages and processes all JSP-UI classes for DSpace Submission steps.
 * <P>
 * This manager is utilized by the SubmissionController to appropriately
 * load each JSP-UI step, and process any information returned by each step
 * 
 * @see org.dspace.submit.AbstractProcessingStep
 * @see org.dspace.app.webui.servlet.SubmissionController
 * @see org.dspace.app.webui.submit.JSPStep
 * 
 * @author Tim Donohue
 * @version $Revision: 5845 $
 */
public class JSPStepManager
{
    /** log4j logger */
    private static Logger log = Logger.getLogger(JSPStepManager.class);

    /**
     * Current Processing class for step that is being processed by the JSPStepManager
     * This is the class that performs processing of information entered in during a step
     */
    private AbstractProcessingStep stepProcessing = null;
    
    /**
     * Current JSP-UI binding class for step that is being processed by the JSPStepManager
     * This is the class that manages calling all JSPs, and determines if additional processing
     * of information (or confirmation) is necessary.
     */
    private JSPStep stepJSPUI = null;
    
    /** 
     * The SubmissionStepConfig object describing the current step 
     **/
    private SubmissionStepConfig stepConfig = null;

    /**
     * Initialize the current JSPStepManager object, by loading the 
     * specified step class.
     * 
     * @param stepConfig
     *            the SubmissionStepConfig object which describes
     *            this step's configuration in the item-submission.xml
     *            
     * @throws Exception
     *             if the JSPStep cannot be loaded or the class
     *             specified doesn't implement the JSPStep interface
     */
    public static JSPStepManager loadStep(SubmissionStepConfig stepConfig) throws Exception
    {
        
        JSPStepManager stepManager = new JSPStepManager();
        
        //save step configuration 
        stepManager.stepConfig = stepConfig;
        
        
        /*
         * First, load the step processing class (using the current class loader)
         */
        ClassLoader loader = stepManager.getClass().getClassLoader();
        Class stepClass = loader
                .loadClass(stepConfig.getProcessingClassName());

        Object stepInstance =  stepClass.newInstance();
        
        if(stepInstance instanceof AbstractProcessingStep)
        {
            // load the JSPStep interface for this step
            stepManager.stepProcessing = (AbstractProcessingStep) stepClass.newInstance();
        }
        else
        {
            throw new Exception("The submission step class specified by '" + stepConfig.getProcessingClassName() + 
                    "' does not extend the class org.dspace.submit.AbstractProcessingStep!" +
                    " Therefore it cannot be used by the Configurable Submission as the <processing-class>!");
        }
        
        
        /*
         * Next, load the step's JSPUI binding class (using the current class loader)
         * (Only load JSPUI binding class if specified...otherwise this is a non-interactive step)
         */
        if(stepConfig.getJSPUIClassName()!=null && stepConfig.getJSPUIClassName().length()>0)
        {
        	stepClass = loader
                	.loadClass(stepConfig.getJSPUIClassName());

        	stepInstance =  stepClass.newInstance();
        
	        if(stepInstance instanceof JSPStep)
	        {
	            // load the JSPStep interface for this step
	            stepManager.stepJSPUI = (JSPStep) stepClass.newInstance();
	        }
	        else
	        {
	            throw new Exception("The submission step class specified by '" + stepConfig.getJSPUIClassName() + 
	                    "' does not extend the class org.dspace.app.webui.JSPStep!" +
	                    " Therefore it cannot be used by the Configurable Submission for the JSP user interface!");
	        }
        }
        return stepManager;
    }

    
    /**
     * Initialize the current JSPStepManager object, to prepare for processing
     * of this step. This method is called by the SubmissionController, and its
     * job is to determine which "page" of the step the current user is on.
     * <P>
     * Once the page has been determined, this method also determines whether
     * the user is completing this page (i.e. there is user input data that
     * needs to be saved to the database) or beginning this page (i.e. the user
     * needs to be sent to the JSP for this page).
     * 
     * @param context
     *            a DSpace Context object
     * @param request
     *            the HTTP request
     * @param response
     *            the HTTP response
     * @param subInfo
     *            submission info object
     * 
     * @return true if step is completed (successfully), false otherwise
     * 
     * @throws ServletException
     *             if a general error occurs
     * @throws IOException
     *             if a i/o error occurs
     * @throws SQLException
     *             if a database error occurs
     * @throws AuthorizeException
     *             if some authorization error occurs
     */
    public final boolean processStep(Context context,
            HttpServletRequest request, HttpServletResponse response,
            SubmissionInfo subInfo) throws ServletException, IOException,
            SQLException, AuthorizeException
    {
        /*
         * This method SHOULD NOT BE OVERRIDDEN, unless it's absolutely
         * necessary. If you override this method, make sure you call the
         * "doStepStart()" and "doStepEnd()" methods at the appropriate time in
         * your processStep() method.
         * 
         */

        /*
         * Figure out Current Page in this Step
         */
        int currentPage = -1;
        // if user came to this Step by pressing "Previous" button
        // from the following step, then we will have to calculate
        // the correct page to go to.
        if ((request.getAttribute("step.backwards") != null)
                && ((Boolean) request.getAttribute("step.backwards"))
                        .booleanValue())
        {
            // current page should be the LAST page in this step
            currentPage = getNumPagesInProgressBar(subInfo, this.stepConfig
                    .getStepNumber());

            AbstractProcessingStep.setCurrentPage(request, currentPage);
        }
        else
        {
            // retrieve current page number from request
            currentPage = AbstractProcessingStep.getCurrentPage(request);
        }

        /*
         * Update Last page reached (if necessary)
         */
        int pageReached = getPageReached(subInfo);

        // TD: Special Case, where an item was just rejected & returned to
        // Workspace. In this case, we have an invalid "pageReached" in
        // the database which needs updating
        if (pageReached == AbstractProcessingStep.LAST_PAGE_REACHED)
        {
            // the first time this flag is encountered, we have to update
            // the Database with the number of the last page in the current
            // StepReached

            // get the number of pages in the last step reached
            int lastPageNum = getNumPagesInProgressBar(subInfo,
                    SubmissionController.getStepReached(subInfo));

            // update database with the number of this last page
            updatePageReached(subInfo, lastPageNum);
            context.commit();
        }
        // otherwise, check if pageReached needs updating
        // (it only needs updating if we're in the latest step reached,
        // and we've made it to a later page in that step)
        else if ((this.stepConfig.getStepNumber() == SubmissionController
                .getStepReached(subInfo))
                && (currentPage > pageReached))
        {
            // reset page number reached & commit to database
            updatePageReached(subInfo, currentPage);
            context.commit();
        }

        /*
         * Determine whether we are Starting or Finishing this Step
         */
        // check if we just started this step
        boolean beginningOfStep = SubmissionController
                .isBeginningOfStep(request);

        // if this step has just been started, do beginning processing
        if (beginningOfStep)
        {
            return doStepStart(context, request, response, subInfo);
        }
        else
        // We just returned from a page (i.e. JSP), so we need to do any end
        // processing
        {
            return doStepEnd(context, request, response, subInfo);
        }
    }

    /**
     * Do the beginning of a Step. First do pre-processing, then display the JSP
     * (if there is one). If there's no JSP, just End the step immediately.
     * 
     * @param context
     *            current DSpace context
     * @param request
     *            current servlet request object
     * @param response
     *            current servlet response object
     * @param subInfo
     *            submission info object
     * 
     * @return true if the step is completed (no JSP was loaded), false
     *         otherwise
     * 
     */
    private boolean doStepStart(Context context, HttpServletRequest request,
            HttpServletResponse response, SubmissionInfo subInfo)
            throws ServletException, IOException, SQLException,
            AuthorizeException
    {
        log.debug("Doing pre-processing for step " + this.getClass().getName());

        // first, do any pre-processing and get the JSP to display
        // (assuming that this step has an interface)
        if(stepJSPUI!=null)
        {
            stepJSPUI.doPreProcessing(context, request, response, subInfo);
        }

        // Complete this step, if this response has not already
        // been committed.
        //
        // Note: the response should only be "committed" if the user
        // has be forwarded on to a JSP page (in which case the step
        // is not finished!).
        if (!response.isCommitted())
        {
            // Otherwise, this is a non-interactive step!
            // There's no JSP to display, so only perform the processing
            // and forward back to the Submission Controller servlet

            log.debug("Calling processing for step "
                    + this.getClass().getName());
            int errorFlag = stepProcessing.doProcessing(context, request, response, subInfo);

            // if it didn't complete successfully, try and log this error!
            if (errorFlag != AbstractProcessingStep.STATUS_COMPLETE)
            {
                // see if an error message was defined!
                String errorMessage = stepProcessing.getErrorMessage(errorFlag);

                // if not defined, construct a dummy error
                if (errorMessage == null)
                {
                    errorMessage = "The doProcessing() method for "
                            + this.getClass().getName()
                            + " returned an error flag = "
                            + errorFlag
                            + ". "
                            + "It is recommended to define a custom error message for this error flag using the addErrorMessage() method!";
                }

                log.error(errorMessage);
            }

            return completeStep(context, request, response, subInfo);
        }
        else
        {
            return false; // step not completed
        }
    }

    /**
     * This method actually displays a JSP page for this "step". This method can
     * be called from "doPostProcessing()" to display an error page, etc.
     * 
     * @param request
     *            current servlet request object
     * @param response
     *            current servlet response object
     * @param subInfo
     *            submission info object
     * @param pathToJSP
     *            context path to the JSP to display
     * 
     */
    public static final void showJSP(HttpServletRequest request,
            HttpServletResponse response, SubmissionInfo subInfo,
            String pathToJSP) throws ServletException, IOException,
            SQLException
    {

        // As long as user is not currently cancelling
        // or saving submission, show the JSP specified
        if (!response.isCommitted()
                && !SubmissionController.isCancellationInProgress(request))
        {
            // set beginningOfStep flag to false
            // (so that we know it will be time for "post-processing"
            // after loading the JSP)
            SubmissionController.setBeginningOfStep(request, false);

            // save our current Submission information into the Request
            // object (so JSP has access to it)
            SubmissionController.saveSubmissionInfo(request, subInfo);

            // save our current page information to Request object
            //setCurrentPage(request, currentPage);

            // save the JSP we are displaying
            setLastJSPDisplayed(request, pathToJSP);

            // load JSP
            JSPManager.showJSP(request, response, pathToJSP);
        }
    }

    /**
     * Do the end of a Step. If "cancel" or "progress bar" was pressed, we need
     * to forward back to the SubmissionManagerServlet immediately. Otherwise,
     * do Post-processing, and figure out if this step is truly finished or not.
     * 
     * @param context
     *            current DSpace context
     * @param request
     *            current servlet request object
     * @param response
     *            current servlet response object
     * @param subInfo
     *            submission info object
     * @return true if the step is completed (successfully), false otherwise
     * 
     */
    private boolean doStepEnd(Context context, HttpServletRequest request,
            HttpServletResponse response, SubmissionInfo subInfo)
            throws ServletException, IOException, SQLException,
            AuthorizeException
    {
        // we've returned from the JSP page
        // and need to do the processing for this step
        log.debug("Calling processing for step " + this.getClass().getName());

        int status = stepProcessing.doProcessing(context, request, response, subInfo);

        log.debug("Calling post-processing for step "
                + this.getClass().getName());

        // After doing the processing, we have to do any post-processing
        // of any potential error messages, in case we need to re-display
        // the JSP
        stepJSPUI.doPostProcessing(context, request, response, subInfo, status);

        int currentPage = AbstractProcessingStep.getCurrentPage(request);
        
        // Assuming step didn't forward back to a JSP during
        // post-processing, then check to see if this is the last page!
        if (!response.isCommitted())
        {
            // check if this step has more pages!
            if (!hasMorePages(request, subInfo, currentPage))
            {
                // this step is complete! (return completion
                // status to SubmissionController)
                return completeStep(context, request, response, subInfo);
            }
            else
            {
                // otherwise, increment to the next page
                AbstractProcessingStep.setCurrentPage(request, currentPage + 1);

                // reset to beginning of step (so that pre-processing is run
                // again)
                SubmissionController.setBeginningOfStep(request, true);

                // recursively call processStep to reload this Step class for
                // the next page
                return processStep(context, request, response, subInfo);
            }
        }
        else
        {
            // this is the case when a response was already given
            // in doPostProcessing(), which means the user was
            // forwarded to a JSP page.
            return false;
        }
    }

    /**
     * This method completes the processing of this "step" and forwards the
     * request back to the SubmissionController (so that the next step can be
     * called).
     * 
     * @param context
     *            current DSpace context
     * @param request
     *            current servlet request object
     * @param response
     *            current servlet response object
     * @param subInfo
     *            submission info object
     * 
     * @return true if step completed (successfully), false otherwise
     */
    protected final boolean completeStep(Context context,
            HttpServletRequest request, HttpServletResponse response,
            SubmissionInfo subInfo) throws ServletException, IOException,
            SQLException, AuthorizeException
    {
        log.debug("Completing Step " + this.getClass().getName());

        // If a response has not already been sent to the web browser,
        // then forward control back to the SubmissionController
        // (and let the controller know this step is complete!
        //
        // Note: The only cases in which a response would have already
        // been sent to the web browser is if an error occurred or the
        // Submission Process had to immediately exit for some reason
        // (e.g. user rejected license, or tried to submit a thesis while theses
        // are blocked, etc.)
        if (!response.isCommitted())
        {
            // save our current Submission information into the Request object
            SubmissionController.saveSubmissionInfo(request, subInfo);

            // save the current step into the Request object
            SubmissionController.saveCurrentStepConfig(request, stepConfig);

            // reset current page back to 1
            AbstractProcessingStep.setCurrentPage(request, 1);

            // return success (true) to controller!
            return true;
        }
        else
        {
            return false; // couldn't return to controller since response is
                            // committed
        }
    }

    /**
     * Checks to see if there are more pages in the current step after the
     * specified page
     * 
     * @param request
     *            The HTTP Request object
     * @param subInfo
     *            The current submission information object
     * @param pageNumber
     *            The current page
     * 
     * @throws ServletException
     *             if there are no more pages in this step
     * 
     */
    protected final boolean hasMorePages(HttpServletRequest request,
            SubmissionInfo subInfo, int pageNumber) throws ServletException
    {
        int numberOfPages = stepProcessing.getNumberOfPages(request, subInfo);

        return (pageNumber < numberOfPages);
    }

    /**
     * Find out which page a user has reached in this particular step.
     * 
     * @param subInfo
     *            Submission information
     * 
     * @return page reached
     */
    public static final int getPageReached(SubmissionInfo subInfo)
    {
        if (subInfo.isInWorkflow() || subInfo.getSubmissionItem() == null)
        {
            return -1;
        }
        else
        {
            WorkspaceItem wi = (WorkspaceItem) subInfo.getSubmissionItem();
            int i = wi.getPageReached();

            return i;
        }
    }

    /**
     * Sets the number of the page reached for the specified step
     * 
     * @param session
     *            HTTP session (where page reached is stored)
     * @param step
     *            the current Submission Process Step (which we want to
     *            increment the page reached)
     * @param pageNumber
     *            new page reached
     */
    private void updatePageReached(SubmissionInfo subInfo, int page)
            throws SQLException, AuthorizeException, IOException
    {
        if (!subInfo.isInWorkflow() && subInfo.getSubmissionItem() != null)
        {
            WorkspaceItem wi = (WorkspaceItem) subInfo.getSubmissionItem();

            if (page > wi.getPageReached())
            {
                wi.setPageReached(page);
                wi.update();
            }
        }
    }

    /**
     * Return the number pages for the given step in the Submission Process
     * according to the Progress Bar. So, if "stepNum" is 7, this will return
     * the number of pages in step #7 (at least according to the progressBar)
     * 
     * @param subInfo
     *            current Submission Information object
     * @param stepNum
     *            the number of the step
     * 
     * @return the number of pages in the step
     */
    private static int getNumPagesInProgressBar(SubmissionInfo subInfo,
            int stepNum)
    {
        // get the keys of the progressBar information (key format:
        // stepNum.pageNum)
        Iterator keyIterator = subInfo.getProgressBarInfo().keySet().iterator();

        // default to last page being page #1
        int lastPage = 1;

        while (keyIterator.hasNext())
        {
            // find step & page info (format: stepNum.pageNum)
            String stepAndPage = (String) keyIterator.next();

            if (stepAndPage.startsWith(stepNum + "."))
            {
                // split into stepNum and pageNum
                String[] fields = stepAndPage.split("\\."); // split on period
                int page = Integer.parseInt(fields[1]);

                if (page > lastPage)
                {
                    // update last page found for this step
                    lastPage = page;
                }
            }
        }

        // return # of last page found for this step (which is also the number
        // of pages)
        return lastPage;
    }

    /**
     * Retrieves the context path of the last JSP that was displayed to the
     * user. This is useful in the "doPostProcessing()" method to determine
     * which JSP has just submitted form information.
     * 
     * @param request
     *            current servlet request object
     * 
     * @return pathToJSP The context path to the JSP page (e.g.
     *         "/submit/select-collection.jsp")
     */
    public static final String getLastJSPDisplayed(HttpServletRequest request)
    {
        String jspDisplayed = (String) request.getAttribute("jsp");

        if ((jspDisplayed == null) || jspDisplayed.length() == 0)
        {
            // try and retrieve the JSP name as a request parameter
            if (request.getParameter("jsp") == null)
            {
                jspDisplayed = "";
            }
            else
            {
                jspDisplayed = request.getParameter("jsp");
            }
        }

        return jspDisplayed;
    }

    /**
     * Saves the context path of the last JSP that was displayed to the user.
     * 
     * @param request
     *            current servlet request object
     * @param pathToJSP
     *            The context path to the JSP page (e.g.
     *            "/submit/select-collection.jsp")
     */
    private static final void setLastJSPDisplayed(HttpServletRequest request,
            String pathToJSP)
    {
        // save to request
        request.setAttribute("jsp", pathToJSP);
    }
    
    
    /**
     * Return the URL path (e.g. /submit/review-metadata.jsp) of the JSP
     * which will review the information that was gathered in the currently
     * loaded Step.
     * <P>
     * This Review JSP is loaded by the 'Verify' Step, in order to dynamically
     * generate a submission verification page consisting of the information
     * gathered in all the enabled submission steps.
     * 
     * @param context
     *            current DSpace context
     * @param request
     *            current servlet request object
     * @param response
     *            current servlet response object
     * @param subInfo
     *            submission info object
     */
    public String getReviewJSP(Context context, HttpServletRequest request,
            HttpServletResponse response, SubmissionInfo subInfo)
    {
        return stepJSPUI.getReviewJSP(context, request, response, subInfo);
    }
    
}
