/// <summary>
        /// Gets future occurrence data for the selected group (including all scheduled dates).
        /// </summary>
        /// <param name="group">The group.</param>
        /// <param name="toDateTime">To date time.  If not supplied, this will default to 6 months from the current date.</param>
        /// <param name="locationIds">The location ids.</param>
        /// <param name="scheduleIds">The schedule ids.</param>
        /// <returns></returns>
        public List <AttendanceOccurrence> GetFutureGroupOccurrences(Group group, DateTime?toDateTime, string locationIds = null, string scheduleIds = null)
        {
            var locationIdList = new List <int>();

            if (!string.IsNullOrWhiteSpace(locationIds))
            {
                locationIdList = locationIds.Split(',').Select(int.Parse).ToList();
            }

            var scheduleIdList = new List <int>();

            if (!string.IsNullOrWhiteSpace(scheduleIds))
            {
                scheduleIdList = scheduleIds.Split(',').Select(int.Parse).ToList();
            }

            var qry = Queryable("Group,Schedule").AsNoTracking().Where(a => a.GroupId == group.Id);

            // Filter by date range
            var fromDate = RockDateTime.Now.Date;
            var toDate   = fromDate.AddMonths(6); // Default to 6 months in the future.

            if (toDateTime.HasValue)
            {
                toDate = toDateTime.Value.Date;
            }
            qry = qry
                  .Where(a => a.OccurrenceDate >= (fromDate))
                  .Where(a => a.OccurrenceDate < (toDate));

            // Location Filter
            if (locationIdList.Any())
            {
                qry = qry.Where(a => locationIdList.Contains(a.LocationId ?? 0));
            }

            // Schedule Filter
            if (scheduleIdList.Any())
            {
                qry = qry.Where(a => scheduleIdList.Contains(a.ScheduleId ?? 0));
            }

            var occurrences = qry.ToList();

            // Create any missing occurrences from the group's schedule (not location schedules)
            Schedule groupSchedule = null;

            if (group.ScheduleId.HasValue)
            {
                groupSchedule = group.Schedule ?? new ScheduleService(( RockContext )Context).Get(group.ScheduleId.Value);
            }

            if (groupSchedule == null)
            {
                return(occurrences);
            }

            var newOccurrences = new List <AttendanceOccurrence>();

            var existingDates = occurrences
                                .Where(o => o.ScheduleId.Equals(groupSchedule.Id))
                                .Select(o => o.OccurrenceDate.Date)
                                .Distinct()
                                .ToList();

            if (!string.IsNullOrWhiteSpace(groupSchedule.iCalendarContent))
            {
                // If schedule has an iCal schedule, get all the past occurrences
                foreach (var occurrence in groupSchedule.GetICalOccurrences(fromDate, toDate))
                {
                    var newOccurrence = new AttendanceOccurrence
                    {
                        OccurrenceDate = occurrence.Period.StartTime.Date,
                        GroupId        = group.Id,
                        Group          = group,
                        ScheduleId     = groupSchedule.Id,
                        Schedule       = groupSchedule
                    };

                    if (existingDates.Contains(newOccurrence.OccurrenceDate.Date))
                    {
                        continue;
                    }

                    newOccurrences.Add(newOccurrence);
                    existingDates.Add(newOccurrence.OccurrenceDate.Date);
                }
            }
            else
            {
                // if schedule does not have an iCal, then check for weekly schedule and calculate occurrences starting with first attendance or current week
                if (groupSchedule.WeeklyDayOfWeek.HasValue)
                {
                    var startDate = fromDate;
                    // Move start time forward to the correct day of week.
                    while (startDate.DayOfWeek != groupSchedule.WeeklyDayOfWeek.Value)
                    {
                        startDate = startDate.AddDays(1);
                    }

                    // Add the start time
                    if (groupSchedule.WeeklyTimeOfDay.HasValue)
                    {
                        startDate = startDate.Add(groupSchedule.WeeklyTimeOfDay.Value);
                    }

                    // Create occurrences up to current time
                    while (startDate < toDate)
                    {
                        if (!existingDates.Contains(startDate.Date))
                        {
                            var newOccurrence = new AttendanceOccurrence
                            {
                                OccurrenceDate = startDate,
                                GroupId        = group.Id,
                                Group          = group,
                                ScheduleId     = groupSchedule.Id,
                                Schedule       = groupSchedule
                            };

                            newOccurrences.Add(newOccurrence);
                        }
                        startDate = startDate.AddDays(7);
                    }
                }
            }

            if (newOccurrences.Any())
            {
                // Filter Exclusions
                var groupType = GroupTypeCache.Get(group.GroupTypeId);
                foreach (var exclusion in groupType.GroupScheduleExclusions)
                {
                    if (!exclusion.Start.HasValue || !exclusion.End.HasValue)
                    {
                        continue;
                    }

                    foreach (var occurrence in newOccurrences.ToList())
                    {
                        if (occurrence.OccurrenceDate >= exclusion.Start.Value.Date &&
                            occurrence.OccurrenceDate < exclusion.End.Value.Date.AddDays(1))
                        {
                            newOccurrences.Remove(occurrence);
                        }
                    }
                }
            }

            foreach (var occurrence in newOccurrences)
            {
                occurrences.Add(occurrence);
            }

            occurrences = occurrences.OrderBy(o => o.OccurrenceDate).ToList();

            return(occurrences);
        }
        /// <summary>
        /// Gets occurrence data for the selected group.
        /// </summary>
        /// <param name="group">The group.</param>
        /// <param name="fromDateTime">From date time.</param>
        /// <param name="toDateTime">To date time.</param>
        /// <param name="locationIds">The location ids.</param>
        /// <param name="scheduleIds">The schedule ids.</param>
        /// <returns></returns>
        public List <AttendanceOccurrence> GetGroupOccurrences(Group group, DateTime?fromDateTime, DateTime?toDateTime, List <int> locationIds, List <int> scheduleIds)
        {
            var qry = Queryable().Where(a => a.GroupId == group.Id);

            // Filter by date range
            if (fromDateTime.HasValue)
            {
                var fromDate = fromDateTime.Value.Date;
                qry = qry.Where(a => a.OccurrenceDate >= (fromDate));
            }

            if (toDateTime.HasValue)
            {
                var toDate = toDateTime.Value.Date;
                qry = qry.Where(a => a.OccurrenceDate < (toDate));
            }

            // Location Filter
            if (locationIds.Any())
            {
                qry = qry.Where(a => locationIds.Contains(a.LocationId ?? 0));
            }

            // Schedule Filter
            if (scheduleIds.Any())
            {
                qry = qry.Where(a => scheduleIds.Contains(a.ScheduleId ?? 0));
            }

            var occurrences = qry.ToList();

            // Create any missing occurrences from the group's schedule (not location schedules)
            Schedule groupSchedule = null;

            if (group.ScheduleId.HasValue)
            {
                groupSchedule = group.Schedule ?? new ScheduleService(( RockContext )Context).Get(group.ScheduleId.Value);
            }

            if (groupSchedule == null)
            {
                return(occurrences);
            }

            var newOccurrences = new List <AttendanceOccurrence>();

            var existingDates = occurrences
                                .Where(o => o.ScheduleId.Equals(groupSchedule.Id))
                                .Select(o => o.OccurrenceDate.Date)
                                .Distinct()
                                .ToList();

            var startDate = fromDateTime ?? RockDateTime.Today.AddMonths(-2);
            var endDate   = toDateTime ?? RockDateTime.Today.AddDays(1);

            if (!string.IsNullOrWhiteSpace(groupSchedule.iCalendarContent))
            {
                // If schedule has an iCal schedule, get all the past occurrences
                foreach (var occurrence in groupSchedule.GetICalOccurrences(startDate, endDate))
                {
                    var newOccurrence = new AttendanceOccurrence
                    {
                        OccurrenceDate = occurrence.Period.StartTime.Date,
                        GroupId        = group.Id,
                        Group          = group,
                        ScheduleId     = groupSchedule.Id,
                        Schedule       = groupSchedule
                    };

                    if (existingDates.Contains(newOccurrence.OccurrenceDate.Date))
                    {
                        continue;
                    }

                    newOccurrences.Add(newOccurrence);
                    existingDates.Add(newOccurrence.OccurrenceDate.Date);
                }
            }
            else
            {
                // if schedule does not have an iCal, then check for weekly schedule and calculate occurrences starting with first attendance or current week
                if (groupSchedule.WeeklyDayOfWeek.HasValue)
                {
                    // default to start with date 2 months earlier
                    startDate = fromDateTime ?? RockDateTime.Today.AddMonths(-2);
                    if (existingDates.Any(d => d < startDate))
                    {
                        startDate = existingDates.Min();
                    }

                    // Roll forward to the first start time that matches the day of the week.
                    while (startDate.DayOfWeek != groupSchedule.WeeklyDayOfWeek.Value)
                    {
                        startDate = startDate.AddDays(1);
                    }

                    // Add the start time
                    if (groupSchedule.WeeklyTimeOfDay.HasValue)
                    {
                        startDate = startDate.Add(groupSchedule.WeeklyTimeOfDay.Value);
                    }

                    // Create occurrences up to current time
                    while (startDate < endDate)
                    {
                        if (!existingDates.Contains(startDate.Date))
                        {
                            var newOccurrence = new AttendanceOccurrence
                            {
                                OccurrenceDate = startDate,
                                GroupId        = group.Id,
                                Group          = group,
                                ScheduleId     = groupSchedule.Id,
                                Schedule       = groupSchedule
                            };

                            newOccurrences.Add(newOccurrence);
                        }
                        startDate = startDate.AddDays(7);
                    }
                }
            }

            if (newOccurrences.Any())
            {
                // Filter Exclusions
                var groupType = GroupTypeCache.Get(group.GroupTypeId);
                foreach (var exclusion in groupType.GroupScheduleExclusions)
                {
                    if (!exclusion.Start.HasValue || !exclusion.End.HasValue)
                    {
                        continue;
                    }

                    foreach (var occurrence in newOccurrences.ToList())
                    {
                        if (occurrence.OccurrenceDate >= exclusion.Start.Value.Date &&
                            occurrence.OccurrenceDate < exclusion.End.Value.Date.AddDays(1))
                        {
                            newOccurrences.Remove(occurrence);
                        }
                    }
                }
            }

            foreach (var occurrence in newOccurrences)
            {
                occurrences.Add(occurrence);
            }

            return(occurrences);
        }