package gov.va.vinci.dart.wf2;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import gov.va.vinci.dart.common.exception.ObjectNotFoundException;
import gov.va.vinci.dart.common.exception.ValidationException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import gov.va.vinci.dart.biz.Comment;
import gov.va.vinci.dart.biz.DataSource;
import gov.va.vinci.dart.biz.DocumentTemplate;
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.Review;
import gov.va.vinci.dart.biz.RequestWorkflow;
import gov.va.vinci.dart.biz.WorkflowTemplate;
import gov.va.vinci.dart.dms.biz.Document;

/**
 * A work flow for VINCI DART Preparatory Requests.
 */
@Service
public class WfPreparatoryRequest extends AbstractWorkflow {

    /** The log. */
    private static Log LOG = LogFactory.getLog(WfPreparatoryRequest.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 FINAL_STATE. All work flows are complete. */
    public static final int FINAL_STATE = 16;

    /** The workflow resolver. */
    @Autowired
    private WorkflowResolver workflowResolver;

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

    @Override
    public final int getFinalReviewState() {
        return SUBMITTED_STATE;
    }

    @Override
    public final 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 final String calculateReviewCompletion(final RequestWorkflow workflow, final Request request) {
        String status = "0%";
        if (request != null) {
            if (workflow != null) {
                try {
                    status = workflowResolver.resolve(workflow).calculateReviewCompletion(workflow, request);
                } catch (WorkflowException e) {
                    LOG.error("Workflow Exception calculating the Review Completion %:  " + e.getMessage());
                    
                }
            } else {
                try {
                    status = request.getStatus().getName();
                } catch (ObjectNotFoundException e) {
                    LOG.error("Exception calculating the Review Completion %:  " + e.getMessage());
                    
                }
            }
        }
        return status;
    }

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

        if (request != null && workflow != null) {
            try {
                return (workflowResolver.resolve(workflow).isReadyForInitialReview(workflow, request));
            } catch (WorkflowException e) {
                LOG.debug("Workflow Exception occurred while determining the initial review state:  " + e.getMessage());
                
            }
        }
        return false;
    }

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

        if (request != null && workflow != null) {
            try {
                return (workflowResolver.resolve(workflow).isInitialReviewCompleted(workflow, request));

            } catch (WorkflowException e) {
                LOG.debug("Workflow Exception occurred while determining the initial review state:  " + e.getMessage());
                
            }
        }
        return false;
    }

    /**
     * Method: isReadyForFinalReview.
     * 
     * @return true, if ready for the final review, based on workflow state
     */
    @Override
    public final boolean isReadyForFinalReview(final RequestWorkflow workflow, final Request request) {

        if (request != null && workflow != null) {
            try {
                return (workflowResolver.resolve(workflow).isReadyForFinalReview(workflow, request));
            } catch (WorkflowException e) {
                LOG.debug("Workflow Exception occurred while determining the final review state:  " + e.getMessage());
               
            }
        }
        return false;
    }

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

        if (request != null && workflow != null) {
            try {
                return (workflowResolver.resolve(workflow).isFinalReviewCompleted(workflow, request));
            } catch (WorkflowException e) {
                LOG.debug("Workflow Exception occurred while determining the final review state:  " + e.getMessage());
                
            }
        }

        return false;
    }

    /**
     * Method: transition.
     * 
     * Handle an event on the current work flow 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 final void transition(final RequestWorkflow workflow, final Review review, final Request request,
            final int operation, final String userLoginId) throws WorkflowException {

        int workflowState = request.getWorkflowState();

        switch (workflowState) {

        case INITIAL_STATE:
            processInitialState(request, operation, userLoginId);
            break;

        case SUBMITTED_STATE:
            processSubmittedState(workflow, review, request, operation, userLoginId);
            break;

        case FINAL_STATE:
            processFinalState(request, operation, userLoginId);
            break;

        default:
            throw new WorkflowException(
                    "Illegal workflow state " + workflowState + " encountered in request " + request.getTrackingNumber());
        }
    }

    /**
     * Method: Initialize - work flow processing for the current workflow item.
     * 
     * Request initiated by the requester.
     * 
     */
    @Override
    public final void initialize(final RequestWorkflow workflow, final Request request, final String userLoginId)
            throws WorkflowException {
        LOG.debug("WfPreparatoryRequest initialize " + request.getTrackingNumber());

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

    /**
     * Method: setState - Set the state of the current work flow item.
     *
     * @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 final void setState(final RequestWorkflow workflow, final Review review, final Request request,
            final int newState, final String userLoginId) throws WorkflowException {

        request.setWorkflowState(newState);
        transition(workflow, review, request, WF_OPERATION_ENTRY, userLoginId);
    }

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

        try {
            EventType.initialize();
            Group.initialize();

            if (operation == Workflow.WF_OPERATION_ENTRY) {
                return;
            }

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

                createEventStateOne(request, userLoginId);

                List<RequestWorkflow> childWorkflowList = new ArrayList<RequestWorkflow>();
                List<WorkflowTemplate> childWorkflowTemplates = new ArrayList<WorkflowTemplate>();

                if (request.isAmendment()) {
                    processAmendmentInitialWorkflowState(request, userLoginId, childWorkflowList, childWorkflowTemplates);
                }

                processDataSourcesInitialWorkflowState(request, userLoginId, childWorkflowList, childWorkflowTemplates);

                if (childWorkflowList != null && childWorkflowList.size() > 0) {
                    for (RequestWorkflow childWorkflow : childWorkflowList) {
                        if (childWorkflow != null) {
                            workflowResolver.resolve(childWorkflow).submit(childWorkflow, request, userLoginId);
                        }
                    }
                }

                request.submit(userLoginId);

                setState(null, null, request, SUBMITTED_STATE, userLoginId);
            } else {
                LOG.error("Error: request in unexpected state.");
            }
        } catch (Exception e) {
            throw new WorkflowException(e);
        }

    }

    /**
     * Process data sources state one.
     *
     * @param request
     *            the request
     * @param userLoginId
     *            the user login id
     * @param childWorkflowList
     *            the child workflow list
     * @param childWorkflowTemplates
     *            the child workflow templates
     */
    private void processDataSourcesInitialWorkflowState(final Request request, final String userLoginId,
            List<RequestWorkflow> childWorkflowList, List<WorkflowTemplate> childWorkflowTemplates) {

        Set<DataSource> requestDataSources = new HashSet<DataSource>();
        if (request != null && request.getDataSources() != null) {
            requestDataSources.addAll(request.getDataSources());

            for (DataSource requestDataSource : requestDataSources) {
                if (requestDataSource != null) {
                    Set<WorkflowTemplate> workflowTemplateSet = requestDataSource.getWorkflowTemplates();
                    if (workflowTemplateSet != null) {
                        for (WorkflowTemplate workflowTemplate : workflowTemplateSet) {
                            if (workflowTemplate != null) {
                                if (!childWorkflowTemplates.contains(workflowTemplate)) {
                                    childWorkflowTemplates.add(workflowTemplate);
                                    try {
                                        RequestWorkflow newChildWorkflow =
                                                createAndInitializeChildWorkflow(request, userLoginId, workflowTemplate);
                                        if (newChildWorkflow != null) {
                                            childWorkflowList.add(newChildWorkflow);
                                        }
                                    } catch (Exception e) {
                                        LOG.error("Error occurred while trying to create the workflow: " + e.getMessage());
                                        
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Method: processAmendmentStateOne - Process amendment for state one.
     *
     * @param request
     *            the request
     * @param userLoginId
     *            the user login id
     * @param childWorkflowList
     *            the child workflow list
     * @param childWorkflowTemplates
     *            the child workflow templates
     */
    private void processAmendmentInitialWorkflowState(final Request request, final String userLoginId,
            List<RequestWorkflow> childWorkflowList, List<WorkflowTemplate> childWorkflowTemplates) {

        List<Request> prevReqList = Request.listAllPreviousRequests(request.getHeadId(), request.getCreatedOn());
        if (prevReqList != null && prevReqList.size() > 0) {

            Request prevReq = prevReqList.get(0);
            if (prevReq != null) {

                Event initiateEvent = determineInitiateEvent(request);

                Set<Document> currDocuments = request.getDocuments();
                if (currDocuments != null) {
                    for (Document doc : currDocuments) {
                        processAmendmentDocumentsInitialWorkflowState(request, userLoginId, childWorkflowList, childWorkflowTemplates, initiateEvent,
                                doc);
                    }
                }
            }
        }
    }

    /**
     * Process documents state one.
     *
     * @param request
     *            the request
     * @param userLoginId
     *            the user login id
     * @param childWorkflowList
     *            the child workflow list
     * @param childWorkflowTemplates
     *            the child workflow templates
     * @param initiateEvent
     *            the initiate event
     * @param doc
     *            the doc
     */
    private void processAmendmentDocumentsInitialWorkflowState(final Request request, final String userLoginId,
            List<RequestWorkflow> childWorkflowList, List<WorkflowTemplate> childWorkflowTemplates, Event initiateEvent,
            Document doc) {
        if (doc != null) {
            if ((doc.getId() != doc.getHead()) || (initiateEvent != null && initiateEvent.getCreatedOn() != null
                    && doc.getUpdatedOn() != null && doc.getUpdatedOn().after(initiateEvent.getCreatedOn()))) {

                DocumentTemplate docTemplate = doc.getDocumentTemplate();
                if (docTemplate != null) {

                    Set<WorkflowTemplate> amendWorkflowTemplateSet = docTemplate.getAmendmentWorkflowTemplates();
                    if (amendWorkflowTemplateSet != null) {

                        for (WorkflowTemplate amendWorkflowTemplate : amendWorkflowTemplateSet) {
                            processAmendmentWorkFlowTempatesInitialWorkflowState(request, userLoginId, childWorkflowList,
                                    childWorkflowTemplates, amendWorkflowTemplate);
                        }

                    }
                }

            }
        }
    }

    /**
     * Process Amendment WorkFlow Templates for the Initial Work flow state
     *
     * @param request
     *            the request
     * @param userLoginId
     *            the user login id
     * @param childWorkflowList
     *            the child workflow list
     * @param childWorkflowTemplates
     *            the child workflow templates
     * @param amendWorkflowTemplate
     *            the amend workflow template
     */
    private void processAmendmentWorkFlowTempatesInitialWorkflowState(final Request request, final String userLoginId,
            List<RequestWorkflow> childWorkflowList, List<WorkflowTemplate> childWorkflowTemplates,
            WorkflowTemplate amendWorkflowTemplate) {
        if (amendWorkflowTemplate != null) {

            if (!childWorkflowTemplates.contains(amendWorkflowTemplate)) {
                childWorkflowTemplates.add(amendWorkflowTemplate);

                try {
                    RequestWorkflow newChildWorkflow =
                            createAndInitializeChildWorkflow(request, userLoginId, amendWorkflowTemplate);
                    if (newChildWorkflow != null) {
                        childWorkflowList.add(newChildWorkflow);
                    }
                } catch (Exception e) {
                    LOG.error("Error occurred while trying to create the workflow: " + e.getMessage());
                    
                }

            }

        }
    }

    /**
     * Determine initiate event.
     *
     * @param request
     *            the request
     * @return the event
     */
    private Event determineInitiateEvent(final Request request) {
        Event initiateEvent = null;
        EventType.initialize();
        List<Event> initiateEventList = Event.listByEventTypeAndRequestId(EventType.INITIATE_REQUEST.getId(), request.getId());
        if (initiateEventList != null && initiateEventList.size() > 0) {
            initiateEvent = initiateEventList.get(0);
        }
        return initiateEvent;
    }

    /**
     * Creates the event state one.
     *
     * @param request
     *            the request
     * @param userLoginId
     *            the user login id
     * @throws ObjectNotFoundException
     *             the object not found exception
     * @throws WorkflowException
     *             the workflow exception
     * @throws ValidationException
     *             the validation exception
     */
    private void createEventStateOne(final Request request, final String userLoginId)
            throws ObjectNotFoundException, WorkflowException, ValidationException {

        if (request.getStatus().getId() == RequestStatus.CHANGE_REQUESTED.getId()) {
            // shouldn't be here for a change request -- should be in stateTwo()
            throw new WorkflowException("Error: request " + request.getTrackingNumber() + " in unexpected state.");
        } else {
            Event.create(EventType.SUBMIT_REQUEST, request, userLoginId);
        }
    }

    /**
     * Workflow state: in-process (the child workflows not yet completed). Request has been submitted and is in-process.
     *
     * @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
     */
    protected final synchronized void processSubmittedState(final RequestWorkflow workflow, final Review review, final Request request,
            final int operation, final String userLoginId) throws WorkflowException {
        LOG.debug("WfPreparatoryRequest state two operation = " + operation + " request = " + request.getTrackingNumber());

        try {
            EventType.initialize();
            Group.initialize();

            Group reviewGroup = null;
            if (review == null) { // NDS
                reviewGroup = Group.NDS;
            } else {
                reviewGroup = review.getReviewer();
            }

            if (operation == Workflow.WF_OPERATION_ENTRY) {
                return;
            }

            if (operation == Workflow.WF_OPERATION_SUBMIT) {
                processSubmitForSubmittedState(workflow, review, request, operation, userLoginId, reviewGroup);
            }

            else if (operation == Workflow.WF_OPERATION_CHANGE_REQUEST) {

                if (workflow != null) {
                    workflowResolver.resolve(workflow).changeRequest(workflow, review, request, userLoginId);
                }

                return;
            }

            else if (operation == Workflow.WF_OPERATION_APPROVE) {
                processApproveForSubmittedState(workflow, review, request, operation, userLoginId, reviewGroup);
            }

            else if (operation == Workflow.WF_OPERATION_DENY) {

                processDenyForSubmittedState(workflow, review, request, operation, userLoginId, reviewGroup);
            }

            else {
                LOG.error("Error: request in unexpected state.");
            }

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

    /**
     * Process deny state two.
     *
     * @param workflow
     *            the workflow
     * @param review
     *            the review
     * @param request
     *            the request
     * @param operation
     *            the operation
     * @param userLoginId
     *            the user login id
     * @param reviewGroup
     *            the review group
     * @throws WorkflowException
     *             the workflow exception
     */
    private void processDenyForSubmittedState(final RequestWorkflow workflow, final Review review, final Request request,
            final int operation, final String userLoginId, Group reviewGroup) throws WorkflowException {
        if (workflow != null) {
            workflowResolver.resolve(workflow).deny(workflow, review, request, userLoginId);
        }

        if (request.allWorkflowsCompleted()) {

            EmailUtils.createAndSendRequestCompletedEmail(request, workflow, reviewGroup, operation);

            setState(null, review, request, FINAL_STATE, userLoginId);
        } else {

            if (workflow != null && workflow.isCompleted()) {

                Set<RequestWorkflow> workflowSet = request.getWorkflows(true);
                if (workflowSet != null && workflowSet.size() > 1) {
                    EmailUtils.createAndSendReviewPendingEmail(request, workflow, reviewGroup, operation);
                }
            }
        }
    }

    /**
     * Process approve state two.
     *
     * @param workflow
     *            the workflow
     * @param review
     *            the review
     * @param request
     *            the request
     * @param operation
     *            the operation
     * @param userLoginId
     *            the user login id
     * @param reviewGroup
     *            the review group
     * @throws WorkflowException
     *             the workflow exception
     */
    private void processApproveForSubmittedState(final RequestWorkflow workflow, final Review review, final Request request,
            final int operation, final String userLoginId, Group reviewGroup) throws WorkflowException {
        if (workflow != null) {
            workflowResolver.resolve(workflow).approve(workflow, review, request, userLoginId);
        }

        if (request.allWorkflowsCompleted()) {
            EmailUtils.createAndSendRequestCompletedEmail(request, workflow, reviewGroup, operation);
            setState(null, review, request, FINAL_STATE, userLoginId);
        } else {
            if (workflow != null && workflow.isCompleted()) {
                Set<RequestWorkflow> workflowSet = request.getWorkflows(true);
                if (workflowSet != null && workflowSet.size() > 1) {
                    EmailUtils.createAndSendReviewPendingEmail(request, workflow, reviewGroup, operation);
                }
            }
        }
    }

    /**
     * Process submit state two.
     *
     * @param workflow
     *            the workflow
     * @param review
     *            the review
     * @param request
     *            the request
     * @param operation
     *            the operation
     * @param userLoginId
     *            the user login id
     * @param reviewGroup
     *            the review group
     * @throws ObjectNotFoundException
     *             the object not found exception
     * @throws ValidationException
     *             the validation exception
     * @throws WorkflowException
     *             the workflow exception
     * @throws Exception
     *             the exception
     */
    private void processSubmitForSubmittedState(final RequestWorkflow workflow, final Review review, final Request request,
            final int operation, final String userLoginId, Group reviewGroup)
            throws ObjectNotFoundException, ValidationException, WorkflowException, Exception {
        LOG.debug("WfPreparatoryRequest: request submitted!");

        if (request != null) {

            if (request.getStatus().getId() == RequestStatus.CHANGE_REQUESTED.getId()) {

                List<WorkflowTemplate> currChildWorkflowTemplates = new ArrayList<WorkflowTemplate>();
                Set<RequestWorkflow> currChildWorkflowSet = request.getWorkflows(true);
                if (currChildWorkflowSet != null) {
                    for (RequestWorkflow currWorkflow : currChildWorkflowSet) {
                        processRequestWorkFlowsStateTwo(currChildWorkflowTemplates, currWorkflow);
                    }
                }

                List<RequestWorkflow> newChildWorkflowList = new ArrayList<RequestWorkflow>();
                Set<WorkflowTemplate> dataSourcesWorkflowTemplates = new HashSet<WorkflowTemplate>();

                Set<DataSource> requestDataSources = new HashSet<DataSource>();
                requestDataSources.addAll(request.getDataSources());

                if (request.isAmendment()) {
                    processAmendmentSubmittedState(request, userLoginId, currChildWorkflowTemplates, newChildWorkflowList,
                            dataSourcesWorkflowTemplates);
                }

                if (requestDataSources != null) {
                    for (DataSource currDataSource : requestDataSources) {
                        processDataSourcesSubmittedState(request, userLoginId, currChildWorkflowTemplates, newChildWorkflowList,
                                dataSourcesWorkflowTemplates, currDataSource);
                    }
                }

                Set<RequestWorkflow> workflowToCloseSet = new HashSet<RequestWorkflow>();
                for (WorkflowTemplate workflowTemplate : currChildWorkflowTemplates) {
                    processWorkflowTemplatesSubmittedState(request, dataSourcesWorkflowTemplates, workflowToCloseSet,
                            workflowTemplate);
                }

                createReqSubmittedEventForAllWorkflows(request, review, workflowToCloseSet, newChildWorkflowList, userLoginId);

                for (RequestWorkflow workflowToClose : workflowToCloseSet) {
                    processRequestWorkflowToCloseSubmittedState(request, review, userLoginId, workflowToClose);
                }

                for (RequestWorkflow currWorkflow : request.getWorkflows(true)) {
                    if (currWorkflow != null && currWorkflow.isChangeRequested()) {
                        workflowResolver.resolve(currWorkflow).submit(currWorkflow, request, userLoginId);
                    }
                }

                if (newChildWorkflowList != null && newChildWorkflowList.size() > 0) {
                    for (RequestWorkflow childWorkflow : newChildWorkflowList) {
                        if (childWorkflow != null) {
                            workflowResolver.resolve(childWorkflow).submit(childWorkflow, request, userLoginId);
                        }
                    }
                }

            } else {
                throw new WorkflowException("Error: request " + request.getTrackingNumber() + " in unexpected state.");
            }

        }

        request.submit(userLoginId);

        if (request.allWorkflowsCompleted()) {

            EmailUtils.createAndSendRequestCompletedEmail(request, workflow, reviewGroup, operation);

            setState(null, review, request, FINAL_STATE, userLoginId);

        }
    }

    /**
     * Process request workflow to close state two.
     *
     * @param request
     *            the request
     * @param userLoginId
     *            the user login id
     * @param workflowToClose
     *            the workflow to close
     * @throws WorkflowException
     *             the workflow exception
     * @throws ValidationException
     *             the validation exception
     * @throws ObjectNotFoundException
     *             the object not found exception
     */
    private void processRequestWorkflowToCloseSubmittedState(final Request request, Review review,final String userLoginId,
            RequestWorkflow workflowToClose) throws WorkflowException, ValidationException, ObjectNotFoundException {
        if (workflowToClose != null && !workflowToClose.isCompleted()) {
            if (!workflowToClose.isClosed()) {

                WorkflowTemplate workflowTemplate = workflowToClose.getWorkflowTemplate();
                if (workflowTemplate != null) {

                    Group group = workflowTemplate.getReviewer();
                    final String groupShortName = group.getShortName();
                    boolean isInitial = isInitialNDSReviewEvent(workflowToClose, request);

                    Event.create((groupShortName + " Request Closed"), (groupShortName + " Request Closed"),
                            EventType.CLOSE_WORKFLOW, group, isInitial, request, review, userLoginId);
                    TaskUtils.closeAllTasksForWorkflowAndRequest(workflowToClose, request, userLoginId);

                    workflowToClose.close(userLoginId);
                }
            }
        }
    }

    /**
     * Process workflow templates state two.
     *
     * @param request
     *            the request
     * @param dataSourcesWorkflowTemplates
     *            the data sources workflow templates
     * @param workflowToCloseSet
     *            the workflow to close set
     * @param workflowTemplate
     *            the workflow template
     */
    private void processWorkflowTemplatesSubmittedState(final Request request, Set<WorkflowTemplate> dataSourcesWorkflowTemplates,
            Set<RequestWorkflow> workflowToCloseSet, WorkflowTemplate workflowTemplate) {
        if (workflowTemplate != null) {

            if (!dataSourcesWorkflowTemplates.contains(workflowTemplate)) {

                RequestWorkflow workflowToClose =
                        RequestWorkflow.findOpenByRequestAndWorkflowTemplateId(request.getId(), workflowTemplate.getId());
                if (workflowToClose != null && !workflowToClose.isCompleted()) {

                    if (!workflowToClose.isClosed()) {
                        workflowToCloseSet.add(workflowToClose);

                    }
                }
            }
        }
    }

    /**
     * Process work flows state two.
     *
     * @param currChildWorkflowTemplates
     *            the curr child workflow templates
     * @param currWorkflow
     *            the curr workflow
     */
    private void processRequestWorkFlowsStateTwo(List<WorkflowTemplate> currChildWorkflowTemplates,
            RequestWorkflow currWorkflow) {
        if (currWorkflow != null && currWorkflow.getWorkflowTemplate() != null) {
            if (currWorkflow.getWorkflowTemplate().getWorkflowTypeId() == WorkflowResolver.WF_NDS) {

                try {
                    WorkflowTemplate workflowTemplate = WorkflowTemplate.findByGroupIdAndWorkflowTypeId(Group.NDS.getId(),
                            currWorkflow.getWorkflowTemplate().getWorkflowTypeId());
                    if (workflowTemplate != null) {
                        currChildWorkflowTemplates.add(workflowTemplate);
                    }

                } catch (ObjectNotFoundException e) {
                    LOG.error("Could not find the WorkflowTemplate for groupId: " + Group.NDS.getId() + " and workflowTypeId: "
                            + WorkflowResolver.WF_NDS + ". " + e.getMessage());
                    
                }
            }
        }
    }

    /**
     * Process data sources state two.
     *
     * @param request
     *            the request
     * @param userLoginId
     *            the user login id
     * @param currChildWorkflowTemplates
     *            the curr child workflow templates
     * @param newChildWorkflowList
     *            the new child workflow list
     * @param dataSourcesWorkflowTemplates
     *            the data sources workflow templates
     * @param currDataSource
     *            the curr data source
     */
    private void processDataSourcesSubmittedState(final Request request, final String userLoginId,
            List<WorkflowTemplate> currChildWorkflowTemplates, List<RequestWorkflow> newChildWorkflowList,
            Set<WorkflowTemplate> dataSourcesWorkflowTemplates, DataSource currDataSource) {
        if (currDataSource != null) {

            Set<WorkflowTemplate> workflowTemplateSet = currDataSource.getWorkflowTemplates();
            if (workflowTemplateSet != null) {
                for (WorkflowTemplate workflowTemplate : workflowTemplateSet) {

                    processAmendmentWorkflowTemplatesSubmittedState(request, userLoginId, currChildWorkflowTemplates,
                            newChildWorkflowList, dataSourcesWorkflowTemplates, workflowTemplate);

                }
            }

        }
    }

    /**
     * Process amendment state two.
     *
     * @param request
     *            the request
     * @param userLoginId
     *            the user login id
     * @param currChildWorkflowTemplates
     *            the curr child workflow templates
     * @param newChildWorkflowList
     *            the new child workflow list
     * @param dataSourcesWorkflowTemplates
     *            the data sources workflow templates
     */
    private void processAmendmentSubmittedState(final Request request, final String userLoginId,
            List<WorkflowTemplate> currChildWorkflowTemplates, List<RequestWorkflow> newChildWorkflowList,
            Set<WorkflowTemplate> dataSourcesWorkflowTemplates) {
        List<Request> prevReqList = Request.listAllPreviousRequests(request.getHeadId(), request.getCreatedOn());
        if (prevReqList != null && prevReqList.size() > 0) {

            Request prevReq = prevReqList.get(0);
            if (prevReq != null) {

                Event initiateEvent = determineInitiateEvent(request);

                Set<Document> currDocuments = request.getDocuments();
                if (currDocuments != null) {
                    for (Document doc : currDocuments) {
                        processDocumentsSubmittedState(request, userLoginId, currChildWorkflowTemplates, newChildWorkflowList,
                                dataSourcesWorkflowTemplates, initiateEvent, doc);
                    }
                }

            }
        }
    }

    /**
     * Process documents state two.
     *
     * @param request
     *            the request
     * @param userLoginId
     *            the user login id
     * @param currChildWorkflowTemplates
     *            the curr child workflow templates
     * @param newChildWorkflowList
     *            the new child workflow list
     * @param dataSourcesWorkflowTemplates
     *            the data sources workflow templates
     * @param initiateEvent
     *            the initiate event
     * @param doc
     *            the doc
     */
    private void processDocumentsSubmittedState(final Request request, final String userLoginId,
            List<WorkflowTemplate> currChildWorkflowTemplates, List<RequestWorkflow> newChildWorkflowList,
            Set<WorkflowTemplate> dataSourcesWorkflowTemplates, Event initiateEvent, Document doc) {
        if (doc != null) {
            if ((doc.getId() != doc.getHead()) || (initiateEvent != null && initiateEvent.getCreatedOn() != null
                    && doc.getUpdatedOn() != null && doc.getUpdatedOn().after(initiateEvent.getCreatedOn()))) {

                DocumentTemplate docTemplate = doc.getDocumentTemplate();
                if (docTemplate != null) {

                    Set<WorkflowTemplate> amendWorkflowTemplateSet = docTemplate.getAmendmentWorkflowTemplates();
                    if (amendWorkflowTemplateSet != null) {

                        for (WorkflowTemplate amendWorkflowTemplate : amendWorkflowTemplateSet) {
                            processAmendmentWorkflowTemplatesSubmittedState(request, userLoginId, currChildWorkflowTemplates,
                                    newChildWorkflowList, dataSourcesWorkflowTemplates, amendWorkflowTemplate);
                        }
                    }
                }
            }
        }
    }

    /**
     * Process amendment workflow templates state two.
     *
     * @param request
     *            the request
     * @param userLoginId
     *            the user login id
     * @param currChildWorkflowTemplates
     *            the curr child workflow templates
     * @param newChildWorkflowList
     *            the new child workflow list
     * @param dataSourcesWorkflowTemplates
     *            the data sources workflow templates
     * @param amendWorkflowTemplate
     *            the amend workflow template
     */
    private void processAmendmentWorkflowTemplatesSubmittedState(final Request request, final String userLoginId,
            List<WorkflowTemplate> currChildWorkflowTemplates, List<RequestWorkflow> newChildWorkflowList,
            Set<WorkflowTemplate> dataSourcesWorkflowTemplates, WorkflowTemplate amendWorkflowTemplate) {
        if (amendWorkflowTemplate != null) {

            if (!dataSourcesWorkflowTemplates.contains(amendWorkflowTemplate)) {
                dataSourcesWorkflowTemplates.add(amendWorkflowTemplate);
            }

            if (!currChildWorkflowTemplates.contains(amendWorkflowTemplate)) {
                currChildWorkflowTemplates.add(amendWorkflowTemplate);

                try {

                    RequestWorkflow newChildWorkflow =
                            createAndInitializeChildWorkflow(request, userLoginId, amendWorkflowTemplate);
                    if (newChildWorkflow != null) {
                        newChildWorkflowList.add(newChildWorkflow);
                    }
                } catch (Exception e) {
                    LOG.error("Error occurred while trying to create the workflow: " + e.getMessage());
                   
                }

            }

        }
    }

    /**
     * Request Completed (final state) state handler.
     *
     * @param request
     *            the request
     * @param operation
     *            the operation
     * @param userLoginId
     *            the user login id
     * @throws WorkflowException
     *             the workflow exception
     */
    protected final void processFinalState(final Request request, final int operation, final String userLoginId)
            throws WorkflowException {
        LOG.debug("WfPreparatoryRequest stateRequestCompleted operation = " + operation + " request = "
                + request.getTrackingNumber());

        try {
            EventType.initialize();
            Group.initialize();

            if (operation == Workflow.WF_OPERATION_ENTRY) {

                if (request.allWorkflowsCompleted()) {

                    // save a communication
                    Comment.create("Request Completed", request, userLoginId,
                            "All reviews of VINCI Dart request " + request.getTrackingNumber() + " are now completed.");

                    // create an event for the Request Completed
                    Event.create(EventType.REQUEST_COMPLETED, request, userLoginId);

                    // update the request status (mark as completed)
                    request.complete(userLoginId);

                }
            } else {
                LOG.error("Error: request in unexpected state.");
            }
        } catch (Exception e) {
            throw new WorkflowException(e);
        }

    }

    /**
     * Creates and initializes a workflow object. Attaches the workflow to the given request.
     *
     * @param request
     *            the request
     * @param userLoginId
     *            the user login id
     * @param workflowTemplate
     *            the workflow template
     * @return the request workflow
     * @throws ValidationException
     *             the validation exception
     * @throws ObjectNotFoundException
     *             the object not found exception
     * @throws WorkflowException
     *             the workflow exception
     */
    private RequestWorkflow createAndInitializeChildWorkflow(final Request request, final String userLoginId,
            final WorkflowTemplate workflowTemplate) throws ValidationException, ObjectNotFoundException, WorkflowException {

        if (workflowTemplate.getWorkflowTypeId() == WorkflowResolver.WF_NDS) {

            RequestWorkflow ndsWorkflow = RequestWorkflow.create(request, workflowTemplate, userLoginId);

            workflowResolver.resolve(ndsWorkflow).initialize(ndsWorkflow, request, userLoginId);

            return ndsWorkflow;

        }

        return null;
    }

    /**
     * Checks if is initial nds review event.
     *
     * @param workflow
     *            the workflow
     * @param request
     *            the request
     * @return true, if is initial nds review event
     * @throws WorkflowException
     *             the workflow exception
     */
    private boolean isInitialNDSReviewEvent(final RequestWorkflow workflow, final Request request) throws WorkflowException {

        boolean isInitial = false;

        if (workflow != null) {

            WorkflowTemplate workflowTemplate = workflow.getWorkflowTemplate();
            if (workflowTemplate != null) {

                Group.initialize();

                Group group = workflowTemplate.getReviewer();
                if (group != null && group.getId() == Group.NDS.getId()) {
                    isInitial = (!(workflowResolver.resolve(workflow).isInitialReviewCompleted(workflow, request)));
                }

            }
        }

        return isInitial;
    }

    /**
     * Checks if is final nds review event.
     *
     * @param workflow
     *            the workflow
     * @param request
     *            the request
     * @return true, if is final nds review event
     * @throws WorkflowException
     *             the workflow exception
     */
    private boolean isFinalNDSReviewEvent(final RequestWorkflow workflow, final Request request) throws WorkflowException {

        boolean isFinal = false;

        if (workflow != null) {

            WorkflowTemplate workflowTemplate = workflow.getWorkflowTemplate();
            if (workflowTemplate != null) {

                Group.initialize();

                Group group = workflowTemplate.getReviewer();
                if (group != null && group.getId() == Group.NDS.getId()) {
                    isFinal = ((workflowResolver.resolve(workflow).isReadyForFinalReview(workflow, request)));
                }
            }
        }

        return isFinal;
    }

    /**
     * Create the top-level event for the "submit with changes".
     *
     * @param request
     *            the request
     * @param workflowToCloseSet
     *            the workflow to close set
     * @param newChildWorkflowList
     *            the new child workflow list
     * @param userLoginId
     *            the user login id
     * @throws ValidationException
     *             the validation exception
     */
    protected final void createReqSubmittedEventForAllWorkflows(final Request request, final Review review,
            final Collection<RequestWorkflow> workflowToCloseSet, final Collection<RequestWorkflow> newChildWorkflowList,
            final String userLoginId) throws ValidationException {

        EventType.initialize();
        Group.initialize();

        Set<Group> allChangeRequestGroupSet = new HashSet<Group>();
        StringBuffer groupStr = new StringBuffer();

        boolean isInitNDSReview = false;

        Set<RequestWorkflow> currentWorkflowSet = request.getWorkflows(true);
        if (currentWorkflowSet != null) {
            for (RequestWorkflow workflow : currentWorkflowSet) {
                isInitNDSReview =
                        processRequestWorkFlows(request, allChangeRequestGroupSet, groupStr, isInitNDSReview, workflow, false);
            }
        }

        if (newChildWorkflowList != null) {
            for (RequestWorkflow newWorkflow : newChildWorkflowList) {
                isInitNDSReview =
                        processRequestWorkFlows(request, allChangeRequestGroupSet, groupStr, isInitNDSReview, newWorkflow, true);
            }
        }

        String groupString = null;
        if (groupStr != null) {
            groupString = " (" + groupStr.toString() + ")";
        }


        //bind the affected groups to the submit event
        Event.create(   ("Request Submitted with Changes " + groupString), ("Request Submitted with Changes " + groupString), 
                        EventType.SUBMIT_CHANGE_REQUEST, allChangeRequestGroupSet, isInitNDSReview,
                        request, userLoginId);
    }
    
    private boolean processRequestWorkFlows(final Request request, Set<Group> allChangeRequestGroupSet, StringBuffer groupStr,
            boolean isInitNDSReview, RequestWorkflow workflow, boolean isNewWorkflow) throws ValidationException {
        if (workflow != null && request != null) {

            if (workflow.isChangeRequested()) {

                if (!isInitNDSReview) {
                    try {
                        isInitNDSReview = isInitialNDSReviewEvent(workflow, request);
                    } catch (WorkflowException e) {
                        e.printStackTrace();
                    }
                }

                String groupsRequestingChangesStr =
                        createReqSubmittedStringForWorkflow(request, workflow, isNewWorkflow, allChangeRequestGroupSet);
                if (groupsRequestingChangesStr != null && !groupsRequestingChangesStr.trim().isEmpty()) {
                    if (!groupStr.toString().trim().isEmpty() )
                        groupStr.append(", ");

                    groupStr.append(groupsRequestingChangesStr);
                }
            }
        }
        return isInitNDSReview;
    }





    /**
     * Creates the req submitted string for workflow.
     *
     * @param request
     *            the request
     * @param workflow
     *            the workflow
     * @param isNewWorkflow
     *            the is new workflow
     * @param allChangeRequestGroupSet
     *            the all change request group set
     * @return the string
     * @throws ValidationException
     *             the validation exception
     */
    protected final String createReqSubmittedStringForWorkflow(final Request request, final RequestWorkflow workflow,
            final boolean isNewWorkflow, Set<Group> allChangeRequestGroupSet) throws ValidationException {

        Group.initialize();

        if (workflow != null && request != null) {

            boolean isInitNDSReview = false;
            boolean isFinalNDSReview = false;
            try {
                isInitNDSReview = isInitialNDSReviewEvent(workflow, request);
                isFinalNDSReview = isFinalNDSReviewEvent(workflow, request);
            } catch (WorkflowException e) {
                LOG.error("Failed to retrieve NDS Review Event Information: Exception message: " + e.getMessage());
                
            }

            if (isNewWorkflow) {
                processNetworkFlow(workflow, allChangeRequestGroupSet);

            } else {

                Set<Group> workflowChangeRequestGroupSet = request.getChangesRequestedGroups(workflow);
                if (isInitNDSReview || isFinalNDSReview)
                    workflowChangeRequestGroupSet.add(Group.NDS);

                if (allChangeRequestGroupSet != null && workflowChangeRequestGroupSet != null)
                    allChangeRequestGroupSet.addAll(workflowChangeRequestGroupSet);

            }

            String groupsRequestingChangesStr =
                    createReqSubmittedForString(workflow, request, isInitNDSReview, isFinalNDSReview);
            return groupsRequestingChangesStr;
        }

        return "";
    }

    /**
     * Process network flow.
     *
     * @param workflow
     *            the workflow
     * @param allChangeRequestGroupSet
     *            the all change request group set
     */
    private void processNetworkFlow(final RequestWorkflow workflow, Set<Group> allChangeRequestGroupSet) {
        if (workflow != null && workflow.getWorkflowTemplate() != null) {
            Group newWorkflowChangeRequestGroup = workflow.getWorkflowTemplate().getReviewer();

            if (allChangeRequestGroupSet != null && newWorkflowChangeRequestGroup != null)
                allChangeRequestGroupSet.add(newWorkflowChangeRequestGroup);
        }
    }

}
