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


/** A workflow for VINCI DART Research Requests.
 * 
 */
@Service
public class WfResearchRequest extends AbstractWorkflow {
	private static Log log = LogFactory.getLog(WfResearchRequest.class);

	public static final int INITIAL_STATE = 1;
	public static final int SUBMITTED_STATE = 2;
	public static final int FINAL_STATE = 16;		//all workflows completed
	
//	public static final long INIT_MASK = 0L;
	
	
	@Autowired
	private WorkflowResolver workflowResolver;

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

	
	/** Calculate the percentage of review completion on the request.
	 * 
	 * @return
	 */
	@Override
	public 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 { 	//DART Admin request details view (use the top-level simple status)

				try {
					status = request.getStatus().getName();
				} catch (ObjectNotFoundException e) {
					log.error("Exception calculating the Review Completion %:  " + e.getMessage());
					
				}

			}//end else

		}//end if -- request

		return status;
	}
	
	/**
	 * Returns true if ready for the initial review, based on workflow state
	 * @return
	 */
	@Override
	public 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());
				
			}

		}//end if
			
		return false;
	}
	
	/**
	 * Returns true if the initial review is completed, based on workflow state
	 * @return
	 */
	@Override
	public 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());
				
			}

		}//end if
			
		return false;
	}

	/**
	 * Returns true if ready for the final review, based on workflow state
	 * @return
	 */
	@Override
	public 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());
				
			}

		}//end if
			
		return false;
	}

	/**
	 * Returns true if the final review has been completed, based on workflow state
	 * @return
	 */
	@Override
	public 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());
				
			}

		}//end if
			
		return false;
	}
	
	
	
	/** Handle an event on the current workflow item.
	 * 
	 */
	@Override
	protected void transition(final RequestWorkflow workflow, final Review review, final Request request, final int operation, final String userLoginId) throws WorkflowException {
		
		int workflowState = request.getWorkflowState();	//top-level workflow state (use the top-level state here, because moving within the parent workflow here)

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

			case SUBMITTED_STATE:
				processSubmittedState(workflow, review, request, operation, userLoginId);	//processing the child workflows
				break;

			case FINAL_STATE:
				processFinalState(request, operation, userLoginId);
				break;
				
			default:
				throw new WorkflowException("Illegal workflow state " + workflowState + " encountered in request " + request.getTrackingNumber());
		}
	}



	/** Initialize workflow processing for the current workflow item.
	 * 
	 * Request initiated by the requestor.
	 * 
	 */
	@Override
	public void initialize(final RequestWorkflow workflow, final Request request, final String userLoginId) throws WorkflowException {
		log.debug("WfResearchRequest initialize " + request.getTrackingNumber());
		
//		request.setWorkflowMask(INIT_MASK);	//top-level workflow
		
		setState(null, null, request, INITIAL_STATE, userLoginId);
	}



	/** Set the state of the current workflow item.
	 * 		
	 * @param request
	 * @param newState
	 * @param userLoginId
	 * @throws WorkflowException
	 */
	@Override
	protected void setState(final RequestWorkflow workflow, final Review review, final Request request, final int newState, final String userLoginId) throws WorkflowException {

		//update the top-level workflow (child workflow is updated based on the action being taken)
		request.setWorkflowState(newState);
		
		//move directly to the specified state
		transition(workflow, review, request, WF_OPERATION_ENTRY, userLoginId);
	}
	
	
	/** request submission state handler.
	 * 
	 * @param request
	 * @param operation
	 * @param userLoginId
	 * @throws WorkflowException
	 */
	protected void processInitialState(final Request request, final int operation, final String userLoginId) throws WorkflowException {
		log.debug("WfResearchRequest state one 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) {
				return ; // nothing to do if we just entered the state
			}
			
			if (operation == Workflow.WF_OPERATION_SUBMIT) {	//request submitted the first time
				log.debug("request submitted!");

				//
				//create event(s)
				Event submitEvent = null;
				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 {
					submitEvent = Event.create(EventType.SUBMIT_REQUEST, request, userLoginId);
				}


				//
				//@submit, determine if this request needs to be sent to NDS and/or which Independent review groups (process in each of the appropriate child workflows)
				List<RequestWorkflow> childWorkflowList = new ArrayList<RequestWorkflow>();
				List<WorkflowTemplate> childWorkflowTemplates = new ArrayList<WorkflowTemplate>();

				//get a COPY of the current set of selected data sources
				Set<DataSource> requestDataSources = new HashSet<DataSource>(); 
				requestDataSources.addAll( request.getDataSources() );


				//
				//At initial submission of an amendment, determine if we need to create any new workflows: based on the uploaded documents and the selected data sources.
				if( request.isAmendment() ) {
					//log.debug("submitted an amendment!");

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

						Request prevReq = prevReqList.get(0);	//use the most recent request (immediate parent of this amendment)
						if( prevReq != null ) {

						    //TODO: don't allow previously approved data sources to be removed, so add any that might be missing here?  Covered by the UI before we get here.

							//get the timestamp of the event when this amendment was created
							Event initiateEvent = null;
							EventType.initialize();	//initialize just in case
							List<Event> initiateEventList = Event.listByEventTypeAndRequestId( EventType.INITIATE_REQUEST.getId(), request.getId() );
							if( initiateEventList != null && initiateEventList.size() > 0 ) {
								initiateEvent = initiateEventList.get(0);	//there should be only one iniated event
							}
							
							//get the modified documents, and determine if we need to send the request to any additional workflows
							Set<Document> currDocuments = request.getDocuments();
							if( currDocuments != null ) {
								for( Document doc : currDocuments ) {

									//Note: if we copy all versions from the original request to the amendment, the document upload test will need to change (check the document content/version or could just check the upload date:  List<Document> docList = Document.listVersionsById(origDocId);)
									
									//if a new version of the amendment document has been uploaded, need to process this workflow again (send back to this group for review)
									if( doc != null ) {
										if( (doc.getId() != doc.getHead()) ||	//uploaded a new version
											(initiateEvent != null && initiateEvent.getCreatedOn() != null && doc.getUpdatedOn() != null && doc.getUpdatedOn().after(initiateEvent.getCreatedOn())) ) {	//uploaded a version of this document after creating the amendment
										
											DocumentTemplate docTemplate = doc.getDocumentTemplate();
											if( docTemplate != null ) {
												
												Set<WorkflowTemplate> amendWorkflowTemplateSet = docTemplate.getAmendmentWorkflowTemplates();
												if( amendWorkflowTemplateSet != null ) {

													for( WorkflowTemplate amendWorkflowTemplate : amendWorkflowTemplateSet ) {
														if( amendWorkflowTemplate != null ) {

															//remember if we have seen this workflow template before (only create one NDS workflow even if multiple NDS data sources are selected)
															if( childWorkflowTemplates.contains(amendWorkflowTemplate) == false ) {	//have NOT yet created a child workflow for this template
																childWorkflowTemplates.add(amendWorkflowTemplate);	//send this amend back to this review group (remember that we have seen this workflow template)

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

															}//end if -- new workflow template

														}//end if -- amendWorkflowTemplate
													}//end for -- amendWorkflowTemplateSet

												}//end if
											}//end if -- docTemplate

										}//end if -- uploaded a new version
									}//end if -- doc
								}//end for
							}//end if -- currDocuments

						}//end if -- prevReq
					}//end if -- prevReqList

				}//end if -- amendment



				//
				//@submit, determine if this request needs to be sent to NDS and/or which Independent review groups (process in each of the appropriate child workflows)
//				List<RequestWorkflow> childWorkflowList = new ArrayList<RequestWorkflow>();
//				List<WorkflowTemplate> childWorkflowTemplates = new ArrayList<WorkflowTemplate>();
//
//				Set<DataSource> requestDataSources = request.getDataSources();
				if( requestDataSources != null ) {
					for( DataSource currDataSource : requestDataSources ) {	//step through the selected Data Sources (new to this amendment, or everything attached to the request if not an amendment)

						if( currDataSource != null ) {

							Set<WorkflowTemplate> workflowTemplateSet = currDataSource.getWorkflowTemplates();	//get the WorkflowTemplates for this data source (top-level group)
							if( workflowTemplateSet != null ) {
								for( WorkflowTemplate workflowTemplate : workflowTemplateSet ) {
							
									if( workflowTemplate != null ) {

										//remember if we have seen this workflow template before (only create one NDS workflow even if multiple NDS data sources are selected)
										if( childWorkflowTemplates.contains(workflowTemplate) == false ) {	//have NOT yet created a child workflow for this template
											childWorkflowTemplates.add(workflowTemplate);	//remember that we have seen this workflow template
											
											try {
												//create the appropriate workflows (NDS + each Independent workflow)
												RequestWorkflow newChildWorkflow = createAndInitializeChildWorkflow(request, userLoginId, workflowTemplate);
												if( newChildWorkflow != null ) {
													childWorkflowList.add(newChildWorkflow);
												}//end if
											} catch( Exception e ) {
												log.error("Error occurred while trying to create the workflow: " + e.getMessage());
												
											}

										}//end if
										
									}//end if -- workflowTemplate

								}//end for -- workflowTemplateSet
							}//end if -- workflowTemplateSet

						}//end if -- currDataSource
					}//end for
				}//end if


/* testing *
//TODO: could bind this submit event to all of the groups for which this submit is performed?  But we also have all of the subsequent "sent for review" events, so we're probably okay
				//determine which workflows this request is being submitted to (or just ignore that)
				if( childWorkflowList != null && childWorkflowList.size() > 0 ) {
					for( RequestWorkflow childWorkflow : childWorkflowList ) {
						if( childWorkflow != null ) {
							Group workflowGroup = childWorkflow.getWorkflowTemplate().getReviewer();
							workflowGroup.getShortName();
						}
					}
				}//end if
/* end testing */				
				
				//
				//process the submit for each child workflow
				if( childWorkflowList != null && childWorkflowList.size() > 0 ) {
					for( RequestWorkflow childWorkflow : childWorkflowList ) {
						if( childWorkflow != null ) {
							workflowResolver.resolve(childWorkflow).submit(childWorkflow, request, userLoginId);		//process the submit for this new workflow
						}
					}
				}//end if


				//
				//update the request state to "submitted"
				request.submit(userLoginId);

				// go to the next state:  ready to be processed by the child workflows
				setState(null, null, request, SUBMITTED_STATE, userLoginId);
			}
			else {
				log.error("Error: request " + request.getTrackingNumber() + " in unexpected state.");
			}
		} catch (Exception e) {
			throw new WorkflowException(e);
		} 
		
	}
	
	
	/** Workflow state: in-process (the child workflows not yet completed).
	 * 		Request has been submitted and is in-process.
	 * 
	 * @param request
	 * @param operation
	 * @param userLoginId
	 * @throws WorkflowException
	 */
	protected synchronized void processSubmittedState(final RequestWorkflow workflow, final Review review, final Request request, final int operation, final String userLoginId) throws WorkflowException {
		log.debug("WfResearchRequest state two operation = " + operation + " request = " + request.getTrackingNumber());
				
		try {
			EventType.initialize();	//this may be extra, but initialize just in case
			Group.initialize();		//initialize the Groups


			Group reviewGroup = null;
			if( review == null ) {	//NDS
				reviewGroup = Group.NDS;
			} else  {	//intermediate review or an Independent Review group
				reviewGroup = review.getReviewer();
			}


			if (operation == Workflow.WF_OPERATION_ENTRY) {
				return;  // stay in this state
			}
			
			if (operation == Workflow.WF_OPERATION_SUBMIT) {	//request submitted by requestor (in response to a change request)
				log.debug("wfResearchRequest: request submitted!");

				if( request != null ) {

					//handle the change request
					if (request.getStatus().getId() == RequestStatus.CHANGE_REQUESTED.getId()) {	

						//determine if the workflows need to change:  Do we need to close any child workflows?  Do we need to create any new child workflows?  Do we need to submit any child workflows with changes?

						//get the current list of workflow templates attached to this request
						List<WorkflowTemplate> currChildWorkflowTemplates = new ArrayList<WorkflowTemplate>();
						Set<RequestWorkflow> currChildWorkflowSet = request.getWorkflows(true);	//get the current open workflows
						if( currChildWorkflowSet != null ) {
							for( RequestWorkflow currWorkflow : currChildWorkflowSet ) {
								
								if( currWorkflow != null && currWorkflow.getWorkflowTemplate() != null ) {
									if( currWorkflow.getWorkflowTemplate().getWorkflowTypeId() == WorkflowResolver.WF_NDS ) {	//NDS workflow?
										
										try {
											//get the NDS workflow template
											WorkflowTemplate workflowTemplate = WorkflowTemplate.findByGroupIdAndWorkflowTypeId(Group.NDS.getId(), currWorkflow.getWorkflowTemplate().getWorkflowTypeId());
											if( workflowTemplate != null ) {
												currChildWorkflowTemplates.add( workflowTemplate );	//remember that we have seen this workflow template
											}
	
										} catch( ObjectNotFoundException e ) {
											log.error("Could not find the WorkflowTemplate for groupId: " + Group.NDS.getId() + " and workflowTypeId: " + WorkflowResolver.WF_NDS + ". " + e.getMessage());
											
										}
	
									} else if( currWorkflow.getWorkflowTemplate().getWorkflowTypeId() == WorkflowResolver.WF_INDEPENDENT) {	//Independent workflow?
										
										Set<Review> currWorkflowReviewSet = currWorkflow.getReviews();
										if( currWorkflowReviewSet != null ) {
											for( Review currReview : currWorkflowReviewSet ) {
												
												if( currReview != null && currReview.getReviewer() != null ) {	//have a non-null review group

													try {
														//get the workflow template for this Independent Review group
														WorkflowTemplate workflowTemplate = WorkflowTemplate.findByGroupIdAndWorkflowTypeId(currReview.getReviewer().getId(), currWorkflow.getWorkflowTemplate().getWorkflowTypeId());
														if( workflowTemplate != null ) {
															currChildWorkflowTemplates.add( workflowTemplate );	//remember that we have seen this workflow template
														}

													} catch( ObjectNotFoundException e ) {
														log.error("Could not find the WorkflowTemplate for groupId: " + currReview.getReviewer().getId() + " and workflowTypeId: " + currWorkflow.getWorkflowTemplate().getWorkflowTypeId() + ". " + e.getMessage());
														
													}

												}
											}
										}
									}
								}
							}
						}

						//determine if there are any new workflows needed (new Data Sources, new documents uploaded for amendment)
						List<RequestWorkflow> newChildWorkflowList = new ArrayList<RequestWorkflow>();
						Set<WorkflowTemplate> dataSourcesWorkflowTemplates = new HashSet<WorkflowTemplate>();
						
						//get a COPY of the current set of selected data sources
						Set<DataSource> requestDataSources = new HashSet<DataSource>(); 
						requestDataSources.addAll( request.getDataSources() );

						//At submission of an amendment, determine if we need to create any new workflows: based on the uploaded documents and the selected data sources.
						//	get the new selected data sources (amendment - previous request)
						//	get the modified documents since the creation of the amendment:  if one of the amendment documents was changed during the change request, create the appropriate workflow if it doesn't already exist
						if( request.isAmendment() ) {
							//log.debug("submitted an amendment with changes!");

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

								Request prevReq = prevReqList.get(0);	//use the most recent request (immediate parent of this amendment)
								if( prevReq != null ) {

//TODO: don't allow previously approved data sources to be removed, so add any that might be missing here?  Covered by the UI before we get here.

//testing -- create a workflow for all of the data sources, not just the new ones for this amendment (so that there is something to submit)
//									//
//									//get the delta of the approved data sources from the previous request and the data sources selected for this new amendment
//									Set<DataSource> prevDataSources = prevReq.getSelectedAndApprovedDataSources();
//									Set<DataSource> currDataSources = request.getDataSources();
//									
//									Set<DataSource> newDataSources = new HashSet<DataSource>();
//									if( prevDataSources != null && currDataSources != null ) {
//										newDataSources.addAll( DataSource.minus(currDataSources, prevDataSources) );	//in the current data sources, but not in the previous data sources
//									}//end if
//									
//									requestDataSources.clear();	//clear the COPY of the selected data sources
//									requestDataSources.addAll(newDataSources);	//keep the new data sources (haven't yet been approved)
//end testing -- create a workflow for all of the data sources, not just the new ones for this amendment (so that there is something to submit)

									//get the timestamp of the event when this amendment was created
									Event initiateEvent = null;
									EventType.initialize();	//initialize just in case
									List<Event> initiateEventList = Event.listByEventTypeAndRequestId( EventType.INITIATE_REQUEST.getId(), request.getId() );
									if( initiateEventList != null && initiateEventList.size() > 0 ) {
										initiateEvent = initiateEventList.get(0);	//there should be only one initiated event
									}

									//get the modified documents, and determine if we need to send the request to any additional workflows
									Set<Document> currDocuments = request.getDocuments();
									if( currDocuments != null ) {
										for( Document doc : currDocuments ) {
											
											//Note: if we copy all versions from the original request to the amendment, the document upload test will need to change (check the document content/version or could just check the upload date:  List<Document> docList = Document.listVersionsById(origDocId);)
											
											//if a new version of the amendment document has been uploaded, need to process this workflow again (send back to this group for review)
											//if( doc != null && (doc.getId() != doc.getHead()) ) {	//uploaded a new version
											if( doc != null ) {
												if( (doc.getId() != doc.getHead()) ||	//uploaded a new version
													(initiateEvent != null && initiateEvent.getCreatedOn() != null && doc.getUpdatedOn() != null && doc.getUpdatedOn().after(initiateEvent.getCreatedOn())) ) {	//uploaded a version of this document after creating the amendment


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

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

															for( WorkflowTemplate amendWorkflowTemplate : amendWorkflowTemplateSet ) {
																if( amendWorkflowTemplate != null ) {

																	//remember that we have seen this workflow template (for the current selected Data Sources / uploaded amendment document)
																	if( dataSourcesWorkflowTemplates.contains(amendWorkflowTemplate) == false ) {
																		dataSourcesWorkflowTemplates.add(amendWorkflowTemplate);
																	}

																	//remember if we have seen this workflow template before (only create one NDS workflow even if multiple NDS data sources are selected)
																	if( currChildWorkflowTemplates.contains(amendWorkflowTemplate) == false ) {	//have NOT yet created a child workflow for this template
																		currChildWorkflowTemplates.add(amendWorkflowTemplate);	//send this amend back to this review group (remember that we have seen this workflow template)
																		
																		try {
																			//create the appropriate workflows (NDS + each Independent workflow)
																			RequestWorkflow newChildWorkflow = createAndInitializeChildWorkflow(request, userLoginId, amendWorkflowTemplate);
																			if( newChildWorkflow != null ) {
																				newChildWorkflowList.add(newChildWorkflow);
																			}//end if
																		} catch( Exception e ) {
																			log.error("Error occurred while trying to create the workflow: " + e.getMessage());
																			
																		}

																	}//end if -- new workflow template

																}//end if -- amendWorkflowTemplate
															}//end for -- amendWorkflowTemplateSet

														}//end if
													}//end if -- docTemplate

												}//end if -- uploaded a new version of the document
											}//end if -- doc
										}//end for
									}//end if -- currDocuments

								}//end if -- prevReq
							}//end if -- prevReqList

						}//end if -- amendment
						
						
						//
						//step through the selected Data Sources, and determine if there are any new data sources (create any new workflows that are needed)
						if( requestDataSources != null ) {
							for( DataSource currDataSource : requestDataSources ) {	//step through the selected Data Sources (new to this amendment, or everything attached to the request if not an amendment)

								if( currDataSource != null ) {

									Set<WorkflowTemplate> workflowTemplateSet = currDataSource.getWorkflowTemplates();	//get the WorkflowTemplates for this data source (top-level group)
									if( workflowTemplateSet != null ) {
										for( WorkflowTemplate workflowTemplate : workflowTemplateSet ) {
									
											if( workflowTemplate != null ) {

												//remember that we have seen this workflow template (for the current selected Data Sources)
												if( dataSourcesWorkflowTemplates.contains(workflowTemplate) == false ) {
													dataSourcesWorkflowTemplates.add(workflowTemplate);
												}//end if

												
												//remember if we have seen this workflow template before (only create one NDS workflow even if multiple NDS data sources are selected)
												if( currChildWorkflowTemplates.contains(workflowTemplate) == false ) {	//have NOT yet created a child workflow for this template
													currChildWorkflowTemplates.add(workflowTemplate);	//remember that we have seen this workflow template

													try {
														//create the appropriate workflows (NDS + each Independent workflow)
														RequestWorkflow newChildWorkflow = createAndInitializeChildWorkflow(request, userLoginId, workflowTemplate);
														if( newChildWorkflow != null ) {
															newChildWorkflowList.add(newChildWorkflow);
														}//end if
													} catch( Exception e ) {
														log.error("Error occurred while trying to create the workflow: " + e.getMessage());
														
													}


												}//end if
												
											}//end if -- workflowTemplate

										}//end for -- workflowTemplateSet
									}//end if -- workflowTemplateSet

								}//end if -- currDataSource
							}//end for
						}//end if


						//
						//determine if any of the workflows have been removed (data sources removed)
						Set<RequestWorkflow> workflowToCloseSet = new HashSet<RequestWorkflow>();
						for( WorkflowTemplate workflowTemplate : currChildWorkflowTemplates ) {	//step through the list of current open child workflows attached to this request
							if( workflowTemplate != null ) {

								if( dataSourcesWorkflowTemplates.contains(workflowTemplate) == false ) {	//this workflow template is no longer covered by any selected Data Source

									//Don't bother to close a workflow that is already in a final state.
									//
									//get the RequestWorkflow for the specified WorkflowTemplate
									RequestWorkflow workflowToClose = RequestWorkflow.findOpenByRequestAndWorkflowTemplateId( request.getId(), workflowTemplate.getId() );
									if( workflowToClose != null && workflowToClose.isCompleted() == false ) {	//have NOT yet completed this data request/workflow

										//
										//process the removed workflows (close the workflow entry, close any tasks, create the close event)
										if( workflowToClose.isClosed() == false ) {	//have NOT yet closed this workflow
											workflowToCloseSet.add( workflowToClose );

										}//end if -- have NOT yet closed this workflow
									}//end if
								}//end if

							}//end if -- workflowTemplate
						}//end for
						
						
						
						//
						//create event to capture that request has been submitted with changes, for all child workflows that are in a change requested state and all new child workflows:  request.getWorkflows(true), newChildWorkflowList
						createReqSubmittedEventForAllWorkflows(request, workflowToCloseSet, newChildWorkflowList, userLoginId);



						//
						//close the workflows that are no longer covered by the selected data sources (process the closed workflows)
						for( RequestWorkflow workflowToClose: workflowToCloseSet ) {	//step through the list of current open child workflows attached to this request

							//Don't bother to close a workflow that is already in a final state.
							if( workflowToClose != null && workflowToClose.isCompleted() == false ) {	//have NOT yet completed this data request/workflow

								//
								//process the removed workflows (close the workflow entry, close any tasks, create the close event)
								if( workflowToClose.isClosed() == false ) {	//have NOT yet closed this workflow

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

//TODO: should probably email the reviewers, etc., to let them know that this review no longer needs to be completed (might need to know where the workflow has gotten to -> let the child workflow handle this?)
										
										//
										//add an event for closing the workflow (removed all of the data sources for this workflow)
										Group group = workflowTemplate.getReviewer();
										final String groupShortName = group.getShortName();
										boolean isInitial = isInitialNDSReviewEvent(workflowToClose, request);	//initial NDS review?
	
										//3/4/2015:  Tim specified that this event should include the group name instead of the workflow name (for consistency)
										Event.create((groupShortName + " Request Closed"), (groupShortName + " Request Closed"), EventType.CLOSE_WORKFLOW, group, isInitial, request, review, userLoginId);	//removed the data sources for this workflow, so closed the workflow
	
	
										//close all tasks associated with this workflow (entire workflow removed)
										TaskUtils.closeAllTasksForWorkflowAndRequest( workflowToClose, request, userLoginId );
	
										//update the workflow status (mark the workflow as closed)
										workflowToClose.close(userLoginId);
									}//end if -- workflowTemplate
								}//end if -- have NOT yet closed this workflow
							}//end if
						}//end for



						//
						//process the submit for any remaining child workflows that are in a *change requested* state
						for( RequestWorkflow currWorkflow : request.getWorkflows(true) ) {	//get the current open workflows
							if( currWorkflow != null && currWorkflow.isChangeRequested() == true ) {
								workflowResolver.resolve(currWorkflow).submit(currWorkflow, request, userLoginId);			//process the submit for this child workflow (in a change requested state)
							}//end if
						}//end for
						

						//
						//process the submit for new child workflows (after processing the change requests)
						if( newChildWorkflowList != null && newChildWorkflowList.size() > 0 ) {
							for( RequestWorkflow childWorkflow : newChildWorkflowList ) {
								if( childWorkflow != null ) {
									workflowResolver.resolve(childWorkflow).submit(childWorkflow, request, userLoginId);	//process the submit for this new workflow
								}
							}
						}//end if						
						
						
					} else {	//NOT a change request -- shouldn't be in this state for an initial submission
						throw new WorkflowException("Error: request " + request.getTrackingNumber() + " in unexpected state.");
					}

				}//end if -- request

				//update the request state to "submitted"
				request.submit(userLoginId);

				//if all child workflows are completed, move to the final state.  Otherwise, stay in this state.
				if( request.allWorkflowsCompleted() ) {


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

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

				} else {	//have at least one workflow still in-process
					
					return;	//stay in this state (processing child workflows: a request was just submitted and needs to be reviewed)

				}//end else
			}
			
			else if (operation == Workflow.WF_OPERATION_CHANGE_REQUEST) {
				
				if( workflow != null ) {

					//
					//perform the change request at the appropriate child workflow
					workflowResolver.resolve(workflow).changeRequest(workflow, review, request, userLoginId);

				}//end if -- workflow

				return;  // stay in this state (processing child workflows)
			}

			else if (operation == Workflow.WF_OPERATION_APPROVE) {

				if( workflow != null ) {

					//
					//perform the approval at the appropriate child workflow
					workflowResolver.resolve(workflow).approve(workflow, review, request, userLoginId);

				}//end if -- workflow


				//
				//if all child workflows are completed, move to the final state.  Otherwise, stay in this state.
				if( request.allWorkflowsCompleted() ) {

					//
					//send out the Request Completed email to the participants with notifications
					EmailUtils.createAndSendRequestCompletedEmail( request, workflow, reviewGroup, operation );


					//
					//move to the final state
					setState(null, review, request, FINAL_STATE, userLoginId);  // go to final state
				} else {

					//
					//if this workflow is now completed (was this a final approval?), then send out the notification to the appropriate groups
					if( workflow != null && workflow.isCompleted() == true ) {
						
						//
						//send out the Review Pending email to the participants with notifications
						Set<RequestWorkflow> workflowSet = request.getWorkflows(true);
						if( workflowSet != null && workflowSet.size() > 1 ) {	//have more than one workflow attached to this request
							EmailUtils.createAndSendReviewPendingEmail( request, workflow, reviewGroup, operation );
						}//end if

					}//end if


					return;	//stay in this state (processing child workflows)
				}
			}

			else if (operation == Workflow.WF_OPERATION_DENY) {

				if( workflow != null ) {

					//
					//perform the denial at the appropriate child workflow
					workflowResolver.resolve(workflow).deny(workflow, review, request, userLoginId);

				}//end if -- workflow
				
				
				//
				//if all child workflows are completed, move to the final state.  Otherwise, stay in this state.
				if( request.allWorkflowsCompleted() ) {

					//
					//send out the Request Completed email to the participants with notifications
					EmailUtils.createAndSendRequestCompletedEmail( request, workflow, reviewGroup, operation );


					//
					//move to the final state
					setState(null, review, request, FINAL_STATE, userLoginId);  // go to final state
				} else {

					//
					//if this workflow is now completed, then send out the notification to the appropriate groups
					if( workflow != null && workflow.isCompleted() == true ) {	//should now be completed, but we may have a workflow type where deny is not a final state)
						
						//
						//send out the Review Pending email to the participants with notifications
						Set<RequestWorkflow> workflowSet = request.getWorkflows(true);
						if( workflowSet != null && workflowSet.size() > 1 ) {	//have more than one workflow attached to this request
							EmailUtils.createAndSendReviewPendingEmail( request, workflow, reviewGroup, operation );
						}//end if

					}//end if


					return;	//stay in this state (processing child workflows)
				}
			}



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


	/** Request Completed (final state) state handler.
	 * 
	 * @param request
	 * @param operation
	 * @param userLoginId
	 * @throws WorkflowException
	 */
	protected void processFinalState(final Request request, final int operation, final String userLoginId) throws WorkflowException {
		log.debug("WfResearchRequest stateRequestCompleted 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) {
				
				//
				//verify that all of the workflows are completed
				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);

				}//end if
					
				return ; // nothing to do if we just entered the state
			}
			else {
				log.error("Error: request " + request.getTrackingNumber() + " 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
	 * @param userLoginId
	 * @param workflowTemplate
	 * @return
	 * @throws ValidationException
	 * @throws ObjectNotFoundException
	 * @throws WorkflowException
	 */
	private RequestWorkflow createAndInitializeChildWorkflow( final Request request, final String userLoginId, final WorkflowTemplate workflowTemplate ) throws ValidationException, ObjectNotFoundException, WorkflowException {

		//create the appropriate workflows (NDS + each Independent workflow)
		if( workflowTemplate.getWorkflowTypeId() == WorkflowResolver.WF_NDS ) {	//NDS workflow?

			//create and initialize an NDS workflow
			RequestWorkflow ndsWorkflow = RequestWorkflow.create(request, workflowTemplate, userLoginId);

			workflowResolver.resolve(ndsWorkflow).initialize(ndsWorkflow, request, userLoginId);	//initialize the child workflow
			
			return ndsWorkflow;
			
		} else if( workflowTemplate.getWorkflowTypeId() == WorkflowResolver.WF_INDEPENDENT ) {	//Independent workflow?

			Group group = workflowTemplate.getReviewer();
			if( group != null ) {
				ReviewTemplate template = ReviewTemplate.findByGroupId( group.getId() );	//find the ReviewTemplate for this group

				//create and initialize an Independent workflow for this group
				RequestWorkflow independentWorkflow = RequestWorkflow.create(request, workflowTemplate, userLoginId);
				Review.create(independentWorkflow, request, template, template.getReviewer(), userLoginId);	//add the top-level review to this workflow

				workflowResolver.resolve(independentWorkflow).initialize(independentWorkflow, request, userLoginId);	//initialize the child workflow

				return independentWorkflow;
			}//end if
		}//end else if

		return null;
	}
	
	
	private boolean isInitialNDSReviewEvent( final RequestWorkflow workflow, final Request request ) {

		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() ) {
					try {
                        isInitial = ((workflowResolver.resolve(workflow).isInitialReviewCompleted(workflow, request)) == false);
                    } catch (WorkflowException e) {
                        log.error("Failed to retrieve initial NDS Review Event: Exception message: " + e.getMessage());  
                    }
				}
			}
		}
		
		return isInitial;
	}

	private boolean isFinalNDSReviewEvent( final RequestWorkflow workflow, final Request request ){

		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() ) {
					try {
                        isFinal = ((workflowResolver.resolve(workflow).isReadyForFinalReview(workflow, request)) == true);
                    } catch (WorkflowException e) {
                        log.error("Failed to retrieve Final NDS Review Event: Exception message: " + e.getMessage());
                       
                    }	
				}
			}
		}

		return isFinal;
	}
	
	
	/**
	 * Create the top-level event for the "submit with changes"
	 */
	protected void createReqSubmittedEventForAllWorkflows(final Request request, final Collection<RequestWorkflow> workflowToCloseSet, final Collection<RequestWorkflow> newChildWorkflowList, final String userLoginId) throws ValidationException {

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

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


		//step through the current open workflows attached to this request
		Set<RequestWorkflow> currentWorkflowSet = request.getWorkflows(true);
		if( currentWorkflowSet != null ) {
			for( RequestWorkflow workflow : currentWorkflowSet ) {
				if( workflow != null && request != null ) {

					if( workflow.isChangeRequested() ) {	//workflow in a change requested state
	
						if( isInitNDSReview == false ) {	//remember if we have found an initial NDS review in any workflow in a change requested state
							isInitNDSReview = isInitialNDSReviewEvent( workflow, request );
						}

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


		//step through the list of new workflows being added to the request (new, so not yet in a change requested state)
		if( newChildWorkflowList != null ) {
			for( RequestWorkflow newWorkflow : newChildWorkflowList ) {
				if( newWorkflow != null && request != null ) {

					if( isInitNDSReview == false ) {	//remember if we have found an initial NDS review in any workflow in a change requested state
						isInitNDSReview = isInitialNDSReviewEvent( newWorkflow, request );
					}

					//the group will NOT be in a change requested state for the newly added workflow (just get the top-level group for this workflow?)
					String groupsRequestingChangesStr = createReqSubmittedStringForWorkflow( request, newWorkflow, true, allChangeRequestGroupSet );
					if( groupsRequestingChangesStr != null && groupsRequestingChangesStr.trim().isEmpty() ==  false ) {
						if( groupStr.toString().trim().isEmpty() == false )
							groupStr.append(", ");
						
						groupStr.append( groupsRequestingChangesStr );
					}
				}
			}
		}

		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);
	}

	
	protected 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 = isInitialNDSReviewEvent( workflow, request );

			boolean isFinalNDSReview = isFinalNDSReviewEvent( workflow, request );

			//the group will NOT be in a change requested state for the newly added workflow (just get the group for this workflow?)
			if( isNewWorkflow ) {	//newly added workflow (get the top-level group)

				//get the top-level group for the newly added workflow
				if( workflow != null && workflow.getWorkflowTemplate() != null ) {
					Group newWorkflowChangeRequestGroup = workflow.getWorkflowTemplate().getReviewer();

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

			} else {

				//get the groups that have requested a change
				Set<Group> workflowChangeRequestGroupSet = request.getChangesRequestedGroups(workflow);
				if( isInitNDSReview || isFinalNDSReview )	//add NDS to the list of groups requesting a change?
					workflowChangeRequestGroupSet.add(Group.NDS);

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

			}//end else

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

		return "";
	}
	
}
