package gov.va.vinci.dart.biz;

import gov.va.vinci.dart.common.ValidationHelper;
import gov.va.vinci.dart.common.exception.ObjectNotFoundException;
import gov.va.vinci.dart.common.exception.ValidationException;
import gov.va.vinci.dart.service.DartObjectFactory;
import gov.va.vinci.dart.wf2.WorkflowException;

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

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;

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

@Entity
@Table(name="requestworkflow", schema="hib")
public class RequestWorkflow extends BusinessObject {
	private static Log log = LogFactory.getLog(RequestWorkflow.class);
	
	
	@Column(name="workflowstate")
	protected int workflowState;

	@Column(name="workflowmask")
	protected long workflowMask;

	@Column(name="requeststate")
	private int requestState;	
	
	@ManyToOne(fetch=FetchType.LAZY)
	@JoinColumn(name="requestid")
	protected Request request;
	
	@OneToMany(fetch = FetchType.LAZY, mappedBy = "workflow")
	protected Set<Review> reviews;

	@ManyToOne(fetch=FetchType.LAZY)
	@JoinColumn(name="workflowtemplateid")
	protected WorkflowTemplate workflowTemplate;



	RequestWorkflow() {}
	
	
	public int getWorkflowState() {
		return workflowState;
	}

	public void setWorkflowState(int workflowState) {
		this.workflowState = workflowState;
	}

	public long getWorkflowMask() {
		return workflowMask;
	}

	public void setWorkflowMask(long workflowMask) {
		this.workflowMask = workflowMask;
	}

	public WorkflowTemplate getWorkflowTemplate() {
		return workflowTemplate;
	}
	
	public Request getRequest() {
		return request;
	}
	
	public RequestStatus getRequestStatus() throws ObjectNotFoundException {
		return RequestStatus.findById(requestState);
	}
	
	//returns the list of reviews that correspond to this workflow
	public Set<Review> getReviews() {
		return reviews;
	}
	
	// TESTING ONLY
	public void setReviews(final Set<Review> reviews) {
		this.reviews = reviews;
	}
	
	public void addReview(final Review review) {
		if( reviews == null ) {
			reviews = new HashSet<Review>();
		}
		
		reviews.add(review);
	}
	
//	public void removeReviewList(final Collection<Review> reviewList, final boolean deleteReview) {
//		if (reviewList == null) {
//			return;
//		}
//
//		this.reviews.removeAll( reviewList );
//		
//		if( deleteReview ) {
//			for( Review review : reviewList ) {
//				review.delete();
//			}
//		}//end if
//	}

	public Group getReviewer() {
		if( workflowTemplate != null ) {
			return workflowTemplate.getReviewer();
		}
		
		return null;
	}	

	
	public static RequestWorkflow create(final Request request, final WorkflowTemplate workflowTemplate, final String createdBy) throws ValidationException {
		
		ValidationHelper.required("Request", request);
		ValidationHelper.required("Workflow Template", workflowTemplate);
		ValidationHelper.required("Created By", createdBy);
		
		RequestWorkflow result = new RequestWorkflow();
		result.createdBy = createdBy;
		result.createdOn = new Date();
		
		result.request = request;
		result.initiate(createdBy);

		result.workflowTemplate = workflowTemplate;

		DartObjectFactory.getInstance().getRequestWorkflowDAO().save(result);
	
		request.addWorkflow(result);	//update the set of workflows for this request (business object)
		
		return result;
	}

	
//	public void delete() {
//
////TODO: close all of the tasks associated with this workflow?
////TODO: delete all events associated with this workflow?
//		
////TODO: we should probably delete all of the reviews associated with the workflow if we delete the workflow
//		removeReviewList( getReviews(), true );	//remove all of the reviews associated with this workflow
//		
//		DartObjectFactory.getInstance().getRequestWorkflowDAO().delete(this);
//	}	
	
	
	public static RequestWorkflow findById(final int workflowId) {
		return DartObjectFactory.getInstance().getRequestWorkflowDAO().findById(workflowId);
	}
	
	public static List<RequestWorkflow> listByRequestId(final int requestId) {
		return DartObjectFactory.getInstance().getRequestWorkflowDAO().listByRequestId(requestId);
	}
	
	public static List<RequestWorkflow> listOpenByRequestId(final int requestId) {
		return DartObjectFactory.getInstance().getRequestWorkflowDAO().listOpenByRequestId(requestId);
	}
	
	public static RequestWorkflow findOpenByRequestAndWorkflowTemplateId(final int requestId, final int workflowTemplateId) {
		return DartObjectFactory.getInstance().getRequestWorkflowDAO().findOpenByRequestAndWorkflowTemplateId(requestId, workflowTemplateId);
	}

//TODO: created this version, but not yet using it.  I created it before moving on to another feature	
	public static List<RequestWorkflow> listByRequestAndWorkflowTemplateId(final int requestId, final int workflowTemplateId) {
		return DartObjectFactory.getInstance().getRequestWorkflowDAO().listByRequestAndWorkflowTemplateId(requestId, workflowTemplateId);
	}
	

	/**
	 * Returns true if this workflow is in its final state.
	 * Otherwise, returns false.
	 * 
	 * @return
	 */
	public boolean isCompleted() {

		try {

			if( workflowState == (DartObjectFactory.getInstance().getWorkflowResolver()).resolve(this).getFinalState() ) {	//in the final state
				return true;
			}
		} catch(WorkflowException exc) {
			log.debug("WorkflowException: " + exc.getMessage());
		}

		return false;
	}

	
	/**
	 * Returns true if this workflow is in the final state and has been approved.
	 * Otherwise, returns false.
	 * 
	 * @return
	 */
	public boolean isApproved() {

		try {
			
			if( workflowState == (DartObjectFactory.getInstance().getWorkflowResolver()).resolve(this).getFinalState() && 
				requestState == RequestStatus.APPROVED.getId() ) {	//approved?
				return true;
			}
		} catch(WorkflowException exc) {
			log.debug("WorkflowException: " + exc.getMessage());
		}

		return false;
	}

	
	/**
	 * Returns true if this workflow is in the final state and has been denied.
	 * Otherwise, returns false.
	 * 
	 * @return
	 */
	public boolean isDenied() {

		try {

			if( workflowState == (DartObjectFactory.getInstance().getWorkflowResolver()).resolve(this).getFinalState() && 
				requestState == RequestStatus.DENIED.getId() ) {	//denied?
				return true;
			}
		} catch(WorkflowException exc) {
			log.debug("WorkflowException: " + exc.getMessage());
		}

		return false;
	}
	
	
	/**
	 * Returns true if this workflow is still in-process, and a change has been requested.
	 * Otherwise, returns false.
	 * 
	 * @return
	 */
	public boolean isChangeRequested() {

		try {

			if( workflowState != (DartObjectFactory.getInstance().getWorkflowResolver()).resolve(this).getFinalState() && 
				requestState == RequestStatus.CHANGE_REQUESTED.getId() ) {	//change requested (and still in-process)?
				return true;
			}
		} catch(WorkflowException exc) {
			log.debug("WorkflowException: " + exc.getMessage());
		}

		return false;
	}
	
	
	/**
	 * Returns true if this workflow has been closed (associated data sources removed).
	 * Otherwise, returns false.
	 * 
	 * @return
	 */
	public boolean isClosed() {

		if( requestState == RequestStatus.CLOSED.getId() ) {	//closed?
			return true;
		}

		return false;
	}
	
	
	/**
	 * Sets the requestState to the specified value 
	 */
	private void setRequestStatus(final RequestStatus status, final String userId) {
		this.requestState = status.getId();
		updatedOn = new Date();
		updatedBy = userId;
	}
	

	/** set the requestState to INITIATED
	 * 
	 * @param userId
	 * @throws ValidationException
	 */
	protected void initiate(final String userId) throws ValidationException {
		setRequestStatus(RequestStatus.INITIATED, userId);
	}
	
	/** set the requestState to SUBMITTED
	 * 
	 * @param userId
	 * @throws Exception
	 */
	public void submit(final String userId) throws Exception {
		setRequestStatus(RequestStatus.SUBMITTED, userId);
	}
	
	/** set the requestState to CLOSED
	 * 
	 * @param userId
	 * @throws ValidationException
	 * @throws ObjectNotFoundException
	 */
	public void close(final String userId) throws ValidationException, ObjectNotFoundException {

		if (RequestStatus.CLOSED.getId() == getRequestStatus().getId()) {
			return;
		}
		
		setRequestStatus(RequestStatus.CLOSED, userId);
	}

	/** set the requestState to CHANGE REQUESTED
	 * 
	 * @param userId
	 * @throws ValidationException
	 * @throws ObjectNotFoundException
	 */
	public void requestChange(final String userId) throws ValidationException, ObjectNotFoundException {

		if (RequestStatus.CHANGE_REQUESTED.getId() == getRequestStatus().getId()) {
			return;
		}
		
		setRequestStatus(RequestStatus.CHANGE_REQUESTED, userId);
	}
	
	/** set the requestState to APPROVED
	 * 
	 * @param userId
	 * @throws ValidationException
	 * @throws ObjectNotFoundException
	 */
	public void approve(final String userId) throws ValidationException, ObjectNotFoundException {
		
		if (RequestStatus.APPROVED.getId() == getRequestStatus().getId()) {
			return;
		}

		if (RequestStatus.CLOSED.getId() == getRequestStatus().getId() || RequestStatus.DENIED.getId() == getRequestStatus().getId() ) { 
			throw new ValidationException("Cannot approve request in current state.");
		}
		
		// TODO lock the current version numbers of documents associated with this request.

		setRequestStatus(RequestStatus.APPROVED, userId);
	}
	
	/** set the requestState to DENIED
	 * 
	 * @param userId
	 * @throws ValidationException
	 * @throws ObjectNotFoundException
	 */
	public void reject(final String userId) throws ValidationException, ObjectNotFoundException {

		if (RequestStatus.DENIED.getId() == getRequestStatus().getId()) {
			return;
		}
		
		if (RequestStatus.CLOSED.getId() == getRequestStatus().getId() || RequestStatus.APPROVED.getId() == getRequestStatus().getId() ) { 
			throw new ValidationException("Cannot reject request in current state.");
		}
		
		setRequestStatus(RequestStatus.DENIED, userId);
	}


	//in both src1 and src2
	public static List<RequestWorkflow> intersect(final Collection<RequestWorkflow> src1, final Collection<RequestWorkflow> src2) {
		List<RequestWorkflow> result = new ArrayList<RequestWorkflow>();
	
		for (RequestWorkflow rw1 : src1) {
			for (RequestWorkflow rw2 : src2) {
				if (rw1.getId() == rw2.getId()) {
					result.add(rw1);
				}
			}
		}
		
		return result;
	}
	
	//src1 - src2: in src1, but NOT in src2
	public static List<RequestWorkflow> minus(final Collection<RequestWorkflow> src1, final Collection<RequestWorkflow> src2) {
		List<RequestWorkflow> result = new ArrayList<RequestWorkflow>();
				
		for (RequestWorkflow rw1 : src1) {
			if( src2.contains(rw1) == false ) {	//in src1, but NOT in src2
				result.add(rw1);
			}
		}
		
		return result;
	}
	
	

	@Override
	public boolean equals(Object obj) {
		if (obj == null) {
			return false;
		}
		
		if ((RequestWorkflow.class.isAssignableFrom(obj.getClass())) == false) {
			return false;
		}
		
		RequestWorkflow rs2 = (RequestWorkflow)obj;
		return rs2.getId() == this.getId();
	}
	
}
