package gov.va.vinci.dart.wf2;


import java.util.List;

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

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


/** A workflow for a VINCI DART operational request.
 */
@Service
public class WfOperationalRequest extends AbstractWorkflow {
	private static Log log = LogFactory.getLog(WfOperationalRequest.class);

	public static final int INITIAL_STATE = 1;
	public static final int SUBMITTED_STATE = 2;	//used by OperationalRequest to determine the state of the request (for the UI)
	public static final int OPS_FINAL_REVIEW_STATE = 3;	//ready for final NDS review
	public static final int FINAL_STATE = 16;
	
	
	/** Get the final state of the current workflow item.
	 * 
	 */
	@Override 
	public int getFinalState() {
		return FINAL_STATE;
	}
	
	@Override
	public int getFinalReviewState() {
		return OPS_FINAL_REVIEW_STATE;
	}

	@Override
	public int getSubmittedState() {
		return SUBMITTED_STATE;
	}
	
	
	/** Calculate the percentage of review completion on the request.
	 * 
	 * @param workflow -- ignored for the Operations workflow
	 * 
	 * @return
	 */
	@Override
	public String calculateReviewCompletion( final RequestWorkflow workflow, final Request request ) {

		final int numReviews = 2;	//for Operations workflow, +2 for the NDS initial review and NDS final review
		int cnt = 0;
		
		if( request != null ) {
			if( OperationalRequest.class.isAssignableFrom(request.getClass()) ) {
		
				if( isReadyForFinalReview(workflow, request) )	//initial review is done
					cnt++;
				
				if( isFinalReviewCompleted(workflow, request) )	//final review is done
					cnt++;
			}
		}//end if
		
		return ((cnt * 100) / numReviews) + "%";
	}
	
	/**
	 * Returns true if ready for the NDS initial review
	 * @return
	 */
	@Override
	public boolean isReadyForInitialReview( final RequestWorkflow workflow, final Request request ) {

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


//				if( workflowState == WfOperationalRequest.SUBMITTED_STATE && request.isSubmittedOrChanged() )	//submitted or change requested
				if( workflowState == getSubmittedState() && request.isSubmittedOrChanged(workflow) )	//submitted or change requested
					return true;

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

		}//end if
			
		return false;
	}
	
	/**
	 * Returns true if the NDS initial review is completed.
	 * @return
	 */
	@Override
	public boolean isInitialReviewCompleted( final RequestWorkflow workflow, final Request request ) {
		return (isReadyForFinalReview(workflow, request));
	}
	
	/**
	 * Returns true if ready for the NDS final review
	 * @return
	 */
	@Override
	public boolean isReadyForFinalReview( final RequestWorkflow workflow, final Request request ) {

		if( request != null ) {
			
			try {
				int workflowState = -1;
				if( workflow != null ) {
					workflowState = workflow.getWorkflowState();	//specific workflow state
				} else {
					workflowState = request.getWorkflowState();		//top-level workflow state (old data)
				}
				
//				if( workflowState == WfOperationalRequest.OPS_FINAL_REVIEW_STATE && request.isSubmittedOrChanged(workflow) )	//initial approval done
				if( workflowState == getFinalReviewState() && request.isSubmittedOrChanged(workflow) )	//initial approval done
					return true;
			
			} catch (ObjectNotFoundException e) {
				log.debug("Exception occurred while determining the final review state:  " + e.getMessage());
				e.printStackTrace();
			}

		}//end if
		
		return false;
	}

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

		if( request != null ) {
			
			try {
				int workflowState = -1;
				if( workflow != null ) {
					workflowState = workflow.getWorkflowState();	//specific workflow state
				} else {
					workflowState = request.getWorkflowState();		//top-level workflow state (old data)
				}
				
//				if( workflowState == WfOperationalRequest.FINAL_STATE )
				if( workflowState == getFinalState() )
					return true;
		
			} catch (Exception e) {
				log.debug("Exception occurred while determining the final review state:  " + e.getMessage());
				e.printStackTrace();
			}

		}//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 {
		switch (request.getWorkflowState()) {
			case 1:
				stateOne(request, operation, userLoginId);
				break;
			case 2:
				stateTwo(request, operation, userLoginId);
				break;
			case 3:
				stateThree(request, operation, userLoginId);
				break;
			case FINAL_STATE:
				return;
			default:
				throw new WorkflowException("Illegal state encountered");
		}
	}


	/** Initialize workflow processing for the current workflow item.
	 * 
	 */
	@Override
	public void initialize(final RequestWorkflow workflow, final Request request, final String userLoginId) throws WorkflowException {
		request.setWorkflowMask(0x0fL);
		log.debug("WfOperationalRequest initialize " + request.getTrackingNumber());
		setState(null, null, request, INITIAL_STATE, userLoginId);
	}

	/** Request submission state handler.
	 * 
	 * @param request
	 * @param operation
	 * @param userLoginId
	 * @throws WorkflowException
	 */
	protected void stateOne(final Request request, final int operation, final String userLoginId) throws WorkflowException {
		log.debug("WfOperationalRequest state one operation = " + operation + " request = " + request.getTrackingNumber());

		final boolean isInitNDSReview = true;
		final boolean isFinalNDSReview = false;
		
		try {
			EventType.initialize();
			Group.initialize();
			
			if (operation == Workflow.WF_OPERATION_ENTRY) {
				return ; // nothing to do if we just entered the state
			}
			
			if (operation == Workflow.WF_OPERATION_SUBMIT) {
				log.debug("request submitted!");
				
				// email NDS administrator
				try {

					//
					//create event(s)
					if (request.getStatus().getId() == RequestStatus.CHANGE_REQUESTED.getId()) {					
						createReqSubmittedEvent(null, request, userLoginId, isInitNDSReview, isFinalNDSReview);
					} else {
						Event.create("Submitted", "Submitting Data Access Request Packet to NDS", EventType.SUBMIT_REQUEST, Group.NDS, true, request, userLoginId);
						
//						//
//						//Per Jeff S. request, also track an event for "Sent for Initial NDS Review"
//						Event.create("Request Sent for Initial NDS Review", "Request Sent for Initial NDS Review", request, userLoginId);					
					}
					//
					//Per Jeff S. request, also track an event for "Sent for Initial NDS Review"
					Event.create("Request Sent for Initial NDS Review", "Request Sent for Initial NDS Review", EventType.SENT_FOR_REVIEW, Group.NDS, true, request, userLoginId);
					
	
					request.submit(userLoginId);
					
				} catch (Exception e) {
					throw new WorkflowException(e);
				} 
				
				// delete tasks relative to this request (if this is a change request, update)
				TaskUtils.closeUserTasksForChangeRequest( null, request, userLoginId );				
				
				// go to the next state
				incrementState(null, null, request, userLoginId);
			}
			else if (operation == Workflow.WF_OPERATION_CLOSE) {

				StringBuffer fullName = new StringBuffer();
				StringBuffer subject = createOperationsEmailSubject(request);
				
				subject.append(getActionName(Workflow.WF_OPERATION_CLOSE));
												
				StringBuffer closedBody = new StringBuffer("The following Data Access Request has been closed:");
				EmailUtils.appendDartRequestAttributes(request, closedBody);

				EmailUtils.appendDartIndexPageInstructions(closedBody);
				
				EmailUtils.emailRequestorAndAllNotifications(request, subject.toString(), closedBody.toString());
					
				setState(null, null, request, FINAL_STATE, userLoginId);  // go to final state
			}
			else {
				log.error("Error: request " + request.getTrackingNumber() + " in unexpected state.");
			}
		} catch (Exception e) {
			throw new WorkflowException(e);
		}
	}
	
	/** NDS initial review state handler.
	 * 
	 * @param request
	 * @param operation
	 * @param userLoginId
	 * @throws WorkflowException
	 */
	protected void stateTwo(final Request request, final int operation, final String userLoginId) throws WorkflowException {
		log.debug("WfOperationalRequest state two operation = " + operation + " request = " + request.getTrackingNumber());
		
		try {
			EventType.initialize();
			Group.initialize();
			
			if (operation == Workflow.WF_OPERATION_ENTRY) {

				StringBuffer subject = createOperationsEmailSubject(request);
				
				subject.append(getActionName(Workflow.WF_OPERATION_SUBMIT));
				
				OperationalRequest oRequest = (OperationalRequest)request; 
				
				// generate the body
				StringBuffer initialNDSBody = new StringBuffer("A new Operational Data Access Request has been submitted for your approval:");
				initialNDSBody.append("\r\rOperational Request Name: ").append(request.getActivity().getOfficialName());
				initialNDSBody.append("\rProgram Office: ").append(oRequest.getProgramOffice());
				initialNDSBody.append("\rJustification: ").append(oRequest.getJustification());
				initialNDSBody.append("\rStart Date: ").append(request.getActivity().getStartDate());
				initialNDSBody.append("\rEnd Date: ").append(request.getActivity().getEndDate());
				
				EmailUtils.appendDartIndexPageInstructions(initialNDSBody);
				
				EmailUtils.sendEmailToNDSAdminList(subject.toString(), initialNDSBody.toString());
				
				RequestWorkflow workflow = null;
				sendNDSAdminATask(workflow, request, userLoginId, true);	//initial NDS
				
				// stay in this state
				return;
			}
			
			if (operation == Workflow.WF_OPERATION_APPROVE) {

				// create an event
				Event.create("Initial NDS Review Complete", "Initial NDS Review Complete", EventType.APPROVE_REVIEW, Group.NDS, true, request, userLoginId);
				
//testing -- duplicate event creation.  Instead, create this event below (when moving into stateThree).
//				Event.create("Request Sent for Final NDS Review", "Request Sent for Final NDS Review", EventType.SENT_FOR_REVIEW, Group.NDS, false, request, userLoginId);

				
//testing -- there shouldn't be any intermediate reviews for an operational request (probably don't need this retrieval of the reviews)
				// email requestor
				//request.emailRequestorAndAllNotifications("Operational Request " + request.getTrackingNumber() + " Approved by initial NDS reviewer", "DART Request " + request.getTrackingNumber() + " Approved by initial NDS reviewer");
				//List<Review> reviewList = Review.listByRequestId(request.getId());
				List<Review> reviewList = null;
				RequestWorkflow workflow = null;
				sendParticipantsIntermediateEmail(workflow, reviewList, request, Group.NDS.getShortName(), userLoginId, Workflow.WF_OPERATION_APPROVE);

				
				// delete tasks relative to this request
				TaskUtils.closeGroupTasksForNDSReview(request, userLoginId);	
				
				// go to the next state
				incrementState(null, null, request, userLoginId);
			}
			else if (operation == Workflow.WF_OPERATION_DENY) {
				
				// create an event
				Event.create("Initial NDS Review Denied", "Initial NDS Review Denied", EventType.DENY_REVIEW, Group.NDS, true, request, userLoginId);
				
//TODO: do we need to send a deny email out to all of NDS here?  (We do so in the other deny cases)
//testing -- there shouldn't be any intermediate reviews for an operational request (probably don't need this retrieval of the reviews)
				// email requestor
				//request.emailRequestorAndAllNotifications("Operational Request " + request.getTrackingNumber() + " Denied", "Operational Request " + request.getTrackingNumber() + " Denied");
				//List<Review> reviewList = Review.listByRequestId(request.getId());
				List<Review> reviewList = null;
				RequestWorkflow workflow = null;
				sendParticipantsDenyEmail(workflow, reviewList, request, Group.NDS.getShortName(), userLoginId, Workflow.WF_OPERATION_DENY);

				// delete tasks relative to this request (actually, we should remove the todo for *all* review groups)
				//TaskUtils.closeAllTasksForRequest(request, userLoginId);
				TaskUtils.closeAllTasksForWorkflowAndRequest(workflow, request, userLoginId);
				
				setState(null, null, request, FINAL_STATE, userLoginId);  // go to final state
			}
			else if (operation == Workflow.WF_OPERATION_CHANGE_REQUEST) {

				// create an event
				Event.create("Change Requested by Initial NDS Review", "Change Requested by Initial NDS Review", EventType.CHANGE_REQUEST, Group.NDS, true, request, userLoginId);
				
				List<Review> reviewList = null;
				RequestWorkflow workflow = null;

				// delete tasks relative to this request
				TaskUtils.closeGroupTasksForNDSReview(request, userLoginId);
				
				setState(null, null, request, 1, userLoginId);  // go back to state 1
			}
			else if (operation == Workflow.WF_OPERATION_CLOSE) {

				StringBuffer subject = createOperationsEmailSubject(request);
				
				subject.append(getActionName(Workflow.WF_OPERATION_CLOSE));
				
				
				//
				// generate the body
				OperationalRequest oRequest = (OperationalRequest)request;
				StringBuffer closedBody = new StringBuffer("The following Data Access Request has been closed:");
				closedBody.append("\r\rOperational Request Name: ").append(request.getActivity().getOfficialName());
				closedBody.append("\rProgram Office: ").append(oRequest.getProgramOffice());
				closedBody.append("\rJustification: ").append(oRequest.getJustification());
				closedBody.append("\rStart Date: ").append(request.getActivity().getStartDate());
				closedBody.append("\rEnd Date: ").append(request.getActivity().getEndDate());
				
				EmailUtils.appendDartIndexPageInstructions(closedBody);
				
				EmailUtils.emailRequestorAndAllNotifications(request, subject.toString(), closedBody.toString());
	
//TODO: these tasks are closed in RequestController instead	(do NOT close them here, because this state can be caused by the requestor and not the reviewer)
//				TaskUtils.closeGroupTasksForNDSReview(request, userLoginId);		
				
				setState(null, null, request, FINAL_STATE, userLoginId);  // go to final state
			}
		} catch (Exception e) {
			throw new WorkflowException(e);
		}
	}

	/** NDS final review state handler.
	 * 
	 * @param request
	 * @param operation
	 * @param userLoginId
	 * @throws WorkflowException
	 */
	protected void stateThree(final Request request, final int operation, final String userLoginId) throws WorkflowException  {
		log.debug("WfOperationalRequest state three operation = " + operation + " request = " + request.getTrackingNumber());
		
		final boolean isInitNDSReview = false;
		final boolean isFinalNDSReview = true;
		
		try {
			EventType.initialize();
			Group.initialize();
			Role.initialize();
			
			if (operation == Workflow.WF_OPERATION_ENTRY) {
				log.debug("state 3 entry.  email NDS reviewers");
				

				StringBuffer subject = createOperationsEmailSubject(request);
				
				//
				// set the status
				//<Action>
				subject.append(getActionName(Workflow.WF_OPERATION_SUBMIT));
				
				OperationalRequest oRequest = (OperationalRequest)request; 
								
				//
				// generate the body
				StringBuffer initialNDSBody = new StringBuffer("A new Data Access Request has been submitted for final approval:");
				initialNDSBody.append("\r\rOperational Request Name: ").append(request.getActivity().getOfficialName());
				initialNDSBody.append("\rProgram Office: ").append(oRequest.getProgramOffice());
				initialNDSBody.append("\rJustification: ").append(oRequest.getJustification());
				initialNDSBody.append("\rStart Date: ").append(request.getActivity().getStartDate());
				initialNDSBody.append("\rEnd Date: ").append(request.getActivity().getEndDate());
				
				EmailUtils.appendDartIndexPageInstructions(initialNDSBody);
				
				EmailUtils.sendEmailToNDSAdminList(subject.toString(), initialNDSBody.toString());
				
				RequestWorkflow workflow = null;
				sendNDSAdminATask(workflow, request, userLoginId, false);	//final NDS
				
				if (request.getStatus().getId() == RequestStatus.CHANGE_REQUESTED.getId()) {					
					createReqSubmittedEvent(null, request, userLoginId, isInitNDSReview, isFinalNDSReview);
				}				
				Event.create("Request Sent for Final NDS Review", "Request Sent for Final NDS Review", EventType.SENT_FOR_REVIEW, Group.NDS, false, request, userLoginId);
				
				return ; // nothing to do if we just entered the state
			}
			
			else if (operation == Workflow.WF_OPERATION_SUBMIT) {	//submitted request with changes (change requested by intermediate review)
				log.debug("request submitted (with changes)!");
				
				//create event(s)
				if (request.getStatus().getId() == RequestStatus.CHANGE_REQUESTED.getId()) {					
					createReqSubmittedEvent(null, request, userLoginId, isInitNDSReview, isFinalNDSReview);
				}
				Event.create("Request Sent for Final NDS Review", "Request Sent for Final NDS Review", EventType.SENT_FOR_REVIEW, Group.NDS, false, request, userLoginId);

				
				request.submit(userLoginId);	//submit the request

				// delete tasks relative to this request (if this is a change request, update)
				TaskUtils.closeUserTasksForChangeRequest( null, request, userLoginId );			
				
				createAndSendReadyForReviewEmail(request);
				
				sendNDSAdminATask(null, request, userLoginId, false);	//final NDS
				
				return;  // stay in this state
			}//end WF_OPERATION_SUBMIT			
			
			if (operation == Workflow.WF_OPERATION_APPROVE) {

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

				
				//save a communication
				Comment.create("NDS Review Completed", request, userLoginId, "VINCI Dart request " + request.getTrackingNumber() + " approved by NDS!");
				
				// add an event (multi-workflow support)
				Event.create("NDS Request Approval", "NDS Request Approval", EventType.APPROVE_WORKFLOW, Group.NDS, false, request, userLoginId);
				
				StringBuffer subject = createOperationsEmailSubject(request);
				
				//
				// set the status
				//<Action>
				subject.append(getActionName(Workflow.WF_OPERATION_APPROVE));
				
				StringBuffer approveBody = new StringBuffer();
				approveBody.append("Data Access Request ");
				approveBody.append( request.getTrackingNumber());
				approveBody.append(" has been reviewed and approved for the project ");
				approveBody.append(request.getActivity().getOfficialName());
				approveBody.append(" for which you are identified as participant.");
				
				
				EmailUtils.appendDartIndexPageInstructions(approveBody);
				
				EmailUtils.emailRequestorAndAllNotifications(request, subject.toString(), approveBody.toString());

				//
				// SEND FINAL Email to DART Admin group
				
				//
				// generate the body
				OperationalRequest oRequest = (OperationalRequest)request; 
				StringBuffer closedBody = new StringBuffer("A Data Access Request has been reviewed and approved:");
				closedBody.append("\r\rOperational Request Name: ").append(request.getActivity().getOfficialName());
				closedBody.append("\rProgram Office: ").append(oRequest.getProgramOffice());
				closedBody.append("\rJustification: ").append(oRequest.getJustification());
				closedBody.append("\rStart Date: ").append(request.getActivity().getStartDate());
				closedBody.append("\rEnd Date: ").append(request.getActivity().getEndDate());
				
				EmailUtils.appendDartIndexPageInstructions(closedBody);
				
				EmailUtils.sendEmailToDARTAdminList( subject.toString(), closedBody.toString() );
				

				subject = createOperationsEmailSubject(request);
				
				subject.append(getActionName(Workflow.WF_OPERATION_APPROVE));
								
				//
				//Send email to Participants of the Operational request
				StringBuffer finalOpsEmail = new StringBuffer("This operational access request is approved by National Data Systems for access to the Corporate Data Warehouse data as identified in DART.");
				finalOpsEmail.append("\r\rDetails of the request are stored in the DART application.");
				finalOpsEmail.append("\r\rNote to user: You will not be able to access CDW data until CDW access has been provisioned.  A second email is forthcoming.");
				finalOpsEmail.append("\r\rThank you,");
				finalOpsEmail.append("\r\rVHA National Data Systems DART Administrator");
				
				EmailUtils.appendDartIndexPageInstructions(finalOpsEmail);
				
				EmailUtils.emailRequestorAndAllNotifications(request, subject.toString(), finalOpsEmail.toString());
				
				// delete tasks relative to this request
				TaskUtils.closeGroupTasksForNDSReview(request, userLoginId);

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

				// create an event
				Event.create("Final NDS Review Denied", "Final NDS Review Denied", EventType.DENY_REVIEW, Group.NDS, false, request, userLoginId);

//testing -- there shouldn't be any intermediate reviews for an operational request (probably don't need this retrieval of the reviews)				
				//in WfNDS, NDS gets an email here...
				//sendNDSAdminAnEmail("VINCI Dart Request Denied", 
					//	"VINCI Dart request " + request.getTrackingNumber() + " is denied.");
				//List<Review> reviewList = Review.listByRequestId(request.getId());
				List<Review> reviewList = null;
				RequestWorkflow workflow = null;
				createAndSendGroupMembersAReviewEmail(workflow, Group.NDS, reviewList, request, Group.NDS.getShortName(), userLoginId, Workflow.WF_OPERATION_DENY);	//email NDS
				
				// email requester				
				sendParticipantsDenyEmail(workflow, reviewList, request, Group.NDS.getShortName(), userLoginId, Workflow.WF_OPERATION_DENY);			
				
				
				// delete tasks relative to this request (actually, we should remove the todo for *all* review groups)
				//TaskUtils.closeAllTasksForRequest(request, userLoginId);
				TaskUtils.closeAllTasksForWorkflowAndRequest(workflow, request, userLoginId);
				
				setState(null, null, request, FINAL_STATE, userLoginId);  // go to final state
			}
			else if (operation == Workflow.WF_OPERATION_CHANGE_REQUEST) {
				// email the requestor
				
				
				
				StringBuffer crBody = new StringBuffer();
				crBody.append("Operational Request ");
				crBody.append(request.getTrackingNumber());
				crBody.append(" Change Requested");
				
				EmailUtils.appendDartIndexPageInstructions(crBody);
				
				StringBuffer subject = createOperationsEmailSubject(request);
				
				EmailUtils.emailRequestorAndAllNotifications(request, subject.append("Operational Request " + request.getTrackingNumber() + " Change Requested").toString(), crBody.toString());

//testing -- there shouldn't be any intermediate reviews for an operational request (probably don't need this retrieval of the reviews)				
				//request.emailRequestorAndAllNotifications("DART Request " + request.getTrackingNumber() + " Change Requested by NDS", "DART Request " + request.getTrackingNumber() + " Change Requested by NDS");
				//List<Review> reviewList = Review.listByRequestId(request.getId());
				List<Review> reviewList = null;
				RequestWorkflow workflow = null;
				sendParticipantsIntermediateEmail(workflow, reviewList, request, Group.NDS.getShortName(), userLoginId, Workflow.WF_OPERATION_CHANGE_REQUEST);
				
				// create an event
				Event.create("Change Requested by Final NDS Review", "Change Requested by Final NDS Review", EventType.CHANGE_REQUEST, Group.NDS, false, request, userLoginId);

				// delete tasks relative to this request
				TaskUtils.closeGroupTasksForNDSReview(request, userLoginId);				
				
				//setState(null, request, INITIAL_STATE, userLoginId);  // go back to state 1
				return;  // stay in this state (only redo the final NDS review)
			}
						
			else if (operation == Workflow.WF_OPERATION_CLOSE) {

				StringBuffer subject = createOperationsEmailSubject(request);
				
				StringBuffer closedBody = new StringBuffer("The following Data Access Request has been closed:");
				closedBody.append("\r\rStudy Name: ").append(request.getActivity().getOfficialName());
				closedBody.append("\rIRB Number: ").append(request.getIrbNumber());
				closedBody.append("\rStudy Start Date: ").append(request.getActivity().getStartDate());
				closedBody.append("\rStudy End Date: ").append(request.getActivity().getEndDate());
				closedBody.append("\rIRB Expiration Date: ").append(request.getActivity().getEndDate());
				closedBody.append("\r\rNDS Tracking Number: ").append(request.getTrackingNumber());

				EmailUtils.appendDartIndexPageInstructions(closedBody);
				
				EmailUtils.emailRequestorAndAllNotifications(request, subject.toString(), closedBody.toString());
	
//TODO: these tasks are closed in RequestController instead	(do NOT close them here, because this state can be caused by the requestor and not the reviewer)
//				TaskUtils.closeGroupTasksForNDSReview(request, userLoginId);		
				
				setState(null, null, request, FINAL_STATE, userLoginId);  // go to final state
			}
			else {
				log.error("Error: request " + request.getTrackingNumber() + " in unexpected state.");
			}
		} catch (Exception e) {
			throw new WorkflowException(e);
		}
	}

    private void createAndSendReadyForReviewEmail(final Request request) {
        StringBuffer subject = createOperationsEmailSubject(request);

        subject.append(getActionName(Workflow.WF_OPERATION_SUBMIT));
        EmailUtils.sendEmailToNDSAdminList( subject.append("VINCI Dart Request Ready for Review").toString(), 
                "VINCI Dart request " + request.getTrackingNumber() + " is ready for final review.");
    }
}
