package gov.va.vinci.dart.biz;

import gov.va.vinci.dart.common.ValidationHelper;
import gov.va.vinci.dart.common.exception.CheckedException;
import gov.va.vinci.dart.common.exception.ObjectNotFoundException;
import gov.va.vinci.dart.common.exception.ValidationException;
import gov.va.vinci.dart.rule.DocumentRuleEvaluatorHelper;
import gov.va.vinci.dart.service.DartObjectFactory;
//import gov.va.vinci.dart.wf2.WfOperationalRequest;
import gov.va.vinci.dart.wf2.WorkflowException;
import gov.va.vinci.dart.wf2.WorkflowResolver;

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

import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

@Entity
@DiscriminatorValue("4")
public class OperationalRequest extends Request {
	private static Log log = LogFactory.getLog(OperationalRequest.class);

	@Column(name="programoffice")
	private String programOffice;
	
	@Column(name="justification")
	private String justification;

	@Column(name="datamart", columnDefinition = "BIT", length = 1)
	private boolean dataMart;

	@Column(name="localserver", columnDefinition = "BIT", length = 1)
	private boolean localServer;

	@Column(name="localserverlocation")
	private String localServerLocation;	//facility name	
	
	@Column(name="localserveraddress")
	private String localServerAddress;

	@Column(name="localserverbuilding")
	private String localServerBuilding;
	
	@Column(name="localserverroomnumber")
	private String localServerRoomNumber;
	
	
	@ManyToMany(fetch=FetchType.LAZY)
	@JoinTable(
		      name="researchstudydatasource", schema="hib",
		      joinColumns={@JoinColumn(name="researchstudyid", referencedColumnName="ID")},
		      inverseJoinColumns={@JoinColumn(name="datasourceid", referencedColumnName="ID")})
	Set<DataSource> dataSources;

	
	OperationalRequest() {}
	
	OperationalRequest(final String createdBy) throws ValidationException {
		super(createdBy);
	}
	
	public static List<OperationalRequest> listByRequestor(final int requestorId) {
		return DartObjectFactory.getInstance().getOperationalRequestDAO().listByRequestor(requestorId);
	}
	
	public static List<OperationalRequest> listByActivityId(final int activityId) {
		return DartObjectFactory.getInstance().getOperationalRequestDAO().listByActivityId(activityId);
	}

	public static List<OperationalRequest> listRecentByRequestor(final int requestorId, final int maxResults) throws ValidationException {
		if (maxResults < 1 || maxResults > 10) {
			throw new ValidationException("Maximum number of results must be between 1 and 10");
		}
		
		return DartObjectFactory.getInstance().getOperationalRequestDAO().listRecentByRequestor(requestorId, maxResults);
	}
	
	public static List<OperationalRequest> listByName(final int requestorId, final String name) throws ValidationException {
		ValidationHelper.required("Operational Request Name", name);
		ValidationHelper.validateSize("Operational Request Name", name, 1, 64);
		
		return DartObjectFactory.getInstance().getOperationalRequestDAO().listByName(requestorId, name);
	}
	
	public static OperationalRequest findMostRecentAmendment(final int headId) {
		return DartObjectFactory.getInstance().getOperationalRequestDAO().findMostRecentAmendment(headId);
	}	
	
	public static List<OperationalRequest> listAllSubmitted() {
		return DartObjectFactory.getInstance().getOperationalRequestDAO().listAllSubmitted();
	}

//	public static List<OperationalRequest> listAllButInitiated() {
//		return DartObjectFactory.getInstance().getOperationalStudyDAO().listAllButInitiated();
//	}
		
	public static List<OperationalRequest> listAll() {
		return DartObjectFactory.getInstance().getOperationalRequestDAO().listAll();
	}
	
	
	public static OperationalRequest create(final String requestName, final Person requestor, final Activity activity, final String createdBy) throws ValidationException {
		
		ValidationHelper.required("Request Activity", activity);
		ValidationHelper.required("Request Requestor", requestor);
		
		OperationalRequest result = new OperationalRequest(createdBy);
		
		// initialize various properties
		result.createdOn = new Date();
		result.createdBy = createdBy;
		result.type = "O";  // this seems redundant to the requesttype column
		result.requestType = 4;  //this is a hack to get the initial email to have a request type before sending.
		result.initiate(createdBy);
		result.requestor = requestor;
		result.activity = activity;
		result.current = true;

		result.workflowId = WorkflowResolver.WF_OPERATIONAL_REQUEST;	//workflow type:  updated after the request is created
		result.workflowState = 0;
		result.workflowMask = 0L;
		
		result.modify(requestName, null, null, createdBy);
		
		DartObjectFactory.getInstance().getOperationalRequestDAO().save(result);
		
		return result;
	}
	
	public void modify(final String name, final String programOffice, final String justification, final String updatedBy) throws ValidationException {

		validateModify(name);
		
		this.name = name;
		this.programOffice = programOffice;
		this.justification = justification;
		this.updatedBy = updatedBy;
		this.updatedOn = new Date();
	}
	
	private void validateModify(final String name) throws ValidationException {
		ValidationHelper.required("Request Name", name);
	}

	public String getProgramOffice() {
		return programOffice;
	}

	public String getJustification() {
		return justification;
	}
	
	
	public OperationalRequest createAmendment(final String createdBy) throws ValidationException, ObjectNotFoundException {

		ValidationHelper.required("Amendment Created By", createdBy);


		// validation - this request has to be approved or denied or closed (consistent with the dashboard query)
		//		Check the top-level status when creating an amendment.  All workflows must be completed before creating an amendment.
		final int requestStatusId = getStatus().getId();
		if( RequestStatus.REQUEST_COMPLETED.getId() != requestStatusId && 
			RequestStatus.APPROVED.getId() != requestStatusId && 
			RequestStatus.DENIED.getId() != requestStatusId && 
			RequestStatus.CLOSED.getId() != requestStatusId ) {
				throw new ValidationException("Request must be approved or denied or closed before creating an amendment.");				
		}//end if


		Request originalRequest = this;
		if (this.amendment == true) {
			originalRequest = DartObjectFactory.getInstance().getOperationalRequestDAO().findById(this.headId);
		}
		
		OperationalRequest result = new OperationalRequest(createdBy);

		result.createdOn = new Date();
		result.createdBy = createdBy;
		result.initiate(createdBy);
		
		result.type = this.type;
		result.requestor = this.requestor;
		result.activity = this.activity;
		result.name = this.name;
		result.description = this.description;		
		result.primaryLocation = this.primaryLocation;
		
		result.dataMart = this.dataMart;
		result.localServer = this.localServer;
		result.localServerLocation = this.localServerLocation;
		result.localServerAddress = this.localServerAddress;
		result.localServerBuilding = this.localServerBuilding;
		result.localServerRoomNumber = this.localServerRoomNumber;		
		
		result.programOffice = this.programOffice;
		result.justification = this.justification;

		result.workflowId = this.workflowId;	//workflow type:  updated after the request is created
		result.workflowState = 0;
		result.workflowMask = 0;  // uh, shouldn't this be the default mask? (gets updated when the request is initialized)
		
		result.submittedOn = null;

		result.createAmendment(originalRequest);	//head request
		result.headId = originalRequest.id;			//note: this also happens inside Request.createAmendment()
		
		DartObjectFactory.getInstance().getOperationalRequestDAO().save(result);

		// compute the tracking number
		result.trackingNumber = generateAmendmentTrackingNumber(originalRequest);

		// do a deep copy of this request's data, like participants, data sources, documents, sites, onlinedatas
		// do NOT copy reviews or events or comments (no intermediate reviews to copy)
		
		
		if (result.participants == null) {
			result.participants = new HashSet<Participant>();
		}
		
		for (Participant participant : participants) {
			log.debug("adding participant " + participant.getPerson().getFullName() + " to amendment " + result.getTrackingNumber());
			result.participants.add(participant.copy(result));
		}


		//copy the data sources
		if (result.dataSources == null) {
			result.dataSources = new HashSet<DataSource>();
		}

		List<Integer> disabledDataSourceIDs = DataSource.findDisabledIdByRequestType( getRequestType() );
		if( dataSources != null && (disabledDataSourceIDs != null && disabledDataSourceIDs.isEmpty() == false) ) {
			for( DataSource currDataSource : dataSources ) {
				if( disabledDataSourceIDs.contains( currDataSource.getId() ) == false ) {	//don't copy the disabled data sources
					result.dataSources.add( currDataSource );	//copy this data source from the previous request
				}//end if
			}//end for
		} else {	//no disabled data sources
			result.dataSources.addAll(dataSources);	//copy all of the selected data sources from the previous request
		}//end else

		
		if (result.sites == null) {
			result.sites = new HashSet<Location>();
		}

		result.sites.addAll(sites);

		// copy RequestLocationDocument and RequestParticipantDocument and RequestAdminLocationDocument and RequestAdminParticipantDocument
		copyDocuments(result);

		if (result.onlineDatas == null) {
			result.onlineDatas = new HashSet<OnlineData>();
		}

		// future feature
		result.onlineDatas.addAll(onlineDatas);

		return result;
	}
	

//	/**
//	 * Returns true if ready for the NDS initial review
//	 * @return
//	 */
//	public boolean isReadyForInitialReview() {
//
//		try {
//
//			if( workflowState == WfOperationalRequest.SUBMITTED_STATE && isSubmittedOrChanged() )	//submitted
//				return true;
//			
//		} catch (ObjectNotFoundException e) {
//			log.debug("Could not find the request: " + id);
//		}
//			
//		return false;
//	}
	
	
//	/**
//	 * Returns true if the NDS initial review is completed.
//	 * @return
//	 */
//	public boolean isInitialReviewCompleted( final RequestWorkflow workflow ) {
//		return (isReadyForFinalReview(workflow));
//	}
//	
//	
//	/**
//	 * Returns true if ready for the NDS final review
//	 * @return
//	 */
//	public boolean isReadyForFinalReview( final RequestWorkflow workflow ) {
//
//		try {
//			if( workflowState == WfOperationalRequest.OPS_FINAL_REVIEW_STATE && isSubmittedOrChanged(workflow) )	//initial approval done
//				return true;
//			
//		} catch (ObjectNotFoundException e) {
//			log.debug("Could not find the request: " + id);
//		}
//		
//		return false;
//	}
	
//	/**
//	 * Returns true if the final NDS review has been completed
//	 * @return
//	 */
//	public boolean isFinalReviewCompleted() {
//
//		if( workflowState == WfOperationalRequest.FINAL_STATE )
//			return true;
//		
//		return false;
//	}

	
//	/** 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 int numReviews = 2;	//for Operations workflow, +2 for the NDS initial review and NDS final review
//		int cnt = 0;
//		
//		if( isReadyForFinalReview() )	//initial review is done
//			cnt++;
//		
//		if( isFinalReviewCompleted() )	//final review is done
//			cnt++;
//		
//		return ((cnt * 100) / numReviews) + "%";
//	}

	
	/** return true if all (intermediate) reviews are either approved or rejected
	 * 		For Operational Requests, there are no intermediate reviews.
	 * 
	 * Used to determine if the request is ready for approval.
	 * 
	 * @param workflow -- ignored for the Operations workflow
	 * 
	 * @return
	 */
	@Override
	public boolean allReviewsCompleted( final RequestWorkflow workflow ) {

//		//have the final review left or have completed the final review
//		return (isReadyForFinalReview(workflow) || isFinalReviewCompleted());
		
		boolean readyForFinalReview = false;
		boolean finalReviewCompleted = false;
		
		try {
			WorkflowResolver workflowResolver = DartObjectFactory.getInstance().getWorkflowResolver();

			if( workflow != null ) {
				readyForFinalReview = workflowResolver.resolve(workflow).isReadyForFinalReview(workflow, this);		//child workflow
				finalReviewCompleted = workflowResolver.resolve(workflow).isFinalReviewCompleted(workflow, this);	//child workflow
			} else {
				readyForFinalReview = workflowResolver.resolve(this).isReadyForFinalReview(workflow, this);		//top-level workflow
				finalReviewCompleted = workflowResolver.resolve(this).isFinalReviewCompleted(workflow, this);	//top-level workflow
			}
			
		} catch (WorkflowException e) {
			log.error("WorkflowException when retrieving the NDS review status: " + e.getMessage());
			e.printStackTrace();
		}				
		
		//have the final review left or have completed the final review
		return (readyForFinalReview || finalReviewCompleted);
	}
	
	
	/** Test if all (intermediate) reviews on the request are approved.
	 * 		For Operational Requests, there are no intermediate reviews.
	 * 
	 * Used to determine if the request is ready for approval.
	 * 
	 * @param workflow -- ignored for the Operations workflow
	 * 
	 * @return
	 */
	@Override
	public boolean allReviewsApproved( final RequestWorkflow workflow ) {
	
//		//have the final review left or have completed the final review		
//		return (isReadyForFinalReview(workflow) || isFinalReviewCompleted());
		
		boolean readyForFinalReview = false;
		boolean finalReviewCompleted = false;
		
		try {
			WorkflowResolver workflowResolver = DartObjectFactory.getInstance().getWorkflowResolver();

			if( workflow != null ) {
				readyForFinalReview = workflowResolver.resolve(workflow).isReadyForFinalReview(workflow, this);		//child workflow
				finalReviewCompleted = workflowResolver.resolve(workflow).isFinalReviewCompleted(workflow, this);	//child workflow
			} else {
				readyForFinalReview = workflowResolver.resolve(this).isReadyForFinalReview(workflow, this);		//top-level workflow
				finalReviewCompleted = workflowResolver.resolve(this).isFinalReviewCompleted(workflow, this);	//top-level workflow
			}
			
		} catch (WorkflowException e) {
			log.error("WorkflowException when retrieving the NDS review status: " + e.getMessage());
			e.printStackTrace();
		}				
		
		//have the final review left or have completed the final review
		return (readyForFinalReview || finalReviewCompleted);
	}
	
	
	@Override
	public Set<DataSource> getDataSources() {
		return dataSources;
	}
	
	// TESTING ONLY
	public void setDataSources(Set<DataSource> dataSources) {
		this.dataSources = dataSources;
	}
	
	public boolean isDataMart() {
		return dataMart;
	}

	public void setDataMart(final boolean dataMart) {
		this.dataMart = dataMart;
	}

	public boolean isLocalServer() {
		return localServer;
	}

	public void setLocalServer(boolean localServer) {
		this.localServer = localServer;
	}

	public String getLocalServerLocation() {
		return localServerLocation;
	}

	public void setLocalServerLocation(final String localServerLocation) {
		this.localServerLocation = localServerLocation;
	}	

	
	public String getLocalServerAddress() {
		return localServerAddress;
	}

	public void setLocalServerAddress(String localServerAddress) {
		this.localServerAddress = localServerAddress;
	}

	public String getLocalServerBuilding() {
		return localServerBuilding;
	}

	public void setLocalServerBuilding(String localServerBuilding) {
		this.localServerBuilding = localServerBuilding;
	}

	public String getLocalServerRoomNumber() {
		return localServerRoomNumber;
	}

	public void setLocalServerRoomNumber(String localServerRoomNumber) {
		this.localServerRoomNumber = localServerRoomNumber;
	}
	
	
	// walk through existing documents, document templates, participants and data sources and figure out what
	// documents should be attached to the request.
	public void createDocuments(final String createdBy) throws CheckedException {

		log.debug("creating required documents from templates for request id = " + getId());
	
		DocumentRuleEvaluatorHelper.evaluateDocs(this, createdBy);
	}
	
	
//	// walk through existing Admin documents, document templates, participants and data sources and figure out what
//	// Admin documents should be attached to the request.
//	//
//	// Ignores the documents that are required for the requestor.  Retrieves only the Admin documents.
//	public void createAdminDocuments(final String createdBy) throws CheckedException {
//
//		log.debug("creating Admin documents from templates for request id = " + getId());
//		
//		DocumentRuleEvaluatorHelper.evaluateAdminDocs(this, createdBy);
//	}
	
	
	/**
	 * Returns the fullName of the principal investigator (at the primary site).
	 * 
	 * OperationalRequest now allows only one participant (and one site) -- if OperationalRequest changes back to allowing multiple sites and multiple participants, this function will need to be modified.
	 * 
	 * @return
	 */
//	@Override
//	public String getPrincipalInvestigator() {
//		for (Participant currParticipant : getParticipants()) {
//			if (currParticipant.getPrincipalInvestigator()) {
//				return currParticipant.getPerson().getFullName();
//			}
//		}
//		
//		
//		return "";
//	}
	
	
	
	// necessary to use (List<OperationalRequest>).contains()
	@Override
	public boolean equals(Object obj) {
		if (obj == null) {
			return false;
		}

//		if ( (OperationalRequest.class.isAssignableFrom(obj.getClass())) == false && 
//			 (obj.getClass().isAssignableFrom(OperationalRequest.class)) == false ) {	//class mis-match
		if ( (OperationalRequest.class.isAssignableFrom(obj.getClass())) == false ) {
			return false;
		}

		OperationalRequest rs2 = (OperationalRequest)obj;
		return rs2.getId() == this.getId();
	}
	
}
