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.WfNDS;

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("2")
public class DartRequest extends Request {
	private static Log log = LogFactory.getLog(DartRequest.class);
	
	@Column(name="hipaaconsent", columnDefinition = "BIT", length = 1)
	private boolean hipaaConsent;
	
	@Column(name="hipaawaiver", columnDefinition = "BIT", length = 1)
	private boolean hipaaWaiver;
	
	@Column(name="datamart", columnDefinition = "BIT", length = 1)
	private boolean dataMart;

	@Column(name="localserver", columnDefinition = "BIT", length = 1)
	private boolean localServer;
	
	@Column(name="datasetstartdate")
	private Date dataSourceStartDate;
	
	@Column(name="datasetenddate")
	private Date dataSourceEndDate;
	
	//not currently exposed in the UI. (If we put this back in, we should probably modify this to use the locationId instead of the location name.)	
	@Column(name="datasourcelocation")
	private String dataSourceLocation;

	@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;

    @Column(name="transferredexternal", columnDefinition = "BIT", length = 1)
    private boolean transferredExternal;

    @Column(name="externalDestination")
    private String externalDestination; //company or agency name

    @Column(name="externalStreet")
    private String externalStreet;

    @Column(name="externalCity")
    private String externalCity;

    @Column(name="externalState")
    private String externalState;

    @Column(name="externalZipCode")
    private String externalZipCode;
		
	@Column(name="realssnflag", columnDefinition = "BIT", length = 1)
	private boolean realSSN;
	
	@Column(name="scrambledssnflag", columnDefinition = "BIT", length = 1)
	private boolean scrambledSSN;
	
	@Column(name="phidataflag", columnDefinition = "BIT", length = 1)
	private boolean phiData;
	
	@ManyToMany(fetch=FetchType.LAZY)
	@JoinTable(
		      name="researchstudydatasource", schema="hib",
		      joinColumns={@JoinColumn(name="researchstudyid", referencedColumnName="ID")},
		      inverseJoinColumns={@JoinColumn(name="datasourceid", referencedColumnName="ID")})
	Set<DataSource> dataSources;
	

	DartRequest() {}
	
	DartRequest(final String createdBy) throws ValidationException {
		super(createdBy);
	}
	
	public static DartRequest findById(final int requestId) throws ObjectNotFoundException {
		return (DartRequest)DartObjectFactory.getInstance().getDartRequestDAO().findById(requestId);
	}
	
	public static List<DartRequest> listByActivityId(final int activityId) {
		return DartObjectFactory.getInstance().getDartRequestDAO().listByActivityId(activityId);
	}
	
	public static List<DartRequest> listByRequestor(final int requestorId) {
		return DartObjectFactory.getInstance().getDartRequestDAO().listByRequestor(requestorId);
	}

	public static List<DartRequest> 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().getDartRequestDAO().listRecentByRequestor(requestorId, maxResults);
	}
	
	public static List<DartRequest> listByName(final int requestorId, final String name) throws ValidationException {
		ValidationHelper.required("Dart Request Name", name);
		ValidationHelper.validateSize("Dart Request Name", name, 1, 64);
		
		return DartObjectFactory.getInstance().getDartRequestDAO().listByName(requestorId, name);
	}
	
	public static DartRequest findMostRecentAmendment(final int headId) {
		return DartObjectFactory.getInstance().getDartRequestDAO().findMostRecentAmendment(headId);
	}
	
	public static List<DartRequest> listAllSubmitted() {
		return DartObjectFactory.getInstance().getDartRequestDAO().listAllSubmitted();
	}

//	public static List<DartRequest> listAllButInitiated() {
//		return DartRequestDAO.listAllButInitiated();
//	}
		
	public static List<DartRequest> listAll() {
		return DartObjectFactory.getInstance().getDartRequestDAO().listAll();
	}

	public static DartRequest create(final String requestName, final Date dataSetStartDate, final Date dataSetEndDate, final Person requestor, final Activity activity, final String createdBy) throws ValidationException {
		
		ValidationHelper.required("Request Activity", activity);
		ValidationHelper.required("Request Requestor", requestor);
		
		DartRequest result = new DartRequest(createdBy);
		
		// initialize various properties
		result.createdOn = new Date();
		result.createdBy = createdBy;
		result.type = "D";  // this seems redundant to the requesttype column
		result.requestType = 2;  //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 = 0;		//workflow type:  updated when the workflow is initialized
		result.workflowState = 0;
		result.workflowMask = 0L;
		
		result.modify(requestName, dataSetStartDate, dataSetEndDate, null, null, createdBy);
		
		DartObjectFactory.getInstance().getDartRequestDAO().save(result);
		
		return result;
	}
	
	public DartRequest 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().getDartRequestDAO().findById(this.headId); 
		}
		
		DartRequest result = new DartRequest(createdBy);

		// copy various data values from this, the most recent amendment (or original request) into the result
		result.createdOn = new Date();
		result.createdBy = createdBy;
		result.initiate(createdBy);
		
		result.type = this.type;
		result.requestType = this.requestType;
		result.requestor = this.requestor;
		result.activity = this.activity;
		result.name = this.name;
		result.description = this.description;
		result.irbNumber = this.irbNumber;
		result.irbExpiration = this.irbExpiration;
		result.primaryLocation = this.primaryLocation;
		result.dataSourceStartDate = ((DartRequest)this).dataSourceStartDate;
		result.dataSourceEndDate = ((DartRequest)this).dataSourceEndDate;
		result.dataSourceLocation = ((DartRequest)this).dataSourceLocation;
		result.hipaaConsent = ((DartRequest)this).hipaaConsent;
		result.hipaaWaiver = ((DartRequest)this).hipaaWaiver;		
		result.dataMart = ((DartRequest)this).dataMart;
		result.localServer = ((DartRequest)this).localServer;
		result.localServerLocation = ((DartRequest)this).localServerLocation;
		result.localServerAddress = ((DartRequest)this).localServerAddress;
		result.localServerBuilding = ((DartRequest)this).localServerBuilding;
		result.localServerRoomNumber = ((DartRequest)this).localServerRoomNumber;
        result.transferredExternal = ((DartRequest)this).transferredExternal;
        result.externalDestination = ((DartRequest)this).externalDestination;
        result.externalStreet = ((DartRequest)this).externalStreet;
        result.externalCity = ((DartRequest)this).externalCity;
        result.externalState = ((DartRequest)this).externalState;
        result.externalZipCode = ((DartRequest)this).externalZipCode;
		result.realSSN = ((DartRequest)this).realSSN;
		result.scrambledSSN = ((DartRequest)this).scrambledSSN;
		result.phiData = ((DartRequest)this).phiData;
		
		result.workflowId = this.workflowId;	//workflow type:  updated after the amendment 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().getDartRequestDAO().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, review groups (but NOT decision)
		// do NOT copy events or comments

//do NOT copy the review groups anymore (dependent on the new workflow type)
//		copyReviewGroups(result);	//copy the reviews (selected intermediate review groups) from the originalRequest
		
		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);

		// future feature
		if (result.onlineDatas == null) {
			result.onlineDatas = new HashSet<OnlineData>();
		}
		result.onlineDatas.addAll(onlineDatas);
		
		return result;
	}
	
	
	public void modify(final String name, final Date dataSourceStartDate, final Date dataSourceEndDate, final String irbNumber, final Date irbExpiration, final String updatedBy) throws ValidationException {

		validateModify(name, dataSourceStartDate, dataSourceEndDate);
		
		this.name = name;
		this.dataSourceStartDate = dataSourceStartDate;
		this.dataSourceEndDate = dataSourceEndDate;
		this.irbNumber = irbNumber;
		this.irbExpiration = irbExpiration;
		this.updatedBy = updatedBy;
		this.updatedOn = new Date();
	}
	
	private void validateModify(final String name, final Date dataSourceStartDate, final Date dataSourceEndDate) throws ValidationException {
	}
	
	public boolean isHipaaConsent() {
		return hipaaConsent;
	}

	public void setHipaaConsent(final boolean hipaaConsent) {
		this.hipaaConsent = hipaaConsent;
	}

	public boolean isHipaaWaiver() {
		return hipaaWaiver;
	}

	public void setHipaaWaiver(final boolean hipaaWaiver) {
		this.hipaaWaiver = hipaaWaiver;
	}

	public Date getDataSourceStartDate() {
		return dataSourceStartDate;
	}

	public Date getDataSourceEndDate() {
		return dataSourceEndDate;
	}

	@Override
	public Set<DataSource> getDataSources() {
		return dataSources;
	}

	// TESTING ONLY
	public void setDataSources(HashSet<DataSource> dataSources) {
		this.dataSources = dataSources;
	}
	
	public String getDataSourceLocation() {
		return dataSourceLocation;
	}

	public void setDataSourceLocation(final String dataSourceLocation) {
		this.dataSourceLocation = dataSourceLocation;
	}

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


    public boolean isTransferredExternal() { return transferredExternal; }

    public void setTransferredExternal(boolean transferredExternal) { this.transferredExternal = transferredExternal; }

    public String getExternalDestination() { return externalDestination; }

    public void setExternalDestination(String externalDestination) { this.externalDestination = externalDestination; }

    public String getExternalStreet() { return externalStreet; }

    public void setExternalStreet(String externalStreet) { this.externalStreet = externalStreet; }

    public String getExternalCity() { return externalCity; }

    public void setExternalCity(String externalCity) { this.externalCity = externalCity; }

    public String getExternalState() { return externalState; }

    public void setExternalState(String externalState) { this.externalState = externalState; }

    public String getExternalZipCode() { return externalZipCode; }

    public void setExternalZipCode(String externalZipCode) { this.externalZipCode = externalZipCode; }

    public boolean isRealSSN() {
		return realSSN;
	}

	public void setRealSSN(boolean realSSN) {
		this.realSSN = realSSN;
	}

	public boolean isScrambledSSN() {
		return scrambledSSN;
	}

	public void setScrambledSSN(boolean scrambledSSN) {
		this.scrambledSSN = scrambledSSN;
	}

	public boolean isPhiData() {
		return phiData;
	}

	public void setPhiData(boolean phiData) {
		this.phiData = phiData;
	}

	
	
	// 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 true if ready for the NDS initial review
//	 * @return
//	 */
//	public boolean isReadyForInitialReview() {
//
//		try {
//			if( workflowState == WfNDS.SUBMITTED_STATE && isSubmittedOrChanged() )	//submitted or change requested
//				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 ) {
//
//		try {
////TODO: might want to update this to be > SUBMITTED_STATE (so that if we've moved past the initial review, it will indicate that the initial review has been completed)
////			if( workflowState == WfNDS.INTERMEDIATE_REVIEW_STATE && isSubmittedOrChanged() )	//initial NDS approval done
//			
//			if( workflowState > WfNDS.SUBMITTED_STATE && isSubmittedOrChanged( workflow ) )	//initial NDS approval done
//				return true;
//			
//		} catch (ObjectNotFoundException e) {
//			log.debug("Could not find the request: " + id);
//		}
//			
//		return false;
//	}
	
	
//	/**
//	 * Returns true if ready for the NDS final review
//	 * 		All intermediate reviews are completed
//	 * @return
//	 */
//	public boolean isReadyForFinalReview( final RequestWorkflow workflow ) {
//
//		try {
//			if( workflowState == WfNDS.NDS_FINAL_REVIEW_STATE && isSubmittedOrChanged(workflow) )
//				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 == WfNDS.FINAL_STATE )
//			return true;
//		
//		return false;
//	}
	
	
	
	// necessary to use (List<DartRequest>).contains()
	@Override
	public boolean equals(Object obj) {
		if (obj == null) {
			return false;
		}

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

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