package gov.va.vinci.dart.wf2;


import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;

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

import gov.va.vinci.dart.biz.DartRequest;
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.Person;
import gov.va.vinci.dart.biz.PreparatoryRequest;
import gov.va.vinci.dart.biz.Request;
import gov.va.vinci.dart.biz.RequestStatus;
import gov.va.vinci.dart.biz.RequestWorkflow;
import gov.va.vinci.dart.biz.Review;
import gov.va.vinci.dart.biz.Role;
import gov.va.vinci.dart.common.exception.ValidationException;
import gov.va.vinci.dart.json.ReviewStatusListView;
import gov.va.vinci.dart.json.ReviewStatusView;
import gov.va.vinci.dart.json.builder.ReviewStatusViewBuilder;

/**
 * An abstract implementation of a workflow.
 */
public abstract class AbstractWorkflow implements Workflow {

    private static Log log = LogFactory.getLog(AbstractWorkflow.class);

    /**
     * Handle an event for the current workflow item. Implemented by concrete classes.
     * 
     * @param request
     * @param operation
     * @param userLoginId
     * @throws WorkflowException
     */
    protected abstract void transition(final RequestWorkflow workflow, final Review review, final Request request,
            final int operation, final String userLoginId) throws WorkflowException;

    /**
     * Handle the submit event
     * 
     */
    @Override
    public void submit(final RequestWorkflow workflow, final Request request, final String userLoginId)
            throws WorkflowException {
        transition(workflow, null, request, WF_OPERATION_SUBMIT, userLoginId); // no review
    }

    /**
     * Handle the approve event.
     * 
     */
    @Override
    public void approve(final RequestWorkflow workflow, final Review review, final Request request, final String userLoginId)
            throws WorkflowException {
        transition(workflow, review, request, WF_OPERATION_APPROVE, userLoginId);
    }

    /**
     * Handle the deny event.
     * 
     */
    @Override
    public void deny(final RequestWorkflow workflow, final Review review, final Request request, final String userLoginId)
            throws WorkflowException {
        transition(workflow, review, request, WF_OPERATION_DENY, userLoginId);
    }

    /**
     * Handle the change requested event.
     * 
     */
    @Override
    public void changeRequest(final RequestWorkflow workflow, final Review review, final Request request,
            final String userLoginId) throws WorkflowException {
        transition(workflow, review, request, WF_OPERATION_CHANGE_REQUEST, userLoginId);
    }

    /**
     * Handle the close event.
     * 
     */
    @Override
    public void close(final Review review, final Request request, final String userLoginId) throws WorkflowException {
        transition(null, review, request, WF_OPERATION_CLOSE, userLoginId);
    }

    /**
     * Increment the state of the current workflow item.
     * 
     * @param request
     * @param userLoginId
     * @throws WorkflowException
     */
    protected void incrementState(final RequestWorkflow workflow, final Review review, final Request request,
            final String userLoginId) throws WorkflowException {

        long mask = request.getWorkflowMask(); // use the top-level workflow
        int idx = request.getWorkflowState() + 1;
        if (workflow != null) {
            mask = workflow.getWorkflowMask(); // use the child workflow
            idx = workflow.getWorkflowState() + 1;
        }

        // keep incrementing the index until we hit a bit that is 1 in the mask
        for (; idx < 64 && isMasked(idx, mask) == false; idx++) {
        }

        if (idx >= getFinalState()) { // do not go past the final state for this workflow
            if (workflow != null) {
                workflow.setWorkflowState(getFinalState()); // set the child workflow state
            } else {
                request.setWorkflowState(getFinalState()); // set the top-level workflow state
            }
        } else if (idx < 64) {
            if (workflow != null) {
                workflow.setWorkflowState(idx); // use the child workflow
            } else {
                request.setWorkflowState(idx); // use the top-level workflow
            }

            transition(workflow, review, request, WF_OPERATION_ENTRY, userLoginId);
        }
    }

    /**
     * Set the state of the current workflow item. If the new state is not permitted by the mask, the state is incremented until
     * an allowed state or a final state is found. If the new state is final no other action is taken.
     * 
     * Overridden in wfResearchRequest (multi-workflow) Overridden in wfNDS (NDS workflow) Overridden in wfIndependent
     * (Independent workflow)
     * 
     * @param request
     * @param newState
     * @param userLoginId
     * @throws WorkflowException
     */
    protected void setState(final RequestWorkflow workflow, final Review review, final Request request, final int newState,
            final String userLoginId) throws WorkflowException {

        if (workflow != null) {
            workflow.setWorkflowState(newState); // set the top-level workflow state
        } else {
            request.setWorkflowState(newState); // set the top-level workflow state
        }

        if (newState != getFinalState()) {

            long workflowMask = request.getWorkflowMask();
            if (workflow != null) {
                workflowMask = workflow.getWorkflowMask();
            }

            if (isMasked(newState, workflowMask) == false) {
                incrementState(workflow, review, request, userLoginId);
            } else {
                transition(workflow, review, request, WF_OPERATION_ENTRY, userLoginId);
            }
        }
    }

    /**
     * Determine if the indicated workflow item state is allowed by the mask.
     * 
     * @param idx
     * @param mask
     * @return
     */
    protected boolean isMasked(final int idx, final long mask) {

        return (mask & (1 << idx)) != 0;
    }

    /**
     * Send a task for the current workflow item to all users in the NDS administrator group.
     *
     * @param workflow
     *            the workflow
     * @param request
     *            the request
     * @param subject
     *            the subject
     * @param text
     *            the text
     * @param userLoginId
     *            the user login id
     * @param isInitial
     *            the is initial
     * @throws ValidationException
     *             the validation exception
     */
    protected void sendNDSAdminATask(final RequestWorkflow workflow, final Request request, final String userLoginId, final boolean isInitial) throws ValidationException {

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

        // create a task for the NDS administrator group
        String taskStr = "Request ready for review.";
        if (isInitial) {
            taskStr = "Request ready for initial NDS review.";
        } else {
            taskStr = "Request ready for final NDS review.";
        }
        GroupTask.create(workflow, request, Group.NDS, taskStr, taskStr, userLoginId);


    }


    /**
     * Creates a "ready for review" task for the specified group.
     * 
     * @param workflow
     * @param group
     * @param review
     * @param userLoginId
     * @throws ValidationException
     */
    protected void createGroupReviewTask(final RequestWorkflow workflow, final Group group, final Review review,
            final String userLoginId) throws ValidationException {

        if (group != null) {

            final String groupShortName = group.getShortName();

            GroupTask task = GroupTask.create(workflow, review.getRequest(), group,
                    (GroupTask.REVIEW_TASK_STR + groupShortName + " review."),
                    (GroupTask.REVIEW_TASK_STR + groupShortName + " review."), userLoginId);
            if (review != null) {
                task.setReview(review);
            }
        }
    }



    /**
     * Send a review task for the current workflow item to all members of the indicated review group.
     * 
     * @param group
     * @param review
     * @param reviewList
     * @param request
     * @param userLoginId
     * @throws ValidationException
     */
    protected void sendParticipantsDenyEmail(final RequestWorkflow workflow, final Collection<Review> reviewList,
            final Request request, final String groupShortName, final String userLoginId, final int action)
            throws ValidationException {

        StringBuffer subject = new StringBuffer();

        // create the subject...
        if (DartRequest.class.isAssignableFrom(request.getClass()) || request.getRequestType() == 2) {
            subject = EmailUtils.createGroupMemberEmailSubject(workflow, request, groupShortName, action);
        } else if (PreparatoryRequest.class.isAssignableFrom(request.getClass()) || request.getRequestType() == 5) {
            subject = EmailUtils.createGroupMemberEmailSubject(workflow, request, groupShortName, action);
        } else {
            subject = createOperationsEmailSubject(request);

            subject.append(getActionName(action));
        }

        //
        // create body for NDS review people...
        StringBuffer body = createDenyGroupMemberEmailBody(reviewList, request, groupShortName, action);

        EmailUtils.emailRequestorAndAllNotifications(request, subject.toString(), body.toString());
    }

    /**
     * Sends an email to the request participants, notifying them that a reviewer action has been performed by the specified
     * group.  DART 3.0 moved Change requests away from this method for all work flows except operational.
     *
     * @param workflow
     *            the workflow
     * @param reviewList
     *            the review list
     * @param request
     *            the request
     * @param groupShortName
     *            the group short name
     * @param userLoginId
     *            the user login id
     * @param action
     *            the action
     * @throws ValidationException
     *             the validation exception
     */
    protected void sendParticipantsIntermediateEmail(final RequestWorkflow workflow, final Collection<Review> reviewList,
            final Request request, final String groupShortName, final String userLoginId, final int action)
            throws ValidationException {

        StringBuffer subject = new StringBuffer();

        // create the subject...
        if (DartRequest.class.isAssignableFrom(request.getClass()) || request.getRequestType() == 2) {
            subject = EmailUtils.createGroupMemberEmailSubject(workflow, request, groupShortName, action);
        } else if (PreparatoryRequest.class.isAssignableFrom(request.getClass()) || request.getRequestType() == 5) {
            subject = EmailUtils.createGroupMemberEmailSubject(workflow, request, groupShortName, action);
        } else {
            subject = createOperationsEmailSubject(request);

            subject.append(getActionName(action));

            if (action == Workflow.WF_OPERATION_APPROVE || action == Workflow.WF_OPERATION_DENY
                    || action == Workflow.WF_OPERATION_CHANGE_REQUEST) {
                subject.append(" by ");
                subject.append(groupShortName);
            }
        }

        StringBuffer body = new StringBuffer();
        if (action != Workflow.WF_OPERATION_CHANGE_REQUEST) {
            body = createGroupMemberEmailBody(reviewList, request, groupShortName, action);
        } else {
            body = createGroupMemberChangeRequestEmailBody(reviewList, request, groupShortName, action);
        }

        EmailUtils.emailRequestorAndAllNotifications(request, subject.toString(), body.toString());
    }

    /**
     * Sends an email to the request participants, notifying them that a reviewer action has been performed by the specified
     * group.
     *
     * @param workflow
     *            the workflow
     * @param reviewList
     *            the review list
     * @param request
     *            the request
     * @param groupShortName
     *            the group short name
     * @param userLoginId
     *            the user login id
     * @param action
     *            the action
     * @throws ValidationException
     *             the validation exception
     */
    protected void sendParticipantsInitialNDSReviewCompletedEmail(final RequestWorkflow workflow,
            final Collection<Review> reviewList, final Request request, final String groupShortName, final String userLoginId,
            final int action) throws ValidationException {

        StringBuffer subject = new StringBuffer();

        subject = createInitialNDSReviewCompletedSubject(request);

        StringBuffer body = new StringBuffer();
        
        body = createInitialNDSReviewCompletedBody(reviewList, request, groupShortName, action);


        EmailUtils.emailRequestorAndAllNotifications(request, subject.toString(), body.toString());
    }

    private StringBuffer createInitialNDSReviewCompletedBody(final Collection<Review> reviewList, final Request request,
            final String groupShortName, final int action) {

        StringBuffer body = new StringBuffer();
        body.append("This request has been reviewed and Initial NDS Review completed.");

        body.append(" The request is now in status of ");

        // <Status>
        if (reviewList != null && reviewList.size() == 0) {
            body.append("Waiting for Final NDS Review");
        } else {
            body.append("Processing Data Access Request");
        }
        body.append(". If this status applies to you, please take action.");

        // reviews yet to be completed...
        body.append("\r\rReviews yet to be completed are: ");
        if (reviewList != null) {
            for (Review reviewGroup : reviewList) {
                body.append(reviewGroup.getReviewer().getShortName());
                body.append(", ");
            }

            if (reviewList.size() > 0) {
                body.append("and ");
            }
        } 

        body.append("NDS Final.\r");

        EmailUtils.appendGenericRequestAttributes(request, body);

        EmailUtils.appendDartIndexPageInstructions(body);

        return body;
    }

    /**
     * Send an email to all members of the indicated review group. - THIS IS A PATHETIC HACK WHICH SHOULD BE REFACTORED FROM
     * sendGroupMembersAReviewEmail....
     * 
     * @param group
     * @param reviewList
     * @param request
     * @param groupShortName
     *            - group that just completed this review
     * @param userLoginId
     * @throws ValidationException
     */
    protected void createAndSendGroupMembersAReviewEmail(final RequestWorkflow workflow, final Group group,
            final Collection<Review> reviewList, final Request request, final String groupShortName, final String userLoginId,
            final int action) throws ValidationException {

        // called when Final NDS denies the request, and when an intermediate review group denies the request
        // called for both Research Requests and Operations Requests when the Final NDS review denies the request

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

        Group groupToNotify = null;

        if (group != null) {

            StringBuffer subject = EmailUtils.createGroupMemberEmailSubject(workflow, request, groupShortName, action);
            List<Person> personList = new ArrayList<Person>();
            StringBuffer body = new StringBuffer();
            if (action != Workflow.WF_OPERATION_DENY) {
                personList = Person.listByGroupAndNotifications(group.getId());
                groupToNotify = group;
                body = createGroupMemberEmailBody(reviewList, request, groupShortName, action);
            } else {
                personList = Person.listAllWithRoleAndGroupAndNotifications(Role.NDS_ADMIN.getId(), Group.NDS.getId());
                groupToNotify = Group.NDS;
                body = createDenyGroupMemberEmailBody(reviewList, request, groupShortName, action);
            }

            EmailUtils.sendEmailToPersonList(personList, subject.toString(), body.toString());
            EmailUtils.sendEmailToGroupMailBox(groupToNotify, subject.toString(), body.toString());

        }
    }

    /**
     * Send a review task for the current workflow item to all members of the indicated review group.
     * 
     * @param group
     *            - group to create a task for
     * @param review
     * @param reviewList
     * @param request
     * @param groupShortName
     *            - group that just completed this review (different than group, above)
     * @param userLoginId
     * @throws ValidationException
     */
    protected void sendGroupMembersAReviewEmail(final RequestWorkflow workflow, final Group groupToNotify, final Review review,
            final Collection<Review> reviewList, final Request request, final String groupShortName, final String userLoginId,
            final int action) throws ValidationException {

        // called when the initial NDS review is completed: first notification to the intermediate review group that they have a
        // request to review

        if (groupToNotify != null) { // group to send this email to

            createGroupReviewTask(workflow, groupToNotify, review, userLoginId);


            StringBuffer subject = createInitialNDSReviewCompletedSubject(request);

            StringBuffer body = createInitialNDSReviewCompletedBody(reviewList, request, groupShortName, action);
            
            EmailUtils.sendEmailToGroupAndGroupMailBox(groupToNotify, subject.toString(), body.toString());

        }
    }

    private StringBuffer createGroupMemberEmailBody(final Collection<Review> reviewList, final Request request,
            final String groupShortName, final int action) {

        StringBuffer body = new StringBuffer();
        body.append("This request has been reviewed and ");
        // <Action>
        body.append(getActionName(action));
        body.append(" by ");
        // <Group>
        body.append(groupShortName);
        body.append(". The request is now in status of ");

        // <Status>
        if (reviewList != null && reviewList.size() == 0) {
            body.append("Waiting for Final NDS Review");
        } else {
            body.append("Processing Data Access Request");
        }
        body.append(". If this status applies to you, please take action.");

        // reviews yet to be completed...
        body.append("\r\rReviews yet to be completed are: ");
        if (reviewList != null) {
            for (Review reviewGroup : reviewList) {
                body.append(reviewGroup.getReviewer().getShortName());
                body.append(", ");
            }

            if (reviewList.size() > 0) {
                body.append("and ");
            }
        }

        body.append("NDS Final.\r");

        EmailUtils.appendGenericRequestAttributes(request, body);

        EmailUtils.appendDartIndexPageInstructions(body);

        return body;
    }

    private StringBuffer createGroupMemberChangeRequestEmailBody(final Collection<Review> reviewList, final Request request,
            final String groupShortName, final int action) {

        StringBuffer body = new StringBuffer();
        body.append("This request has been reviewed and ");
        // <Action>
        body.append(getActionName(action));
        body.append(" by ");
        // <Group>
        body.append(groupShortName);
        body.append(". The request is now in status of ");
        // <Status>
        body.append("Change Requested");

        body.append(". If this status applies to you, please take action.");

        EmailUtils.appendGenericRequestAttributes(request, body);

        EmailUtils.appendDartIndexPageInstructions(body);

        return body;
    }

    private StringBuffer createDenyGroupMemberEmailBody(final Collection<Review> reviewList, final Request request,
            final String groupShortName, final int action) {

        StringBuffer body = new StringBuffer();
        body.append("This request has been reviewed and ");
        // <Action>
        body.append(getActionName(action));
        body.append(" by ");
        // <Group>
        body.append(groupShortName);
        body.append(". The request is now in status of Denied. If this status applies to you, please take action.");

        EmailUtils.appendGenericRequestAttributes(request, body);

        EmailUtils.appendDartIndexPageInstructions(body);

        return body;
    }


    public StringBuffer createInitialNDSReviewCompletedSubject(final Request request) {
        StringBuffer subject = EmailUtils.createDARTDefaultSubject(request);
        subject.append("Initial NDS Review Completed");

        return subject;
    }

    protected StringBuffer createOperationsEmailSubject(final Request request) {
        StringBuffer subject = EmailUtils.createDARTDefaultSubject(request);

        return subject;
    }

    protected void createReqSubmittedEvent(final RequestWorkflow workflow, final Request request, final String userLoginId,
            final boolean isInitNDSReview, final boolean isFinalNDSReview) throws ValidationException {

        EventType.initialize(); // this may be extra, but initialize just in case
        Group.initialize();

        // get the groups that have requested a change
        Set<Group> changeRequestGroupSet = request.getChangesRequestedGroups(workflow);
        if (isInitNDSReview || isFinalNDSReview) // add NDS to the list of groups requesting a change?
            changeRequestGroupSet.add(Group.NDS);

        String groupsRequestingChangesStr = createReqSubmittedForString(workflow, request, isInitNDSReview, isFinalNDSReview);
        if (groupsRequestingChangesStr != null && groupsRequestingChangesStr.trim().isEmpty() == false) {
            groupsRequestingChangesStr = " (" + groupsRequestingChangesStr + ")";
        }

        Event.create(("Request Submitted with Changes" + groupsRequestingChangesStr),
                ("Request Submitted with Changes" + groupsRequestingChangesStr), EventType.SUBMIT_CHANGE_REQUEST,
                changeRequestGroupSet, isInitNDSReview, request, userLoginId);
    }

    protected String createReqSubmittedForString(final RequestWorkflow workflow, final Request request,
            final boolean isInitNDSReview, final boolean isFinalNDSReview) {

        // StringBuilder str = new StringBuilder(" (");
        StringBuilder str = new StringBuilder();

        if (request != null) {
            Group.initialize();

            if (isInitNDSReview)
                str.append(Review.INITIAL_NDS_GROUP);

            for (Review review : request.getReviews(workflow)) { // get the reviews for this workflow

                if (review.isChangeRequested()) { // this group requested changes

                    Group group = review.getReviewer();
                    if (group != null) {

                        // if( !str.toString().trim().equals("(") )
                        if (str.toString().trim().isEmpty() == false)
                            str.append(", ");

                        str.append(group.getShortName());
                    }
                } 
            } 


            if (isFinalNDSReview) {
                if (str.toString().trim().isEmpty() == false)
                    str.append(", ");
                str.append(Review.FINAL_NDS_GROUP);
            } 

        } 

        if (str.toString().trim().isEmpty() == true) // no group info to return
            return "";

        return (str.toString());
    }

    public static String getActionName(final int actionId) {

        switch (actionId) {

        case WF_OPERATION_ENTRY:
            return "Entry";

        case WF_OPERATION_INITIALIZE:
            return "Initialized";

        case WF_OPERATION_SUBMIT:
            return "Submitted";

        case WF_OPERATION_APPROVE:
            return "Approved";

        case WF_OPERATION_DENY:
            return "Denied";

        case WF_OPERATION_CHANGE_REQUEST:
            return "Change Requested";

        case WF_OPERATION_CLOSE:
            return "Closed";
        }

        return "";
    }

    // TODO: Privacy requests changes, ORD approves -> getting a status of "waiting for final NDS review" in the email body
    // here? (Reviews yet to be completed: Final NDS) -> do we want to consider "change requested" as not yet reviewed?
    protected ArrayList<Review> getRemainingReviewersList(final RequestWorkflow workflow, final Request request) {
        // get the intermediate reviews for this request (none for OperationalRequest)
        Set<Review> reviewSet = request.getReviews(workflow);
        ArrayList<Review> finalReviewSet = new ArrayList<Review>();

        if (reviewSet != null) {

            for (Review review : reviewSet) { // step through the intermediate reviews

                // review status (for this intermediate review, get its status
                String reviewStatus = review.getReviewStatus(); // get the review status

                if (reviewStatus.equals(Review.WAITING_FOR_REVIEW)) {
                    finalReviewSet.add(review);
                }

            } // end for loop
        } // end if -- reviewSet

        return finalReviewSet;
    }// end getRemainingReviewersList

    /**
     * Calculate the percentage of review completion on the request.
     * 
     * @return
     */
    public abstract String calculateReviewCompletion(final RequestWorkflow workflow, final Request request);

    /**
     * Returns true if ready for the initial review (initial NDS review, or whatever group performs the initial review)
     * 
     * @return
     */
    public abstract boolean isReadyForInitialReview(final RequestWorkflow workflow, final Request request);

    /**
     * Returns true if the initial review is completed (initial NDS review, or whatever group performs the initial review)
     * 
     * @return
     */
    public abstract boolean isInitialReviewCompleted(final RequestWorkflow workflow, final Request request);

    /**
     * Returns true if ready for the final review (final NDS review, or whatever group performs the final review)
     * 
     * @return
     */
    public abstract boolean isReadyForFinalReview(final RequestWorkflow workflow, final Request request);
    
    /**
     * Returns true if ready for the intermediate or Group review (in between initial review and final review)
     * This is only applies to NDS currently so all overs return false.  This method is overridden in the NDS work flow.
     * 
     * @return
     */
    public boolean isReadyForGroupReview(final RequestWorkflow workflow, final Request request) {
        return false;
    
    }

    /**
     * Returns true if the final review has been completed (final NDS review, or whatever group performs the final review)
     * 
     * @return
     */
    public abstract boolean isFinalReviewCompleted(final RequestWorkflow workflow, final Request request);

    /**
     * Retrieves the list of ReviewStatusView details for the specified NDS workflow. Used for the Median Wait Time display.
     * 
     * @param workflow
     * @param request
     * @param result
     */
    @SuppressWarnings("unchecked")
    public void populateReviewStatusList(final RequestWorkflow workflow, final Request request, ReviewStatusListView result) {

        if (result != null) {

            // get the initial NDS review for this request
            ReviewStatusView initReview = ReviewStatusViewBuilder.populateInitialNDSReviewStatus(workflow, request);
            result.getReviews().add(initReview);

            // Until the initial NDS review has been completed, no other reviews should be shown. including the total
            if (initReview.getStatus() != null && initReview.getStatus().equals(RequestStatus.APPROVED.getName())) {

                // get the intermediate reviews for this request and workflow (none for OperationalRequest)
                Set<Review> reviewSet = request.getReviews(workflow); // get the intermediate reviews for this workflow
                if (reviewSet != null) {

                    List<ReviewStatusView> intermedReviewList = new ArrayList<ReviewStatusView>();

                    for (Review review : reviewSet) {
                        ReviewStatusView reviewStatusView =
                                ReviewStatusViewBuilder.populateReviewStatus(workflow, request, review);
                        intermedReviewList.add(reviewStatusView);
                    }

                    // sort the list of intermediate reviews (Privacy, Security, ORD, others)
                    Collections.sort(intermedReviewList);
                    result.getReviews().addAll(intermedReviewList); // add the list of intermediate reviews to the overall list
                } // end if -- reviewSet

                // get the final NDS review for this request
                ReviewStatusView finalReview = ReviewStatusViewBuilder.populateFinalNDSReviewStatus(workflow, request);
                result.getReviews().add(finalReview);

                result.setDisplayTotals(true);

            } else if (initReview.getStatus() != null && initReview.getStatus().equals(RequestStatus.DENIED.getName())) {

                // if the initial NDS review was denied, display the totals
                result.setDisplayTotals(true);

            } else { // initial NDS review not yet approved/denied

                result.setDisplayTotals(false); // don't display the totals until the initial NDS review has been completed
            } // end else

        } // end if
    }

}
