package gov.va.vinci.dart.wf2;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Service;

import gov.va.vinci.dart.biz.Comment;
import gov.va.vinci.dart.biz.Event;
import gov.va.vinci.dart.biz.EventType;
import gov.va.vinci.dart.biz.Group;
import gov.va.vinci.dart.biz.Request;
import gov.va.vinci.dart.biz.RequestStatus;
import gov.va.vinci.dart.biz.RequestWorkflow;
import gov.va.vinci.dart.biz.Review;
import gov.va.vinci.dart.biz.Role;
import gov.va.vinci.dart.common.exception.ObjectNotFoundException;
import gov.va.vinci.dart.common.exception.ValidationException;

/**
 * A workflow for VINCI DART NDS requests.
 * 
 */
@Service
public class WfNDS extends AbstractWorkflow {

    /** The log. */
    private static Log log = LogFactory.getLog(WfNDS.class);

    /** The Constant INITIAL_STATE. */
    public static final int INITIAL_STATE = 1;

    /** The Constant SUBMITTED_STATE. */
    public static final int SUBMITTED_STATE = 2;

    /** The Constant INTERMEDIATE_REVIEW_STATE. */
    public static final int INTERMEDIATE_REVIEW_STATE = 32;

    /** The Constant NDS_FINAL_REVIEW_STATE. */
    public static final int NDS_FINAL_REVIEW_STATE = 9; // ready for final NDS review

    /** The Constant FINAL_STATE. */
    public static final int FINAL_STATE = 16;

    /** The Constant ALL_REVIEW_MASK. */
    public static final long ALL_REVIEW_MASK = 0x01F8L; // all of the possible review groups -> needs to correspond to

    /**
     * The Constant INIT_MASK. 2047, allClear (no intermediate reviews remaining to be done) = 1543 (before adding Homeless
     * Registry group)
     */

    public static final long INIT_MASK = 0x7ffL;

    /***
     * Get the final state for the current workflow item.
     *
     * @return the final state
     */
    @Override
    public int getFinalState() {
        return FINAL_STATE;
    }

    @Override
    public int getFinalReviewState() {
        return NDS_FINAL_REVIEW_STATE;
    }

    /**
     * Gets the intermediate review state.
     *
     * @return the intermediate review state
     */
    public int getIntermediateReviewState() {
        return INTERMEDIATE_REVIEW_STATE;
    }

    @Override
    public int getSubmittedState() {
        return SUBMITTED_STATE;
    }

    /**
     * Calculate the percentage of review completion on the request.
     *
     * @param workflow
     *            the workflow
     * @param request
     *            the request
     * @return the string
     */
    @Override
    public String calculateReviewCompletion(final RequestWorkflow workflow, final Request request) {
        int completedCount = 0;
        int totalReviewCount = 0;

        if (request != null) {

            Set<Review> reviewSet = request.getReviews(workflow);

            if ((reviewSet == null || reviewSet.size() < 1) || (isReadyForInitialReview(workflow, request))) {
                // if the initial NDS review has not yet occurred, default to 0% completed
                return "0%";
            } else {
                // NDS Initial Review added to both the complete count and total count
                completedCount += 1;
                totalReviewCount += 1;
            }

            // Reviews could have been approved and then withdrawn
            for (Review rev : reviewSet) {
                if (!rev.isWithdrawn()) {
                    totalReviewCount += 1;
                    if (rev.isApproved() || rev.isRejected()) {
                        completedCount += 1;
                    }
                }

            }

            totalReviewCount += 1; // NDS final review?
        }

        return ((completedCount * 100) / totalReviewCount) + "%";
    }

    /**
     * Returns true if ready for the NDS initial review, based on workflow state.
     *
     * @param workflow
     *            the workflow
     * @param request
     *            the request
     * @return true, if is ready for initial review
     */
    @Override
    public boolean isReadyForInitialReview(final RequestWorkflow workflow, final Request request) {

        if (request != null) {

            try {
                int workflowState = -1;
                if (workflow != null) {
                    workflowState = workflow.getWorkflowState(); // specific workflow state
                } else {
                    workflowState = request.getWorkflowState(); // top-level workflow state (old data)
                }

                if (workflowState == getSubmittedState() && request.isSubmittedOrChanged(workflow))
                    // Initial NDS review state, and submitted or change requested
                    return true;

            } catch (ObjectNotFoundException e) {
                log.debug("Exception occurred while determining the initial NDS review state:  " + e.getMessage());

            }
        }

        return false;
    }

    /**
     * Returns true if the NDS initial review is completed, based on workflow state.
     *
     * @param workflow
     *            the workflow
     * @param request
     *            the request
     * @return true, if is initial review completed
     */
    @Override
    public boolean isInitialReviewCompleted(final RequestWorkflow workflow, final Request request) {

        if (request != null) {

            try {
                int workflowState = -1;
                if (workflow != null) {
                    workflowState = workflow.getWorkflowState(); // specific workflow state
                } else {
                    workflowState = request.getWorkflowState(); // top-level workflow state (old data)
                }

                if (workflowState > getSubmittedState() && request.isSubmittedOrChanged(workflow))
                    // initial NDS approval done (moved on to the intermediate review state)
                    return true;

            } catch (ObjectNotFoundException e) {
                log.debug("Exception occurred while determining the initial NDS review state:  " + e.getMessage());

            }
        }

        return false;
    }

    /**
     * Returns true if ready for the NDS final review, based on workflow state All intermediate reviews are completed.
     *
     * @param workflow
     *            the workflow
     * @param request
     *            the request
     * @return true, if is ready for final review
     */
    @Override
    public boolean isReadyForFinalReview(final RequestWorkflow workflow, final Request request) {

        if (request != null) {

            try {
                int workflowState = -1;
                if (workflow != null) {
                    workflowState = workflow.getWorkflowState(); // specific workflow state
                } else {
                    workflowState = request.getWorkflowState(); // top-level workflow state (old data)
                }

                if (workflowState == getFinalReviewState() && request.isSubmittedOrChanged(workflow))
                    return true;

            } catch (ObjectNotFoundException e) {
                log.debug("Exception occurred while determining the final NDS review state:  " + e.getMessage());

            }

        }

        return false;
    }

    /**
     * Returns true if ready for the NDS final review, based on workflow state All intermediate reviews are completed.
     *
     * @param workflow
     *            the workflow
     * @param request
     *            the request
     * @return true, if is ready for final review
     */
    @Override
    public boolean isReadyForGroupReview(final RequestWorkflow workflow, final Request request) {

        if (request != null) {

            try {
                int workflowState = -1;
                if (workflow != null) {
                    workflowState = workflow.getWorkflowState(); // specific workflow state
                } else {
                    workflowState = request.getWorkflowState(); // top-level workflow state (old data)
                }

                if (workflowState == getIntermediateReviewState() && request.isSubmittedOrChanged(workflow))
                    return true;

            } catch (ObjectNotFoundException e) {
                log.debug("Exception occurred while determining the final NDS review state:  " + e.getMessage());

            }

        }

        return false;
    }

    /**
     * Returns true if the final NDS review has been completed, based on workflow state.
     *
     * @param workflow
     *            the workflow
     * @param request
     *            the request
     * @return true, if is final review completed
     */
    @Override
    public boolean isFinalReviewCompleted(final RequestWorkflow workflow, final Request request) {

        if (request != null) {

            try {
                int workflowState = -1;
                if (workflow != null) {
                    workflowState = workflow.getWorkflowState(); // specific workflow state
                } else {
                    workflowState = request.getWorkflowState(); // top-level workflow state (old data)
                }

                if (workflowState == getFinalState())
                    return true;

            } catch (Exception e) {
                log.debug("Exception occurred while determining the final NDS review state:  " + e.getMessage());

            }

        }

        return false;
    }

    /**
     * Return the mask for the (intermediate) review, based on group ID.
     *
     * @param group
     *            the group
     * @return the group workflow mask
     */
    public long getGroupWorkflowMask(final Group group) {

        Group.initialize(); // this may be extra, but initialize just in case

        if (group != null) {

            if (group.getId() == Group.ORD.getId()) {
                return 0x08L;
            } else if (group.getId() == Group.PRIVACY.getId()) {
                return 0x10L;
            } else if (group.getId() == Group.SECURITY.getId()) {
                return 0x020L;
            } else if (group.getId() == Group.OEFOIF.getId()) {
                return 0x040L;
            } else if (group.getId() == Group.SQDUG.getId()) {
                return 0x080L;
            } else if (group.getId() == Group.CAPRI.getId()) {
                return 0x100L;
            } else if (group.getId() == Group.HOMELESS_REGISTRY.getId()) {
                return 0x200L;
            }

        }

        return 0L;
    }

    public long resetGroupWorkflowMask(final List<Group> groupList) {

        Group.initialize();
        long workflowMask = 0L;

        if (CollectionUtils.isNotEmpty(groupList)) {

            for (Group group : groupList) {
                long reviewGroupMask = getGroupWorkflowMask(group);

                workflowMask |= reviewGroupMask; // add this group to the overall mask
            }
        }

        return workflowMask;
    }

    /**
     * Handle an event on the current workflow item.
     *
     * @param workflow
     *            the workflow
     * @param review
     *            the review
     * @param request
     *            the request
     * @param operation
     *            the operation
     * @param userLoginId
     *            the user login id
     * @throws WorkflowException
     *             the workflow exception
     */
    @Override
    protected void transition(final RequestWorkflow workflow, final Review review, final Request request, final int operation,
            final String userLoginId) throws WorkflowException {

        int workflowState = -1;

        if (workflow != null) {
            workflowState = workflow.getWorkflowState();
        } else {
            workflowState = request.getWorkflowState(); // use the top-level workflow state
        }

        switch (workflowState) {

        case INITIAL_STATE:
            processInitialState(workflow, request, operation, userLoginId);
            break;
        case SUBMITTED_STATE:
            processSubmittedState(workflow, request, operation, userLoginId);
            break;
        case INTERMEDIATE_REVIEW_STATE:
            processIntermediateState(workflow, review, request, operation, userLoginId);
            break;
        case NDS_FINAL_REVIEW_STATE:
            processNDSFinalReviewState(workflow, request, operation, userLoginId);
            break;
        case FINAL_STATE:
            return;
        default:
            throw new WorkflowException(
                    "Illegal workflow state " + workflowState + " encountered in request " + request.getTrackingNumber());
        }
    }

    /**
     * Initialize workflow processing for the current workflow item.
     *
     * @param workflow
     *            the workflow
     * @param request
     *            the request
     * @param userLoginId
     *            the user login id
     * @throws WorkflowException
     *             the workflow exception
     */
    @Override
    public void initialize(final RequestWorkflow workflow, final Request request, final String userLoginId)
            throws WorkflowException {

        long mask = -1;
        // workflow is null when being initialized by the create activity or creating an amendment
        // else the NDS workflow was created by Prep or Research workflows
        if (workflow != null) {
            workflow.setWorkflowMask(INIT_MASK); // set the child workflow mask
            mask = workflow.getWorkflowMask();
        } else {
            request.setWorkflowMask(INIT_MASK); // set the top-level workflow mask
            mask = request.getWorkflowMask();
        }

        log.debug("initialized workflow mask to:  " + mask);
        log.debug("WfNDS initialize " + request.getTrackingNumber());

        setState(workflow, null, request, INITIAL_STATE, userLoginId);
    }

    /**
     * Set the state of the current workflow item. If at the beginning, increment the state normally (increment to an
     * appropriate mask value) If in an intermediate review, go to intermediate review state
     * 
     * If the new state is not permitted by the mask, the state is incremented until an allowed state or a final state is found.
     * If the new state is final no other action is taken.
     *
     * @param workflow
     *            the workflow
     * @param review
     *            the review
     * @param request
     *            the request
     * @param newState
     *            the new state
     * @param userLoginId
     *            the user login id
     * @throws WorkflowException
     *             the workflow exception
     */
    @Override
    protected void setState(final RequestWorkflow workflow, final Review review, final Request request, final int newState,
            final String userLoginId) throws WorkflowException {

        if (workflow != null) {
            workflow.setWorkflowState(newState); // set the workflow state for this specific workflow (NOT just for the request
                                                 // as a whole)
        } else {
            request.setWorkflowState(newState); // set the top-level workflow state
        }

        // move directly to the specified state
        transition(workflow, review, request, WF_OPERATION_ENTRY, userLoginId);

    }

    /**
     * request submission state handler.
     *
     * @param workflow
     *            the workflow
     * @param request
     *            the request
     * @param operation
     *            the operation
     * @param userLoginId
     *            the user login id
     * @throws WorkflowException
     *             the workflow exception
     */
    protected void processInitialState(final RequestWorkflow workflow, final Request request, final int operation,
            final String userLoginId) throws WorkflowException {
        log.debug("WfNDS state one operation = " + operation + " request = " + request.getTrackingNumber());

        final boolean isInitNDSReview = true;
        final boolean isFinalNDSReview = false;

        try {
            EventType.initialize(); // this may be extra, but initialize just in case
            Group.initialize();

            if (operation == Workflow.WF_OPERATION_ENTRY) {
                return; // nothing to do if we just entered the state
            }

            if (operation == Workflow.WF_OPERATION_SUBMIT) {
                log.debug("request submitted!");

                // get the workflow / request status
                RequestStatus requestStatus = request.getStatus(); // use the request-level status
                if (workflow != null) {
                    requestStatus = workflow.getRequestStatus(); // use the workflow-level status
                }

                // create event(s)
                if (requestStatus.getId() == RequestStatus.CHANGE_REQUESTED.getId()) {
                    if (workflow == null) { // old request
                        createReqSubmittedEvent(workflow, request, userLoginId, isInitNDSReview, isFinalNDSReview);
                    }
                } else {
                    if (workflow == null) { // old request
                        Event.create("Submitted", "Submitting Data Access Request Packet to NDS", EventType.SUBMIT_REQUEST,
                                Group.NDS, true, request, userLoginId);
                    }
                }

                // Per Jeff S. request, also track an event for "Sent for Initial NDS Review"
                Event.create("Request Sent for Initial NDS Review", "Request Sent for Initial NDS Review",
                        EventType.SENT_FOR_REVIEW, Group.NDS, true, request, userLoginId);

                // TODO: might want to create the "submitted" email for NDS in this state instead of in stateTwo() so that the
                // subject has "submitted with changes"?

                // update the workflow / request status
                if (workflow != null) {
                    workflow.submit(userLoginId); // update the child workflow status
                } else {
                    request.submit(userLoginId); // update the request status
                }

                // close tasks relative to this request (if this is a change request, update)
                TaskUtils.closeUserTasksForChangeRequest(workflow, request, userLoginId);

                // go to the next state (initial NDS review)
                incrementState(workflow, null, request, userLoginId);
            } else if (operation == Workflow.WF_OPERATION_CLOSE) {

                EmailUtils.createAndSendNDSOperationalCloseEmails(workflow, request);

                setState(workflow, null, request, FINAL_STATE, userLoginId); // go to final state
            } else {
                log.error("Error: request " + request.getTrackingNumber() + " in unexpected state.");
            }
        } catch (Exception e) {
            throw new WorkflowException(e);
        }

    }

    /**
     * NDS initial review state handler.
     *
     * @param workflow
     *            the workflow
     * @param request
     *            the request
     * @param operation
     *            the operation
     * @param userLoginId
     *            the user login id
     * @throws WorkflowException
     *             the workflow exception
     */
    protected void processSubmittedState(final RequestWorkflow workflow, final Request request, final int operation,
            final String userLoginId) throws WorkflowException {
        log.debug("WfNDS state two operation = " + operation + " request = " + request.getTrackingNumber());

        try {
            EventType.initialize(); // this may be extra, but initialize just in case
            Group.initialize();

            if (operation == Workflow.WF_OPERATION_ENTRY) {

                createAndSendInitialReviewEmail(workflow, request);

                sendNDSAdminATask(workflow, request, userLoginId, true);
                return; // stay in this state
            }

            if (operation == Workflow.WF_OPERATION_CHANGE_REQUEST) {

                Set<Review> reviewSet = request.getReviews(workflow); // get the reviews for this workflow

                Event.create("Change Requested by Initial NDS Review", "Change Requested by Initial NDS Review",
                        EventType.CHANGE_REQUEST, Group.NDS, true, request, userLoginId);

                TaskUtils.closeGroupTasksForNDSReview(request, userLoginId);

                setState(workflow, null, request, 1, userLoginId); // go back to state 1
            } else if (operation == Workflow.WF_OPERATION_APPROVE) {

                processSubmittedStateApproval(workflow, request, userLoginId);

            } else if (operation == Workflow.WF_OPERATION_DENY) {
                // create an event
                Event.create("Initial NDS Review Denied", "Initial NDS Review Denied", EventType.DENY_REVIEW, Group.NDS, true,
                        request, userLoginId);

                if (workflow == null) { // old NDS request

                    Set<Review> reviewSet = request.getReviews(workflow); // get the reviews for this workflow
                    sendParticipantsDenyEmail(workflow, reviewSet, request, Group.NDS.getShortName(), userLoginId,
                            Workflow.WF_OPERATION_DENY);
                }

                TaskUtils.closeAllTasksForWorkflowAndRequest(workflow, request, userLoginId);

                setState(workflow, null, request, FINAL_STATE, userLoginId); // go to final state
            } else if (operation == Workflow.WF_OPERATION_CLOSE) {

                EmailUtils.createAndSendNDSOperationalCloseEmails(workflow, request);

                setState(workflow, null, request, FINAL_STATE, userLoginId); // go to final state
            } else {
                log.error("Error: request " + request.getTrackingNumber() + " in unexpected state.");
            }

        } catch (Exception e) {
            throw new WorkflowException(e);
        }
    }

    /**
     * Process submitted state approval.
     *
     * @param workflow
     *            the workflow
     * @param request
     *            the request
     * @param userLoginId
     *            the user login id
     * @throws ValidationException
     *             the validation exception
     * @throws WorkflowException
     *             the workflow exception
     */
    private void processSubmittedStateApproval(final RequestWorkflow workflow, final Request request, final String userLoginId)
            throws ValidationException, WorkflowException {
        // create an event
        Event.create("Initial NDS Review Complete", "Initial NDS Review Complete", EventType.APPROVE_REVIEW, Group.NDS, true,
                request, userLoginId);

        // update the workflow mask for the reviews
        long tmpWorkflowMask = request.getWorkflowMask(); // use the top-level workflow mask
        if (workflow != null) {
            tmpWorkflowMask = workflow.getWorkflowMask(); // use the child workflow mask
        }

        log.debug("tmpWorkflowMask = " + tmpWorkflowMask);

        tmpWorkflowMask &= ~(ALL_REVIEW_MASK); // clear review state bits from mask

        log.debug("tmpWorkflowMask after clearing = " + tmpWorkflowMask);

        // which reviews are selected by the NDS administrator?
        boolean hasReview = false;

        Set<Review> reviewSet = request.getReviews(workflow); // get the reviews for this workflow
        if (reviewSet != null) {
            for (Review review : reviewSet) { // step through the reviews for this workflow

                Group reviewGroup = review.getReviewer();
                if (reviewGroup != null) {

                    long reviewGroupMask = getGroupWorkflowMask(review.getReviewer()); // mask for this review group
                    if (reviewGroupMask != 0L) {
                        tmpWorkflowMask |= reviewGroupMask; // add this group to the overall mask
                        hasReview = true; // at least one intermediate review has been selected by NDS

                        // track this event (sent the review to this group)
                        String reviewGroupShortName = reviewGroup.getShortName();
                        Event.create("Request Sent for " + reviewGroupShortName + " Review",
                                "Request Sent for " + reviewGroupShortName + " Review", EventType.SENT_FOR_REVIEW, reviewGroup,
                                false, request, review, userLoginId);

                        sendGroupMembersAReviewEmail(workflow, reviewGroup, review, reviewSet, request,
                                Group.NDS.getShortName(), userLoginId, Workflow.WF_OPERATION_APPROVE);
                    }
                }
            }
        }

        log.debug("tmpWorkflowMask after adding review states = " + tmpWorkflowMask);

        log.debug("hasReview = " + hasReview);

        if (!hasReview) {
            // the review controller will call the request controller approve method
            // so let's go to the correct state.
            setState(workflow, null, request, NDS_FINAL_REVIEW_STATE, userLoginId);
            return;
        }

        // save the selected reviews in the request workflow mask
        if (workflow != null) {
            workflow.setWorkflowMask(tmpWorkflowMask); // set the child workflow mask
            // log.debug("set workflowMask: " + workflow.getWorkflowMask());
        } else {
            request.setWorkflowMask(tmpWorkflowMask); // set the top-level workflow mask
            // log.debug("set workflowMask: " + request.getWorkflowMask());
        }

        sendParticipantsInitialNDSReviewCompletedEmail(workflow, reviewSet, request, Group.NDS.getShortName(), userLoginId,
                Workflow.WF_OPERATION_APPROVE);

        TaskUtils.closeGroupTasksForNDSReview(request, userLoginId);

        // go to intermediate review state
        setState(workflow, null, request, INTERMEDIATE_REVIEW_STATE, userLoginId);
        return;
    }

    /**
     * Send initial review email.
     *
     * @param workflow
     *            the workflow
     * @param request
     *            the request
     */
    private void createAndSendInitialReviewEmail(final RequestWorkflow workflow, final Request request) {
        StringBuffer fullName = new StringBuffer();
        String groupShortName = "the Study Participants";
        StringBuffer subject =
                EmailUtils.createGroupMemberEmailSubject(workflow, request, groupShortName, Workflow.WF_OPERATION_SUBMIT);

        StringBuffer initialNDSBody = EmailUtils.createInitialReviewEmailBody(request);

        EmailUtils.sendEmailToNDSAdminList(subject.toString(), initialNDSBody.toString());
    }

    /**
     * NDS Final review state handler.
     *
     * @param workflow
     *            the workflow
     * @param request
     *            the request
     * @param operation
     *            the operation
     * @param userLoginId
     *            the user login id
     * @throws WorkflowException
     *             the workflow exception
     */
    protected void processNDSFinalReviewState(final RequestWorkflow workflow, final Request request, final int operation,
            final String userLoginId) throws WorkflowException {
        log.debug("WfNDS state nine operation = " + operation + " request = " + request.getTrackingNumber());

        final boolean isInitNDSReview = false;
        final boolean isFinalNDSReview = true;

        try {
            EventType.initialize(); // this may be extra, but initialize just in case
            Group.initialize();
            Role.initialize();

            if (operation == Workflow.WF_OPERATION_ENTRY) {

                log.debug("state 9 entry.  email NDS reviewers");

                // don't send emails if there were no intermediate reviews selected
                Set<Review> reviewSet = request.getReviews(workflow);
                if (reviewSet != null && reviewSet.size() > 0) {

                    EmailUtils.createAndSendRequestReadyForFinalApprovalEmail(workflow, request);

                    sendNDSAdminATask(workflow, request, userLoginId, false);

                }

                processChangeRequested(workflow, request, userLoginId, isInitNDSReview, isFinalNDSReview);
                Event.create("Request Sent for Final NDS Review", "Request Sent for Final NDS Review",
                        EventType.SENT_FOR_REVIEW, Group.NDS, false, request, userLoginId);

                return; // stay in this state
            }

            else if (operation == Workflow.WF_OPERATION_SUBMIT) { // submitted request with changes (change requested by final
                                                                  // NDS review)
                log.debug("request submitted (with changes)!");

                processChangeRequested(workflow, request, userLoginId, isInitNDSReview, isFinalNDSReview);
                Event.create("Request Sent for Final NDS Review", "Request Sent for Final NDS Review",
                        EventType.SENT_FOR_REVIEW, Group.NDS, false, request, userLoginId);

                if (workflow != null) {
                    workflow.submit(userLoginId); // update the child workflow status
                } else {
                    request.submit(userLoginId); // update the request status
                }

                // close tasks relative to this request (if this is a change request, update)
                TaskUtils.closeUserTasksForChangeRequest(workflow, request, userLoginId);

                EmailUtils.createAndSendChangeSubmittedEmails(request, Group.NDS, new Date());

                // don't send emails if there were no intermediate reviews selected
                Set<Review> reviewSet = request.getReviews(workflow);
                if (reviewSet != null && reviewSet.size() > 0) {
                    sendNDSAdminATask(workflow, request, userLoginId, false); // final NDS
                }

                return; // stay in this state
            }

            if (operation == Workflow.WF_OPERATION_APPROVE) {

                Event.create("Final NDS Approval Complete", "Final NDS Approval Complete", EventType.APPROVE_REVIEW, Group.NDS,
                        false, request, userLoginId);

                // save a communication
                Comment.create("NDS Review Completed", request, userLoginId,
                        "VINCI Dart request " + request.getTrackingNumber() + " approved by NDS!");

                // add an event (multi-workflow support)
                Event.create("NDS Request Approval", "NDS Request Approval", EventType.APPROVE_WORKFLOW, Group.NDS, false,
                        request, userLoginId);

                if (workflow == null) { // old NDS request: email the requestor and read-only groups about this workflow.

                    EmailUtils.createAndSendOperationalApprovalEmails(workflow, request);

                }

                TaskUtils.closeGroupTasksForNDSReview(request, userLoginId);

                setState(workflow, null, request, FINAL_STATE, userLoginId); // go to final state

            } else if (operation == Workflow.WF_OPERATION_CHANGE_REQUEST) {

                Set<Review> reviewSet = request.getReviews(workflow); // get the reviews for this workflow

                // create an event
                Event.create("Change Requested by Final NDS Review", "Change Requested by Final NDS Review",
                        EventType.CHANGE_REQUEST, Group.NDS, false, request, userLoginId);

                // close tasks relative to this request
                TaskUtils.closeGroupTasksForNDSReview(request, userLoginId);

                return; // stay in this state
            } else if (operation == Workflow.WF_OPERATION_DENY) {
                // create an event
                Event.create("Final NDS Review Denied", "Request Denied", EventType.DENY_REVIEW, Group.NDS, false, request,
                        userLoginId);

                if (workflow == null) { // old NDS request

                    Set<Review> reviewSet = request.getReviews(workflow); // get the reviews for this workflow
                    createAndSendGroupMembersAReviewEmail(workflow, Group.NDS, reviewSet, request, Group.NDS.getShortName(),
                            userLoginId, Workflow.WF_OPERATION_DENY);

                    sendParticipantsDenyEmail(workflow, reviewSet, request, Group.NDS.getShortName(), userLoginId,
                            Workflow.WF_OPERATION_DENY);
                }

                TaskUtils.closeAllTasksForWorkflowAndRequest(workflow, request, userLoginId);

                setState(workflow, null, request, FINAL_STATE, userLoginId); // go to final state
            } else if (operation == Workflow.WF_OPERATION_CLOSE) {

                EmailUtils.createAndSendNDSOperationalCloseEmails(workflow, request);
                // NOTE: these tasks are closed in RequestController instead
                setState(workflow, null, request, FINAL_STATE, userLoginId); // go to final state
            } else {
                log.error("Error: request " + request.getTrackingNumber() + " in unexpected state.");
            }
        } catch (Exception e) {
            throw new WorkflowException(e);
        }
    }

    /**
     * intermediate review state handler
     * 
     * If this review hasn't yet been done, allow it to be approved / denied Compare to the overall mask (at the request) ->
     * list of reviews to be done Update the mask for this review once it has been done.
     *
     * @param workflow
     *            the workflow
     * @param review
     *            -- review that is currently being processed
     * @param request
     *            the request
     * @param operation
     *            the operation
     * @param userLoginId
     *            the user login id
     * @throws WorkflowException
     *             the workflow exception
     */
    @SuppressWarnings("rawtypes")
    protected synchronized void processIntermediateState(final RequestWorkflow workflow, final Review review,
            final Request request, final int operation, final String userLoginId) throws WorkflowException {
        log.debug("WfNDS stateIntermediateReview operation = " + operation + " request = " + request.getTrackingNumber());

        final boolean isInitNDSReview = false;
        final boolean isFinalNDSReview = false;
        Review reviewForGroup = null;

        try {
            EventType.initialize(); // this may be extra, but initialize just in case
            Group.initialize();

            if (review == null) {

                if (operation == Workflow.WF_OPERATION_ENTRY) {

                    processChangeRequested(workflow, request, userLoginId, isInitNDSReview, isFinalNDSReview);

                    return; // stay in this state
                }

                else if (operation == Workflow.WF_OPERATION_SUBMIT) {
                    RequestStatus requestStatus = request.getStatus(); // use the request-level status
                    if (workflow != null) {
                        requestStatus = workflow.getRequestStatus(); // use the workflow-level status
                    }

                    
                    if (request.isProcessWithdraw()) {
                        processModificationToReviewGroupsByNDS(workflow, review, request, userLoginId);

                    } else if (requestStatus.getId() == RequestStatus.CHANGE_REQUESTED.getId()) {
                        processIntermediateStateChange(workflow, request, userLoginId, isInitNDSReview, isFinalNDSReview);
                    }

                    return;
                }

                else if (operation == Workflow.WF_OPERATION_CLOSE) {

                    EmailUtils.createAndSendNDSOperationalCloseEmails(workflow, request);

                    setState(workflow, review, request, FINAL_STATE, userLoginId);
                }

                else {
                    log.error("Error: request " + request.getTrackingNumber() + " unexpected workflow operation " + operation
                            + " for state Intermediate Review.");
                }

            } else { // have the review to work with (intermediate review)

                Group group = review.getReviewer();

                final String groupShortName = group.getShortName();
                long reviewMask = getGroupWorkflowMask(group); // mask for this review group

                // verify that there is a review for this current group . . .
                reviewForGroup = request.findReviewAssignedToGroup(workflow, group);
                if (reviewForGroup == null) {
                    throw new WorkflowException("Error: could not find review assigned to " + groupShortName
                            + " group for request " + request.getTrackingNumber());
                }

                // current reviewer group approves
                if (operation == Workflow.WF_OPERATION_APPROVE) { // intermediate review approved

                    processIntermediateStateApprovalHasReview(workflow, review, request, userLoginId, group, groupShortName,
                            reviewMask);

                } else if (operation == Workflow.WF_OPERATION_CHANGE_REQUEST) { // change requested by intermediate review group

                    processIntermediateStateChangeHasReview(workflow, request, review, userLoginId, group, groupShortName);

                    // stay in this state (intermediate review) -> send the change request to the requestor
                } else if (operation == Workflow.WF_OPERATION_DENY) {

                    processIntermediateStateDenyHasReview(workflow, review, request, userLoginId, group, groupShortName);
                } else if (operation == Workflow.WF_OPERATION_CLOSE) {

                    EmailUtils.createAndSendNDSOperationalCloseEmails(workflow, request);

                    setState(workflow, review, request, FINAL_STATE, userLoginId);
                } else {
                    log.error("Error: request " + request.getTrackingNumber() + " unexpected workflow operation " + operation
                            + " for state Intermediate Review.");
                }
            }
        } catch (Exception e) {
            throw new WorkflowException(e);
        }
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void processModificationToReviewGroupsByNDS(final RequestWorkflow workflow, final Review review,
            final Request request, final String userLoginId) throws WorkflowException {
        try {
        List<Group> groupList = new ArrayList();
        Set<Review> reviews = request.getReviews(workflow);
        for (Review tempReview : reviews) {
            if (!tempReview.isWithdrawn() && !tempReview.isApproved()) {
                groupList.add(tempReview.getReviewer());
            }
        }

        request.setWorkflowMask(resetGroupWorkflowMask(groupList));
        
        if (!request.anyReviewChangeRequested(workflow)){
            if (workflow != null) {
                workflow.submit(userLoginId); // update the child workflow status
            } else {
                request.submit(userLoginId); // update the request status
            }
        }

        
        if (request.allReviewsApproved(workflow)) {
            setState(workflow, review, request, NDS_FINAL_REVIEW_STATE, userLoginId);
        } 
        
        // else stay in this state
        }
        
        catch (Exception e) {
            throw new WorkflowException(e);
        }
       
    }

    /**
     * Process intermediate state change has review.
     *
     * @param workflow
     *            the workflow
     * @param request
     *            the request
     * @param userLoginId
     *            the user login id
     * @param group
     *            the group
     * @param groupShortName
     *            the group short name
     * @throws ValidationException
     *             the validation exception
     */
    private void processIntermediateStateChangeHasReview(final RequestWorkflow workflow, final Request request, Review review,
            final String userLoginId, Group group, final String groupShortName) throws ValidationException {

        Set<Review> reviewSet = request.getReviews(workflow); // get the reviews for this workflow

        // create an event
        Event.create("Change Requested by " + groupShortName, "Change Requested by " + groupShortName, EventType.CHANGE_REQUEST,
                group, false, request, review, userLoginId);

        // close the task for this group (for this workflow, if applicable)
        TaskUtils.closeTasksForWorkflowAndGroup(workflow, group, request, userLoginId);
    }

    /**
     * Process intermediate state approval has review.
     *
     * @param workflow
     *            the workflow
     * @param review
     *            the review
     * @param request
     *            the request
     * @param userLoginId
     *            the user login id
     * @param group
     *            the group
     * @param groupShortName
     *            the group short name
     * @param reviewMask
     *            the review mask
     * @throws ValidationException
     *             the validation exception
     * @throws WorkflowException
     *             the workflow exception
     */
    private void processIntermediateStateApprovalHasReview(final RequestWorkflow workflow, final Review review,
            final Request request, final String userLoginId, Group group, final String groupShortName, long reviewMask)
            throws ValidationException, WorkflowException {
        // verify that this review has not yet been approved
        if (request.isReviewOpen(workflow, group)) {

            // approve this review (now that we have verified that this review is open) -> use the attached reviews
            // to determine which state to move to next
            review.approve(userLoginId);

            Event.create(groupShortName + " Review Approval", groupShortName + " Review Approval", EventType.APPROVE_REVIEW,
                    group, false, request, review, userLoginId);

            List<Review> reviewList = getRemainingReviewersList(workflow, request);
            sendParticipantsIntermediateEmail(workflow, reviewList, request, groupShortName, userLoginId,
                    Workflow.WF_OPERATION_APPROVE); // intermediate review approved

            TaskUtils.closeTasksForWorkflowAndGroup(workflow, group, request, userLoginId);

            // update the review flags in the request (finished this review)
            long tmpWorkflowMask = request.getWorkflowMask(); // use the top-level workflow mask
            if (workflow != null) {
                tmpWorkflowMask = workflow.getWorkflowMask(); // use the child workflow mask
            }

            tmpWorkflowMask &= ~(reviewMask); // clear review state bits from mask
            if (workflow != null) {
                workflow.setWorkflowMask(tmpWorkflowMask); // set the child workflow mask

            } else {
                request.setWorkflowMask(tmpWorkflowMask); // set the top-level workflow mask

            }

            // if all intermediate reviews have been approved, move to the final NDS review state. Otherwise, stay
            // in this state (intermediate review).
            if (request.allReviewsApproved(workflow)) { // no intermediate reviews left
                // go to final NDS review state
                setState(workflow, review, request, NDS_FINAL_REVIEW_STATE, userLoginId);
            }
        } else {
            log.error("Error: request " + request.getTrackingNumber() + " has already been approved by " + groupShortName);
        }
    }

    /**
     * Process change requested
     *
     * @param workflow
     *            the workflow
     * @param request
     *            the request
     * @param userLoginId
     *            the user login id
     * @param isInitNDSReview
     *            the is init nds review
     * @param isFinalNDSReview
     *            the is final nds review
     * @throws ObjectNotFoundException
     *             the object not found exception
     * @throws ValidationException
     *             the validation exception
     */
    private void processChangeRequested(final RequestWorkflow workflow, final Request request, final String userLoginId,
            final boolean isInitNDSReview, final boolean isFinalNDSReview) throws ObjectNotFoundException, ValidationException {
        // get the workflow / request status
        RequestStatus requestStatus = request.getStatus(); // use the request-level status
        if (workflow != null) {
            requestStatus = workflow.getRequestStatus(); // use the workflow-level status
        }

        // create event(s)
        if (requestStatus.getId() == RequestStatus.CHANGE_REQUESTED.getId()) {
            if (workflow == null) { // old NDS request
                createReqSubmittedEvent(workflow, request, userLoginId, isInitNDSReview, isFinalNDSReview);
            }
        }
    }

    /**
     * Process intermediate state change.
     *
     * @param workflow
     *            the workflow
     * @param request
     *            the request
     * @param userLoginId
     *            the user login id
     * @param isInitNDSReview
     *            the is init nds review
     * @param isFinalNDSReview
     *            the is final nds review
     * @throws ObjectNotFoundException
     *             the object not found exception
     * @throws ValidationException
     *             the validation exception
     * @throws Exception
     *             the exception
     */
    private void processIntermediateStateChange(final RequestWorkflow workflow, final Request request, final String userLoginId,
            final boolean isInitNDSReview, final boolean isFinalNDSReview)
            throws ObjectNotFoundException, ValidationException, Exception {
        // submitted request with changes (change requested by intermediate review)
        log.debug("request submitted (with changes)!");

        processChangeRequested(workflow, request, userLoginId, isInitNDSReview, isFinalNDSReview);

        if (workflow != null) {
            workflow.submit(userLoginId); // update the child workflow status
        } else {
            request.submit(userLoginId); // update the request status
        }

        TaskUtils.closeUserTasksForChangeRequest(workflow, request, userLoginId);

        // create a task for each group that requested a change (send a task to all groups who requested a change)
        for (Review rev : request.getReviews(workflow)) {

            if (rev.isChangeRequested()) {

                Group reviewGroup = rev.getReviewer();
                if (reviewGroup != null) {

                    String reviewGroupShortName = reviewGroup.getShortName();

                    // track this event (sent the review to this group)
                    Event.create("Request Sent for " + reviewGroupShortName + " Review",
                            "Request Sent for " + reviewGroupShortName + " Review", EventType.SENT_FOR_REVIEW, reviewGroup,
                            false, request, rev, userLoginId);
                    final Group group = reviewGroup;
                    final Review review1 = rev;

                    if (group != null) {

                        createGroupReviewTask(workflow, group, review1, userLoginId);
                        EmailUtils.createAndSendChangeSubmittedEmails(request, group, new Date());

                    }
                }

                // request re-submitted, so update the review status (should go back to waiting for review)
                rev.clearStatus(userLoginId);
            }
        }
    }

    /**
     * Process intermediate state deny.
     *
     * @param workflow
     *            the workflow
     * @param review
     *            the review
     * @param request
     *            the request
     * @param userLoginId
     *            the user login id
     * @param group
     *            the group
     * @param groupShortName
     *            the group short name
     * @throws ValidationException
     *             the validation exception
     * @throws ObjectNotFoundException
     *             the object not found exception
     * @throws WorkflowException
     *             the workflow exception
     */
    private void processIntermediateStateDenyHasReview(final RequestWorkflow workflow, final Review review,
            final Request request, final String userLoginId, Group group, final String groupShortName)
            throws ValidationException, ObjectNotFoundException, WorkflowException {
        if (request.isReviewOpen(workflow, group)) { // has this review already been denied?

            // set the review to denied -> use the attached reviews to determine which state to move to next
            review.reject(userLoginId);
            if (workflow != null) {
                workflow.reject(userLoginId); // update the workflow state
            } else { // NO workflow, so this is older data -> set the overall request state to denied
                // Set the entire request and activity to rejected status.
                review.getRequest().reject(userLoginId);
            }

            Event.create(groupShortName + " Review Denied", groupShortName + " Review Denied", EventType.DENY_REVIEW, group,
                    false, request, review, userLoginId);

            if (workflow == null) { // old NDS request

                // When the review is rejected, send the NDS administrator and all participants an email.
                List<Review> reviewList = getRemainingReviewersList(workflow, request);
                createAndSendGroupMembersAReviewEmail(workflow, group, reviewList, request, groupShortName, userLoginId,
                        Workflow.WF_OPERATION_DENY); // email NDS
                sendParticipantsDenyEmail(workflow, reviewList, request, groupShortName, userLoginId,
                        Workflow.WF_OPERATION_DENY); // email the participiants
            }

            TaskUtils.closeAllTasksForWorkflowAndRequest(workflow, request, userLoginId);

            setState(workflow, review, request, FINAL_STATE, userLoginId); // go to final state
        } else {
            log.error("Error: request " + request.getTrackingNumber() + " is not in the correct state for a review by "
                    + groupShortName);
        }
    }

}
