                                           eventsService: eventsService,
                                           shortFormDayAndTimeFormatter: shortFormDayAndTimeFormatter,
                                           hoursDateFormatter: hoursDateFormatter)
-
+        
         eventsService.add(self)
     }
-
+    
     // MARK: ScheduleInteractor
-
+    
     func makeViewModel(completionHandler: @escaping (ScheduleViewModel) -> Void) {
         completionHandler(viewModel)
     }
-
+    
     func makeSearchViewModel(completionHandler: @escaping (ScheduleSearchViewModel) -> Void) {
         completionHandler(searchViewModel)
     }
-
+    
     // MARK: EventsServiceObserver
-
+    
     func favouriteEventsDidChange(_ identifiers: [EventIdentifier]) {
         viewModel.favouriteEventsDidChange(identifiers)
         searchViewModel.favouriteEventsDidChange(identifiers)
     }
-
+    
     func eventsDidChange(to events: [Event]) {}
     func runningEventsDidChange(to events: [Event]) {}
     func upcomingEventsDidChange(to events: [Event]) {}
-
+    
     // MARK: Private
-
+    
     private class ViewModel: ScheduleViewModel, EventsScheduleDelegate, RefreshServiceObserver {
-
+        
         private struct EventsGroupedByDate {
             var date: Date
             var events: [Event]
-
+            
             func compare(against: EventsGroupedByDate) -> Bool {
                 return date < against.date
             }
         }
-
+        
         private var delegate: ScheduleViewModelDelegate?
         private var rawModelGroups = [EventsGroupedByDate]()
-
+        
         private var days = [Day]()
         var dayViewModels: [ScheduleDayViewModel] = [] {
             didSet {
                 delegate?.scheduleViewModelDidUpdateDays(dayViewModels)
             }
         }
-
+        
         var eventGroupViewModels: [ScheduleEventGroupViewModel] = [] {
             didSet {
                 delegate?.scheduleViewModelDidUpdateEvents(eventGroupViewModels)
             }
         }
-
+        
         private let schedule: EventsSchedule
         private let eventsService: EventsService
         private let hoursDateFormatter: HoursDateFormatter

            self.hoursDateFormatter = hoursDateFormatter
            self.shortFormDateFormatter = shortFormDateFormatter
            self.refreshService = refreshService

            refreshService.add(self)
            schedule.setDelegate(self)
        }

        var selectedDayIndex = 0

        fileprivate func regenerateEventViewModels() {
            let groupedByDate = Dictionary(grouping: events, by: { $0.startDate })
            rawModelGroups = groupedByDate.map(EventsGroupedByDate.init).sorted(by: { (first, second) -> Bool in
                return first.compare(against: second)
            })

            eventGroupViewModels = rawModelGroups.map { (group) -> ScheduleEventGroupViewModel in
                let title = hoursDateFormatter.hoursString(from: group.date)
                let viewModels = group.events.map { (event) -> ScheduleEventViewModel in
                    return ScheduleEventViewModel(title: event.title,
                                                  startTime: hoursDateFormatter.hoursString(from: event.startDate),
                                                  endTime: hoursDateFormatter.hoursString(from: event.endDate),
                                                  location: event.room.name,
                                                  bannerGraphicPNGData: event.bannerGraphicPNGData,
                                                  isFavourite: favouriteEvents.contains(event.identifier),
                                                  isSponsorOnly: event.isSponsorOnly,
                                                  isSuperSponsorOnly: event.isSuperSponsorOnly,
                                                  isArtShow: event.isArtShow,
                                                  isKageEvent: event.isKageEvent,
                                                  isDealersDenEvent: event.isDealersDen,
                                                  isMainStageEvent: event.isMainStage,
                                                  isPhotoshootEvent: event.isPhotoshoot)
                }

                return ScheduleEventGroupViewModel(title: title, events: viewModels)
            }
        }

        func scheduleEventsDidChange(to events: [Event]) {
            self.events = events
            regenerateEventViewModels()
        }

        func eventDaysDidChange(to days: [Day]) {
            self.days = days
            self.dayViewModels = days.map { (day) -> ScheduleDayViewModel in
                return ScheduleDayViewModel(title: shortFormDateFormatter.dateString(from: day.date))
            }
        }

        func currentEventDayDidChange(to day: Day?) {
            guard let day = day else { return }
            schedule.restrictEvents(to: day)

            guard let idx = days.index(where: { $0.date == day.date }) else { return }
            selectedDayIndex = idx
        }

        func setDelegate(_ delegate: ScheduleViewModelDelegate) {
            self.delegate = delegate

            delegate.scheduleViewModelDidUpdateDays(dayViewModels)
            delegate.scheduleViewModelDidUpdateEvents(eventGroupViewModels)
            delegate.scheduleViewModelDidUpdateCurrentDayIndex(to: selectedDayIndex)
        }

        func refresh() {
            refreshService.refreshLocalStore { (_) in }
        }

        func showEventsForDay(at index: Int) {
            let day = days[index]
            schedule.restrictEvents(to: day)
        }

        func identifierForEvent(at indexPath: IndexPath) -> EventIdentifier? {
            return rawModelGroups[indexPath.section].events[indexPath.row].identifier
        }

        func favouriteEvent(at indexPath: IndexPath) {
            let event = rawModelGroups[indexPath.section].events[indexPath.item]
            event.favourite()
        }

        func unfavouriteEvent(at indexPath: IndexPath) {
            let event = rawModelGroups[indexPath.section].events[indexPath.item]
            event.unfavourite()
        }

        func refreshServiceDidBeginRefreshing() {
            delegate?.scheduleViewModelDidBeginRefreshing()
        }

        func refreshServiceDidFinishRefreshing() {
            delegate?.scheduleViewModelDidFinishRefreshing()
        }

        func favouriteEventsDidChange(_ identifiers: [EventIdentifier]) {
            favouriteEvents = identifiers
            regenerateEventViewModels()
        }

    }

    private class SearchViewModel: ScheduleSearchViewModel, EventsSearchControllerDelegate {

        private struct EventsGroupedByDate {
            var date: Date
            var events: [Event]

            func compare(against: EventsGroupedByDate) -> Bool {
                return date < against.date
            }
        }

        private let searchController: EventsSearchController
        private let eventsService: EventsService
        private let shortFormDayAndTimeFormatter: ShortFormDayAndTimeFormatter
        private let hoursDateFormatter: HoursDateFormatter
        private var rawModelGroups = [EventsGroupedByDate]()
        private var searchResults = [Event]()
        private var favouriteEvents = [EventIdentifier]()

        init(searchController: EventsSearchController,
             eventsService: EventsService,
             shortFormDayAndTimeFormatter: ShortFormDayAndTimeFormatter,
             hoursDateFormatter: HoursDateFormatter) {
            self.eventsService = eventsService
            self.searchController = searchController
            self.hoursDateFormatter = hoursDateFormatter
            self.shortFormDayAndTimeFormatter = shortFormDayAndTimeFormatter

            searchController.setResultsDelegate(self)
        }

        private var delegate: ScheduleSearchViewModelDelegate?
        func setDelegate(_ delegate: ScheduleSearchViewModelDelegate) {
            self.delegate = delegate
        }

        func updateSearchResults(input: String) {
            searchController.changeSearchTerm(input)
        }

        func identifierForEvent(at indexPath: IndexPath) -> EventIdentifier? {
            return rawModelGroups[indexPath.section].events[indexPath.row].identifier
        }

        func filterToFavourites() {
            searchController.restrictResultsToFavourites()
        }

        func filterToAllEvents() {
            searchController.removeFavouritesEventsRestriction()
        }

        func searchResultsDidUpdate(to results: [Event]) {
            searchResults = results
            regenerateViewModel()
        }

        func favouriteEventsDidChange(_ identifiers: [EventIdentifier]) {
            favouriteEvents = identifiers
            regenerateViewModel()
        }

        func favouriteEvent(at indexPath: IndexPath) {
            let event = rawModelGroups[indexPath.section].events[indexPath.item]
            event.favourite()
        }

        func unfavouriteEvent(at indexPath: IndexPath) {
            let event = rawModelGroups[indexPath.section].events[indexPath.item]
            event.unfavourite()
        }

        private func regenerateViewModel() {
            let groupedByDate = Dictionary(grouping: searchResults, by: { $0.startDate })
            rawModelGroups = groupedByDate.map(EventsGroupedByDate.init).sorted(by: { (first, second) -> Bool in
                return first.compare(against: second)
            })

            let eventGroupViewModels = rawModelGroups.map(makeScheduleGroupViewModel)

            delegate?.scheduleSearchResultsUpdated(eventGroupViewModels)
        }

        private func makeScheduleGroupViewModel(_ group: EventsGroupedByDate) -> ScheduleEventGroupViewModel {
            let title = shortFormDayAndTimeFormatter.dayAndHoursString(from: group.date)
            let viewModels = group.events.map(makeEventViewModel)

            return ScheduleEventGroupViewModel(title: title, events: viewModels)
        }

        private func makeEventViewModel(_ event: Event) -> ScheduleEventViewModel {
            return ScheduleEventViewModel(title: event.title,
                                          startTime: hoursDateFormatter.hoursString(from: event.startDate),
                                          endTime: hoursDateFormatter.hoursString(from: event.endDate),
                                          location: event.room.name,

import Foundation

class DefaultScheduleInteractor: ScheduleInteractor, EventsServiceObserver {
    
    // MARK: Properties
    
    private let viewModel: ViewModel
    private let searchViewModel: SearchViewModel
    
    // MARK: Initialization
    
    convenience init() {
        self.init(eventsService: SharedModel.instance.services.events,
                  hoursDateFormatter: FoundationHoursDateFormatter.shared,
