package gov.va.vinci.dart;

import gov.va.vinci.dart.biz.Activity;
import gov.va.vinci.dart.biz.Comment;
import gov.va.vinci.dart.biz.DartRequest;
import gov.va.vinci.dart.biz.DataSource;
import gov.va.vinci.dart.biz.Event;
import gov.va.vinci.dart.biz.EventType;
import gov.va.vinci.dart.biz.Group;
import gov.va.vinci.dart.biz.GroupTask;
import gov.va.vinci.dart.biz.Location;
import gov.va.vinci.dart.biz.MedianWaitTime;
import gov.va.vinci.dart.biz.OperationalRequest;
import gov.va.vinci.dart.biz.Participant;
import gov.va.vinci.dart.biz.Person;
import gov.va.vinci.dart.biz.PersonTask;
import gov.va.vinci.dart.biz.PreparatoryRequest;
import gov.va.vinci.dart.biz.Request;
import gov.va.vinci.dart.biz.RequestLocationDocument;
import gov.va.vinci.dart.biz.RequestParticipantDocument;
import gov.va.vinci.dart.biz.RequestStatus;
import gov.va.vinci.dart.biz.RequestSummary;
import gov.va.vinci.dart.biz.RequestWorkflow;
import gov.va.vinci.dart.biz.Review;
import gov.va.vinci.dart.biz.ReviewTemplate;
import gov.va.vinci.dart.biz.Role;
import gov.va.vinci.dart.biz.WorkflowSummary;
import gov.va.vinci.dart.common.exception.ObjectNotFoundException;
import gov.va.vinci.dart.common.exception.ValidationException;
import gov.va.vinci.dart.common.json.ErrorView;
import gov.va.vinci.dart.db.util.HibernateSessionManager;
import gov.va.vinci.dart.dms.biz.Document;
import gov.va.vinci.dart.json.AdminRequestListView;
import gov.va.vinci.dart.json.AmendmentView;
import gov.va.vinci.dart.json.DenyRequestView;
import gov.va.vinci.dart.json.ErrorListView;
import gov.va.vinci.dart.json.LocationListView;
import gov.va.vinci.dart.json.ModifyReviewGroupsView;
import gov.va.vinci.dart.json.RequestChangeView;
import gov.va.vinci.dart.json.RequestDetailsListView;
import gov.va.vinci.dart.json.RequestDetailsView;
import gov.va.vinci.dart.json.RequestIdTypeView;
import gov.va.vinci.dart.json.RequestIdView;
import gov.va.vinci.dart.json.RequestListView;
import gov.va.vinci.dart.json.RequestSearchView;
import gov.va.vinci.dart.json.RequestView;
import gov.va.vinci.dart.json.ReviewStatusListView;
import gov.va.vinci.dart.json.ReviewStatusView;
import gov.va.vinci.dart.json.UserIdView;
import gov.va.vinci.dart.json.builder.FieldValidationBuilder;
import gov.va.vinci.dart.json.builder.RequestViewBuilder;
import gov.va.vinci.dart.service.DartObjectFactory;
import gov.va.vinci.dart.usr.UserPreferences;
import gov.va.vinci.dart.wf2.EmailUtils;
import gov.va.vinci.dart.wf2.TaskUtils;
import gov.va.vinci.dart.wf2.Workflow;
import gov.va.vinci.dart.wf2.WorkflowException;
import gov.va.vinci.dart.wf2.WorkflowResolver;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.validation.Valid;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * The Class RequestController.
 */
@Controller
public class RequestController extends DartController {

    /** The Constant MAX_RESULTS. */
    private static final int MAX_RESULTS = 6;

    /** The Constant VITAL_STATUS_FILE_REAL_SSN_CROSSWALK_FILE_ID. */
    private static final int VITAL_STATUS_FILE_REAL_SSN_CROSSWALK_FILE_ID = 3;

    /** The Constant VITAL_STATUS_FILE_WITH_SCRAMBLED_SSN_ID. */
    private static final int VITAL_STATUS_FILE_WITH_SCRAMBLED_SSN_ID = 6;

    /** The Constant BIRLS_REAL_110JJ02_ID. */
    private static final int BIRLS_REAL_110JJ02_ID = 7;

    /** The Constant MCA_FORMERLY_DSS_WEB_REPORTS_ID. */
    private static final int MCA_FORMERLY_DSS_WEB_REPORTS_ID = 8;

    /** The Constant VITAL_STATUS_FILE_REAL_SSN_CROSSWALK_FILE_110TT20_ID. */
    private static final int VITAL_STATUS_FILE_REAL_SSN_CROSSWALK_FILE_110TT20_ID = 11;

    /** The Constant VITAL_STATUS_FILES_WITH_SCRAMBLED_SSN_110NN06_ID. */
    private static final int VITAL_STATUS_FILES_WITH_SCRAMBLED_SSN_110NN06_ID = 12;

    /** The Constant VSSC_WEB_REPORTS_ID. */
    private static final int VSSC_WEB_REPORTS_ID = 13;

    /** The Constant CAPRI_VISTAWEB_ID. */
    private static final int CAPRI_VISTAWEB_ID = 1001;

    /** The Constant CDW_PRODUCTION_DOMAINS_ID. */
    private static final int CDW_PRODUCTION_DOMAINS_ID = 1022;

    /** The Constant CDW_RAW_DOMAINS_ID. */
    private static final int CDW_RAW_DOMAINS_ID = 1023;

    /** The Constant CDW_MCA_FORMERLY_DSS_NDE. */
    private static final int CDW_MCA_FORMERLY_DSS_NDE_ID = 1024;

    /** The Constant TIU_TEXT_NOTES_ID. */
    private static final int TIU_TEXT_NOTES_ID = 1025;

    /** The Constant VITAL_STATUS_ID. */
    private static final int VITAL_STATUS_ID = 1026;

    /** The Constant BIRLS_ID. */
    private static final int BIRLS_ID = 1027;

    /** The Constant MCA_FORMERLY_DSS_NDE_LEGACY_ID. */
    private static final int MCA_FORMERLY_DSS_NDE_LEGACY_ID = 1028;

    /** The Constant MEDSAS_FILES_INCLUDING_VETSNET_FILES_ID. */
    private static final int MEDSAS_FILES_INCLUDING_VETSNET_FILES_ID = 1029;

    /** The Constant MEDSAS_INCLUDING_VETSNET_FILES_FOR_NATIONAL_LEVEL_REAL_SSN_1100TT01_ID. */
    private static final int MEDSAS_INCLUDING_VETSNET_FILES_FOR_NATIONAL_LEVEL_REAL_SSN_1100TT01_ID = 1030;

    /** The Constant MEDSAS_FILES_FOR_VISN_LEVEL_REAL_SSN_1100TT05_ID. */
    private static final int MEDSAS_FILES_FOR_VISN_LEVEL_REAL_SSN_1100TT05_ID = 1031;

    /** The Constant LEGACY_DATA_WAREHOUSES_I_E_VISN_21. */
    private static final int LEGACY_DATA_WAREHOUSES_I_E_VISN_21 = 1032;

    /** The Constant OEF_OIF_ROSTER_FILE_ID. */
    private static final int OEF_OIF_ROSTER_FILE_ID = 1033;

    /** The Constant SAS_GRID_ID. */
    private static final int SAS_GRID_ID = 1035;

    /** The Constant HOMELESS_REGISTRY_ID. */
    private static final int HOMELESS_REGISTRY_ID = 1036;

    /** The Constant CARE_ASSESSMENT_NEED_CAN_SCORE_REQUIRES_SCRAMBLED_SSN_LEVEL_ACCESS_ID. */
    private static final int CARE_ASSESSMENT_NEED_CAN_SCORE_REQUIRES_SCRAMBLED_SSN_LEVEL_ACCESS_ID = 1040;

    /** The Constant DATA_SOURCE_ERROR. */
    public static final String DATA_SOURCE_ERROR = "Data Source Error: ";

    /** The Constant VINCI_SERVICES_USER_ID. */
    public static final String VINCI_SERVICES_USER_ID = "123";

    /** The Constant SDF. */
    public static final SimpleDateFormat SDF = new SimpleDateFormat("MM/dd/yyyy");

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

    /** The workflow resolver. */
    @Autowired
    private WorkflowResolver workflowResolver;

    /** The request view builder. */
    @Autowired
    private RequestViewBuilder requestViewBuilder;

    /**
     * Retrieve request.
     *
     * @param requestId
     *            the request id
     * @return the request
     * @throws ObjectNotFoundException
     *             the object not found exception
     */
    public static Request retrieveRequest(final int requestId) throws ObjectNotFoundException {

        Request request = null;

        try {
            request = DartRequest.findById(requestId);
        } catch (ObjectNotFoundException e) {
            LOG.debug("DartRequest was not found by given id");
        }

        if (request == null) {
            try {
                request = PreparatoryRequest.findById(requestId);
            } catch (ObjectNotFoundException e) {
                LOG.debug("PreparatoryRequest was not found by given id");
            }
        }

        if (request == null) {
            try {
                request = OperationalRequest.findById(requestId);
            } catch (ObjectNotFoundException e) {
                LOG.debug("OperationRequest was not found by given id");
            }
        }

        return request;
    }

    /**
     * Find most recent amendment.
     *
     * @param headId
     *            the head id
     * @return the request
     * @throws ObjectNotFoundException
     *             the object not found exception
     */
    private static Request findMostRecentAmendment(final int headId) throws ObjectNotFoundException {

        Request request = null;
        try {
            request = DartRequest.findMostRecentAmendment(headId);
        } catch (Exception e) {
            LOG.debug("DartRequest was not found by given id");
        }

        if (request == null) {
            try {
                request = PreparatoryRequest.findMostRecentAmendment(headId);
            } catch (Exception e) {
                LOG.debug("PreparatoryRequest was not found by given id");
            }
        }

        if (request == null) {
            try {
                request = OperationalRequest.findMostRecentAmendment(headId);
            } catch (Exception e) {
                LOG.debug("OperationRequest was not found by given id");
            }
        }

        return request;

    }

    /**
     * Gets the request.
     *
     * @param view
     *            the view
     * @return the request
     */
    @RequestMapping(value = "/getRequest", method = RequestMethod.POST)
    @ResponseBody
    public Object getRequest(@RequestBody @Valid final RequestIdView view) {
        LOG.debug("getRequest");

        try {
            HibernateSessionManager.start();

            UserPreferences prefs = getUserPreferences();

            Role.initialize();
            Group.initialize();

            Request request = null;
            try {
                request = retrieveRequest(view.getRequestId());

            } catch (ObjectNotFoundException e) {
                LOG.error("Error loading request " + view.getRequestId(), e);
                return new ErrorView("Error loading request " + view.getRequestId());
            }

            Person person = null;
            try {
                person = Person.findById(prefs.getUserId());
            } catch (ObjectNotFoundException e) {
                throw new ObjectNotFoundException("Cannot find person with id: " + prefs.getUserId());
            }

            if (request != null) {

                Activity activity = request.getActivity();
                if (activity == null) {
                    return new ErrorView("Error retrieving request.");
                }

                // verify the access permissions
                if (!activity.verifyReadAccessPermissions(person)) {
                    LOG.error("Error retrieving request: invalid user permissions.");
                    return new ErrorView("Error retrieving request.");
                }

                try {
                    RequestWorkflow workflow = null;
                    if (view.getWorkflowId() != 0) { // new request (after the Independent Workflow updates)
                        workflow = RequestWorkflow.findById(view.getWorkflowId());
                    }

                    return populateRequestDetailsView(workflow, request, person);
                } catch (ObjectNotFoundException e) {
                    LOG.error("Error loading request " + view.getRequestId(), e);
                    return new ErrorView("Error loading request " + view.getRequestId());
                }
            }

        } catch (Exception e) {
            LOG.error("Error retrieving request data.", e);
            HibernateSessionManager.rollback();
            return new ErrorView("Error retrieving request data.");
        } finally {
            HibernateSessionManager.close();
        }
        return new ErrorView("Request not found.");
    }

    /**
     * Gets the recent user requests.
     *
     * @param userIdView
     *            the user id view
     * @return the recent user requests
     */
    @RequestMapping(value = "/getRecentUserRequests", method = RequestMethod.POST)
    @ResponseBody
    public Object getRecentUserRequests(@RequestBody @Valid final UserIdView userIdView) {

        LOG.debug("getRecentUserRequests");
        try {
            HibernateSessionManager.start();
            UserPreferences prefs = getUserPreferences();

            Person person = null;
            try {
                person = Person.findById(prefs.getUserId());
            } catch (ObjectNotFoundException e) {
                throw new ObjectNotFoundException("Cannot find person with id: " + prefs.getUserId());
            }

            // TODO: might want to create one function in the base class
            List<DartRequest> rlist = DartRequest.listRecentByRequestor(prefs.getUserId(), MAX_RESULTS);
            List<OperationalRequest> orlist = OperationalRequest.listRecentByRequestor(prefs.getUserId(), MAX_RESULTS);

            RequestDetailsListView result = new RequestDetailsListView();

            // Note: not currently used. If we start using this function, we should probably update the workflow information
            // here (instead of using only the top-level workflow)
            for (Request req : rlist) {
                result.getRequests().add(populateRequestDetailsView(null, req, person));
            }
            for (Request req : orlist) {
                result.getRequests().add(populateRequestDetailsView(null, req, person));
            }

            return result;
        } catch (Exception e) {
            LOG.error("Error retrieving list of recent requests.", e);
            HibernateSessionManager.rollback();
            return new ErrorView("Error retrieving list of recent requests.");
        } finally {
            HibernateSessionManager.close();
        }
    }

    /**
     * Gets the recent submitted requests.
     *
     * @param userIdView
     *            the user id view
     * @return the recent submitted requests
     */
    @RequestMapping(value = "/getRecentSubmittedRequests", method = RequestMethod.POST)
    @ResponseBody
    public Object getRecentSubmittedRequests(@RequestBody @Valid final UserIdView userIdView) {

        LOG.debug("getRecentSubmittedRequests");
        try {
            HibernateSessionManager.start();

            UserPreferences prefs = getUserPreferences();

            Person person = null;
            try {
                person = Person.findById(prefs.getUserId());
            } catch (ObjectNotFoundException e) {
                throw new ObjectNotFoundException("Cannot find person with id: " + prefs.getUserId());
            }

            // TODO: might want to create one function in the base class
            List<DartRequest> rlist = DartRequest.listRecentByRequestor(prefs.getUserId(), MAX_RESULTS);
            List<OperationalRequest> orlist = OperationalRequest.listRecentByRequestor(prefs.getUserId(), MAX_RESULTS);

            RequestDetailsListView result = new RequestDetailsListView();

            // Note: not currently used. If we start using this function, we should probably update the workflow information
            // here (instead of using only the top-level workflow)
            for (Request req : rlist) {
                if (req.isCurrent() && req.getSubmittedOn() != null) {
                    result.getRequests().add(populateRequestDetailsView(null, req, person));
                }
            }
            for (Request req : orlist) {
                if (req.isCurrent() && req.getSubmittedOn() != null) {
                    result.getRequests().add(populateRequestDetailsView(null, req, person));
                }
            }

            return result;

        } catch (Exception e) {
            LOG.error("Error retrieving list of recent requests.", e);
            HibernateSessionManager.rollback();
            return new ErrorView("Error retrieving list of recent requests.");
        } finally {
            HibernateSessionManager.close();
        }
    }

    /**
     * Gets the all user requests.
     *
     * @param userIdView
     *            the user id view
     * @return the all user requests
     */
    @RequestMapping(value = "/getAllUserRequests", method = RequestMethod.POST)
    @ResponseBody
    public Object getAllUserRequests(@RequestBody @Valid final UserIdView userIdView) {

        LOG.debug("getAllUserRequests");
        try {
            HibernateSessionManager.start();

            RequestListView result = new RequestListView();

            UserPreferences prefs = getUserPreferences();

            // super-user can see all requests, regardless of state
            Person person = null;
            try {
                person = Person.findById(prefs.getUserId());
            } catch (ObjectNotFoundException e) {
                return new ErrorView("Error: " + e.getMessage());
            }
            Role.initialize();

            // find requests that superusers (And NDS reviews + debug mode) can view.
            final boolean isSuperUser = person.hasRole(Role.SUPER_USER);
            if (isSuperUser || (person.hasRole(Role.NDS_ADMIN) && DartController.isDebugMode())) {

                AdminRequestListView adminRequestListView = new AdminRequestListView();

                // get all requests for the super-user (including the ones that are not yet submitted)
                List<RequestSummary> requestList = Request.listAllRequestSummary();
                if (requestList != null) {

                    for (RequestSummary req : requestList) {

                        RequestView currRequestView =
                                populateRequestViewBrief(req, isSuperUser, DartController.isDebugMode(), true);
                        // default to request tab (more details)

                        adminRequestListView.getRequestList().getRequests().add(currRequestView);
                        // add this request to the list for the request tab (more details)

                        // get the request status out of the list
                        if (req.getWorkflowSummaryList() != null && req.getWorkflowSummaryList().size() > 0) {
                            int reqSummaryStatusId = req.getWorkflowSummaryList().get(0).getStatus();

                            if (reqSummaryStatusId != RequestStatus.INITIATED.getId()) {
                                // requests that have been initiated (but not submitted) will NOT be displayed in the review tab
                                adminRequestListView.getReviewableRequestList().getRequests().add(currRequestView);
                                // add this request to the list for the review tab
                            }
                        }
                    }
                }

                return adminRequestListView;

            } else { // NOT a super-user

                // retrieve the request info if this is a requestor or if in debug mode
                boolean getRequests = true;
                if (person.hasRole(Role.REVIEWER) || person.hasRole(Role.NDS_ADMIN)) {
                    if (!DartController.isDebugMode())
                        getRequests = false;
                }

                if (getRequests) {

                    List<RequestSummary> allRequests = Request.listAllRequestSummaryByParticipant(prefs.getUserLoginId());
                    if (allRequests != null) {

                        for (RequestSummary rs : allRequests) {

                            result.getRequests().add(requestViewBuilder.buildRequestorTab(rs, person, false));
                            // requestor tab (don't bother to add the links)
                        }
                    }
                }
            }

            return result;

        } catch (Exception e) {
            LOG.error("Error loading requests.", e);
            HibernateSessionManager.rollback();
            return new ErrorView("Error loading requests.");
        } finally {
            HibernateSessionManager.close();
        }
    }

    /**
     * List user reviewable requests.
     *
     * @param userIdView
     *            the user id view
     * @return the object
     */
    @RequestMapping(value = "/listUserReviewableRequests", method = RequestMethod.POST)
    @ResponseBody
    public Object listUserReviewableRequests(@RequestBody @Valid final UserIdView userIdView) {

        LOG.debug("listUserReviewableRequests");
        try {
            HibernateSessionManager.start();
            UserPreferences prefs = getUserPreferences();

            Person person = null;
            try {
                person = Person.findById(prefs.getUserId());
            } catch (ObjectNotFoundException e) {
                return new ErrorView("Error: " + e.getMessage());
            }

            Role.initialize();
            Group.initialize();

            RequestListView result = new RequestListView();

            // find requests that NDS and superusers can view.
            boolean getNDSRequests = false;
            if (person.hasRole(Role.NDS_ADMIN) || person.inGroup(Group.NDS)) {
                // has the NDS role and/or belongs to the NDS group?
                getNDSRequests = true;
            }

            boolean isSuperUser = person.hasRole(Role.SUPER_USER);

            if (getNDSRequests || isSuperUser) {

                List<RequestSummary> requestList = Request.listAllButInitiated(); // get the submitted requests

                // find all requests that NDS should review, either initially or finally
                for (RequestSummary req : requestList) {

                    result.getRequests().add(populateRequestViewBrief(req, isSuperUser, DartController.isDebugMode(), false));
                    // review tab

                }
            } else if (person.hasRole(Role.REVIEWER) || person.hasRole(Role.READ_ONLY_STAFF)) {

                HashSet<RequestSummary> requestList = new HashSet<RequestSummary>();

                // for all groups the user belongs to, get group reviewable requests and add them to the hash set
                // (so we build a distinct list)
                for (Group grp : person.getGroups()) {
                    requestList.addAll(Request.listAllGroupReviewable(grp.getName())); // get the requests for this group
                }

                // build result list from the distinct request summary list
                for (RequestSummary req : requestList) {
                    RequestView rv = populateRequestViewBrief(req, isSuperUser, DartController.isDebugMode(), false); // review
                                                                                                                      // tab

                    rv.setReviewId(req.getReviewId()); // set the reviewId (intermediate review group)

                    result.getRequests().add(rv);
                }
            }

            return result;

        } catch (Exception e) {
            LOG.error("Error loading requests.", e);
            HibernateSessionManager.rollback();
            return new ErrorView("Error loading requests.");
        } finally {
            HibernateSessionManager.close();
        }
    }

    /**
     * Search requests.
     *
     * @param request
     *            the request
     * @return the object
     */
    @RequestMapping(value = "/searchRequests", method = RequestMethod.POST)
    @ResponseBody
    public Object searchRequests(@RequestBody @Valid final RequestSearchView request) {

        LOG.debug("searchRequests");
        try {
            HibernateSessionManager.start();

            UserPreferences prefs = getUserPreferences();

            Person person = null;
            try {
                person = Person.findById(prefs.getUserId());
            } catch (ObjectNotFoundException e) {
                throw new ObjectNotFoundException("Cannot find person with id: " + prefs.getUserId());
            }

            Role.initialize();

            if (person != null && !person.hasRole(Role.SUPER_USER)) {
                return new ErrorView("Error: User does not have permission to perform this action.");
            }

            // TODO: might want to create one function in the base class
            List<DartRequest> rlist = DartRequest.listByName(prefs.getUserId(), request.getKey().trim());
            List<OperationalRequest> orlist = OperationalRequest.listByName(prefs.getUserId(), request.getKey().trim());

            RequestDetailsListView result = new RequestDetailsListView();

            // Note: not currently used. If we start using this function, we should probably update the workflow information
            // here (instead of using only the top-level workflow)
            for (Request req : rlist) {
                result.getRequests().add(populateRequestDetailsView(null, req, person));
            }
            for (Request req : orlist) {
                result.getRequests().add(populateRequestDetailsView(null, req, person));
            }

            return result;

        } catch (Exception e) {
            LOG.error("Error searching requests", e);
            HibernateSessionManager.rollback();
            return new ErrorView("An error occurred searching requests.");
        } finally {
            HibernateSessionManager.close();
        }
    }

    /**
     * Gets the request locations.
     *
     * @param view
     *            the view
     * @return the request locations
     */
    @RequestMapping(value = "/getRequestLocations", method = RequestMethod.POST)
    @ResponseBody
    public Object getRequestLocations(@RequestBody @Valid final RequestIdView view) {

        LOG.debug("getRequestLocations");
        try {
            HibernateSessionManager.start();

            UserPreferences prefs = getUserPreferences();

            Person person = null;
            try {
                person = Person.findById(prefs.getUserId());
            } catch (ObjectNotFoundException e) {
                throw new ObjectNotFoundException("Cannot find person with id: " + prefs.getUserId());
            }

            Request request = retrieveRequest(view.getRequestId());
            if (request == null) {
                return new ErrorView("Error: Failed to load request.");
            }

            Role.initialize();
            if (person == null || !person.hasRole(Role.SUPER_USER)) {
                return new ErrorView("Error retrieving request locations.");
            }

            Set<String> stringSet = new HashSet<String>();

            for (Location loc : request.getSites()) {
                stringSet.add(loc.getName());
            }

            return new LocationListView(stringSet);

        } catch (Exception e) {
            LOG.error("Error retrieving request locations.", e);
            HibernateSessionManager.rollback();
            return new ErrorView("Error retrieving request locations.");
        } finally {
            HibernateSessionManager.close();
        }
    }

    /**
     * Creates the amendment.
     *
     * @param view
     *            the view
     * @return the object
     */
    @RequestMapping(value = "/createAmendment", method = RequestMethod.POST)
    @ResponseBody
    public Object createAmendment(@RequestBody @Valid final AmendmentView view) {

        LOG.debug("createAmendment");
        try {
            HibernateSessionManager.start();

            UserPreferences prefs = getUserPreferences();

            Request request = retrieveRequest(view.getRequestId());
            if (request == null) {
                return new ErrorView("Error: Failed to load request.");
            }

            Person person = Person.findById(prefs.getUserId());
            if (!request.hasCreatorOrParticipant(person)) { // participant or the request creator?
                return new ErrorView("Error: User does not have permission to perform this action.");
            }

            // does this request have a later amendment? if so return an error
            int headId = request.getHeadId();
            if (headId == 0) {
                headId = request.getId();
            }

            Request lastAmendment = findMostRecentAmendment(headId); // get the amendment by request type
            if (lastAmendment != null && lastAmendment.getId() != request.getId()) {
                return new ErrorView("Error: Cannot create amendment to request.  A more recent amendment already exists.");
            }
            // so at this point we guarantee that request is either the original request, or the most recent amendment.

            Request amendmentRequest = null;
            RequestIdTypeView result = new RequestIdTypeView();

            if (DartRequest.class.isAssignableFrom(request.getClass())) {
                amendmentRequest = ((DartRequest) request).createAmendment(prefs.getUserLoginId());
                amendmentRequest.setWorkflowTypeId(WorkflowResolver.WF_RESEARCH_REQUEST);
                // set the new workflow type (just in case this request chain includes an old NDS workflow)
                result.setType(Request.DATA_ACCESS);
            } else if (PreparatoryRequest.class.isAssignableFrom(request.getClass())) {
                amendmentRequest = ((PreparatoryRequest) request).createAmendment(prefs.getUserLoginId());
                amendmentRequest.setWorkflowTypeId(WorkflowResolver.WF_PREPARATORY_REQUEST);
                // set the new workflow type (just in case this request chain includes an old NDS workflow)
                result.setType(Request.PREPARATORY_TO_RESEARCH_ACCESS);
            } else if (OperationalRequest.class.isAssignableFrom(request.getClass())) {
                amendmentRequest = ((OperationalRequest) request).createAmendment(prefs.getUserLoginId());
                result.setType(Request.OPERATIONS_DATA_ACCESS);
            }

            if (amendmentRequest == null) {
                return new ErrorView("Error: Failed to create amendment to request.");
            }

            result.setUserId(prefs.getUserId());
            result.setRequestId(amendmentRequest.getId());

            // save the amendment narrative
            amendmentRequest.createAmendmentNarrative("Amendment to request " + request.getTrackingNumber(),
                    prefs.getUserLoginId(), view.getNarrative());

            // TODO: might want to generate this event inside of the Request
            // create an event
            EventType.initialize();
            Group.initialize();
            Event.create(EventType.AMEND_REQUEST, amendmentRequest, prefs.getUserLoginId());

            workflowResolver.resolve(amendmentRequest).initialize(null, amendmentRequest, prefs.getUserLoginId());
            // resolve the new amendment (use the top-level workflow)

            // send an email to the requestor and all participants with notifications turned on
            EmailUtils.createAndSendRequestInitiatedEmail(amendmentRequest, prefs.getUserLoginId());

            return result;
        } catch (Exception e) {
            LOG.error("Error amending request", e);
            HibernateSessionManager.rollback();
            return new ErrorView("Error amending request");
        } finally {
            HibernateSessionManager.close();
        }
    }

    /**
     * Submit request.
     *
     * @param view
     *            the view
     * @return the object
     */
    @RequestMapping(value = "/submitRequest", method = RequestMethod.POST)
    @ResponseBody
    public Object submitRequest(@RequestBody @Valid final RequestIdView view) {

        LOG.debug("submitRequest");
        try {
            HibernateSessionManager.start();
            UserPreferences prefs = getUserPreferences();

            Request request = retrieveRequest(view.getRequestId());
            if (request == null) {
                return new ErrorView("Error: Failed to load request.");
            }

            Person person = Person.findById(prefs.getUserId());
            if (!request.hasCreatorOrParticipant(person)) { // participant or the request creator?
                return new ErrorView("Error: User does not have permission to perform this action.");
            }

            workflowResolver.resolve(request).submit(null, request, prefs.getUserLoginId()); // use the top-level workflow
                        
            createParticipantsEvents(request);

            return new ErrorView("OK");
        } catch (Exception e) {
            LOG.error("Error submitting request", e);
            HibernateSessionManager.rollback();
            return new ErrorView("Error submitting request");
        } finally {
            HibernateSessionManager.close();
        }
    }

    private void createParticipantsEvents(Request currentRequest) throws ValidationException {
        
        if (currentRequest.isAmendment()){
            List<Request> prevReqList = Request.listAllPreviousRequests(currentRequest.getHeadId(), currentRequest.getCreatedOn());
            if (prevReqList != null && prevReqList.size() > 0){
                
                //we just keep track of the possible participants events for the last time the request is submitted
                // so we deleted any events created before
                Event.deleteEvents(EventType.PARTICIPANT_ADDED.getId(), currentRequest.getId());
                Event.deleteEvents(EventType.PARTICIPANT_REMOVED.getId(), currentRequest.getId());
                
                //create possible participant events
                Request parentRequest = prevReqList.get(0);                

                Participant[] currentParticipantsArray = currentRequest.getParticipants().
                                                        toArray(new Participant[currentRequest.getParticipants().size()]);
                Participant[] parentParticipantsArray = parentRequest.getParticipants().
                                                        toArray(new Participant[parentRequest.getParticipants().size()]);
                
                // Create added participants event                
                for(Participant currentParticipant : currentParticipantsArray){
                    boolean addParticipant = true;
                    for (Participant parentParticipant : parentParticipantsArray){                        
                        if (parentParticipant.isSamePersonAndLocation(currentParticipant)){
                            addParticipant = false;
                            break;
                        }                            
                    }
                    if (addParticipant)
                        createParticipantEvent(EventType.PARTICIPANT_ADDED, currentRequest, currentParticipant);
                }

                // Create removed participants event                
                for(Participant parentParticipant : parentParticipantsArray){
                    boolean removeParticipant = true;
                    for (Participant currentParticipant : currentParticipantsArray){                        
                        if (currentParticipant.isSamePersonAndLocation(parentParticipant)){
                            removeParticipant = false;
                            break;
                        }                            
                    }
                    if (removeParticipant)
                        createParticipantEvent(EventType.PARTICIPANT_REMOVED, currentRequest, parentParticipant);
                }                
      
            
            }
        }
        
    }
    

    private void createParticipantEvent(EventType eventType, Request request, Participant participant) throws ValidationException {
        String description = eventType.getDescription()
                + ": " 
                + participant.getPerson().getFullName()
                + " "
                + participant.getLocation().getName();
        
        UserPreferences prefs = getUserPreferences();
               
        Event.create(eventType.getName(), description, eventType, request, prefs.getUserLoginId());
        
    }

    /**
     * Close request.
     *
     * @param view
     *            the view
     * @return the object
     */
    @RequestMapping(value = "/closeRequest", method = RequestMethod.POST)
    @ResponseBody
    public Object closeRequest(@RequestBody @Valid final RequestIdView view) {

        LOG.debug("closeRequest");
        try {
            HibernateSessionManager.start();

            UserPreferences prefs = getUserPreferences();

            Person person = null;
            try {
                person = Person.findById(prefs.getUserId());
            } catch (ObjectNotFoundException e) {
                throw new ObjectNotFoundException("Cannot find person with id: " + prefs.getUserId());
            }
            Role.initialize();

            if (person == null || !person.hasRole(Role.SUPER_USER)) {
                return new ErrorView("Error: User does not have permission to perform this action.");
            }

            Request req = retrieveRequest(view.getRequestId());
            if (req == null) {
                return new ErrorView("Error: request does not exist.");
            }

            if (RequestStatus.CLOSED.getId() != req.getStatus().getId()) {
                req.close(prefs.getUserLoginId());
            }

            RequestWorkflow workflow = null;
            if (view.getWorkflowId() != 0) { // new request (after the Independent Workflow updates)
                workflow = RequestWorkflow.findById(view.getWorkflowId());
            }

            // find all open tasks that refer to this request and complete them.
            // TaskUtils.closeAllTasksForRequest( req, prefs.getUserLoginId() );
            TaskUtils.closeAllTasksForWorkflowAndRequest(workflow, req, prefs.getUserLoginId());

            // don't remove the intermediate review (leave it so that we know the status of the intermediate reviews)
            // // find all reviews that refer to this request and kill them
            // for (Review rev : Review.listByRequestId(req.getId())) {
            // //rev.delete();
            // }

            // TODO: separate the user-based "close" from the Data Source-based "close"
            workflowResolver.resolve(req).close(null, req, prefs.getUserLoginId());

        } catch (Exception e) {
            LOG.error("Error closing request.", e);
            HibernateSessionManager.rollback();
            return new ErrorView("Error closing request.");
        } finally {
            HibernateSessionManager.close();
        }

        return view;
    }

    /**
     * Changes requested by a review group.
     *
     * @param view
     *            the view
     * @return the object
     */
    @RequestMapping(value = "/changeRequest", method = RequestMethod.POST)
    @ResponseBody
    public Object changeRequest(@RequestBody @Valid final RequestChangeView view) {

        LOG.debug("changeRequest");
        try {
            HibernateSessionManager.start();

            UserPreferences prefs = getUserPreferences();

            Request request = retrieveRequest(view.getRequestId());
            if (request == null) {
                return new ErrorView("Error:  Failed to load request.");
            }

            RequestWorkflow workflow = null;
            if (view.getWorkflowId() != 0) {
                workflow = RequestWorkflow.findById(view.getWorkflowId()); // get the workflow associated with this request
            }

            if (request.isSubmittedOrChanged(workflow)) {

                Person approver = Person.findById(prefs.getUserId());

                Role.initialize();
                Group.initialize();

                // is this an NDS review or an intermediate review?
                String groupShortName = "";
                Review review = null;
                if (view.getReviewId() != 0) { // not an NDS review

                    review = Review.findById(view.getReviewId());
                    if (review == null) {
                        return new ErrorView("Error:  Failed to load review.");
                    }

                    groupShortName = review.getReviewer().getShortName(); // group requesting this change

                    // verify the role of this reviewer
                    if (!approver.hasRole(Role.REVIEWER)) {
                        return new ErrorView("User does not have permission to submit a change request.");
                    }

                    // verify the group membership of this reviewer
                    if (!approver.inGroup(review.getReviewer())) {
                        return new ErrorView("User does not belong to appropriate group to perform this review: "
                                + review.getReviewer().getShortName());
                    }
                } else {

                    groupShortName = Group.NDS.getShortName(); // group requesting this change (NDS)

                    // verify the role of this reviewer (NDS)
                    if (!approver.hasRole(Role.NDS_ADMIN)) {
                        return new ErrorView("User does not have permission to submit a change request.");
                    }

                    // verify the group of this reviewer (NDS)
                    if (!approver.inGroup(Group.NDS)) {
                        return new ErrorView("User does not belong to appropriate group to perform this review: "
                                + Group.NDS.getShortName());
                    }
                }

                // update the review if it exists (NOT NDS)
                // get the workflow if it exists (new data)
                // RequestWorkflow workflow = null;
                if (review != null) {

                    review.requestChanges(prefs.getUserLoginId()); // update the review status (non-NDS review group)

                }

                //
                // update the request status (and the workflow status if it exists)
                if (workflow != null) {
                    workflow.requestChange(prefs.getUserLoginId()); // update the workflow status, also updated in the workflow
                                                                    // engine (this may be unnecessary)
                }
                request.requestChange(prefs.getUserLoginId()); // update the top-level request status
                // resolve top-level
                workflowResolver.resolve(request).changeRequest(workflow, review, request, prefs.getUserLoginId());
                // save a communication
                Comment.create(view.getInputTopic(), request, prefs.getUserLoginId(), view.getInputMessage());
                if (review != null) {
                    EmailUtils.createAndSendChangeRequestedEmails(request, review.getReviewer(), new Date(),
                            view.getInputTopic(), view.getInputMessage());
                } else {
                    EmailUtils.createAndSendChangeRequestedEmails(request, Group.NDS, new Date(), view.getInputTopic(),
                            view.getInputMessage());
                }
                // create a task for each participant and the original requestor
                Set<Person> personSet = new HashSet<Person>();

                Person requestor = Person.findByName(request.getCreatedBy()); // original creator of this request
                personSet.add(requestor);

                for (Participant participant : request.getParticipants()) { // add all of the request participants
                    personSet.add(participant.getPerson());
                }

                // create a task for each participant and the original requestor
                for (Person person : personSet) {
                    // PersonTask.create(req, person, "Change to request is required by " + groupShortName,
                    // "Change to request is required by " + groupShortName, prefs.getUserLoginId());
                    PersonTask.create(workflow, request, person, (PersonTask.CHANGE_REQUEST_TASK_STR + groupShortName),
                            (PersonTask.CHANGE_REQUEST_TASK_STR + groupShortName), prefs.getUserLoginId());
                }
            } else {
                return new ErrorView("Error: Request is not in the correct state to request a change.");
            }
        } catch (Exception e) {
            LOG.error("Error marking request for required change.", e);

            HibernateSessionManager.rollback();
            return new ErrorView("Error marking request for required change.");
        } finally {
            HibernateSessionManager.close();
        }

        return new ErrorView("OK");
    }

    /**
     * NDS Adding or Removing Intermediate Review Groups.
     *
     * @param view
     *            the modifyReviewGroupsView
     * @return the object
     */
    @RequestMapping(value = "/modifyReviewGroups", method = RequestMethod.POST)
    @ResponseBody
    public Object modifyReviewGroups(@RequestBody @Valid final ModifyReviewGroupsView view) {

        LOG.debug("modifyReviewGroups");

        try {
            HibernateSessionManager.start();

            UserPreferences prefs = getUserPreferences();

            if (view != null) {

                Request request = retrieveRequest(view.getRequestId());

                if (request != null) {

                    // Communication
                    Comment.create("Additional Reviews Have Changed!", request, prefs.getUserLoginId(), view.getInputMessage());
                    // History
                    Event.create(EventType.CHANGE_REVIEWS, false, request, prefs);

                    RequestWorkflow workflow = null;
                    if (view.getWorkflowId() != 0) { // new request (after the Independent Workflow updates)
                        workflow = RequestWorkflow.findById(view.getWorkflowId());
                    }

                    withdrawReviews(view, workflow, request, prefs);

                    addReviews(view, request, prefs);

                    // Emails
                    EmailUtils.createAndSendModifyReviewGroupsEmails(request, findAffectedModifyReviewGroups(view), new Date(),
                            view.getInputTopic(), view.getInputMessage());
                    request.setProcessWithdraw(true);

                    workflowResolver.resolve(workflow).submit(workflow, request, prefs.getUserLoginId());

                } else {
                    return new ErrorView("Failed to retrieve request");
                }
            }

            return new ErrorView("OK");
        } catch (Exception e) {
            LOG.error("Error denying review", e);
            HibernateSessionManager.rollback();
            return new ErrorView("Error modifing review groups");
        } finally {
            HibernateSessionManager.close();
        }
    }

    /**
     * Adds the reviews.
     *
     * @param view
     *            the view
     * @param request
     *            the request
     * @param prefs
     *            the prefs
     * @throws ObjectNotFoundException
     *             the object not found exception
     * @throws ValidationException
     *             the validation exception
     */
    private void addReviews(final ModifyReviewGroupsView view, Request request, UserPreferences prefs)
            throws ObjectNotFoundException, ValidationException {

        if (!CollectionUtils.isEmpty(view.getReviewsToBeAdded())) {
            RequestWorkflow workflow = null;
            if (view.getWorkflowId() != 0) {
                workflow = RequestWorkflow.findById(view.getWorkflowId());
            }
            for (Integer reviewTemplateId : view.getReviewsToBeAdded()) {

                ReviewTemplate reviewTemplate = findReviewTemplateByReviewTemplateId(reviewTemplateId);
                List<Review> reviews =
                        Review.listByRequestIdAndGroupId(view.getRequestId(), reviewTemplate.getReviewer().getId());
                if (CollectionUtils.isEmpty(reviews)) {
                    processAddReview(request, prefs, workflow, reviewTemplate);

                } else {
                    if (reviews != null && reviews.size() > 0) {
                        // there might be a previous withdraw?
                        boolean allWithdrawn = true;
                        for (Review review : reviews) {
                            if (!review.isWithdrawn()) {
                                allWithdrawn = false;
                            }
                        }

                        if (allWithdrawn) {
                            processAddReview(request, prefs, workflow, reviewTemplate);

                        } else {
                            LOG.error("Found Review for Group that was not prevously withdrawn");
                        }

                    }
                }
            }
        }
    }

    /**
     * Process add review.
     *
     * @param request
     *            the request
     * @param prefs
     *            the prefs
     * @param workflow
     *            the workflow
     * @param reviewTemplate
     *            the review template
     * @throws ValidationException
     *             the validation exception
     */
    private void processAddReview(Request request, UserPreferences prefs, RequestWorkflow workflow,
            ReviewTemplate reviewTemplate) throws ValidationException {
        Review newReview =
                Review.create(workflow, request, reviewTemplate, reviewTemplate.getReviewer(), prefs.getUserLoginId());
        Event.create(EventType.SENT_FOR_REVIEW, reviewTemplate.getReviewer(), false, request, newReview, prefs);

        final String groupShortName = reviewTemplate.getReviewer().getShortName();

        GroupTask task = GroupTask.create(workflow, newReview.getRequest(), reviewTemplate.getReviewer(),
                (GroupTask.REVIEW_TASK_STR + groupShortName + " review."),
                (GroupTask.REVIEW_TASK_STR + groupShortName + " review."), prefs.getUserLoginId());

        task.setReview(newReview);
    }

    /**
     * With draw reviews.
     *
     * @param view
     *            the view
     * @param workflow
     *            the workflow
     * @param request
     *            the request
     * @param prefs
     *            the prefs
     */
    private void withdrawReviews(ModifyReviewGroupsView view, RequestWorkflow workflow, Request request,
            UserPreferences prefs) {
        if (view.getReviewsToBeRemoved() != null) {
            for (Integer reviewTemplateId : view.getReviewsToBeRemoved()) {

                ReviewTemplate reviewTemplate = findReviewTemplateByReviewTemplateId(reviewTemplateId);
                List<Review> reviews =
                        Review.listByRequestIdAndGroupId(view.getRequestId(), reviewTemplate.getReviewer().getId());
                if (!CollectionUtils.isEmpty(reviews)) {

                    for (Review review : reviews) {
                        if (!review.isWithdrawn()) {
                            review.withdrawReview(prefs.getUserLoginId());
                            try {
                                TaskUtils.closeTasksForWorkflowAndGroupByNDS(workflow, reviewTemplate.getReviewer(), request,
                                        prefs.getUserLoginId());
                                Event.create(EventType.WITHDRAW_REVIEW, reviewTemplate.getReviewer(), false, request, review, prefs);
                            } catch (ValidationException e) {
                                LOG.error("Failed to Close Tasks");

                            }

                        }
                    }
                }
            }
        }
    }

    /**
     * Find review template by review template id.
     *
     * @param reviewTemplateId
     *            the review template id
     * @return the review template
     */
    private ReviewTemplate findReviewTemplateByReviewTemplateId(Integer reviewTemplateId) {
        ReviewTemplate reviewTemplate = null;
        try {
            reviewTemplate = ReviewTemplate.findById(reviewTemplateId);
        } catch (ObjectNotFoundException e) {
            LOG.debug("Failed to retrieve ReviewTemplate from database");
        }
        return reviewTemplate;
    }

    /**
     * Find the Group object for all of the groups effected by Additional Reviews changed process.
     *
     * @param view
     *            the view
     * @return the list
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    private List<Group> findAffectedModifyReviewGroups(ModifyReviewGroupsView view) {
        Group.initialize();

        List<Integer> combinedList = new ArrayList();
        if (!CollectionUtils.isEmpty(view.getReviewsToBeRemoved())) {
            combinedList.addAll(view.getReviewsToBeRemoved());
        }
        if (!CollectionUtils.isEmpty(view.getReviewsToBeAdded())) {
            combinedList.addAll(view.getReviewsToBeAdded());
        }

        List<Group> groupList = new ArrayList();

        for (Integer reviewTemplateId : combinedList) {
            ReviewTemplate reviewTemplate = findReviewTemplateByReviewTemplateId(reviewTemplateId);
            groupList.add(reviewTemplate.getReviewer());
        }

        return groupList;
    }

    /**
     * Validate the request (requestor).
     *
     * @param view
     *            the view
     * @return the object
     */
    @RequestMapping(value = "/validateRequest", method = RequestMethod.POST)
    @ResponseBody
    public Object validateRequest(@RequestBody @Valid final RequestIdView view) {
        LOG.debug("validateRequest");

        try {
            HibernateSessionManager.start();

            UserPreferences prefs = getUserPreferences();

            List<String> msgs = new ArrayList<String>();
            ErrorListView result = new ErrorListView("Error");

            Request req = null;

            try {
                req = retrieveRequest(view.getRequestId());

            } catch (ObjectNotFoundException e) {
                LOG.error("Error loading request " + view.getRequestId(), e);
                return new ErrorView("Error loading request " + view.getRequestId());
            }

            if (req != null) {

                Role.initialize();

                String requestStatusName = req.getStatus().getName(); // display the state of the request (top-level workflow)
                if (RequestStatus.INITIATED.getId() == req.getStatus().getId())
                    requestStatusName = "Not Submitted";

                Person person = null;
                try {
                    person = Person.findById(prefs.getUserId());
                } catch (ObjectNotFoundException e) {
                    throw new ObjectNotFoundException("Cannot find person with id: " + prefs.getUserId());
                }

                // verify the access permissions
                if (!person.hasRole(Role.SUPER_USER) && !person.hasRole(Role.NDS_ADMIN) && !person.hasRole(Role.REVIEWER)
                        && !person.hasRole(Role.READ_ONLY_STAFF)) {

                    if (!req.hasCreatorOrParticipant(person)) { // participant of this request?
                        msgs.add("Error: User does not have permission to perform this action.");
                    }

                } else {

                    if (person.hasRole(Role.NDS_ADMIN) || person.hasRole(Role.REVIEWER)
                            || person.hasRole(Role.READ_ONLY_STAFF)) {
                        msgs.add("Error: User does not have permission to perform this action.");
                    }
                }

                if (RequestStatus.INITIATED.getId() == req.getStatus().getId())
                    result.setInitiated(true);
                if (RequestStatus.SUBMITTED.getId() == req.getStatus().getId())
                    result.setSubmitted(true);

                result.getStatus().add(requestStatusName); // display the state of the request (for super-user)

                // check the top-level state of the request -- can it be submitted?
                if (RequestStatus.INITIATED.getId() != req.getStatus().getId()
                        && RequestStatus.CHANGE_REQUESTED.getId() != req.getStatus().getId()) {

                    if (!person.hasRole(Role.SUPER_USER)) {

                        msgs.add("Error:  Request is in the wrong state to be submitted (" + requestStatusName
                                + ").  Cannot submit this request.");

                    }
                }

                // if this request is not in a final state, validate the contents of the request
                // If the request has already been approved (or is in another final state), don't bother to validate the content
                // of the request
                if (!req.isCompletedStatus()) { // request is not yet in a "final" state

                    // validate the specifics of each request type
                    if (DartRequest.class.isAssignableFrom(req.getClass())) {
                        msgs.addAll(validateDartRequest((DartRequest) req));
                    } else if (PreparatoryRequest.class.isAssignableFrom(req.getClass())) {
                        msgs.addAll(validatePreparatoryRequest((PreparatoryRequest) req));
                    } else if (OperationalRequest.class.isAssignableFrom(req.getClass())) {
                        msgs.addAll(validateOperationalRequest((OperationalRequest) req));
                    }

                }

                // if any message msgs has a value of "", it is breaking the validation GUI - this should be trimmed out...
                List<String> msgsFinal = new ArrayList<String>();
                for (String msg : msgs) {
                    if (msg != null && !msg.isEmpty()) {
                        msgsFinal.add(msg);
                    }
                }

                // all these error messages should be collected into a single list to return
                result.getErrors().addAll(msgsFinal);

                if (result.getErrors().size() < 1) {
                    result.setMsg("OK");
                }

            }

            return result;
        } catch (Exception e) {
            LOG.error("General error validating request.", e);
            HibernateSessionManager.rollback();
            return new ErrorListView("General error validating request.");
        } finally {
            HibernateSessionManager.close();
        }
    }

    /**
     * Validate dart request.
     *
     * @param req
     *            the req
     * @return the list
     */
    private static List<String> validateDartRequest(final DartRequest req) {
        List<String> msgs = new ArrayList<String>();

        if (req != null) {

            validateDartRequestActivityPageInfo(req, msgs);

            validateParticipantPageInfo(req, msgs);

            // must select at least one Data Storage Location: VINCI or Local VA Server Location
            if (!req.isDataMart() && !req.isLocalServer()) {
                msgs.add("Error: request must specify at least one Data Storage Location (VINCI or Local VA Server Location).");
            }

            // must have a data source (when not in debug mode)
            if (!DartController.isDebugMode()) {
                Set<DataSource> dataSourceSet = req.getDataSources();
                if (dataSourceSet == null || dataSourceSet.isEmpty()) {
                    msgs.add("Error: request must have at least one data source.");

                } else {

                    // are there data sources other than SAS grid selected? (care for HIPAA options, below)
                    boolean bOtherThanSASGrid = false;
                    for (DataSource currDataSource : dataSourceSet) {
                        if (currDataSource.getId() != SAS_GRID_ID) // SAS Grid
                            bOtherThanSASGrid = true;
                    }
                    if (!bOtherThanSASGrid) {
                        msgs.add("Error: request must have at least one data source.");
                        // require at least one data source (ignoring SAS Grid)
                    }

                    // require either the HIPAA consent or the HIPAA waiver to be selected (don't care for SAS Grid)
                    if (bOtherThanSASGrid) { // don't care if only SAS Grid data source
                        if (!req.isHipaaConsent() && !req.isHipaaWaiver()) {
                            msgs.add("Error: request must specify either the Informed Consent "
                                    + "and HIPAA Authorization, or the HIPAA Waiver.");
                        }
                    }

                    // if the datasource is to be transferred externally, all sub-fields are required
                    if (req.isTransferredExternal()) {
                        msgs.addAll(new FieldValidationBuilder()
                                .checkStringField(req.getExternalDestination(),
                                        "the name of the External Company/Agency is required.")
                                .checkStringField(req.getExternalStreet(),
                                        "the street address for the External Company/Agency is required.")
                                .checkStringField(req.getExternalCity(),
                                        "the city for the External Company/Agency is required.")
                                .checkStringField(req.getExternalState(),
                                        "the state for the External Company/Agency is required.")
                                .checkStringField(req.getExternalZipCode(),
                                        "the zipcode for the External Company/Agency is required.")
                                .getMessages());
                    }

                    // TODO: support amendments (cannot remove a previously approved data source either)
                    // already approved data sources cannot be removed from the request
                    try {
                        if (req.isEditable(null)) { // only care if the request is editable

                            Set<DataSource> approvedDataSourceSet = req.getSelectedAndApprovedDataSources();
                            if (approvedDataSourceSet != null && approvedDataSourceSet.size() > 0) {
                                for (DataSource currApprovedDataSource : approvedDataSourceSet) {
                                    if (currApprovedDataSource != null && !dataSourceSet.contains(currApprovedDataSource)) {
                                        // this approved data // source cannot be removed from the request
                                        msgs.add("Error: previously approved Data Sources cannot be removed from the request ("
                                                + currApprovedDataSource.getName() + ")");
                                    }
                                }
                            }
                        }
                    } catch (ObjectNotFoundException e) {
                        LOG.error("Error retrieving the request status: " + e.getMessage());

                    }

                }
            }

            // validate that data sources and associated identifiers line up
            msgs.addAll(validateDartRequestDataSources(req));

            // validate that all required documents have been uploaded.
            msgs.addAll(validateDocumentsUploaded(req));
        }

        return msgs;
    }

    /**
     * Validate dart request activity page info.
     *
     * @param req
     *            the req
     * @param msgs
     *            the msgs
     */
    private static void validateDartRequestActivityPageInfo(final DartRequest req, List<String> msgs) {
        // all these error messages should be collected into a single list to return
        if (isEmpty(req.getName())) {
            msgs.add("Error: request Short Name is required.");
        }

        if (isEmpty(req.getActivity().getOfficialName())) {
            msgs.add("Error: request Official Name is required.");
        }

        if (isEmpty(req.getIrbNumber())) {
            msgs.add("Error: request IRB Number is required.");
        }

        if (req.getIrbExpiration() == null) {
            msgs.add("Error: request IRB Expiration Date is required.");
        } else {
            if (req.getIrbExpiration().before(new Date())) { // has the IRB# expired?
                msgs.add("Error: request IRB Expiration Date is in the past.");
            }
        }

        if (req.getActivity().getStartDate() == null) {
            msgs.add("Error: request Start Date is required.");
        }

        if (req.getActivity().getEndDate() == null) {
            msgs.add("Error: request End Date is required.");
        }
    }

    /**
     * Validate preparatory request.
     *
     * @param request
     *            the request
     * @return the list
     */
    private static List<String> validatePreparatoryRequest(final PreparatoryRequest request) {
        List<String> msgs = new ArrayList<String>();

        if (request != null) {

            if (isEmpty(request.getActivity().getOfficialName())) {
                msgs.add("Error: request Official Name is required.");
            }

            validatePreparatoryActivityPageInfo(request, msgs);

            validateParticipantPageInfo(request, msgs);

            // must have a data source
            validatePreparatoryDataSources(request, msgs);

            // validate that data sources and associated identifiers line up
            validateIndividualPreparatoryRequestDataSources(request, msgs);

            // validate that all required documents have been uploaded.
            msgs.addAll(validateDocumentsUploaded(request));
        }

        return msgs;
    }

    /**
     * Validate participant page info.
     *
     * @param request
     *            the request
     * @param msgs
     *            the msgs
     */
    private static void validateParticipantPageInfo(final Request request, List<String> msgs) {
        if (request.getSites() == null || request.getSites().size() < 1) {
            msgs.add("Error: request must have at least one site.");
        }

        if (request.getPrimaryLocation() == null) {
            msgs.add("Error: request must have a primary site.");
        }

        // each site must have a primary investigator
        Set<Participant> participantSet = request.getParticipants();
        for (Location site : request.getSites()) {
            boolean foundSite = false;
            boolean foundSitePI = false;
            for (Participant participant : participantSet) {
                if (participant.getLocation().getId() == site.getId()) {
                    foundSite = true;

                    if (participant.getPrincipalInvestigator()) {
                        foundSitePI = true;
                    }
                }
            }

            if (!foundSitePI) {
                msgs.add("Error: Principal Investigator not found for site: " + site.getName());
            }

            if (!foundSite) {
                msgs.add("Error: No participants found for site: " + site.getName());
            }
        }

        // validate that each participant is only in the list once
        for (Participant part1 : participantSet) {
            for (Participant part2 : participantSet) {

                if ((part1.getPerson().getName() != null && part2.getPerson().getName() != null)
                        && part1.getPerson().getName().equalsIgnoreCase(part2.getPerson().getName())) {

                    if ((part1.getLocation().getName() != null && part2.getLocation().getName() != null)
                            && !part1.getLocation().getName().equalsIgnoreCase(part2.getLocation().getName())) {
                        msgs.add("Error: Duplicate participant found: " + part1.getPerson().getFullName());
                    }
                }
            }
        }
    }

    /**
     * Validate preparatory activity page info.
     *
     * @param request
     *            the request
     * @param msgs
     *            the msgs
     */
    private static void validatePreparatoryActivityPageInfo(final PreparatoryRequest request, List<String> msgs) {
        if (isEmpty(request.getName())) {
            msgs.add("Error: request Proposed Protocol Name is required.");
        }

        if (request.getExpectedIRBSubmissionDate() == null) {
            msgs.add("Error: request Expected IRB Submission Date is required.");
        } else {
            if (request.getExpectedIRBSubmissionDate().before(new Date())) {
                msgs.add("Error: request Expected IRB Submission Date is in the past.");
            }
        }
    }

    /**
     * Validate individual preparatory request data sources.
     *
     * @param req
     *            the req
     * @param msgs
     *            the msgs
     * @return the list
     */
    private static List<String> validateIndividualPreparatoryRequestDataSources(final PreparatoryRequest req,
            List<String> msgs) {

        if (req != null) {

            final String scrambledSSNStr = " requires the Scrambled SSN Identifier to be selected";
            boolean hasVitalStatusFileRealSSNCrosswalkFile = false;
            boolean hasVitalStatusFileScrambledSSN = false;

            Set<DataSource> dataSources = req.getDataSources();
            if (dataSources != null) {

                for (DataSource currDataSource : dataSources) {

                    // If the specified data source is checked, verify the value of the corresponding data source identifiers
                    switch (currDataSource.getId()) {
                    case VITAL_STATUS_FILES_WITH_SCRAMBLED_SSN_110NN06_ID:
                        hasVitalStatusFileScrambledSSN = true; // remember this data source
                        break;
                    case TIU_TEXT_NOTES_ID:
                        msgs.add(requireRealSSNIdentifier(req.isRealSSN(), currDataSource.getName()));
                        break;
                    case BIRLS_REAL_110JJ02_ID:
                        msgs.add(requireRealSSNIdentifier(req.isRealSSN(), currDataSource.getName()));
                        break;
                    case MEDSAS_INCLUDING_VETSNET_FILES_FOR_NATIONAL_LEVEL_REAL_SSN_1100TT01_ID:
                        msgs.add(requireRealSSNIdentifier(req.isRealSSN(), currDataSource.getName()));
                        break;
                    case MEDSAS_FILES_FOR_VISN_LEVEL_REAL_SSN_1100TT05_ID:
                        msgs.add(requireRealSSNIdentifier(req.isRealSSN(), currDataSource.getName()));
                        break;
                    case CAPRI_VISTAWEB_ID:
                        msgs.add(requireRealSSNIdentifier(req.isRealSSN(), currDataSource.getName()));
                        break;
                    case MCA_FORMERLY_DSS_WEB_REPORTS_ID:
                        msgs.add(requireRealSSNIdentifier(req.isRealSSN(), currDataSource.getName()));
                        break;
                    case VSSC_WEB_REPORTS_ID:
                        msgs.add(requireRealSSNIdentifier(req.isRealSSN(), currDataSource.getName()));
                        break;
                    case VITAL_STATUS_FILE_REAL_SSN_CROSSWALK_FILE_110TT20_ID:
                        hasVitalStatusFileRealSSNCrosswalkFile = true;
                        msgs.add(requireRealSSNIdentifier(req.isRealSSN(), currDataSource.getName()));
                        break;
                    default:
                        break;

                    }
                }

                for (DataSource currDataSource : dataSources) {

                    switch (currDataSource.getId()) {
                    case CARE_ASSESSMENT_NEED_CAN_SCORE_REQUIRES_SCRAMBLED_SSN_LEVEL_ACCESS_ID:

                        // either real or scrambled SSN -> one or the other must be set
                        if (!req.isRealSSN() && !req.isScrambledSSN()) {
                            msgs.add(DATA_SOURCE_ERROR + currDataSource.getName()
                                    + " requires that either the Real SSN Identifier "
                                    + "or the Scrambled SSN Identifier be selected");
                        }
                        break;
                    case CDW_PRODUCTION_DOMAINS_ID:
                        msgs.add(requireAnyIdentifier(req, currDataSource.getName()));
                        break;
                    case CDW_RAW_DOMAINS_ID:
                        msgs.add(requireAnyIdentifier(req, currDataSource.getName()));
                        break;
                    case CDW_MCA_FORMERLY_DSS_NDE_ID:
                        msgs.add(requireAnyIdentifier(req, currDataSource.getName()));
                        break;
                    case VITAL_STATUS_ID:
                        msgs.add(requireAnyIdentifier(req, currDataSource.getName()));
                        break;
                    case MEDSAS_FILES_INCLUDING_VETSNET_FILES_ID:
                        msgs.add(requireAnyIdentifier(req, currDataSource.getName()));
                        break;
                    default:
                        break;

                    }
                }

                if (hasVitalStatusFileScrambledSSN) {
                    if (!hasVitalStatusFileRealSSNCrosswalkFile) {
                        if (!req.isScrambledSSN()) {
                            msgs.add(DATA_SOURCE_ERROR + "Vital Status Files with Scrambled SSN (110NN06)" + scrambledSSNStr);
                        }
                    }
                }
            }
        }

        return msgs;
    }

    /**
     * Require real ssn identifier.
     *
     * @param isRealSSN
     *            the request.isRealSSN()
     * @param dataSourceName
     *            the data source name
     * @return the string message - empty string when no required message
     */
    private static String requireRealSSNIdentifier(final boolean isRealSSN, final String dataSourceName) {

        final String realSSNStr = " requires the Real SSN Identifier to be selected";

        if (!isRealSSN) {
            return (DATA_SOURCE_ERROR + dataSourceName + realSSNStr);
        }

        return "";
    }

    /**
     * Require any identifier.
     *
     * @param req
     *            the req
     * @param dataSourceName
     *            the data source name
     * @return the string
     */
    private static String requireAnyIdentifier(final DartRequest req, final String dataSourceName) {

        final String idStr = " requires that at least one Identifier be selected";

        if (req != null) {
            // require an identifier, but we don't care which identifier has been selected (Can be any identifier)
            if (!req.isRealSSN() && !req.isScrambledSSN() && !req.isPhiData()) {
                return (DATA_SOURCE_ERROR + dataSourceName + idStr);
            }

        }

        return "";
    }

    /**
     * Require any identifier. Overloaded method that takes a Preparatory method.
     * 
     * @param request
     *            the request
     * @param dataSourceName
     *            the data source name
     * @return the string message - empty string if no required message created.
     */
    private static String requireAnyIdentifier(final PreparatoryRequest request, final String dataSourceName) {

        final String idStr = " requires that at least one Identifier be selected";

        if (request != null) {

            if (!request.isRealSSN() && !request.isScrambledSSN() && !request.isPhiData()) {
                return (DATA_SOURCE_ERROR + dataSourceName + idStr);
            }
        }

        return "";
    }

    /**
     * Validate dart request data sources.
     *
     * @param req
     *            the req
     * @return the list
     */
    private static List<String> validateDartRequestDataSources(final DartRequest req) {

        List<String> msgs = new ArrayList<String>();

        if (req != null) {

            final String scrambledSSNStr = " requires the Scrambled SSN Identifier to be selected";

            // Scrambled SSN ( if Vital Status File Real SSN Crosswalk File (110TT20) has been checked, scrambled SSN does not
            // need to be checked )
            boolean hasVitalStatusFileRealSSNCrosswalkFile = false;
            boolean hasVitalStatusFileScrambledSSN = false;

            Set<DataSource> dataSources = req.getDataSources();
            if (dataSources != null) {

                for (DataSource currDataSource : dataSources) {

                    // If the specified data source is checked, verify the value of the corresponding data source identifiers
                    switch (currDataSource.getId()) {

                    case VITAL_STATUS_FILE_WITH_SCRAMBLED_SSN_ID:
                        if (!req.isScrambledSSN()) {
                            msgs.add(DATA_SOURCE_ERROR + currDataSource.getName() + scrambledSSNStr);
                        }
                        break;

                    case VITAL_STATUS_FILES_WITH_SCRAMBLED_SSN_110NN06_ID:
                        hasVitalStatusFileScrambledSSN = true;
                        break;

                    case TIU_TEXT_NOTES_ID:
                        msgs.add(requireRealSSNIdentifier(req.isRealSSN(), currDataSource.getName()));
                        break;

                    case VITAL_STATUS_FILE_REAL_SSN_CROSSWALK_FILE_ID:
                        msgs.add(requireRealSSNIdentifier(req.isRealSSN(), currDataSource.getName()));
                        break;

                    case BIRLS_REAL_110JJ02_ID:
                        msgs.add(requireRealSSNIdentifier(req.isRealSSN(), currDataSource.getName()));
                        break;

                    case MEDSAS_INCLUDING_VETSNET_FILES_FOR_NATIONAL_LEVEL_REAL_SSN_1100TT01_ID:
                        msgs.add(requireRealSSNIdentifier(req.isRealSSN(), currDataSource.getName()));
                        break;

                    case MEDSAS_FILES_FOR_VISN_LEVEL_REAL_SSN_1100TT05_ID:
                        msgs.add(requireRealSSNIdentifier(req.isRealSSN(), currDataSource.getName()));
                        break;

                    case CAPRI_VISTAWEB_ID:
                        msgs.add(requireRealSSNIdentifier(req.isRealSSN(), currDataSource.getName()));
                        break;

                    case MCA_FORMERLY_DSS_WEB_REPORTS_ID:
                        msgs.add(requireRealSSNIdentifier(req.isRealSSN(), currDataSource.getName()));
                        break;

                    case VSSC_WEB_REPORTS_ID:
                        msgs.add(requireRealSSNIdentifier(req.isRealSSN(), currDataSource.getName()));
                        break;

                    case VITAL_STATUS_FILE_REAL_SSN_CROSSWALK_FILE_110TT20_ID:
                        hasVitalStatusFileRealSSNCrosswalkFile = true;
                        msgs.add(requireRealSSNIdentifier(req.isRealSSN(), currDataSource.getName()));

                        break;

                    default:
                        break;

                    // 1035: SAS Grid -> ignore it (No identifier required)

                    // 1034: Surgery Quality Data Users Group (SQDUG) -> N/A

                    }
                }

                // step through the DataSources again, and determine if the "or", "any" identifiers have been satisfied
                for (DataSource currDataSource : dataSources) {

                    switch (currDataSource.getId()) {

                    case OEF_OIF_ROSTER_FILE_ID:
                        if (!req.isRealSSN() && !req.isScrambledSSN()) {
                            msgs.add(DATA_SOURCE_ERROR + currDataSource.getName()
                                    + " requires that either the Real SSN Identifier or "
                                    + "the Scrambled SSN Identifier be selected");
                        }

                        break;

                    case HOMELESS_REGISTRY_ID:
                        // either real or scrambled SSN -> one or the other must be set
                        if (!req.isRealSSN() && !req.isScrambledSSN()) {
                            msgs.add(DATA_SOURCE_ERROR + currDataSource.getName()
                                    + " requires that either the Real SSN Identifier or "
                                    + "the Scrambled SSN Identifier be selected");
                        }

                        break;

                    // Real or Scrambled SSN (Requires Scrambled SSN Level Access)
                    case CARE_ASSESSMENT_NEED_CAN_SCORE_REQUIRES_SCRAMBLED_SSN_LEVEL_ACCESS_ID:

                        // either real or scrambled SSN -> one or the other must be set
                        if (!req.isRealSSN() && !req.isScrambledSSN()) {
                            msgs.add(DATA_SOURCE_ERROR + currDataSource.getName()
                                    + " requires that either the Real SSN Identifier or "
                                    + "the Scrambled SSN Identifier be selected");
                        }

                        break;

                    case CDW_PRODUCTION_DOMAINS_ID:
                        msgs.add(requireAnyIdentifier(req, currDataSource.getName()));
                        break;

                    case CDW_RAW_DOMAINS_ID:
                        msgs.add(requireAnyIdentifier(req, currDataSource.getName()));
                        break;

                    case CDW_MCA_FORMERLY_DSS_NDE_ID:
                        msgs.add(requireAnyIdentifier(req, currDataSource.getName()));
                        break;

                    case VITAL_STATUS_ID:
                        msgs.add(requireAnyIdentifier(req, currDataSource.getName()));
                        break;

                    case BIRLS_ID:
                        msgs.add(requireAnyIdentifier(req, currDataSource.getName()));
                        break;

                    case MCA_FORMERLY_DSS_NDE_LEGACY_ID:
                        msgs.add(requireAnyIdentifier(req, currDataSource.getName()));
                        break;

                    case MEDSAS_FILES_INCLUDING_VETSNET_FILES_ID:
                        msgs.add(requireAnyIdentifier(req, currDataSource.getName()));
                        break;

                    case LEGACY_DATA_WAREHOUSES_I_E_VISN_21:
                        msgs.add(requireAnyIdentifier(req, currDataSource.getName()));
                        break;

                    default:
                        break;

                    }
                }

                // Vital Status Files with Scrambled SSN (110NN06) -> Scrambled SSN ( if Vital Status File Real SSN Crosswalk
                // File (110TT20) has been checked, scrambled SSN does not need to be checked )
                // datasource.id 12 -> scrambled SSN (unless 11 is checked, then real SSN)
                if (hasVitalStatusFileScrambledSSN) {

                    // Scrambled SSN ( if Vital Status File Real SSN Crosswalk File (110TT20) has been checked, scrambled SSN
                    // does not need to be checked )
                    if (!hasVitalStatusFileRealSSNCrosswalkFile) {

                        if (!req.isScrambledSSN()) {
                            msgs.add(DATA_SOURCE_ERROR + "Vital Status Files with Scrambled SSN (110NN06)" + scrambledSSNStr);
                        }
                    }
                }
            }
        }

        return msgs;
    }

    /**
     * Validate preparatory data sources.
     *
     * @param request
     *            the request
     * @param msgs
     *            the msgs
     */
    private static void validatePreparatoryDataSources(final PreparatoryRequest request, List<String> msgs) {

        Set<DataSource> dataSourceSet = request.getDataSources();
        if (dataSourceSet == null || dataSourceSet.isEmpty()) {
            msgs.add("Error: request must have at least one data source.");

        } else {

            // are there data sources other than SAS grid selected? (care for HIPAA options, below)
            boolean bOtherThanSASGrid = false;
            for (DataSource currDataSource : dataSourceSet) {
                if (currDataSource.getId() != SAS_GRID_ID) // SAS Grid
                    bOtherThanSASGrid = true;
            }
            if (!bOtherThanSASGrid) {
                msgs.add("Error: request must have at least one data source.");
            }

            try {
                if (request.isEditable(null)) {

                    Set<DataSource> approvedDataSourceSet = request.getSelectedAndApprovedDataSources();
                    if (approvedDataSourceSet != null && approvedDataSourceSet.size() > 0) {
                        for (DataSource currApprovedDataSource : approvedDataSourceSet) {
                            if (currApprovedDataSource != null && !dataSourceSet.contains(currApprovedDataSource)) {
                                msgs.add("Error: previously approved Data Sources cannot be removed from the request ("
                                        + currApprovedDataSource.getName() + ")");
                            }
                        }
                    }
                }
            } catch (ObjectNotFoundException e) {
                LOG.error("Error retrieving the request status: " + e.getMessage());

            }

        }
    }

    /**
     * Validate operational request.
     *
     * @param req
     *            the req
     * @return the list
     */
    private static List<String> validateOperationalRequest(final OperationalRequest req) {

        List<String> msgs = new ArrayList<String>();

        if (req != null) {

            // all these error messages should be collected into a single list to return
            if (isEmpty(req.getName())) {
                msgs.add("Error: request Short Name is required.");
            }

            if (isEmpty(req.getActivity().getOfficialName())) {
                msgs.add("Error: request Official Name is required.");
            }

            if (isEmpty(req.getJustification())) {
                msgs.add("Error: request Justification is required.");
            }

            if (isEmpty(req.getProgramOffice())) {
                msgs.add("Error: request Program Office is required.");
            }

            if (req.getActivity().getStartDate() == null) {
                msgs.add("Error: request Start Date is required.");
            }

            if (req.getActivity().getEndDate() == null) {
                msgs.add("Error: request End Date is required.");
            }

            // check off of the 'class' of request for this verification
            // if is RESEARCH, warn for sites. IF Operational, warn for participants.
            if (req.getSites() == null || req.getSites().size() < 1) {
                // if (DartRequest.class.isAssignableFrom(req.getClass())) {
                msgs.add("Error: request must have at least one site.");
                // }
                // else if (OperationalRequest.class.isAssignableFrom(req.getClass())) {
                msgs.add("Error: request must have at least one participant.");
                // }
            }

            // each site must have a participant
            Set<Participant> participantSet = req.getParticipants();
            for (Location site : req.getSites()) {
                boolean foundSite = false;
                for (Participant part : participantSet) {
                    if (part.getLocation().getId() == site.getId()) {
                        foundSite = true;
                    }
                }

                if (!foundSite) {
                    msgs.add("Error: No participants found for site: " + site.getName());
                }
            }

            // validate that each participant is only in the list once
            for (Participant part1 : participantSet) {
                for (Participant part2 : participantSet) {

                    if ((part1.getPerson().getName() != null && part2.getPerson().getName() != null)
                            && part1.getPerson().getName().equalsIgnoreCase(part2.getPerson().getName())) {

                        if ((part1.getLocation().getName() != null && part2.getLocation().getName() != null)
                                && !part1.getLocation().getName().equalsIgnoreCase(part2.getLocation().getName())) {
                            // different location (so that we don't find the same entry twice)

                            msgs.add("Error: Duplicate participant found: " + part1.getPerson().getFullName());
                        }
                    }
                }
            }

            // must select at least one Data Storage Location: VINCI or Local VA Server Location
            if (!req.isDataMart() && !req.isLocalServer()) {
                msgs.add("Error: request must specify at least one Data Storage Location (VINCI or Local VA Server Location).");
            }

            // must have a data source (when not in debug mode)
            if (!DartController.isDebugMode()) {
                Set<DataSource> dataSourceSet = req.getDataSources();
                if (dataSourceSet == null || dataSourceSet.isEmpty()) {
                    msgs.add("Error: request must have at least one data source.");
                }
            }

            // validate that all required documents have been uploaded.
            msgs.addAll(validateDocumentsUploaded(req));
        }

        return msgs;
    }

    /**
     * Validate documents uploaded.
     *
     * @param req
     *            the req
     * @return the list
     */
    private static List<String> validateDocumentsUploaded(final Request req) {

        List<String> msgs = new ArrayList<String>();

        if (req != null) {

            List<RequestLocationDocument> rldList = RequestLocationDocument.listByRequestId(req.getId());
            List<RequestParticipantDocument> rpdList = RequestParticipantDocument.listByRequestId(req.getId());

            for (RequestParticipantDocument rpd : rpdList) {
                if (rpd.isActive()) {
                    Document doc = Document.findById(rpd.getDocumentId());

                    if (doc != null && !doc.isUploaded()) {
                        msgs.add("Error: Document must be uploaded: " + doc.getDescription());
                    }
                }
            }

            for (RequestLocationDocument rld : rldList) {
                if (rld.isActive()) {
                    Document doc = Document.findById(rld.getDocumentId());

                    if (doc != null && !doc.isUploaded()) {
                        msgs.add("Error: Document must be uploaded: " + doc.getDescription());
                    }
                }
            }
        }

        return msgs;
    }

    /**
     * Checks if is empty.
     *
     * @param src
     *            the src
     * @return true, if is empty
     */
    public static boolean isEmpty(String src) {
        return (src == null || src.length() < 1);
    }

    /**
     * Final NDS review.
     *
     * @param view
     *            the view
     * @return the object
     */
    @RequestMapping(value = "/approveRequest", method = RequestMethod.POST)
    @ResponseBody
    public Object approveRequest(@RequestBody @Valid final RequestIdView view) {
        LOG.debug("approveRequest");

        try {
            HibernateSessionManager.start();

            UserPreferences prefs = getUserPreferences();
            Request req = retrieveRequest(view.getRequestId());

            // get the workflow for this NDS review
            RequestWorkflow ndsWorkflow = null;
            if (view.getWorkflowId() != 0) { // new request (after the Independent Workflow updates)
                ndsWorkflow = RequestWorkflow.findById(view.getWorkflowId());
            }

            if (!req.isSubmittedOrChanged(ndsWorkflow)) {
                return new ErrorView("Cannot approve request: Request is in the wrong state for approval.");
            }

            Person approver = Person.findById(prefs.getUserId());

            Role.initialize();
            Group.initialize();
            if (!approver.hasRole(Role.NDS_ADMIN)) {
                return new ErrorView("User does not have permission to approve request.");
            }
            if (!approver.inGroup(Group.NDS)) {
                return new ErrorView(
                        "User does not belong to appropriate group to perform this review: " + Group.NDS.getShortName());
            }

            // wait, what if there are unfinished reviews?
            // boolean allDone = req.allReviewsCompleted(ndsWorkflow);
            boolean allDone = req.allReviewsApproved(ndsWorkflow);
            if (!allDone) {
                return new ErrorView("Cannot approve request: Not all reviews are approved.");
            }

            if (ndsWorkflow == null) {
                req.approve(ndsWorkflow, prefs.getUserLoginId());
            } else {
                ndsWorkflow.approve(prefs.getUserLoginId());
            }

            workflowResolver.resolve(req).approve(ndsWorkflow, null, req, prefs.getUserLoginId());

        } catch (Exception e) {
            LOG.error("Error approving request.", e);
            HibernateSessionManager.rollback();
            return new ErrorView("Error approving request.");
        } finally {
            HibernateSessionManager.close();
        }

        return new ErrorView("OK");
    }

    /**
     * NDS review: deny.
     *
     * @param view
     *            the view
     * @return the object
     */
    @RequestMapping(value = "/rejectRequest", method = RequestMethod.POST)
    @ResponseBody
    public Object rejectRequest(@RequestBody @Valid final DenyRequestView view) {

        LOG.debug("rejectRequest");
        try {
            HibernateSessionManager.start();

            UserPreferences prefs = getUserPreferences();
            Request req = retrieveRequest(view.getRequestId());

            RequestWorkflow ndsWorkflow = null;
            if (view.getWorkflowId() != 0) { // new request (after the Independent Workflow updates)
                ndsWorkflow = RequestWorkflow.findById(view.getWorkflowId());
            }

            if (!req.isSubmittedOrChanged(ndsWorkflow)) {
                return new ErrorView("Cannot reject request: Request is in the wrong state.");
            }

            Person approver = Person.findById(prefs.getUserId());

            Role.initialize();
            Group.initialize();
            if (!approver.hasRole(Role.NDS_ADMIN)) {
                return new ErrorView("User does not have permission to reject request.");
            }
            if (!approver.inGroup(Group.NDS)) {
                return new ErrorView(
                        "User does not belong to appropriate group to perform this review: " + Group.NDS.getShortName());
            }

            // save a communication
            Comment.create("Review Denied", req, prefs.getUserLoginId(), "VINCI Dart request " + req.getTrackingNumber()
                    + " is denied by NDS. Reason supplied by reviewer: " + view.getText());

            if (ndsWorkflow == null) { // at the top-level workflow
                req.reject(prefs.getUserLoginId()); // update the request status
            } else {
                ndsWorkflow.reject(prefs.getUserLoginId()); // update the workflow status
            }

            // deny this request (NDS)
            workflowResolver.resolve(req).deny(ndsWorkflow, null, req, prefs.getUserLoginId());

        } catch (Exception e) {
            LOG.error("Error denying request.", e);
            HibernateSessionManager.rollback();
            return new ErrorView("Error denying request.");
        } finally {
            HibernateSessionManager.close();
        }

        return new ErrorView("OK");
    }

    /**
     * Get the Median Wait Time Dialog display statuses.
     *
     * @param view
     *            the view
     * @return the object
     */
    @RequestMapping(value = "/listReviewStatus", method = RequestMethod.POST)
    @ResponseBody
    public Object listReviewStatus(@RequestBody @Valid final RequestIdView view) {
        LOG.debug("listReviewStatus");

        try {
            HibernateSessionManager.start();

            ReviewStatusListView result = new ReviewStatusListView();

            Request req = null;
            try {
                req = RequestController.retrieveRequest(view.getRequestId());

            } catch (ObjectNotFoundException e) {
                LOG.error("Error loading request " + view.getRequestId(), e);
                return new ErrorView("Error loading request " + view.getRequestId());
            }

            if (req != null) {

                RequestWorkflow workflow = null;
                if (view.getWorkflowId() != 0) { // new request (after the Independent Workflow updates)
                    workflow = RequestWorkflow.findById(view.getWorkflowId());
                }

                result.setTrackingNumber(req.getTrackingNumber()); // set the tracking number for the overall request

                // get the list of ReviewStatusView details for this workflow
                if (workflow != null) {
                    workflowResolver.resolve(workflow).populateReviewStatusList(workflow, req, result);
                    // use the child workflow
                } else {
                    workflowResolver.resolve(req).populateReviewStatusList(workflow, req, result);
                    // use the top-level request
                }

                // retrieve the median days elapsed (add the calculated median wait times to each group's info)
                List<MedianWaitTime> medianTimeList = MedianWaitTime.retrieveMedianWaitTimes(req);
                for (MedianWaitTime currMedianTime : medianTimeList) {

                    // find the view entry for this group and update the median times
                    for (ReviewStatusView currView : result.getReviews()) {
                        if (currMedianTime.getMetric() != null
                                && currMedianTime.getMetric().equalsIgnoreCase(currView.getGroupName())) {

                            final float reviewerMedian = currMedianTime.getReviewerMedian();
                            currView.setMedianDaysElapsed(ReviewStatusView.formatTo2DecimalPlaces(reviewerMedian));
                            // reviewer median days elapsed

                            final float requestorMedian = currMedianTime.getRequestorMedian();
                            currView.setRequestorMedianDaysElapsed(ReviewStatusView.formatTo2DecimalPlaces(requestorMedian));

                        }
                    }
                }

                // calculate totals (median and actual days elapsed; requestor median and actual days elapsed)
                // totals (days elapsed)
                double totalDaysElapsed = 0;
                double totalMedianDaysElapsed = 0;
                double requestorTotalDaysElapsed = 0;
                double requestorTotalMedianDaysElapsed = 0;
                for (ReviewStatusView currView : result.getReviews()) {

                    totalDaysElapsed += (Double.parseDouble(currView.getDaysElapsed()));

                    totalMedianDaysElapsed += (Float.parseFloat(currView.getMedianDaysElapsed()));

                    requestorTotalDaysElapsed += (Double.parseDouble(currView.getRequestorDaysElapsed()));
                    requestorTotalMedianDaysElapsed += (Float.parseFloat(currView.getRequestorMedianDaysElapsed()));
                }

                result.setTotalDaysElapsed(ReviewStatusView.formatTo2DecimalPlaces(totalDaysElapsed));
                result.setTotalMedianDaysElapsed(ReviewStatusView.formatTo2DecimalPlaces(totalMedianDaysElapsed));

                result.setTotalRequestorDaysElapsed(ReviewStatusView.formatTo2DecimalPlaces(requestorTotalDaysElapsed));
                result.setTotalRequestorMedianDaysElapsed(
                        ReviewStatusView.formatTo2DecimalPlaces(requestorTotalMedianDaysElapsed));

                // set the date range string for the median calculations
                final String dateRange = "Past 3 Months"; // default date range for the median calculations
                result.setDateRange(dateRange);
            }

            return result;
        } catch (Exception e) {
            LOG.error("Error retrieving the review status.", e);
            HibernateSessionManager.rollback();

            return new ErrorView("Error retrieving the review status.");
        } finally {
            HibernateSessionManager.close();
        }
    }

    /**
     * Populate the Request details (for a single request), returned by getRequest().
     * 
     * Used for the requestor wizard and for the review wizard.
     *
     * @param workflow
     *            the workflow
     * @param request
     *            the request
     * @param person
     *            the person
     * @return the request details view
     * @throws ObjectNotFoundException
     *             the object not found exception
     */
    public static RequestDetailsView populateRequestDetailsView(final RequestWorkflow workflow, Request request,
            final Person person) throws ObjectNotFoundException {
        RequestDetailsView view = new RequestDetailsView();

        view.setRequestId(request.getId());
        view.setActivityId(request.getActivity().getId());
        view.setTrackingNumber(request.getTrackingNumber());

        view.setSubmitted(request.getSubmittedOn() == null ? "" : SDF.format(request.getSubmittedOn()));

        int workflowId = 0;
        if (workflow != null) {
            workflowId = workflow.getId();
        }
        view.setWorkflowId(workflowId);

        view.setStatus(request.getElaborateStatus(workflow));
        // Note: use of elaborate status here is okay, because this is only used for the request details view

        // set the initial/final review state
        view.setInitialReview(false); // default to NOT in the initial review state
        view.setFinalReview(false); // default to NOT in the final review state

        // Note: we really only care about the initialReview and finalReview calculations if this is an NDS workflow
        if (workflow == null || workflow.getWorkflowTemplate().getWorkflowTypeId() == WorkflowResolver.WF_NDS) {

            populatedViewSubmitedOrChanged(workflow, request, view);
        }

        view.setAmendment(request.isAmendment()); // is this an amendment?

        // return amendment narrative if one exists
        if (request.getNarratives() != null && request.getNarratives().size() > 0) {
            view.setAmendmentNarrative(request.getNarratives().get(0).getText());
        }

        // superuser can view everything, but cannot edit (including creating an amendment)
        Role.initialize();
        if (person != null && person.hasRole(Role.SUPER_USER)) {
            view.setEditable(false);
        } else { // NOT a super-user
            // is the request editable for the requestor role (top-level request status)
            view.setEditable(request.isEditable(null));

        }

        return view;
    }

    /**
     * Process submited or changed.
     *
     * @param workflow
     *            the workflow
     * @param request
     *            the request
     * @param view
     *            the view
     * @throws ObjectNotFoundException
     *             the object not found exception
     */
    private static void populatedViewSubmitedOrChanged(final RequestWorkflow workflow, Request request, RequestDetailsView view)
            throws ObjectNotFoundException {
        if (request.isSubmittedOrChanged(workflow)) {

            boolean isReadyForInitialReview = false;

            boolean isReadyForFinalReview = false;
            boolean isFinalReviewCompleted = false;
            boolean isReadyForGroupReview = false;
            try {
                WorkflowResolver workflowResolver = DartObjectFactory.getInstance().getWorkflowResolver();
                if (workflowResolver != null) {

                    if (workflow != null) { // child workflow
                        isReadyForInitialReview = workflowResolver.resolve(workflow).isReadyForInitialReview(workflow, request);
                        isReadyForFinalReview = workflowResolver.resolve(workflow).isReadyForFinalReview(workflow, request);
                        isFinalReviewCompleted = workflowResolver.resolve(workflow).isFinalReviewCompleted(workflow, request);
                        isReadyForGroupReview = workflowResolver.resolve(workflow).isReadyForGroupReview(workflow, request);
                    } else { // top-level workflow
                        isReadyForInitialReview = workflowResolver.resolve(request).isReadyForInitialReview(workflow, request);
                        isReadyForFinalReview = workflowResolver.resolve(request).isReadyForFinalReview(workflow, request);
                        isFinalReviewCompleted = workflowResolver.resolve(request).isFinalReviewCompleted(workflow, request);
                        isReadyForGroupReview = workflowResolver.resolve(request).isReadyForGroupReview(workflow, request);
                    }

                }
            } catch (WorkflowException e) {
                LOG.error("WorkflowException retrieving the review state: " + e.getMessage());

            }

            view.setReadyForInitialReview(isReadyForInitialReview); // has the initial review been performed yet?
            view.setReadyForGroupReview(isReadyForGroupReview);

            if (isReadyForFinalReview || isFinalReviewCompleted) {
                view.setFinalReview(true);
            } else {
                // if NOT the final NDS review, then it is the initial NDS review (or in the intermediate review state)
                view.setInitialReview(true);
            }
        }
    }

    /**
     * Populate the dashboard display of Review tab for: NDS, non-NDS Review groups, DART Admin. Populate the dashboard display
     * of Request tab for: DART Admin.
     * 
     * DART Admin (used for both tabs), NDS (Review Tab), other Review groups (Review Tab)
     * 
     * Assumes that the request is NOT editable and is NOT amendable.
     *
     * @param request
     *            the request
     * @param isSuperUser
     *            the is super user
     * @param isDebugMode
     *            the is debug mode
     * @param isRequestorTab
     *            the is requestor tab
     * @return the request view
     */
    private RequestView populateRequestViewBrief(RequestSummary request, final boolean isSuperUser, final boolean isDebugMode,
            final boolean isRequestorTab) {
        RequestView view = new RequestView();

        view.setRequestId(request.getRequestId());
        view.setActivityId(request.getActivityId());
        view.setTrackingNumber(request.getTrackingNumber());
        view.setActivityName(request.getActivityName());
        view.setUpdated(request.getUpdatedOn() == null ? "" : SDF.format(request.getUpdatedOn())); // TODO: not used in the
                                                                                                   // dashboard UI

        view.setSubmitted(request.getSubmittedOn() == null ? "" : SDF.format(request.getSubmittedOn()));
        if (!view.getSubmitted().isEmpty()) { // only display the time elapsed if the request has been submitted
            view.setDaysElapsedWithLabel(request.getDaysElapsed()); // TODO: not used in the dashboard UI
        }

        // set the workflowID
        if (isSuperUser) {
            // no workflow ID for the super-user (retrieve all information attached to this request, not just a single workflow)
            view.setWorkflowId(0);

        } else {
            // view.setWorkflowId(request.getWorkflowId());
            if (request.getWorkflowSummaryList() != null && request.getWorkflowSummaryList().size() > 0) {
                // get the first entry in the workflow info list
                WorkflowSummary workflowSummary = request.getWorkflowSummaryList().get(0);

                if (workflowSummary != null) {
                    view.setWorkflowId(workflowSummary.getWorkflowId());
                }
            }
        }

        // get and display the details of each workflow attached to this request
        if (request.getWorkflowSummaryList() != null && request.getWorkflowSummaryList().size() > 0) {
            for (WorkflowSummary workflowSummary : request.getWorkflowSummaryList()) {
                if (workflowSummary != null) {
                    // use the status calculated by the query (includes the "elaborate status" calculation)
                    RequestViewBuilder.populateRequestStatusClass(view, workflowSummary.getRequestState(),
                            workflowSummary.getWorkflowId(), workflowSummary.getWorkflowGroupId());

                }
            }

            RequestViewBuilder.populateWorkflowNamePadLeft(view); // update the padding once per request (for all workflows)
        }

        view.setRequestType(request.getRequestType());
        view.setResearchRequest(request.isResearchRequest());
        view.setPreparatoryRequest(request.isPreparatoryRequest());
        view.setOperationsRequest(request.isOperationsRequest());
        view.setContact(request.getPrincipalInvestigatorName());
        view.setEditable(request.isEditable());
        view.setAmendable(request.isAmendable());

        // superuser can view everything, but cannot edit (including creating an amendment)
        if (!isRequestorTab || (isSuperUser && !isDebugMode)) {
            // don't care if request is editable/amendable if this is for the review tab
            view.setEditable(false);
            view.setAmendable(false);
        }

        return view;
    }

}
