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 javax.persistence.Temporal;
import javax.persistence.TemporalType;

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

/**
 * The Class PreparatoryRequest.
 */
@Entity
@DiscriminatorValue("5")
public class PreparatoryRequest extends Request {
    
    /** The Constant MAX_NAME_SIZE. */
    private static final int MAX_NAME_SIZE = 64;

    /** The Constant MAX_RESULTS_ALLOWED. */
    private static final int MAX_RESULTS_ALLOWED = 10;

    /** The log. */
    private static Log log = LogFactory.getLog(PreparatoryRequest.class);
    
    //Preparatory Only
    @Temporal(TemporalType.DATE)
     @Column(name="expectedIRBSubmissionDate")
     protected Date expectedIRBSubmissionDate;


    /** The data mart. */
    @Column(name = "datamart", columnDefinition = "BIT", length = 1)
    private boolean dataMart;


    // not currently exposed in the UI. (If we put this back in, we should probably modify this to use the locationId instead of
    /** The data source location. */
    // the location name.)
    @Column(name = "datasourcelocation")
    private String dataSourceLocation;


    /** The real ssn. */
    @Column(name = "realssnflag", columnDefinition = "BIT", length = 1)
    private boolean realSSN;

    /** The scrambled ssn. */
    @Column(name = "scrambledssnflag", columnDefinition = "BIT", length = 1)
    private boolean scrambledSSN;

    /** The phi data. */
    @Column(name = "phidataflag", columnDefinition = "BIT", length = 1)
    private boolean phiData;

    /** The data sources. */
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "researchstudydatasource", schema = "hib", joinColumns = { @JoinColumn(
            name = "researchstudyid", referencedColumnName = "ID") }, inverseJoinColumns = { @JoinColumn(
            name = "datasourceid", referencedColumnName = "ID") })
    private Set<DataSource> dataSources;

    /**
     * Instantiates a new preparatory request.
     */
    PreparatoryRequest() {
    }

    /**
     * Instantiates a new preparatory request.
     *
     * @param createdBy the created by
     * @throws ValidationException the validation exception
     */
    PreparatoryRequest(final String createdBy) throws ValidationException {
        super(createdBy);
    }

    /**
     * Find by id.
     *
     * @param requestId the request id
     * @return the preparatory request
     * @throws ObjectNotFoundException the object not found exception
     */
    public static PreparatoryRequest findById(final int requestId) throws ObjectNotFoundException {
        return (PreparatoryRequest) DartObjectFactory.getInstance().getPreparatoryRequestDAO().findById(requestId);
    }

    /**
     * List by activity id.
     *
     * @param activityId the activity id
     * @return the list
     */
    public static List<PreparatoryRequest> listByActivityId(final int activityId) {
        return DartObjectFactory.getInstance().getPreparatoryRequestDAO().listByActivityId(activityId);
    }

    /**
     * List by requestor.
     *
     * @param requestorId the requestor id
     * @return the list
     */
    public static List<PreparatoryRequest> listByRequestor(final int requestorId) {
        return DartObjectFactory.getInstance().getPreparatoryRequestDAO().listByRequestor(requestorId);
    }

    /**
     * List recent by requestor.
     *
     * @param requestorId the requestor id
     * @param maxResults the max results
     * @return the list
     * @throws ValidationException the validation exception
     */
    public static List<PreparatoryRequest> listRecentByRequestor(final int requestorId, final int maxResults)
            throws ValidationException {
        if (maxResults < 1 || maxResults > MAX_RESULTS_ALLOWED) {
            throw new ValidationException("Maximum number of results must be between 1 and 10");
        }

        return DartObjectFactory.getInstance().getPreparatoryRequestDAO().listRecentByRequestor(requestorId, maxResults);
    }

    /**
     * List by name.
     *
     * @param requestorId the requestor id
     * @param name the name
     * @return the list
     * @throws ValidationException the validation exception
     */
    public static List<PreparatoryRequest> listByName(final int requestorId, final String name) throws ValidationException {
        ValidationHelper.required("Preparatory Request Name", name);
        ValidationHelper.validateSize("Preparatory Request Name", name, 1, MAX_NAME_SIZE);

        return DartObjectFactory.getInstance().getPreparatoryRequestDAO().listByName(requestorId, name);
    }

    /**
     * Find most recent amendment.
     *
     * @param headId the head id
     * @return the preparatory request
     */
    public static PreparatoryRequest findMostRecentAmendment(final int headId) {
        return DartObjectFactory.getInstance().getPreparatoryRequestDAO().findMostRecentAmendment(headId);
    }

    /**
     * List all submitted.
     *
     * @return the list
     */
    public static List<PreparatoryRequest> listAllSubmitted() {
        return DartObjectFactory.getInstance().getPreparatoryRequestDAO().listAllSubmitted();
    }

    /**
     * List all.
     *
     * @return the list
     */
    public static List<PreparatoryRequest> listAll() {
        return DartObjectFactory.getInstance().getPreparatoryRequestDAO().listAll();
    }

    /**
     * Creates the.
     *
     * @param requestName the request name
     * @param dataSetStartDate the data set start date
     * @param dataSetEndDate the data set end date
     * @param requestor the requestor
     * @param activity the activity
     * @param createdBy the created by
     * @return the preparatory request
     * @throws ValidationException the validation exception
     */
    public static PreparatoryRequest 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);

        PreparatoryRequest result = new PreparatoryRequest(createdBy);

        // initialize various properties
        result.createdOn = new Date();
        result.createdBy = createdBy;
        result.type = "P"; // this seems redundant to the requesttype column
        result.requestType = 5;  //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, null, createdBy);

        DartObjectFactory.getInstance().getPreparatoryRequestDAO().save(result);

        return result;
    }

    /**
     * Creates the amendment.
     *
     * @param createdBy the created by
     * @return the preparatory request
     * @throws ValidationException the validation exception
     * @throws ObjectNotFoundException the object not found exception
     */
    public PreparatoryRequest 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.
        validateStatusId();

        Request originalRequest = this;
        if (this.amendment) {
            originalRequest = DartObjectFactory.getInstance().getPreparatoryRequestDAO().findById(this.headId);
        }

        PreparatoryRequest result = createPreparatoryRequest(createdBy, originalRequest);

        DartObjectFactory.getInstance().getPreparatoryRequestDAO().save(result);

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

        if (result.participants == null) {
            result.participants = new HashSet<Participant>();
        }

        for (Participant participant : participants) {
            result.participants.add(participant.copy(result));
        }

        // copy the data sources
        populateResultDataSources(result);

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

    /**
     * Populate result data sources.
     *
     * @param result the result
     */
    private void populateResultDataSources(PreparatoryRequest result) {
        if (result.dataSources == null) {
            result.dataSources = new HashSet<DataSource>();
        }

        List<Integer> disabledDataSourceIDs = DataSource.findDisabledIdByRequestType(getRequestType());
        if (dataSources != null && (disabledDataSourceIDs != null && !disabledDataSourceIDs.isEmpty())) {
            for (DataSource currDataSource : dataSources) {
                if (!disabledDataSourceIDs.contains(currDataSource.getId())) { 
                    result.dataSources.add(currDataSource); 
                }
            }
        } else { 
            result.dataSources.addAll(dataSources); 
        }
    }

    /**
     * Creates the preparatory request.
     *
     * @param createdBy the created by
     * @param originalRequest the original request
     * @return the preparatory request
     * @throws ValidationException the validation exception
     */
    private PreparatoryRequest createPreparatoryRequest(final String createdBy, Request originalRequest)
            throws ValidationException {
        PreparatoryRequest result = new PreparatoryRequest(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.expectedIRBSubmissionDate = this.expectedIRBSubmissionDate;
        result.primaryLocation = this.primaryLocation;
        result.dataSourceLocation = ((PreparatoryRequest) this).dataSourceLocation;
        result.dataMart = ((PreparatoryRequest) this).dataMart;
        result.realSSN = ((PreparatoryRequest) this).realSSN;
        result.scrambledSSN = ((PreparatoryRequest) this).scrambledSSN;
        result.phiData = ((PreparatoryRequest) 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()
        return result;
    }

    /**
     * Validate status id.
     *
     * @throws ObjectNotFoundException the object not found exception
     * @throws ValidationException the validation exception
     */
    private void validateStatusId() throws ObjectNotFoundException, ValidationException {
        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
    }

    /**
     * Modify.
     *
     * @param name the name
     * @param expectedIRBSubmissionDate the expected irb submission date
     * @param updatedBy the updated by
     * @throws ValidationException the validation exception
     */
    public void modify(final String name, final Date expectedIRBSubmissionDate, final String updatedBy) throws ValidationException {

        validateModify(name, expectedIRBSubmissionDate);

        this.name = name;
        this.expectedIRBSubmissionDate = expectedIRBSubmissionDate;
        this.updatedBy = updatedBy;
        this.updatedOn = new Date();
    }

    /**
     * Validate modify.
     *
     * @param name the name
     * @param startDate the data source start date
     * @param endDate the data source end date
     * @throws ValidationException the validation exception
     */
    private void validateModify(final String name, final Date date)
            throws ValidationException {
    }

    /* (non-Javadoc)
     * @see gov.va.vinci.dart.biz.Request#getDataSources()
     */
    @Override
    public Set<DataSource> getDataSources() {
        return dataSources;
    }

    /**
     * Sets the data sources.
     *
     * @param dataSources the new data sources
     */
    // TESTING ONLY
    public void setDataSources(HashSet<DataSource> dataSources) {
        this.dataSources = dataSources;
    }

    /**
     * Gets the data source location.
     *
     * @return the data source location
     */
    public String getDataSourceLocation() {
        return dataSourceLocation;
    }

    /**
     * Sets the data source location.
     *
     * @param dataSourceLocation the new data source location
     */
    public void setDataSourceLocation(final String dataSourceLocation) {
        this.dataSourceLocation = dataSourceLocation;
    }

    /**
     * Checks if is data mart.
     *
     * @return true, if is data mart
     */
    public boolean isDataMart() {
        return dataMart;
    }

    /**
     * Sets the data mart.
     *
     * @param dataMart the new data mart
     */
    public void setDataMart(final boolean dataMart) {
        this.dataMart = dataMart;
    }

   
    public Date getExpectedIRBSubmissionDate() {
        return expectedIRBSubmissionDate;
    }

    public void setExpectedIRBSubmissionDate(Date expectedIRBSubmissionDate) {
        this.expectedIRBSubmissionDate = expectedIRBSubmissionDate;
    }

    /**
     * Checks if is real ssn.
     *
     * @return true, if is real ssn
     */
    public boolean isRealSSN() {
        return realSSN;
    }

    /**
     * Sets the real ssn.
     *
     * @param realSSN the new real ssn
     */
    public void setRealSSN(boolean realSSN) {
        this.realSSN = realSSN;
    }

    /**
     * Checks if is scrambled ssn.
     *
     * @return true, if is scrambled ssn
     */
    public boolean isScrambledSSN() {
        return scrambledSSN;
    }

    /**
     * Sets the scrambled ssn.
     *
     * @param scrambledSSN the new scrambled ssn
     */
    public void setScrambledSSN(boolean scrambledSSN) {
        this.scrambledSSN = scrambledSSN;
    }

    /**
     * Checks if is phi data.
     *
     * @return true, if is phi data
     */
    public boolean isPhiData() {
        return phiData;
    }

    /**
     * Sets the phi data.
     *
     * @param phiData the new phi data
     */
    public void setPhiData(boolean phiData) {
        this.phiData = phiData;
    }

    // walk through existing documents, document templates, participants and data sources and figure out what
    /**
     * Creates the documents.
     *
     * @param createdBy the created by
     * @throws CheckedException the checked exception
     */
    // 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);
    }

    /* (non-Javadoc)
     * @see gov.va.vinci.dart.biz.Request#equals(java.lang.Object)
     */
    // necessary to use (List<PreparatoryRequest>).contains()
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }

        if ((!PreparatoryRequest.class.isAssignableFrom(obj.getClass()))) {
            return false;
        }

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

}
