package gov.va.vinci.dart.rule;

import gov.va.vinci.dart.biz.DataSource;
import gov.va.vinci.dart.biz.DocumentTemplate;
import gov.va.vinci.dart.biz.Group;
import gov.va.vinci.dart.biz.Location;
import gov.va.vinci.dart.biz.Participant;
import gov.va.vinci.dart.biz.Request;
import gov.va.vinci.dart.biz.RequestAdminLocationDocument;
import gov.va.vinci.dart.biz.RequestAdminParticipantDocument;
import gov.va.vinci.dart.biz.RequestLocationDocument;
import gov.va.vinci.dart.biz.RequestParticipantDocument;
import gov.va.vinci.dart.biz.WorkflowTemplate;
import gov.va.vinci.dart.common.exception.CheckedException;
import gov.va.vinci.dart.common.exception.ValidationException;
import gov.va.vinci.dart.dms.biz.Document;
import gov.va.vinci.dart.wf2.WorkflowResolver;

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

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


/**
 * The Class DocumentRuleEvaluatorHelper.
 */
public class DocumentRuleEvaluatorHelper {

    /** The log. */
    private static Log log = LogFactory.getLog(DocumentRuleEvaluatorHelper.class);

    /**
     * Evaluate docs.
     *
     * @param request
     *            the request
     * @param createdBy
     *            the created by
     * @throws CheckedException
     *             the checked exception
     */
    public static void evaluateDocs(final Request request, final String createdBy) throws CheckedException {

        Set<Document> requiredDocuments = new HashSet<Document>();

        if (request.getDataSources() != null) {

            for (DataSource dataSource : request.getDataSources()) {

                String evaluatorStr = dataSource.getEvaluator();

                DocumentRuleEvaluator documentRuleEvaluator = DocumentRuleEvaluatorFactory.getEvaluator(evaluatorStr);

                log.debug("Data Source evaluator = " + documentRuleEvaluator);

                if (documentRuleEvaluator == null) {
                    throw new CheckedException("Cannot find the appropriate DocumentRuleEvaluator");
                }

                final boolean isIndependentOnly = evaluateIndependentOnlyDataSource(request, dataSource);

                evaluateLocationDocs(request, dataSource, documentRuleEvaluator, createdBy, requiredDocuments,
                        isIndependentOnly);
                evaluateParticipantDocs(request, dataSource, documentRuleEvaluator, createdBy, requiredDocuments,
                        isIndependentOnly);
                evaluateAdminLocationDocs(request, dataSource, documentRuleEvaluator, createdBy, requiredDocuments,
                        isIndependentOnly);
                evaluateAdminParticipantDocs(request, dataSource, documentRuleEvaluator, createdBy, requiredDocuments,
                        isIndependentOnly);
            }
        }

        // find the difference of documents attached to the request and documents required by the selected data sources.
        Set<Document> difference = new HashSet<Document>();
        difference.addAll(request.getDocuments());
        difference.removeAll(requiredDocuments);

        log.debug("documents attached to request that are not required by selected data sources = " + difference);

        // now remove location and participant and admin-location and admin-participant table references to those difference
        // documents.
        removeLocationDocs(request, difference);
        removeParticipantDocs(request, difference);
        removeAdminLocationDocs(request, difference);
        removeAdminParticipantDocs(request, difference);
    }

    /**
     * Evaluate independent only data source.
     *
     * @param req
     *            the req
     * @param ds
     *            the ds
     * @return true, if successful
     */
    private static boolean evaluateIndependentOnlyDataSource(final Request req, final DataSource ds) {

        // if this is a new request, don't require the Real SSN Access Request Form for independent workflows
        if (req != null && req.getWorkflowTypeId() == WorkflowResolver.WF_RESEARCH_REQUEST) { // new request

            Set<WorkflowTemplate> dataSourceWorkflowTemplates = ds.getWorkflowTemplates();
            if (dataSourceWorkflowTemplates != null) {
                for (WorkflowTemplate workflowTemplate : dataSourceWorkflowTemplates) { // step through the workflow templates
                                                                                        // associated with this data source

                    if (workflowTemplate != null && workflowTemplate.getWorkflowTypeId() != WorkflowResolver.WF_INDEPENDENT) {
                        return false; // found a non-Independent workflow template for this data source
                    }// end if

                }// end for
            }// end if

            return true; // new request, has only Independent workflow templates
        }// end if

        return false; // legacy request
    }

  

    /**
     * Removes the location docs.
     *
     * @param req
     *            the req
     * @param orphans
     *            the orphans
     */
    private static void removeLocationDocs(final Request req, final Set<Document> orphans) {
        List<RequestLocationDocument> locationList = RequestLocationDocument.listByRequestId(req.getId());

        for (RequestLocationDocument rld : locationList) {

            // check for dirty data from old DART app
            Document rldDoc = Document.findById(rld.getDocumentId());
            if (rldDoc == null) {
                log.error("Removing document id " + rld.getDocumentId() + " from request location document list.");
                rld.setActive(false); // this RequestLocationDocument has NO corresponding Document entry, so set it to inactive
                                      // (caused by dirty data from old DART app)

            } else { // only bother to match the documentId if the document exists

                for (Document doc : orphans) {
                    if (rld.getDocumentId() == doc.getId()) {
                        log.debug("Removing document id " + doc.getId() + " from request location document list.");

                        // remove it
                        rld.setActive(false);
                    }
                }
            }// end else
        }// end for
    }

    /**
     * Removes the admin location docs.
     *
     * @param req
     *            the req
     * @param orphans
     *            the orphans
     */
    private static void removeAdminLocationDocs(final Request req, final Set<Document> orphans) {
        List<RequestAdminLocationDocument> locationList = RequestAdminLocationDocument.listByRequestId(req.getId());

        for (RequestAdminLocationDocument rald : locationList) {

            // check for dirty data from old DART app
            Document raldDoc = Document.findById(rald.getDocumentId());
            if (raldDoc == null) {
                log.error("Removing document id " + rald.getDocumentId()
                        + " from request administrator location document list.");
                rald.setActive(false); // this RequestAdminLocationDocument has NO corresponding Document entry, so set it to
                                       // inactive (caused by dirty data from old DART app)

            } else { // only bother to match the documentId if the document exists

                for (Document doc : orphans) {
                    if (rald.getDocumentId() == doc.getId()) {
                        log.debug("Removing document id " + doc.getId() + " from request administrator location document list.");

                        // remove it
                        rald.setActive(false);
                    }
                }
            }// end else
        }// end for
    }

    /**
     * Removes the participant docs.
     *
     * @param req
     *            the req
     * @param orphans
     *            the orphans
     */
    private static void removeParticipantDocs(final Request req, final Set<Document> orphans) {
        List<RequestParticipantDocument> participantList = RequestParticipantDocument.listByRequestId(req.getId());

        for (RequestParticipantDocument rpd : participantList) {

            // check for dirty data from old DART app
            Document rpdDoc = Document.findById(rpd.getDocumentId());
            if (rpdDoc == null) {
                log.error("Removing document id " + rpd.getDocumentId() + " from request participant document list.");
                rpd.setActive(false); // this RequestParticipantDocument has NO corresponding Document entry, so set it to
                                      // inactive

            } else { // only bother to match the documentId if the document exists

                for (Document doc : orphans) {
                    if (rpd.getDocumentId() == doc.getId()) {
                        log.debug("Removing document id " + doc.getId() + " from request participant document list.");

                        // remove it
                        rpd.setActive(false);
                    }
                }

            }// end else
        }// end for
    }

    /**
     * Removes the admin participant docs.
     *
     * @param req
     *            the req
     * @param orphans
     *            the orphans
     */
    private static void removeAdminParticipantDocs(final Request req, final Set<Document> orphans) {
        List<RequestAdminParticipantDocument> participantList = RequestAdminParticipantDocument.listByRequestId(req.getId());

        for (RequestAdminParticipantDocument rapd : participantList) {

            // check for dirty data from old DART app
            Document rapdDoc = Document.findById(rapd.getDocumentId());
            if (rapdDoc == null) {
                log.error("Removing document id " + rapd.getDocumentId()
                        + " from request administrator participant document list.");
                rapd.setActive(false); // this RequestAdminParticipantDocument has NO corresponding Document entry, so set it to
                                       // inactive

            } else { // only bother to match the documentId if the document exists

                for (Document doc : orphans) {
                    if (rapd.getDocumentId() == doc.getId()) {
                        log.debug("Removing document id " + doc.getId()
                                + " from request administrator participant document list.");

                        // remove it
                        rapd.setActive(false);
                    }
                }
            }// end else
        }// end for
    }

    /**
     * Evaluate location documents.
     *
     * @param request
     *            the request
     * @param datasource
     *            the datasource
     * @param documentRuleEvaluator
     *            the document rule evaluator
     * @param createdBy
     *            the created by
     * @param requiredDocuments
     *            the required documents
     * @param isIndependentOnly
     *            the is independent only
     * @throws ValidationException
     *             the validation exception
     */
    private static void evaluateLocationDocs(final Request request, final DataSource datasource,
            final DocumentRuleEvaluator documentRuleEvaluator, final String createdBy, final Set<Document> requiredDocuments,
            final boolean isIndependentOnly) throws ValidationException {

        List<RequestLocationDocument> locationList = RequestLocationDocument.listByRequestId(request.getId());

        for (Location site : request.getSites()) {

            for (DocumentTemplate temp : DocumentTemplate.listAll()) {

                Document doc = null;

                if (documentRuleEvaluator.isRequired(request, datasource, temp, site, isIndependentOnly)) {
                    log.debug(documentRuleEvaluator.getClass().getName() + " isRequired " + request + "  " + datasource + " "
                            + temp + "  " + site);

                    // TODO: verify that the mapping record exists too? RequestLocationDocument: verify what happens if you
                    // remove and then add a site to the request. We seem to be missing some mapping records for requestid=7050?

                    // does the request already have a document for this location and this template?
                    doc = findDocForLocationAndTemplate(locationList, site, temp);
                    if (doc == null) {

                        // no - better create one then

                        doc = Document.create(request, temp, createdBy);
                        request.getDocuments().add(doc);

                        log.debug("Created new document id = " + doc.getId() + " at location id " + site.getId()
                                + " for request id " + request.getId() + " from document template id " + temp.getId());

                        // tie that document to the specific location for this request
                        RequestLocationDocument.create(request, site, doc);
                    }
                }

                if (doc != null) {
                    requiredDocuments.add(doc);
                }
            }
        }
    }

    /**
     * Find document for location and template.
     *
     * @param requestLocationDocuments
     *            the request location documents
     * @param location
     *            the location
     * @param documentTemplate
     *            the document template
     * @return the document
     */
    private static Document findDocForLocationAndTemplate(final List<RequestLocationDocument> requestLocationDocuments,
            final Location location, final DocumentTemplate documentTemplate) {

        for (RequestLocationDocument rld : requestLocationDocuments) {
            if (rld.getLocationId() == location.getId()) {
                Document doc = Document.findById(rld.getDocumentId());
                if (doc != null) {
                    if (doc.getDocumentTemplate().getId() == documentTemplate.getId()) {
                        rld.setActive(true);
                        return doc;
                    }
                } else {
                    log.debug("Failed to find RequestLocationDocument documentId: " + rld.getDocumentId() + ", location = "
                            + location.getName() + ":  requestId = " + rld.getRequestId() + ", docId = " + rld.getDocumentId()
                            + ", locationId = " + rld.getLocationId());
                }
            }
        }

        return null;
    }

    /**
     * Evaluate participant docs.
     *
     * @param req
     *            the req
     * @param ds
     *            the ds
     * @param rev
     *            the rev
     * @param createdBy
     *            the created by
     * @param requiredDocuments
     *            the required documents
     * @param isIndependentOnly
     *            the is independent only
     * @throws ValidationException
     *             the validation exception
     */
    private static void evaluateParticipantDocs(final Request req, final DataSource ds, final DocumentRuleEvaluator rev,
            final String createdBy, final Set<Document> requiredDocuments, final boolean isIndependentOnly)
            throws ValidationException {

        List<RequestParticipantDocument> participantList = RequestParticipantDocument.listByRequestId(req.getId());

        for (Participant participant : req.getParticipants()) {

            for (DocumentTemplate temp : DocumentTemplate.listAll()) {
                Document doc = null;

                if (rev.isRequired(req, ds, temp, participant, isIndependentOnly)) {

                    log.debug(rev.getClass().getName() + " isRequired " + req + "  " + ds + " " + temp + "  " + participant);

                    // does the request already have a document for this participant and this template?
                    doc = findDocForParticipantAndTemplate(participantList, participant, temp);
                    if (doc == null) {

                        // no - better create one then

                        doc = Document.create(req, temp, createdBy);
                        req.getDocuments().add(doc);

                        log.debug("Created new document id = " + doc.getId() + " for principal investigator id "
                                + participant.getId() + " for request id " + req.getId() + " from document template id "
                                + temp.getId());

                        // tie that document to the specific participant for this request
                        RequestParticipantDocument.create(req, participant, doc);
                    }
                }

                if (doc != null) {
                    requiredDocuments.add(doc);
                }
            }
        }
    }

    /**
     * Find doc for participant and template.
     *
     * @param participantList
     *            the participant list
     * @param part
     *            the part
     * @param template
     *            the template
     * @return the document
     */
    private static Document findDocForParticipantAndTemplate(final List<RequestParticipantDocument> participantList,
            final Participant part, final DocumentTemplate template) {

        for (RequestParticipantDocument rpd : participantList) {
            if (rpd.getParticipantId() == part.getId()) {
                Document doc = Document.findById(rpd.getDocumentId());
                if (doc != null) {
                    if (doc.getDocumentTemplate().getId() == template.getId()) {
                        rpd.setActive(true);
                        return doc;
                    }
                } else {
                    log.debug("Failed to find RequestParticipantDocument, documentId: " + rpd.getDocumentId()
                            + ", participant = " + part.getPerson().getFullName() + ":  requestId = " + rpd.getRequestId()
                            + ", docId = " + rpd.getDocumentId() + ", participantId = " + rpd.getParticipantId());
                }
            }
        }

        return null;
    }

    /**
     * Evaluate admin location docs.
     *
     * @param req
     *            the req
     * @param ds
     *            the ds
     * @param rev
     *            the rev
     * @param createdBy
     *            the created by
     * @param requiredDocuments
     *            the required documents
     * @param isIndependentOnly
     *            the is independent only
     * @throws ValidationException
     *             the validation exception
     */
    private static void evaluateAdminLocationDocs(Request req, DataSource ds, DocumentRuleEvaluator rev, String createdBy,
            final Set<Document> requiredDocuments, final boolean isIndependentOnly) throws ValidationException {

        List<RequestAdminLocationDocument> locationList = RequestAdminLocationDocument.listByRequestId(req.getId());

        // check all groups, not just the ones selected for review.
        for (Group reviewer : Group.listAll()) {

            for (Location site : req.getSites()) {

                for (DocumentTemplate temp : DocumentTemplate.listAll()) {

                    Document doc = null;

                    if (rev.isRequired(req, ds, temp, reviewer, site, isIndependentOnly)) {
                        log.debug(rev.getClass().getName() + " isRequired " + req + "  " + ds + " " + temp + "  " + site);

                        // does the request already have a document for this location and this template?
                        doc = findDocForAdminLocationAndTemplate(locationList, site, temp);
                        if (doc == null) {

                            // no - better create one then

                            doc = Document.create(req, temp, createdBy);
                            req.getDocuments().add(doc);

                            log.debug("Created new document id = " + doc.getId() + " at location id " + site.getId()
                                    + " for request id " + req.getId() + " from document template id " + temp.getId());

                            // tie that document to the specific location for this request
                            RequestAdminLocationDocument.create(req, site, doc, reviewer);
                        }
                    }

                    if (doc != null) {
                        requiredDocuments.add(doc);
                    }
                }
            }
        }
    }

    /**
     * Find doc for admin location and template.
     *
     * @param locationList
     *            the location list
     * @param loc
     *            the loc
     * @param template
     *            the template
     * @return the document
     */
    private static Document findDocForAdminLocationAndTemplate(final List<RequestAdminLocationDocument> locationList,
            final Location loc, final DocumentTemplate template) {

        for (RequestAdminLocationDocument rld : locationList) {
            if (rld.getLocationId() == loc.getId()) {
                Document doc = Document.findById(rld.getDocumentId());
                if (doc != null) {
                    if (doc.getDocumentTemplate().getId() == template.getId()) {
                        rld.setActive(true);
                        return doc;
                    }
                } else {
                    log.debug("Failed to find RequestAdminLocationDocument documentId: " + rld.getDocumentId()
                            + ", location = " + loc.getName() + ":  requestId = " + rld.getRequestId() + ", docId = "
                            + rld.getDocumentId() + ", locationId = " + rld.getLocationId());
                }
            }
        }

        return null;
    }

    /**
     * Evaluate admin participant docs.
     *
     * @param req
     *            the req
     * @param ds
     *            the ds
     * @param rev
     *            the rev
     * @param createdBy
     *            the created by
     * @param requiredDocuments
     *            the required documents
     * @param isIndependentOnly
     *            the is independent only
     * @throws ValidationException
     *             the validation exception
     */
    private static void evaluateAdminParticipantDocs(Request req, DataSource ds, DocumentRuleEvaluator rev, String createdBy,
            final Set<Document> requiredDocuments, final boolean isIndependentOnly) throws ValidationException {

        List<RequestAdminParticipantDocument> participantList = RequestAdminParticipantDocument.listByRequestId(req.getId());

        // check all groups, not just the ones selected for review.
        for (Group reviewer : Group.listAll()) {

            for (Participant participant : req.getParticipants()) {

                for (DocumentTemplate temp : DocumentTemplate.listAll()) {

                    Document doc = null;

                    if (rev.isRequired(req, ds, temp, reviewer, participant, isIndependentOnly)) {
                        log.debug(rev.getClass().getName() + " isRequired " + req + "  " + ds + " " + temp + "  " + participant);

                        // does the request already have a document for this participant and this template?
                        doc = findDocForAdminParticipantAndTemplate(participantList, participant, temp);
                        if (doc == null) {

                            // no - better create one then

                            doc = Document.create(req, temp, createdBy);
                            req.getDocuments().add(doc);

                            log.debug("Created new document id = " + doc.getId() + " for principal investigator id "
                                    + participant.getId() + " for request id " + req.getId() + " from document template id "
                                    + temp.getId());

                            // tie that document to the specific participant for this request
                            RequestAdminParticipantDocument.create(req, participant, doc, reviewer);
                        }
                    }

                    if (doc != null) {
                        requiredDocuments.add(doc);
                    }
                }
            }
        }
    }

    /**
     * Find doc for admin participant and template.
     *
     * @param participantList
     *            the participant list
     * @param part
     *            the part
     * @param template
     *            the template
     * @return the document
     */
    private static Document findDocForAdminParticipantAndTemplate(final List<RequestAdminParticipantDocument> participantList,
            final Participant part, final DocumentTemplate template) {

        for (RequestAdminParticipantDocument rpd : participantList) {
            if (rpd.getParticipantId() == part.getId()) {
                Document doc = Document.findById(rpd.getDocumentId());
                if (doc != null) {
                    if (doc.getDocumentTemplate().getId() == template.getId()) {
                        rpd.setActive(true);
                        return doc;
                    }
                } else {
                    log.debug("Failed to find RequestAdminParticipantDocument, documentId: " + rpd.getDocumentId()
                            + ", participant = " + part.getPerson().getFullName() + ":  requestId = " + rpd.getRequestId()
                            + ", docId = " + rpd.getDocumentId() + ", participantId = " + rpd.getParticipantId());
                }
            }
        }

        return null;
    }

}
