org.kuali.student.enrollment.class2.courseoffering.service.facade.CourseOfferingServiceFacadeImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.kuali.student.enrollment.class2.courseoffering.service.facade.CourseOfferingServiceFacadeImpl.java

Source

/**
 * Copyright 2013 The Kuali Foundation Licensed under the
 * Educational Community License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may
 * obtain a copy of the License at
 *
 * http://www.osedu.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * Created by Charles on 2/5/13
 */
package org.kuali.student.enrollment.class2.courseoffering.service.facade;

import org.apache.commons.lang.StringUtils;
import org.kuali.rice.core.api.criteria.PredicateFactory;
import org.kuali.rice.core.api.criteria.QueryByCriteria;
import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
import org.kuali.rice.core.api.util.ConcreteKeyValue;
import org.kuali.rice.core.api.util.KeyValue;
import org.kuali.student.common.collection.KSCollectionUtils;
import org.kuali.student.enrollment.class2.courseoffering.dao.ActivityOfferingClusterDaoApi;
import org.kuali.student.enrollment.class2.courseoffering.model.ActivityOfferingClusterEntity;
import org.kuali.student.enrollment.class2.courseoffering.service.extender.CourseOfferingServiceExtender;
import org.kuali.student.enrollment.class2.courseoffering.service.facade.issue.ActivityOfferingNotInAocSubissue;
import org.kuali.student.enrollment.class2.courseoffering.service.facade.issue.CourseOfferingAutogenIssue;
import org.kuali.student.enrollment.class2.courseoffering.service.facade.issue.FormatOfferingAutogenIssue;
import org.kuali.student.enrollment.class2.courseoffering.service.facade.issue.InvalidRegGroupSubissue;
import org.kuali.student.enrollment.class2.courseoffering.service.facade.issue.RegGroupNotGeneratedByAocSubissue;
import org.kuali.student.enrollment.class2.coursewaitlist.service.facade.CourseWaitListServiceFacade;
import org.kuali.student.enrollment.class2.examoffering.service.facade.ExamOfferingContext;
import org.kuali.student.enrollment.class2.examoffering.service.facade.ExamOfferingResult;
import org.kuali.student.enrollment.class2.examoffering.service.facade.ExamOfferingServiceFacade;
import org.kuali.student.enrollment.courseoffering.dto.ActivityOfferingClusterInfo;
import org.kuali.student.enrollment.courseoffering.dto.ActivityOfferingInfo;
import org.kuali.student.enrollment.courseoffering.dto.ActivityOfferingSetInfo;
import org.kuali.student.enrollment.courseoffering.dto.CourseOfferingInfo;
import org.kuali.student.enrollment.courseoffering.dto.FormatOfferingInfo;
import org.kuali.student.enrollment.courseoffering.dto.RegistrationGroupInfo;
import org.kuali.student.enrollment.courseoffering.dto.SeatPoolDefinitionInfo;
import org.kuali.student.enrollment.courseoffering.service.CourseOfferingService;
import org.kuali.student.enrollment.courseofferingset.dto.SocRolloverResultItemInfo;
import org.kuali.student.enrollment.coursewaitlist.dto.CourseWaitListInfo;
import org.kuali.student.enrollment.coursewaitlist.service.CourseWaitListService;
import org.kuali.student.r2.common.datadictionary.DataDictionaryValidator;
import org.kuali.student.r2.common.dto.BulkStatusInfo;
import org.kuali.student.r2.common.dto.ContextInfo;
import org.kuali.student.r2.common.dto.ValidationResultInfo;
import org.kuali.student.r2.common.exceptions.AlreadyExistsException;
import org.kuali.student.r2.common.exceptions.DataValidationErrorException;
import org.kuali.student.r2.common.exceptions.DependentObjectsExistException;
import org.kuali.student.r2.common.exceptions.DoesNotExistException;
import org.kuali.student.r2.common.exceptions.InvalidParameterException;
import org.kuali.student.r2.common.exceptions.MissingParameterException;
import org.kuali.student.r2.common.exceptions.OperationFailedException;
import org.kuali.student.r2.common.exceptions.PermissionDeniedException;
import org.kuali.student.r2.common.exceptions.ReadOnlyException;
import org.kuali.student.r2.common.exceptions.VersionMismatchException;
import org.kuali.student.r2.common.permutation.PermutationCounter;
import org.kuali.student.r2.common.util.constants.CourseOfferingServiceConstants;
import org.kuali.student.r2.common.util.constants.CourseWaitListServiceConstants;
import org.kuali.student.r2.common.util.constants.LuiServiceConstants;
import org.kuali.student.r2.core.acal.dto.TermInfo;
import org.kuali.student.r2.core.class1.search.ActivityOfferingSearchServiceImpl;
import org.kuali.student.r2.core.class1.type.dto.TypeInfo;
import org.kuali.student.r2.core.class1.type.dto.TypeTypeRelationInfo;
import org.kuali.student.r2.core.class1.type.service.TypeService;
import org.kuali.student.r2.core.constants.TypeServiceConstants;
import org.kuali.student.r2.core.search.dto.SearchRequestInfo;
import org.kuali.student.r2.core.search.dto.SearchResultCellInfo;
import org.kuali.student.r2.core.search.dto.SearchResultInfo;
import org.kuali.student.r2.core.search.dto.SearchResultRowInfo;
import org.kuali.student.r2.core.search.service.SearchService;
import org.kuali.student.r2.lum.course.service.CourseService;
import org.kuali.student.r2.lum.util.constants.CourseServiceConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Resource;
import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Implementation of the Application Service Layer to provide the functionally specified functionality
 * using several service calls. (Used to be AutogenRegGroupServiceAdapterImpl)
 *
 * @author Kuali Student Team
 */
public class CourseOfferingServiceFacadeImpl implements CourseOfferingServiceFacade {
    private static final Logger LOGGER = LoggerFactory.getLogger(CourseOfferingServiceFacadeImpl.class);

    @Resource(name = "CourseOfferingService")
    private CourseOfferingService coService;

    @Resource(name = "SearchService")
    private SearchService searchService;

    private TypeService typeService;

    private CourseService courseService;

    private CourseWaitListService courseWaitListService;

    private ActivityOfferingClusterDaoApi activityOfferingClusterDao;

    private CourseWaitListServiceFacade waitListServiceFacade;

    private ExamOfferingServiceFacade examOfferingServiceFacade;

    private CourseOfferingServiceExtender courseOfferingServiceExtender;

    /* (non-Javadoc)
     * @see org.kuali.student.enrollment.class2.courseoffering.service.adapter.CourseOfferingServiceFacade#getDefaultClusterName(int)
     */
    @Override
    public String getDefaultClusterName(int numberOfExistingClusters) {

        String clusterName = String.format("CL %d", (numberOfExistingClusters + 1));

        return clusterName;
    }

    @Override
    public String getDefaultClusterNamePerCO(String courseOfferingId, ContextInfo context) {
        List<String> foIds = new ArrayList<String>();
        int prefixNum = 1;
        String defaultClusterNameToCreate = String.format("CL %d", prefixNum);
        Set<String> clusterNames = new HashSet<String>();

        try {
            //retrieve all the FOs associated with the given CO
            List<FormatOfferingInfo> formatOfferingList = coService
                    .getFormatOfferingsByCourseOffering(courseOfferingId, context);
            for (FormatOfferingInfo foInfo : formatOfferingList) {
                foIds.add(foInfo.getId());
            }

            if (!foIds.isEmpty()) {
                QueryByCriteria.Builder qbcBuilder = QueryByCriteria.Builder.create();
                qbcBuilder.setPredicates(PredicateFactory.in("formatOfferingId", foIds.toArray()));
                QueryByCriteria criteria = qbcBuilder.build();

                //retrieve all the clusters associated with the given CO (by fetching clusters for all the FOs in the given CO)
                List<ActivityOfferingClusterInfo> aoClusterList = coService
                        .searchForActivityOfferingClusters(criteria, context);

                for (ActivityOfferingClusterInfo aoCluster : aoClusterList) {
                    clusterNames.add(aoCluster.getPrivateName());
                }

                //assign the default cluster a name that hasn't been used within the given CO
                while (clusterNames.contains(defaultClusterNameToCreate)) {
                    prefixNum++;
                    defaultClusterNameToCreate = String.format("CL %d", prefixNum);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return defaultClusterNameToCreate;
    }

    public void setCourseOfferingService(CourseOfferingService coService) {
        this.coService = coService;
    }

    @Override
    public int getAoClusterCountByFoId(String foId, ContextInfo contextInfo) throws MissingParameterException,
            InvalidParameterException, OperationFailedException, PermissionDeniedException {

        int iRet = 0;

        SearchRequestInfo request = new SearchRequestInfo(
                ActivityOfferingSearchServiceImpl.AO_CLUSTER_COUNT_BY_FO_SEARCH_KEY);

        request.addParam(ActivityOfferingSearchServiceImpl.SearchParameters.FO_ID, foId);

        SearchResultInfo searchResult = getSearchService().search(request, contextInfo);
        for (SearchResultRowInfo row : searchResult.getRows()) {
            for (SearchResultCellInfo cell : row.getCells()) {
                if (ActivityOfferingSearchServiceImpl.SearchResultColumns.AO_CLUSTER_COUNT.equals(cell.getKey())) {
                    String aoClusterCount = cell.getValue();

                    if (aoClusterCount != null) {
                        iRet = Integer.valueOf(aoClusterCount);
                    }
                }
            }
        }

        return iRet;

    }

    @Override
    public List<KeyValue> getAoIdAndAoTypeByFO(String foId, ContextInfo contextInfo)
            throws MissingParameterException, InvalidParameterException, OperationFailedException,
            PermissionDeniedException {

        List<KeyValue> lRet = new ArrayList<KeyValue>();

        SearchRequestInfo request = new SearchRequestInfo(
                ActivityOfferingSearchServiceImpl.AO_ID_AND_TYPE_BY_FO_SEARCH_KEY);

        request.addParam(ActivityOfferingSearchServiceImpl.SearchParameters.FO_ID, foId);

        SearchResultInfo searchResult = getSearchService().search(request, contextInfo);
        for (SearchResultRowInfo row : searchResult.getRows()) {
            String aoId = null;
            String aoType = null;

            for (SearchResultCellInfo cell : row.getCells()) {

                if (ActivityOfferingSearchServiceImpl.SearchResultColumns.AO_ID.equals(cell.getKey())) {
                    aoId = cell.getValue();
                } else if (ActivityOfferingSearchServiceImpl.SearchResultColumns.AO_TYPE.equals(cell.getKey())) {
                    aoType = cell.getValue();
                }
            }

            if (aoId != null && aoType != null) {
                KeyValue kv = new ConcreteKeyValue(aoId, aoType);
                lRet.add(kv);
            }
        }

        return lRet;

    }

    @Override
    public ActivityOfferingClusterInfo createDefaultCluster(String foId, ContextInfo context)
            throws PermissionDeniedException, MissingParameterException, InvalidParameterException,
            OperationFailedException, DoesNotExistException, ReadOnlyException, DataValidationErrorException {

        int clusterCount = getAoClusterCountByFoId(foId, context);
        if (clusterCount != 0) {
            throw new OperationFailedException("Cluster already exists");
        }
        FormatOfferingInfo fo = coService.getFormatOffering(foId, context);
        String coId = fo.getCourseOfferingId();

        try {
            _addActivityOfferingTypesToFormatOffering(fo, context);
        } catch (VersionMismatchException e) {
            LOGGER.warn("VersionMismatchException thrown in createDefaultCluster, part 1");
            throw new OperationFailedException(e.getMessage());
        }

        List<KeyValue> aoKVList = getAoIdAndAoTypeByFO(foId, context);

        if (aoKVList != null && !aoKVList.isEmpty()) {
            LOGGER.warn("There are AOs without an AOC for this format ({}).  Indicates bad ref data.", foId);
        }
        // Now we're good...create the AOC
        ActivityOfferingClusterInfo clusterInfo = new ActivityOfferingClusterInfo();
        clusterInfo.setFormatOfferingId(foId);

        String defaultClusterName = getDefaultClusterNamePerCO(coId, context);

        clusterInfo.setPrivateName(defaultClusterName);
        clusterInfo.setName(defaultClusterName);
        clusterInfo.setStateKey(CourseOfferingServiceConstants.AOC_ACTIVE_STATE_KEY);
        clusterInfo.setTypeKey(CourseOfferingServiceConstants.AOC_ROOT_TYPE_KEY);
        ActivityOfferingClusterInfo aoc = coService.createActivityOfferingCluster(foId,
                CourseOfferingServiceConstants.AOC_ROOT_TYPE_KEY, clusterInfo, context);
        if (aoKVList != null && !aoKVList.isEmpty()) {
            List<ActivityOfferingSetInfo> sets = aoc.getActivityOfferingSets();
            // Put stray AOs into this cluster (considered a problem)
            LOGGER.warn("Adding stray AOs to this cluster--shouldn't happen");
            for (KeyValue aoIdType : aoKVList) {
                boolean added = false;
                String aoId = aoIdType.getKey();
                String aoType = aoIdType.getValue();
                // Iterate over each set to find a matching AO set to put the stray AOs into
                for (ActivityOfferingSetInfo set : sets) {
                    if (set.getActivityOfferingType().equals(aoType)) {
                        // Add AO ID to correct AO set
                        set.getActivityOfferingIds().add(aoId);
                        added = true;
                        break;
                    }
                }
                if (!added) {
                    throw new OperationFailedException(
                            "Unable to find correct AO set for AO ID (" + aoId + ").  Bad data");
                }
            }
            try {
                coService.updateActivityOfferingCluster(foId, aoc.getId(), aoc, context);
            } catch (VersionMismatchException e) {
                LOGGER.warn("VersionMismatchException thrown in createDefaultCluster");
                throw new OperationFailedException(e.getMessage());
            }
        }
        return aoc;
    }

    private void _addActivityOfferingTypesToFormatOffering(FormatOfferingInfo fo, ContextInfo context)
            throws PermissionDeniedException, MissingParameterException, InvalidParameterException,
            OperationFailedException, DoesNotExistException, ReadOnlyException, DataValidationErrorException,
            VersionMismatchException {
        if (fo.getActivityOfferingTypeKeys() != null && !fo.getActivityOfferingTypeKeys().isEmpty()) {
            // Only bother with this if there are no AO type keys
            return;
        }

        // Get the activity types
        List<String> activityTypes = courseOfferingServiceExtender.getActivityTypesForFormatId(fo.getFormatId(),
                context);
        if (activityTypes.isEmpty()) {
            throw new OperationFailedException("No format could be found to match id: " + fo.getFormatId()
                    + " or Formats contains no activities.  Error!");
        }

        // Use type service to find corresponding AO types--assumes 1-1 mapping of Activity types to AO types
        TypeService typeService = getTypeService();
        List<String> aoTypeKeys = new ArrayList<String>();
        for (String activityType : activityTypes) {
            List<TypeTypeRelationInfo> typeTypeRels = typeService.getTypeTypeRelationsByOwnerAndType(activityType,
                    TypeServiceConstants.TYPE_TYPE_RELATION_ALLOWED_TYPE_KEY, context);
            if (typeTypeRels.size() != 1) {
                // Ref data currently only has a 1-1 mapping between Activity types (CLU) and AO types (LUI)
                // The UI screens only support this.  Should there be a many-to-1 relation between AO types and Activity
                // types (as they were originally envisioned), then this exception will be thrown.
                throw new UnsupportedOperationException(
                        "Can't handle Activity Type -> AO Type that isn't 1-1.  Search for this message in Java code");
            } else {
                String aoType = KSCollectionUtils.getRequiredZeroElement(typeTypeRels).getRelatedTypeKey();
                aoTypeKeys.add(aoType);
            }
        }
        // Finally, set the ao types for this fo
        fo.setActivityOfferingTypeKeys(aoTypeKeys);

        getCoService().updateFormatOffering(fo.getId(), fo, context);
    }

    /**
     * User Story 2: I need the system to automatically create reg group(s) when I add a CO via Copy to eliminate
     * the need to manually create them
     * Note: this only handles copying an existing CO to a term, not creating from canonical which will
     * be a separate method (not written as of now).
     * Note: not yet unit tested
     * Note: rollover will handle generation of RGs (not yet implemented)
     */
    @Override
    public CourseOfferingInfo copyCourseOfferingToTargetTerm(CourseOfferingInfo coInfo, TermInfo targetTerm,
            List<String> optionKeys, ContextInfo context) throws InvalidParameterException,
            PermissionDeniedException, DataValidationErrorException, AlreadyExistsException, ReadOnlyException,
            OperationFailedException, MissingParameterException, DoesNotExistException {
        // Impl based on CourseOfferingManagementController::copyCourseOfferingCreateCopy
        if (optionKeys == null) {
            optionKeys = new ArrayList<String>();
        }

        optionKeys.add(CourseOfferingServiceConstants.APPEND_COURSE_OFFERING_IN_SUFFIX_OPTION_KEY);

        // Copy CO by using rollover.  Note that rollover will take care of generating RGs
        SocRolloverResultItemInfo item = coService.rolloverCourseOffering(coInfo.getId(), targetTerm.getId(),
                optionKeys, context);
        CourseOfferingInfo targetCo = coService.getCourseOffering(item.getTargetCourseOfferingId(), context);
        return targetCo;
    }

    @Override
    public ActivityOfferingResult createActivityOffering(ActivityOfferingInfo aoInfo, String aocId,
            ContextInfo context) throws PermissionDeniedException, DataValidationErrorException,
            InvalidParameterException, ReadOnlyException, OperationFailedException, MissingParameterException,
            DoesNotExistException, VersionMismatchException {
        // Fetch cluster first, so if it fails, we don't continue on
        ActivityOfferingClusterInfo cluster = coService.getActivityOfferingCluster(aocId, context);
        // Make sure FO IDs match up
        if (!cluster.getFormatOfferingId().equals(aoInfo.getFormatOfferingId())) {
            throw new DataValidationErrorException("Format Offering Ids do not match");
        }

        //set AO scheduling state to unscheduled
        aoInfo.setSchedulingStateKey(LuiServiceConstants.LUI_AO_SCHEDULING_STATE_UNSCHEDULED_KEY);

        ActivityOfferingInfo created = coService.createActivityOffering(aoInfo.getFormatOfferingId(),
                aoInfo.getActivityId(), aoInfo.getTypeKey(), aoInfo, context);

        ActivityOfferingResult aoResult = _addActivityOfferingToClusterCommon(created, cluster, context);

        //create and persist a WaitlistInfo for AO
        CourseWaitListInfo theWaitListInfo = getWaitListServiceFacade().createDefaultCourseWaitlist(created,
                context);

        aoResult.setWaitListInfo(theWaitListInfo);

        return aoResult;

    }

    /**
     * Used by both createActivityOffering and copyActivityOfferingToCluster
     *
     * @param aoInfo  AO info just created/copied
     * @param cluster The cluster to place it in
     * @param context context of service call
     * @return A result with RGs generated
     */
    private ActivityOfferingResult _addActivityOfferingToClusterCommon(ActivityOfferingInfo aoInfo,
            ActivityOfferingClusterInfo cluster, ContextInfo context) throws DataValidationErrorException,
            DoesNotExistException, InvalidParameterException, MissingParameterException, OperationFailedException,
            PermissionDeniedException, ReadOnlyException, VersionMismatchException {

        // Now add the AO ID to the correct AOC set
        boolean checkType = false;
        for (ActivityOfferingSetInfo set : cluster.getActivityOfferingSets()) {
            if (set.getActivityOfferingType().equals(aoInfo.getTypeKey())) {
                set.getActivityOfferingIds().add(aoInfo.getId()); // Found set, add the ID
                checkType = true;
                break;
            }
        }
        // if missing set of certain type in cluster -> have to create it
        if (!checkType) {
            ActivityOfferingSetInfo set = new ActivityOfferingSetInfo();
            set.setActivityOfferingType(aoInfo.getTypeKey());
            set.getActivityOfferingIds().add(aoInfo.getId());
            cluster.getActivityOfferingSets().add(set);
        }

        // Update the AOC
        ActivityOfferingClusterInfo updated = coService.updateActivityOfferingCluster(cluster.getFormatOfferingId(),
                cluster.getId(), cluster, context);
        // Note: this may generate RGs that do NOT include the AO just added
        ActivityOfferingResult aoResult = new ActivityOfferingResult();
        aoResult.setCreatedActivityOffering(aoInfo);
        if (_clusterCanGenerateRgs(updated)) {
            List<BulkStatusInfo> status = coService.generateRegistrationGroupsForCluster(updated.getId(), context);
            aoResult.setGeneratedRegistrationGroups(status);
        } else {
            aoResult.setGeneratedRegistrationGroups(new ArrayList<BulkStatusInfo>());
            aoResult.getClusterstatus().setSuccess(Boolean.FALSE);
            aoResult.getClusterstatus().setMessage("Error: empty AO sets exist--can't generate reg groups");
        }
        return aoResult;
    }

    private boolean _clusterCanGenerateRgs(ActivityOfferingClusterInfo updated) {
        for (ActivityOfferingSetInfo set : updated.getActivityOfferingSets()) {
            if (set.getActivityOfferingIds().isEmpty()) {
                return false;
            }
        }
        return true;
    }

    @Override
    public ActivityOfferingResult copyActivityOfferingToCluster(String origAoId, String aocId, ContextInfo context)
            throws PermissionDeniedException, DataValidationErrorException, InvalidParameterException,
            ReadOnlyException, OperationFailedException, MissingParameterException, DoesNotExistException,
            VersionMismatchException {
        // Fetch cluster first, so if it fails, we don't continue on
        ActivityOfferingClusterInfo cluster = coService.getActivityOfferingCluster(aocId, context);
        // Make a new copy of AO
        ActivityOfferingInfo copyAoInfo = coService.copyActivityOffering(origAoId, context);
        // Make a new copy of the associated WL
        copyToCreateWL(copyAoInfo, origAoId, context);
        // Make sure FO IDs match up
        if (!cluster.getFormatOfferingId().equals(copyAoInfo.getFormatOfferingId())) {
            throw new DataValidationErrorException("Format Offering Ids do not match");
        }

        ActivityOfferingResult aoResult = _addActivityOfferingToClusterCommon(copyAoInfo, cluster, context);

        return aoResult;
    }

    private CourseWaitListInfo copyToCreateWL(ActivityOfferingInfo newAOInfo, String origAoId, ContextInfo context)
            throws InvalidParameterException, MissingParameterException, OperationFailedException,
            PermissionDeniedException, DoesNotExistException, DataValidationErrorException, ReadOnlyException {

        List<CourseWaitListInfo> waitListInfos = getCourseWaitListService()
                .getCourseWaitListsByActivityOffering(origAoId, context);
        CourseWaitListInfo origWaitListInfo, newWaitListInfo;
        if (!waitListInfos.isEmpty()) {

            // waitListInfos can return more than two values
            origWaitListInfo = KSCollectionUtils.getRequiredZeroElement(waitListInfos);
            newWaitListInfo = new CourseWaitListInfo(origWaitListInfo);
            if (origWaitListInfo.getActivityOfferingIds().size() > 1) {
                origWaitListInfo.getActivityOfferingIds().add(newAOInfo.getId());
                try {
                    origWaitListInfo = getCourseWaitListService().updateCourseWaitList(origWaitListInfo.getId(),
                            origWaitListInfo, context);
                } catch (VersionMismatchException e) {
                    throw new OperationFailedException(e);
                }
                return origWaitListInfo;

            } else {
                newWaitListInfo.setId(null);
                newWaitListInfo.setActivityOfferingIds(new ArrayList<String>());
                newWaitListInfo.setFormatOfferingIds(new ArrayList<String>());
                newWaitListInfo.getActivityOfferingIds().add(newAOInfo.getId());
                newWaitListInfo.getFormatOfferingIds().add(newAOInfo.getFormatOfferingId());
                newWaitListInfo = getCourseWaitListService().createCourseWaitList(
                        CourseWaitListServiceConstants.COURSE_WAIT_LIST_WAIT_TYPE_KEY, newWaitListInfo, context);
            }

        } else {
            // Assume that every AO should have an associated WL. if not, treat it as a reference data problem.
            // We will just create a new WL
            newWaitListInfo = getWaitListServiceFacade().createDefaultCourseWaitlist(newAOInfo, context);
        }
        return newWaitListInfo;

    }

    public CourseWaitListInfo createUncolocatedWaitList(CourseWaitListInfo courseWaitListInfo, String waitlistType,
            boolean hasWaitlist, boolean limitWaitlistSize, String aoId, String foId, ContextInfo context) {

        setAutoProcConfReq(courseWaitListInfo, waitlistType, hasWaitlist, limitWaitlistSize);

        try {
            if (courseWaitListInfo.getActivityOfferingIds().size() == 1) { // only current AO - meaning no sharing of WL
                courseWaitListInfo = getCourseWaitListService().updateCourseWaitList(courseWaitListInfo.getId(),
                        courseWaitListInfo, context);
            } else {
                // remove un-colocated AO from shared WL, do NOT want to save new parameters
                CourseWaitListInfo courseWaitListInfoShared = getCourseWaitListService()
                        .getCourseWaitList(courseWaitListInfo.getId(), context);
                courseWaitListInfoShared.getActivityOfferingIds().remove(aoId);
                courseWaitListInfoShared.getFormatOfferingIds().remove(foId);
                getCourseWaitListService().updateCourseWaitList(courseWaitListInfo.getId(),
                        courseWaitListInfoShared, context);

                // create new WL for un-colo AO
                courseWaitListInfo.setId(null);
                courseWaitListInfo.setActivityOfferingIds(new ArrayList<String>());
                courseWaitListInfo.setFormatOfferingIds(new ArrayList<String>());
                courseWaitListInfo.getActivityOfferingIds().add(aoId);
                courseWaitListInfo.getFormatOfferingIds().add(foId);
                courseWaitListInfo = getCourseWaitListService().createCourseWaitList(
                        CourseWaitListServiceConstants.COURSE_WAIT_LIST_WAIT_TYPE_KEY, courseWaitListInfo, context);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return courseWaitListInfo;
    }

    public CourseWaitListInfo createColocatedWaitList(CourseWaitListInfo courseWaitListInfo, String waitlistType,
            boolean hasWaitlist, boolean limitWaitlistSize, boolean isColocatedAO, boolean isMaxEnrollmentShared,
            HashMap<String, String> aoIdfoIdMap, ContextInfo context) {

        setAutoProcConfReq(courseWaitListInfo, waitlistType, hasWaitlist, limitWaitlistSize);

        try {
            // whether co-locating or already existing colos
            if (!aoIdfoIdMap.isEmpty() && isColocatedAO) {
                CourseWaitListInfo courseWaitListInfoShared = new CourseWaitListInfo();
                if (!isMaxEnrollmentShared) { // want to keep old params for the new WLs for the rest of AOs
                    courseWaitListInfoShared = getCourseWaitListService()
                            .getCourseWaitList(courseWaitListInfo.getId(), context);
                }

                for (Map.Entry<String, String> entry : aoIdfoIdMap.entrySet()) {
                    String aoId = entry.getKey();
                    String foId = entry.getValue();
                    if (isMaxEnrollmentShared) { // create shared WL
                        // Put WL logic in. Shared WL ONLY when max enrollement is shared. Adding new aoID and foID(s)
                        if (!courseWaitListInfo.getActivityOfferingIds().contains(aoId)) {
                            courseWaitListInfo.getActivityOfferingIds().add(aoId);
                            // Delete WL for other AO
                            List<CourseWaitListInfo> courseWaitLists = getCourseWaitListService()
                                    .getCourseWaitListsByActivityOffering(aoId, context);
                            for (CourseWaitListInfo courseWaitlist : courseWaitLists) {
                                if (!StringUtils.equals(courseWaitlist.getId(), courseWaitListInfo.getId())) {
                                    getCourseWaitListService().deleteCourseWaitList(courseWaitlist.getId(),
                                            context);
                                }
                            }
                        }
                        if (!courseWaitListInfo.getFormatOfferingIds().contains(foId)) {
                            courseWaitListInfo.getFormatOfferingIds().add(foId);
                        }
                    } else { // each AO keeps (or have to split shared) own WL
                        if (courseWaitListInfo.getActivityOfferingIds().contains(aoId)) {
                            courseWaitListInfo.getActivityOfferingIds().remove(aoId);
                            // creating new (non-shared) WL per AO
                            CourseWaitListInfo courseWaitListInfoNew = new CourseWaitListInfo(
                                    courseWaitListInfoShared);
                            courseWaitListInfoNew.setId(null);
                            courseWaitListInfoNew.setActivityOfferingIds(new ArrayList<String>());
                            courseWaitListInfoNew.setFormatOfferingIds(new ArrayList<String>());
                            courseWaitListInfoNew.getActivityOfferingIds().add(aoId);
                            courseWaitListInfoNew.getFormatOfferingIds().add(foId);
                            getCourseWaitListService().createCourseWaitList(
                                    CourseWaitListServiceConstants.COURSE_WAIT_LIST_WAIT_TYPE_KEY,
                                    courseWaitListInfoNew, context);
                        }
                        if (courseWaitListInfo.getFormatOfferingIds().contains(foId)) {
                            courseWaitListInfo.getFormatOfferingIds().remove(foId);
                        }
                    }
                }
            }

            courseWaitListInfo = getCourseWaitListService().updateCourseWaitList(courseWaitListInfo.getId(),
                    courseWaitListInfo, context);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return courseWaitListInfo;
    }

    private void setAutoProcConfReq(CourseWaitListInfo courseWaitListInfo, String waitlistType, boolean hasWaitlist,
            boolean limitWaitlistSize) {

        if (waitlistType != null) {
            if (waitlistType.equals(LuiServiceConstants.AUTOMATIC_WAITLIST_TYPE_KEY)) {
                courseWaitListInfo.setAutomaticallyProcessed(true);
                courseWaitListInfo.setConfirmationRequired(false);
            } else if (waitlistType.equals(LuiServiceConstants.CONFIRMATION_WAITLIST_TYPE_KEY)) {
                courseWaitListInfo.setAutomaticallyProcessed(true);
                courseWaitListInfo.setConfirmationRequired(true);
            } else if (waitlistType.equals(LuiServiceConstants.MANUAL_WAITLIST_TYPE_KEY)) {
                courseWaitListInfo.setAutomaticallyProcessed(false);
                courseWaitListInfo.setConfirmationRequired(false);
            }
        }

        if (hasWaitlist) {
            courseWaitListInfo.setStateKey(CourseWaitListServiceConstants.COURSE_WAIT_LIST_ACTIVE_STATE_KEY);
        } else {
            courseWaitListInfo.setStateKey(CourseWaitListServiceConstants.COURSE_WAIT_LIST_INACTIVE_STATE_KEY);
        }

        if (!limitWaitlistSize) {
            courseWaitListInfo.setMaxSize(null);
        }
    }

    public ActivityOfferingResult updateRegistrationGroups(ActivityOfferingInfo activityOfferingInfo,
            ContextInfo context) throws PermissionDeniedException, DataValidationErrorException,
            InvalidParameterException, ReadOnlyException, OperationFailedException, MissingParameterException,
            DoesNotExistException, VersionMismatchException {
        try {
            ActivityOfferingResult aoResult = new ActivityOfferingResult();
            aoResult.setCreatedActivityOffering(activityOfferingInfo);

            // fetch the associated RGs
            String aoId = activityOfferingInfo.getId();
            List<RegistrationGroupInfo> rgs = coService.getRegistrationGroupsByActivityOffering(aoId, context);

            if (rgs != null && !rgs.isEmpty()) {
                //fetch the associated AOC
                ActivityOfferingClusterInfo cluster = getActivityOfferingClusterDao()
                        .getByActivityOffering(activityOfferingInfo.getId()).toDto();

                if (cluster != null) {
                    // Make sure FO IDs match up
                    if (!cluster.getFormatOfferingId().equals(activityOfferingInfo.getFormatOfferingId())) {
                        throw new DataValidationErrorException("Format Offering Ids do not match");
                    }

                    //check the max enroll number of the AOC
                    BulkStatusInfo clusterStatusInfo = new BulkStatusInfo();
                    clusterStatusInfo.setId(cluster.getId());
                    clusterStatusInfo.setSuccess(true);

                    List<ValidationResultInfo> validations = coService.validateActivityOfferingCluster(
                            DataDictionaryValidator.ValidationType.FULL_VALIDATION.toString(),
                            activityOfferingInfo.getFormatOfferingId(), cluster, context);
                    for (ValidationResultInfo validation : validations) {
                        if (validation.isWarn()) {
                            // If any validation is an error, then make this invalid
                            clusterStatusInfo.setSuccess(false);
                            break;
                        }
                    }
                    aoResult.setClusterstatus(clusterStatusInfo);

                }

                //check time conflict
                List<BulkStatusInfo> rgStatusInfos = new ArrayList<BulkStatusInfo>(rgs.size());

                for (RegistrationGroupInfo rgInfo : rgs) {
                    BulkStatusInfo rgStatusInfo = new BulkStatusInfo();
                    rgStatusInfo.setId(rgInfo.getId());
                    rgStatusInfo.setSuccess(true);

                    //Canceled state handling is out of scope. We ignore the state for now
                    // TODO: KSENROLL-9934 this checking will be removed when canceled state handling is available.
                    if (!rgInfo.getStateKey().equals(LuiServiceConstants.REGISTRATION_GROUP_CANCELED_STATE_KEY)) {
                        if (_areRegistrationGroupAoIdsValid(rgInfo.getActivityOfferingIds(), context)) {
                            // We don't know what the next state should be for this registration group, but the state
                            // service kinda knows. So, try to send it to the highest state, offered.
                            // If that doesn't work, try to send it to pending
                            // if that doesn't work, the state change has failed and log. (suspended aos should not get moved.
                            if (!(coService
                                    .changeRegistrationGroupState(rgInfo.getId(),
                                            LuiServiceConstants.REGISTRATION_GROUP_OFFERED_STATE_KEY, context)
                                    .getIsSuccess()
                                    || coService.changeRegistrationGroupState(rgInfo.getId(),
                                            LuiServiceConstants.REGISTRATION_GROUP_PENDING_STATE_KEY, context)
                                            .getIsSuccess())) {
                                LOGGER.warn("State change failed for RG: {} From state: {}", rgInfo.getId(),
                                        rgInfo.getStateKey());
                            }
                        } else {
                            coService.changeRegistrationGroupState(rgInfo.getId(),
                                    LuiServiceConstants.REGISTRATION_GROUP_INVALID_STATE_KEY, context);
                            rgStatusInfo.setSuccess(false);
                        }
                    }

                    rgStatusInfos.add(rgStatusInfo);
                }
                aoResult.setGeneratedRegistrationGroups(rgStatusInfos);

            }
            return aoResult;

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    public ActivityOfferingResult updateActivityOffering(ActivityOfferingInfo aoInfo, ContextInfo context)
            throws PermissionDeniedException, DataValidationErrorException, InvalidParameterException,
            ReadOnlyException, OperationFailedException, MissingParameterException, DoesNotExistException,
            VersionMismatchException {
        try {
            //update AO
            ActivityOfferingInfo activityOfferingInfo = coService.updateActivityOffering(aoInfo.getId(), aoInfo,
                    context);
            return this.updateRegistrationGroups(activityOfferingInfo, context);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private boolean _areRegistrationGroupAoIdsValid(List<String> aoIds, ContextInfo context) {
        try {
            List<ValidationResultInfo> validations = courseOfferingServiceExtender.verifyRegistrationGroup(aoIds,
                    context);
            for (ValidationResultInfo validation : validations) {
                if (!validation.isOk()) {
                    // If any validation is an error, then make this invalid
                    return false;
                }
            }
            return true;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    public void deleteActivityOfferingCascaded(String aoId, ContextInfo context) throws PermissionDeniedException,
            MissingParameterException, InvalidParameterException, OperationFailedException, DoesNotExistException {
        coService.deleteActivityOfferingCascaded(aoId, context);
    }

    @Override
    public List<BulkStatusInfo> moveActivityOffering(String aoId, String sourceAocId, String targetAocId,
            ContextInfo context) throws PermissionDeniedException, MissingParameterException,
            InvalidParameterException, OperationFailedException, DoesNotExistException, ReadOnlyException,
            DataValidationErrorException, VersionMismatchException {
        if (coService == null) {
            coService = getCoService();
        }
        // Fetch the AOInfo
        ActivityOfferingInfo aoInfo = coService.getActivityOffering(aoId, context);
        // Fetch the source AOC
        ActivityOfferingClusterInfo sourceAoc = coService.getActivityOfferingCluster(sourceAocId, context);
        // Verify that aoId is in this AOC
        ActivityOfferingSetInfo setWithAoId = null;
        for (ActivityOfferingSetInfo set : sourceAoc.getActivityOfferingSets()) {
            if (set.getActivityOfferingType().equals(aoInfo.getTypeKey())
                    && set.getActivityOfferingIds().contains(aoId)) {
                setWithAoId = set;
                break;
            }
        }
        if (setWithAoId == null) {
            // Not in source AOC
            throw new InvalidParameterException("aoId, " + aoId + ", does not appear in cluster, " + sourceAocId);
        }
        // Fetch target AOC
        ActivityOfferingClusterInfo targetAoc = coService.getActivityOfferingCluster(targetAocId, context);
        // Verify the FOs of source/target match up
        if (!sourceAoc.getFormatOfferingId().equals(targetAoc.getFormatOfferingId())) {
            throw new InvalidParameterException("Source/target AOCs do not have matching format offering IDs");
        }
        // Also, check for trivial case of the source/target AOC being identical
        if (sourceAocId.equals(targetAocId)) {
            return null; // exit if same
        }
        setWithAoId.getActivityOfferingIds().remove(aoId); // Delete the AO ID
        // This will delete RGs
        coService.updateActivityOfferingCluster(aoInfo.getFormatOfferingId(), sourceAocId, sourceAoc, context);
        // Now, add the AO ID to the target AOC
        boolean inserted = false;
        for (ActivityOfferingSetInfo set : targetAoc.getActivityOfferingSets()) {
            // Pick first AO set with matching type
            if (set.getActivityOfferingType().equals(aoInfo.getTypeKey())) {
                if (set.getActivityOfferingIds().contains(aoId)) {
                    throw new OperationFailedException("aoId already exists in target AOC--shouldn't happen");
                }
                set.getActivityOfferingIds().add(aoId);
                inserted = true;
                break;
            }
        }
        if (!inserted) {
            // Didn't actually add it
            throw new OperationFailedException(
                    "Unable to find AO set in target AOC with correct type: " + aoInfo.getTypeKey());
        }
        // update target AOC
        ActivityOfferingClusterInfo updated = coService.updateActivityOfferingCluster(aoInfo.getFormatOfferingId(),
                targetAocId, targetAoc, context);
        // Generate missing RGs
        if (_clusterCanGenerateRgs(updated)) {
            List<BulkStatusInfo> created = coService.generateRegistrationGroupsForCluster(updated.getId(), context);
            return created; //To change body of implemented methods use File | Settings | File Templates.
        } else {
            return new ArrayList<BulkStatusInfo>(); // Unable to generate so return empty list
        }
    }

    @Override
    public void deleteActivityOfferingCluster(String aocId, ContextInfo context)
            throws PermissionDeniedException, MissingParameterException, InvalidParameterException,
            OperationFailedException, DoesNotExistException, DependentObjectsExistException {
        List<ActivityOfferingInfo> aoInfos = coService.getActivityOfferingsByCluster(aocId, context);
        coService.deleteActivityOfferingClusterCascaded(aocId, context);
        // Delete each AO
        for (ActivityOfferingInfo aoInfo : aoInfos) {
            // get seat pools to delete
            List<SeatPoolDefinitionInfo> seatPools = coService
                    .getSeatPoolDefinitionsForActivityOffering(aoInfo.getId(), context);

            // remove seat pool reference  to AO then delete orphaned seat pool
            for (SeatPoolDefinitionInfo seatPool : seatPools) {
                coService.removeSeatPoolDefinitionFromActivityOffering(seatPool.getId(), aoInfo.getId(), context);
                coService.deleteSeatPoolDefinition(seatPool.getId(), context);
            }

            // delete AO
            coService.deleteActivityOffering(aoInfo.getId(), context);
        }
    }

    /* (non-Javadoc)
     * @see org.kuali.student.enrollment.class2.courseoffering.service.applayer.CourseOfferingServiceFacade#getMaxEnrollmentByCourseOffering(java.lang.String, org.kuali.student.r2.common.dto.ContextInfo)
     */
    @Override
    public Integer getSeatCountByCourseOffering(String courseOfferingId, ContextInfo contextInfo)
            throws OperationFailedException, PermissionDeniedException {

        try {
            CourseOfferingInfo co = coService.getCourseOffering(courseOfferingId, contextInfo);

            return co.getMaximumEnrollment();
        } catch (DoesNotExistException e) {
            throw new OperationFailedException(
                    "getSeatCountByCourseOffering (courseOfferingId=" + courseOfferingId + "): failed", e);
        } catch (InvalidParameterException e) {
            throw new OperationFailedException(
                    "getSeatCountByCourseOffering (courseOfferingId=" + courseOfferingId + "): failed", e);
        } catch (MissingParameterException e) {
            throw new OperationFailedException(
                    "getSeatCountByCourseOffering (courseOfferingId=" + courseOfferingId + "): failed", e);
        }

        //        try {
        //            List<ActivityOfferingInfo> aos = coService.getActivityOfferingsByCourseOffering(courseOfferingId, contextInfo);
        //            
        //            return _computeMaxEnrollment(aos);
        //        } catch (DoesNotExistException e) {
        //           throw new OperationFailedException("getSeatCountByCourseOffering (courseOfferingId=" + courseOfferingId + "): failed", e);
        //           
        //        } catch (InvalidParameterException e) {
        //            throw new OperationFailedException("getSeatCountByCourseOffering (courseOfferingId=" + courseOfferingId + "): failed", e);
        //        } catch (MissingParameterException e) {
        //            throw new OperationFailedException("getSeatCountByCourseOffering (courseOfferingId=" + courseOfferingId + "): failed", e);
        //        }
    }

    /* (non-Javadoc)
     * @see org.kuali.student.enrollment.class2.courseoffering.service.applayer.CourseOfferingServiceFacade#getMaxEnrollmentByActivityOfferingCluster(java.lang.String, org.kuali.student.r2.common.dto.ContextInfo)
     */
    @Override
    public Integer getSeatCountByActivityOfferingCluster(String aocId, ContextInfo contextInfo)
            throws OperationFailedException, PermissionDeniedException {

        try {
            List<ActivityOfferingInfo> aos = coService.getActivityOfferingsByCluster(aocId, contextInfo);

            Map<String, ActivityOfferingInfo> aoMap = new HashMap<String, ActivityOfferingInfo>();

            for (ActivityOfferingInfo activityOfferingInfo : aos) {
                aoMap.put(activityOfferingInfo.getId(), activityOfferingInfo);
            }

            ActivityOfferingClusterInfo aoc = coService.getActivityOfferingCluster(aocId, contextInfo);
            List<ActivityOfferingSetInfo> aoSets = aoc.getActivityOfferingSets();
            int maxAOCEnrollment = Integer.MAX_VALUE;

            for (ActivityOfferingSetInfo activityOfferingSetInfo : aoSets) {
                int maxActivityTypeEnrollment = _computeMaxEnrollment(
                        activityOfferingSetInfo.getActivityOfferingIds(), aoMap);

                if (maxActivityTypeEnrollment < maxAOCEnrollment) {
                    maxAOCEnrollment = maxActivityTypeEnrollment;
                }
            }

            FormatOfferingInfo fo = coService.getFormatOffering(aoc.getFormatOfferingId(), contextInfo);
            CourseOfferingInfo co = coService.getCourseOffering(fo.getCourseOfferingId(), contextInfo);
            int maxCOEnrollment = co.getMaximumEnrollment();

            // This assumes that the seat count for an aoc is the smallest ao.maxEnrollment number in this cluster.
            List<RegistrationGroupInfo> rgs = coService.getRegistrationGroupsByActivityOfferingCluster(aocId,
                    contextInfo);

            int maxAOEnrollment = _computeMaxEnrollment(aos);

            // cap is the smaller of the CO or AO or AOC enrollment limit
            int maxEnrollment = Math.min(Math.min(maxCOEnrollment, maxAOEnrollment), maxAOCEnrollment);
            int minEnrollment = Integer.MAX_VALUE;

            for (RegistrationGroupInfo registrationGroupInfo : rgs) {
                List<ActivityOfferingInfo> rgAOList = new ArrayList<ActivityOfferingInfo>();

                for (String aoId : registrationGroupInfo.getActivityOfferingIds()) {
                    rgAOList.add(aoMap.get(aoId));
                }

                int currentSeats = _computeMaxEnrollment(rgAOList);
                if (minEnrollment > currentSeats) {
                    minEnrollment = currentSeats;
                }
            }

            // actual seats can be smaller but not larger than max enrollment. 
            return Math.min(minEnrollment, maxEnrollment);

        } catch (DoesNotExistException e) {
            throw new OperationFailedException(
                    "getSeatCountByActivityOfferingCluster (aocId=" + aocId + "): failed", e);
        } catch (InvalidParameterException e) {
            throw new OperationFailedException(
                    "getSeatCountByActivityOfferingCluster (aocId=" + aocId + "): failed", e);
        } catch (MissingParameterException e) {
            throw new OperationFailedException(
                    "getSeatCountByActivityOfferingCluster (aocId=" + aocId + "): failed", e);
        }

    }

    private Integer _computeMaxEnrollment(List<String> aoIds, Map<String, ActivityOfferingInfo> aoMap) {

        List<ActivityOfferingInfo> aoList = new ArrayList<ActivityOfferingInfo>();
        for (String aoId : aoIds) {
            aoList.add(aoMap.get(aoId));
        }
        return _computeMaxEnrollment(aoList);

    }

    /*
     * The maxEnrollment of a list of Activity Offering's is the smallest max enrollment number.
     * 
     * This method will extract that number.
     * 
     */
    private Integer _computeMaxEnrollment(List<ActivityOfferingInfo> aos) {

        int minEnrollment = Integer.MAX_VALUE;

        for (ActivityOfferingInfo activityOfferingInfo : aos) {
            Integer maxEnrollment = activityOfferingInfo.getMaximumEnrollment();
            if (maxEnrollment != null) {
                if (minEnrollment > maxEnrollment) {
                    minEnrollment = maxEnrollment;
                }
            }
        }

        if (minEnrollment == Integer.MAX_VALUE) {
            // a data error that none of the AO's have a max enrollment specified
            return null;
        } else {
            return minEnrollment;
        }

    }

    /* (non-Javadoc)
     * @see org.kuali.student.enrollment.class2.courseoffering.service.applayer.CourseOfferingServiceFacade#getMaxEnrollmentByRegistrationGroup(java.lang.String, org.kuali.student.r2.common.dto.ContextInfo)
     */
    @Override
    public Integer getSeatCountByRegistrationGroup(String registrationGroupId, ContextInfo contextInfo)
            throws OperationFailedException, PermissionDeniedException {

        try {
            RegistrationGroupInfo rg = coService.getRegistrationGroup(registrationGroupId, contextInfo);

            List<ActivityOfferingInfo> aos = coService.getActivityOfferingsByIds(rg.getActivityOfferingIds(),
                    contextInfo);

            return _computeMaxEnrollment(aos);
        } catch (DoesNotExistException e) {
            throw new OperationFailedException(
                    "getSeatCountByRegistrationGroup (registrationGroupId=" + registrationGroupId + "): failed", e);
        } catch (InvalidParameterException e) {
            throw new OperationFailedException(
                    "getSeatCountByRegistrationGroup (registrationGroupId=" + registrationGroupId + "): failed", e);
        } catch (MissingParameterException e) {
            throw new OperationFailedException(
                    "getSeatCountByRegistrationGroup (registrationGroupId=" + registrationGroupId + "): failed", e);
        }
    }

    private Set<Set<String>> _convertToSetOfSetOfStrings(Set<List<String>> setOfRgAoIdLists) {
        Set<Set<String>> rgAoIdSets = new HashSet<Set<String>>();
        for (List<String> rgAoIds : setOfRgAoIdLists) {
            rgAoIdSets.add(new HashSet<String>(rgAoIds));
        }
        return rgAoIdSets;
    }

    private Set<Set<String>> _generatePotentialRgAoIdSets(ActivityOfferingClusterInfo cluster) {
        try {
            Set<List<String>> setOfRgAoIdLists = PermutationCounter.computeMissingRegGroupAoIdsInCluster(cluster,
                    new ArrayList<RegistrationGroupInfo>());
            Set<Set<String>> setOfRgAoIdSets = _convertToSetOfSetOfStrings(setOfRgAoIdLists);
            return setOfRgAoIdSets;
        } catch (DataValidationErrorException e) {
            return null;
        }
    }

    @Override
    public List<CourseOfferingAutogenIssue> findAutogenIssuesByTerm(String termId, ContextInfo context)
            throws PermissionDeniedException, MissingParameterException, InvalidParameterException,
            OperationFailedException, DoesNotExistException {
        List<String> coIds = coService.getCourseOfferingIdsByTerm(termId, Boolean.TRUE, context);
        List<CourseOfferingAutogenIssue> termCoIssues = new ArrayList<CourseOfferingAutogenIssue>();
        for (String coId : coIds) {
            CourseOfferingAutogenIssue coIssue = findAutogenIssuesByCourseOffering(coId, context);
            if (coIssue != null) {
                termCoIssues.add(coIssue);
            }
        }
        return termCoIssues;
    }

    @Override
    public CourseOfferingAutogenIssue findAutogenIssuesByCourseOffering(String courseOfferingId,
            ContextInfo context) throws PermissionDeniedException, MissingParameterException,
            InvalidParameterException, OperationFailedException, DoesNotExistException {
        List<FormatOfferingInfo> fos = coService.getFormatOfferingsByCourseOffering(courseOfferingId, context);
        CourseOfferingAutogenIssue coIssue = new CourseOfferingAutogenIssue(courseOfferingId);
        for (FormatOfferingInfo fo : fos) {
            String foId = fo.getId();
            FormatOfferingAutogenIssue foIssue = new FormatOfferingAutogenIssue(foId);
            List<ActivityOfferingInfo> aoInfos = coService.getActivityOfferingsByFormatOffering(fo.getId(),
                    context);
            Set<String> aoIdSet = new HashSet<String>();
            for (ActivityOfferingInfo ao : aoInfos) {
                aoIdSet.add(ao.getId());
            }
            // Find AOs without clusters
            List<ActivityOfferingInfo> aosWoClusters = coService
                    .getActivityOfferingsWithoutClusterByFormatOffering(foId, context);
            // Gather only the IDs
            Set<String> aoIdsWoClusters = new HashSet<String>();
            for (ActivityOfferingInfo aoInfo : aosWoClusters) {
                aoIdsWoClusters.add(aoInfo.getId());
            }
            // Then create issues associated with it
            if (!aoIdsWoClusters.isEmpty()) {
                ActivityOfferingNotInAocSubissue aoNotInAoc = new ActivityOfferingNotInAocSubissue(courseOfferingId,
                        fo.getId());
                aoNotInAoc.getActivityOfferingIds().addAll(aoIdsWoClusters);
                foIssue.getSubIssues().add(aoNotInAoc); // Add the issue
            }
            // --------------------
            // Now verify RGs have correct AOs
            // First create a map
            List<Set<String>> aocAoIdList = new ArrayList<Set<String>>();
            List<ActivityOfferingClusterInfo> clusters = coService.getActivityOfferingClustersByFormatOffering(foId,
                    context);
            Set<Set<String>> possibleRgAOIds = new HashSet<Set<String>>();
            Set<Set<String>> actualRgAOIds = new HashSet<Set<String>>();
            for (ActivityOfferingClusterInfo cluster : clusters) {
                Set<String> clusterAoIds = new HashSet<String>();
                for (ActivityOfferingSetInfo set : cluster.getActivityOfferingSets()) {
                    clusterAoIds.addAll(set.getActivityOfferingIds());
                }
                aocAoIdList.add(clusterAoIds);
                Set<Set<String>> possibleRgAoIdsByAoc = _generatePotentialRgAoIdSets(cluster);
                // Add to list of possible RgAOIds for this format offering
                possibleRgAOIds.addAll(possibleRgAoIdsByAoc);
            }
            // Now go through the RGs to check for invalid ones
            List<RegistrationGroupInfo> rgInfos = coService.getRegistrationGroupsByFormatOffering(fo.getId(),
                    context);
            for (RegistrationGroupInfo rg : rgInfos) {
                boolean found = false;
                List<String> rgAoIds = rg.getActivityOfferingIds();
                for (Set<String> aocAoIds : aocAoIdList) {
                    if (aocAoIds.containsAll(rgAoIds)) {
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    // No AOC contains this RG
                    InvalidRegGroupSubissue rgIssue = new InvalidRegGroupSubissue(courseOfferingId, foId);
                    foIssue.getSubIssues().add(rgIssue); // Add the issue
                } else {
                    // Valid RG, store that info
                    Set<String> rgAOIds = new HashSet<String>(rg.getActivityOfferingIds());
                    actualRgAOIds.add(rgAOIds);
                }
            }
            // Now find RGs that should have been created, but weren't
            possibleRgAOIds.removeAll(actualRgAOIds);
            Set<Set<String>> missingRgAOIds // renaming to make it easier to see what's going on
                    = new HashSet<Set<String>>(possibleRgAOIds);
            for (Set<String> rgAoIdSet : missingRgAOIds) {
                // Create an issue
                RegGroupNotGeneratedByAocSubissue subissue = new RegGroupNotGeneratedByAocSubissue(courseOfferingId,
                        foId);
                subissue.getActivityOfferingIds().addAll(rgAoIdSet);
                foIssue.getSubIssues().add(subissue);
            }
            if (!foIssue.getSubIssues().isEmpty()) {
                coIssue.getFormatOfferingIssues().add(foIssue);
            }
        }
        if (coIssue.getFormatOfferingIssues().isEmpty()) {
            return null;
        } else {
            return coIssue;
        }
    }

    /* (non-Javadoc)
     * @see org.kuali.student.enrollment.class2.courseoffering.service.adapter.CourseOfferingServiceFacade#getAutogenCountByCourseOffering(java.lang.String, org.kuali.student.r2.common.dto.ContextInfo)
     */
    @Override
    public AutogenCount getAutogenCountByCourseOffering(String courseOfferingId, ContextInfo context)
            throws DoesNotExistException, InvalidParameterException, MissingParameterException,
            OperationFailedException, PermissionDeniedException {

        SearchRequestInfo request = new SearchRequestInfo(CourseOfferingServiceConstants.AUTOGEN_COUNTS_BY_CO);

        request.addParam(CourseOfferingServiceConstants.AUTOGEN_COUNTS_BY_CO_ID_PARAM, courseOfferingId);

        return runAutogenCountSearch(request, context);
    }

    /*
     * Helper to dispatch the search request and accumulate the results into an AutogenCount object.
     */
    private AutogenCount runAutogenCountSearch(SearchRequestInfo request, ContextInfo context)
            throws MissingParameterException, InvalidParameterException, OperationFailedException,
            PermissionDeniedException, DoesNotExistException {

        request.setMaxResults(1);

        SearchResultInfo results = getSearchService().search(request, context);

        if (results.getRows().size() == 0) {
            // handle the no match case (This probably should never happen but this guards against such a case causing problems)
            throw new DoesNotExistException("No Results");
        }
        // else:
        SearchResultRowInfo row = KSCollectionUtils.getRequiredZeroElement(results.getRows());

        AutogenCount count = new AutogenCount();

        // setup the defaults
        count.setNumberOfActivityOfferingClusters(0);
        count.setNumberOfActivityOfferings(0);
        count.setNumberOfInvalidRegistrationGroups(0);
        count.setNumberOfRegistrationGroups(0);

        List<SearchResultCellInfo> cells = row.getCells();

        for (SearchResultCellInfo cellInfo : cells) {
            String cellKey = cellInfo.getKey();

            if (cellKey.equals(CourseOfferingServiceConstants.AUTOGEN_COUNTS_TOTAL_AOS)) {
                count.setNumberOfActivityOfferings(Integer.parseInt(cellInfo.getValue()));
            } else if (cellKey.equals(CourseOfferingServiceConstants.AUTOGEN_COUNTS_TOTAL_AOCS)) {
                count.setNumberOfActivityOfferingClusters(Integer.parseInt(cellInfo.getValue()));
            } else if (cellKey.equals(CourseOfferingServiceConstants.AUTOGEN_COUNTS_TOTAL_RGS)) {
                count.setNumberOfRegistrationGroups(Integer.parseInt(cellInfo.getValue()));
            } else if (cellKey.equals(CourseOfferingServiceConstants.AUTOGEN_COUNTS_TOTAL_INVALID_RGS)) {
                count.setNumberOfInvalidRegistrationGroups(Integer.parseInt(cellInfo.getValue()));
            }
        }

        return count;
    }

    /* (non-Javadoc)
     * @see org.kuali.student.enrollment.class2.courseoffering.service.adapter.CourseOfferingServiceFacade#getAutogenCountByFormatOffering(java.lang.String, org.kuali.student.r2.common.dto.ContextInfo)
     */
    @Override
    public AutogenCount getAutogenCountByFormatOffering(String formatOfferingId, ContextInfo context)
            throws MissingParameterException, InvalidParameterException, OperationFailedException,
            PermissionDeniedException, DoesNotExistException {

        SearchRequestInfo request = new SearchRequestInfo(CourseOfferingServiceConstants.AUTOGEN_COUNTS_BY_FO);

        request.addParam(CourseOfferingServiceConstants.AUTOGEN_COUNTS_BY_FO_ID_PARAM, formatOfferingId);

        return runAutogenCountSearch(request, context);
    }

    /* (non-Javadoc)
     * @see org.kuali.student.enrollment.class2.courseoffering.service.adapter.CourseOfferingServiceFacade#getAutogenCountByActivtyOfferingCluster(java.lang.String, org.kuali.student.r2.common.dto.ContextInfo)
     */
    @Override
    public AutogenCount getAutogenCountByActivtyOfferingCluster(String activiyOfferingClusterId,
            ContextInfo context) throws MissingParameterException, InvalidParameterException,
            OperationFailedException, PermissionDeniedException, DoesNotExistException {

        SearchRequestInfo request = new SearchRequestInfo(CourseOfferingServiceConstants.AUTOGEN_COUNTS_BY_AOC);

        request.addParam(CourseOfferingServiceConstants.AUTOGEN_COUNTS_BY_AOC_ID_PARAM, activiyOfferingClusterId);

        return runAutogenCountSearch(request, context);

    }

    /**
     * Returns all ActivityOfferingClusterInfos that map back to a single course offering
     *
     * @param courseOfferingId CO id search param
     * @return all ActivityOfferingClusterInfos that map back to a single course offering
     */
    @Override
    public List<ActivityOfferingClusterInfo> getActivityOfferingClusterByCourseOffering(String courseOfferingId) {

        List<ActivityOfferingClusterInfo> lRet = new ArrayList<ActivityOfferingClusterInfo>();
        List<ActivityOfferingClusterEntity> aoClusters = getActivityOfferingClusterDao()
                .getByCourseOffering(courseOfferingId);

        for (ActivityOfferingClusterEntity aoc : aoClusters) {
            lRet.add(aoc.toDto());
        }
        return lRet;
    }

    public CourseOfferingService getCoService() {
        if (coService == null) {
            coService = GlobalResourceLoader.getService(new QName(CourseOfferingServiceConstants.NAMESPACE,
                    CourseOfferingServiceConstants.SERVICE_NAME_LOCAL_PART));
        }

        return coService;
    }

    public CourseWaitListService getCourseWaitListService() {
        if (courseWaitListService == null) {
            courseWaitListService = GlobalResourceLoader
                    .getService(new QName(CourseWaitListServiceConstants.NAMESPACE,
                            CourseWaitListServiceConstants.SERVICE_NAME_LOCAL_PART));
        }

        return courseWaitListService;
    }

    public void setCourseWaitListService(CourseWaitListService courseWaitListService) {
        this.courseWaitListService = courseWaitListService;
    }

    public void setCoService(CourseOfferingService coService) {
        this.coService = coService;
    }

    public TypeService getTypeService() {
        if (typeService == null) {
            QName qname = new QName(TypeServiceConstants.NAMESPACE, TypeServiceConstants.SERVICE_NAME_LOCAL_PART);
            typeService = GlobalResourceLoader.getService(qname);
        }
        return typeService;
    }

    public void setTypeService(TypeService typeService) {
        this.typeService = typeService;
    }

    public CourseService getCourseService() {
        if (courseService == null) {
            QName qname = new QName(CourseServiceConstants.NAMESPACE,
                    CourseServiceConstants.SERVICE_NAME_LOCAL_PART);
            courseService = GlobalResourceLoader.getService(qname);
        }
        return courseService;
    }

    public void setCourseService(CourseService courseService) {
        this.courseService = courseService;
    }

    public ActivityOfferingClusterDaoApi getActivityOfferingClusterDao() {
        return activityOfferingClusterDao;
    }

    public void setActivityOfferingClusterDao(ActivityOfferingClusterDaoApi activityOfferingClusterDao) {
        this.activityOfferingClusterDao = activityOfferingClusterDao;
    }

    public CourseWaitListServiceFacade getWaitListServiceFacade() {
        return waitListServiceFacade;
    }

    public void setWaitListServiceFacade(CourseWaitListServiceFacade waitListServiceFacade) {
        this.waitListServiceFacade = waitListServiceFacade;
    }

    public ExamOfferingServiceFacade getExamOfferingServiceFacade() {
        return examOfferingServiceFacade;
    }

    public void setExamOfferingServiceFacade(ExamOfferingServiceFacade examOfferingServiceFacade) {
        this.examOfferingServiceFacade = examOfferingServiceFacade;
    }

    public SearchService getSearchService() {
        return searchService;
    }

    public void setSearchService(SearchService searchService) {
        this.searchService = searchService;
    }

    public void setCourseOfferingServiceExtender(CourseOfferingServiceExtender courseOfferingServiceExtender) {
        this.courseOfferingServiceExtender = courseOfferingServiceExtender;
    }

    public CourseOfferingServiceExtender getCourseOfferingServiceExtender() {
        return courseOfferingServiceExtender;
    }
}