//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache 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.apache.org/licenses/LICENSE-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.
//

import Foundation
import CoreImage

@objcMembers
public class RecentsListService: NSObject, RecentsListServiceProtocol {
    
    private weak var session: MXSession?
    public private(set) var mode: RecentsDataSourceMode {
        didSet {
            refresh()
        }
    }
    
    public private(set) var query: String?
    public private(set) var space: MXSpace?
    private var fetchersCreated: Bool = false
    private var uncompletedVoiceBroadcastCleaningDone: Bool = false
    
    //  MARK: - Fetchers
    
    private var invitedRoomListDataFetcher: MXRoomListDataFetcher? {
        switch mode {
        case .home, .allChats, .roomInvites:
            return invitedRoomListDataFetcherForHome
        case .people:
            return invitedRoomListDataFetcherForPeople
        case .rooms:
            return invitedRoomListDataFetcherForRooms
        default:
            return nil
        }
    }
    private var favoritedRoomListDataFetcher: MXRoomListDataFetcher?
    private var directRoomListDataFetcher: MXRoomListDataFetcher? {
        switch mode {
        case .home, .allChats:
            return directRoomListDataFetcherForHome
        case .people:
            return directRoomListDataFetcherForPeople
        default:
            return nil
        }
    }
    private var conversationRoomListDataFetcher: MXRoomListDataFetcher? {
        switch mode {
        case .home:
            return conversationRoomListDataFetcherForHome
        case .rooms:
            return conversationRoomListDataFetcherForRooms
        default:
            return nil
        }
    }
    private var lowPriorityRoomListDataFetcher: MXRoomListDataFetcher?
    private var serverNoticeRoomListDataFetcher: MXRoomListDataFetcher?
    private var suggestedRoomListDataFetcher: MXRoomListDataFetcher?
    private var breadcrumbsRoomListDataFetcher: MXRoomListDataFetcher?
    private var allChatsRoomListDataFetcher: MXRoomListDataFetcher?

    private var conversationRoomListDataFetcherForHome: MXRoomListDataFetcher?
    private var conversationRoomListDataFetcherForRooms: MXRoomListDataFetcher?
    private var directRoomListDataFetcherForHome: MXRoomListDataFetcher?
    private var directRoomListDataFetcherForPeople: MXRoomListDataFetcher?
    private var invitedRoomListDataFetcherForHome: MXRoomListDataFetcher?
    private var invitedRoomListDataFetcherForPeople: MXRoomListDataFetcher?
    private var invitedRoomListDataFetcherForRooms: MXRoomListDataFetcher?

    //  MARK: - Private
    
    private var fetcherTypesForMode: [RecentsDataSourceMode: FetcherTypes] = [
        .home: [.invited, .favorited, .directHome, .conversationHome, .lowPriority, .serverNotice, .suggested],
        .favourites: [.favorited],
        .people: [.invited, .directPeople],
        .rooms: [.invited, .conversationRooms, .suggested],
        .roomInvites: [.invited],
        .allChats: [.breadcrumbs, .favorited, .directHome, .invited, .allChats, .lowPriority, .serverNotice, .suggested]
    ]
    
    private var allFetchers: [MXRoomListDataFetcher] {
        return [
            invitedRoomListDataFetcherForHome,
            invitedRoomListDataFetcherForPeople,
            invitedRoomListDataFetcherForRooms,
            favoritedRoomListDataFetcher,
            directRoomListDataFetcherForHome,
            directRoomListDataFetcherForPeople,
            conversationRoomListDataFetcherForHome,
            conversationRoomListDataFetcherForRooms,
            lowPriorityRoomListDataFetcher,
            serverNoticeRoomListDataFetcher,
            suggestedRoomListDataFetcher,
            breadcrumbsRoomListDataFetcher,
            allChatsRoomListDataFetcher
        ].compactMap({ $0 })
    }
    
    private var visibleFetchers: [MXRoomListDataFetcher] {
        guard let fetcherTypes = fetcherTypesForMode[mode] else {
            return []
        }
        var result: [MXRoomListDataFetcher] = []
        if let fetcher = invitedRoomListDataFetcher, fetcherTypes.contains(.invited) {
            result.append(fetcher)
        }
        if let fetcher = favoritedRoomListDataFetcher, fetcherTypes.contains(.favorited) {
            result.append(fetcher)
        }
        if let fetcher = directRoomListDataFetcherForHome, fetcherTypes.contains(.directHome) {
            result.append(fetcher)
        }
        if let fetcher = directRoomListDataFetcherForPeople, fetcherTypes.contains(.directPeople) {
            result.append(fetcher)
        }
        if let fetcher = conversationRoomListDataFetcherForHome, fetcherTypes.contains(.conversationHome) {
            result.append(fetcher)
        }
        if let fetcher = conversationRoomListDataFetcherForRooms, fetcherTypes.contains(.conversationRooms) {
            result.append(fetcher)
        }
        if let fetcher = lowPriorityRoomListDataFetcher, fetcherTypes.contains(.lowPriority), shouldShowLowPriority {
            result.append(fetcher)
        }
        if let fetcher = serverNoticeRoomListDataFetcher, fetcherTypes.contains(.serverNotice) {
            result.append(fetcher)
        }
        if space != nil, let fetcher = suggestedRoomListDataFetcher, fetcherTypes.contains(.suggested) {
            result.append(fetcher)
        }
        if let fetcher = breadcrumbsRoomListDataFetcher, fetcherTypes.contains(.breadcrumbs), shouldShowBreadcrumbs {
            result.append(fetcher)
        }
        if let fetcher = allChatsRoomListDataFetcher, fetcherTypes.contains(.allChats) {
            result.append(fetcher)
        }
        return result
    }

    private var hideInvitedSection: Bool {
        return MXSDKOptions.sharedInstance().autoAcceptRoomInvites
    }
    
    private var showAllRoomsInHomeSpace: Bool {
        return RiotSettings.shared.showAllRoomsInHomeSpace
    }
    
    // swiftlint:disable weak_delegate
    private let multicastDelegate: MXMulticastDelegate<RecentsListServiceDelegate> = MXMulticastDelegate()
    // swiftlint:enable weak_delegate
    
    private var noSortOptions: MXRoomListDataSortOptions {
        return MXRoomListDataSortOptions(invitesFirst: false, sentStatus: false, lastEventDate: false, favoriteTag: false, suggested: false, alphabetical: false, missedNotificationsFirst: false, unreadMessagesFirst: false)
    }
    
    private var sortOptions: MXRoomListDataSortOptions {
        switch mode {
        case .home:
            let pinMissed = RiotSettings.shared.pinRoomsWithMissedNotificationsOnHome
            let pinUnread = RiotSettings.shared.pinRoomsWithUnreadMessagesOnHome
            return MXRoomListDataSortOptions(missedNotificationsFirst: pinMissed,
                                             unreadMessagesFirst: pinUnread)
        case .allChats:
            let pinMissed = RiotSettings.shared.pinRoomsWithMissedNotificationsOnHome
            let pinUnread = RiotSettings.shared.pinRoomsWithUnreadMessagesOnHome
            switch AllChatsLayoutSettingsManager.shared.allChatLayoutSettings.sorting {
            case .alphabetical:
                return MXRoomListDataSortOptions(invitesFirst: false,
                                                 sentStatus: false,
                                                 lastEventDate: false,
                                                 favoriteTag: false,
                                                 suggested: false,
                                                 alphabetical: true,
                                                 missedNotificationsFirst: false,
                                                 unreadMessagesFirst: false)
            case .activity:
                return MXRoomListDataSortOptions(missedNotificationsFirst: pinMissed,
                                                 unreadMessagesFirst: pinUnread)
            @unknown default:
                return MXRoomListDataSortOptions(missedNotificationsFirst: pinMissed,
                                                 unreadMessagesFirst: pinUnread)
            }
        default:
            return MXRoomListDataSortOptions(missedNotificationsFirst: false,
                                             unreadMessagesFirst: false)
        }
    }
    
    //  MARK: - Public API
    
    public convenience init(withSession session: MXSession) {
        self.init(withSession: session,
                  mode: .home,
                  query: nil,
                  space: nil)
    }
    
    private var allChatLayoutSettingsManagerObserver: Any?
    private var allChatLayoutSettingsObserver: Any?

    private init(withSession session: MXSession,
                mode: RecentsDataSourceMode,
                query: String?,
                space: MXSpace?) {
        self.session = session
        self.mode = mode
        self.query = query
        self.space = space
        super.init()
        createFetchers()
        addRiotSettingsObserver()
        addSessionStateObserver()
        
        allChatLayoutSettingsManagerObserver = NotificationCenter.default.addObserver(forName: AllChatsLayoutSettingsManager.didUpdateSettings, object: nil, queue: OperationQueue.main) { [weak self] notification in
            guard let self = self else { return }
            self.refresh()
            for fetcher in self.allFetchers {
                fetcher.refresh()
            }
            if let fetcher = self.allChatsRoomListDataFetcher {
                self.updateConversationFetcher(fetcher, for: .allChats)
                fetcher.paginate()
            }
        }
        
        allChatLayoutSettingsObserver = NotificationCenter.default.addObserver(forName: AllChatsLayoutSettingsManager.didUpdateActiveFilters, object: nil, queue: OperationQueue.main) { [weak self] notification in
            guard let self = self else { return }
            if let fetcher = self.allChatsRoomListDataFetcher {
                self.updateConversationFetcher(fetcher, for: .allChats)
                fetcher.paginate()
            }
        }
    }
    
    //  MARK: - View Data
    
    public var invitedRoomListData: MXRoomListData? {
        guard shouldShowInvited else { return nil }
        return invitedRoomListDataFetcher?.data
    }
    public var favoritedRoomListData: MXRoomListData? {
        guard shouldShowFavorited else { return nil }
        return favoritedRoomListDataFetcher?.data
    }
    public var peopleRoomListData: MXRoomListData? {
        guard shouldShowDirect else { return nil }
        return directRoomListDataFetcher?.data
    }
    public var conversationRoomListData: MXRoomListData? {
        guard shouldShowConversation else { return nil }
        return conversationRoomListDataFetcher?.data
    }
    public var lowPriorityRoomListData: MXRoomListData? {
        guard shouldShowLowPriority else { return nil }
        return lowPriorityRoomListDataFetcher?.data
    }
    public var serverNoticeRoomListData: MXRoomListData? {
        guard shouldShowServerNotice else { return nil }
        return serverNoticeRoomListDataFetcher?.data
    }
    public var suggestedRoomListData: MXRoomListData? {
        guard shouldShowSuggested else { return nil }
        return suggestedRoomListDataFetcher?.data
    }
    public var breadcrumbsRoomListData: MXRoomListData? {
        guard shouldShowBreadcrumbs else { return nil }
        return breadcrumbsRoomListDataFetcher?.data
    }
    public var allChatsRoomListData: MXRoomListData? {
        guard shouldShowAllChats else { return nil }
        return allChatsRoomListDataFetcher?.data
    }
    
    public var favoritedMissedDiscussionsCount: DiscussionsCount {
        guard let totalCounts = favoritedRoomListDataFetcher?.data?.counts.total else {
            return .zero
        }
        return DiscussionsCount(withRoomListDataCounts: [totalCounts])
    }
    
    public var peopleMissedDiscussionsCount: DiscussionsCount {
        let invitesCount = invitedRoomListDataFetcherForPeople?.data?.counts.total
        let directCount = directRoomListDataFetcherForPeople?.data?.counts.total
        let totalCounts = [invitesCount, directCount].compactMap { $0 }
        return DiscussionsCount(withRoomListDataCounts: totalCounts)
    }
    
    public var conversationMissedDiscussionsCount: DiscussionsCount {
        let invitesCount = invitedRoomListDataFetcherForRooms?.data?.counts.total
        let conversationCount = conversationRoomListDataFetcherForRooms?.data?.counts.total
        let totalCounts = [invitesCount, conversationCount].compactMap { $0 }
        return DiscussionsCount(withRoomListDataCounts: totalCounts)
    }
    
    public var totalVisibleItemCount: Int {
        return visibleFetchers.reduce(0, { $0 + ($1.data?.counts.numberOfRooms ?? 0) })
    }
    
    public func paginate(inSection section: RecentsListServiceSection) {
        guard let fetcher = fetcher(forSection: section) else {
            return
        }
        guard let data = fetcher.data else {
            //  first page is not fetched yet
            return
        }
        guard data.paginationOptions != .none else {
            //  pagination is not enabled
            return
        }
        fetcher.paginate()
    }
    
    public func updateMode(_ mode: RecentsDataSourceMode) {
        self.mode = mode
        if let fetcher = favoritedRoomListDataFetcher {
            updateFavoritedFetcher(fetcher, for: mode)
        }
        allFetchers.forEach({ notifyDataChange(on: $0, totalCountsChanged: true) })
    }
    
    public func updateQuery(_ query: String?) {
        self.query = query
        visibleFetchers.forEach({ $0.fetchOptions.filterOptions.query = query })
    }
    
    public func updateSpace(_ space: MXSpace?) {
        self.space = space
        allFetchers.forEach({ $0.fetchOptions.filterOptions.space = space })

        if let fetcher = conversationRoomListDataFetcherForHome {
            self.updateConversationFetcher(fetcher, for: .home)
            fetcher.paginate()
        }
        
        if let fetcher = allChatsRoomListDataFetcher {
            self.updateConversationFetcher(fetcher, for: .allChats)
            fetcher.paginate()
        }
    }
    
    public func refresh() {
        allFetchers.forEach({ $0.fetchOptions.sortOptions = $0.fetchOptions.filterOptions.onlyBreadcrumbs ? noSortOptions : sortOptions })
        allFetchers.forEach({ $0.fetchOptions.filterOptions.showAllRoomsInHomeSpace = showAllRoomsInHomeSpace })
    }
    
    public func stop() {
        removeSessionStateObserver()
        removeRiotSettingsObserver()
        removeAllDelegates()
        allFetchers.forEach({ $0.stop() })
        
        invitedRoomListDataFetcherForHome = nil
        invitedRoomListDataFetcherForPeople = nil
        invitedRoomListDataFetcherForRooms = nil
        favoritedRoomListDataFetcher = nil
        directRoomListDataFetcherForHome = nil
        directRoomListDataFetcherForPeople = nil
        conversationRoomListDataFetcherForHome = nil
        conversationRoomListDataFetcherForRooms = nil
        lowPriorityRoomListDataFetcher = nil
        serverNoticeRoomListDataFetcher = nil
        suggestedRoomListDataFetcher = nil
        breadcrumbsRoomListDataFetcher = nil
        allChatsRoomListDataFetcher = nil
    }
    
    //  MARK: - Delegate
    
    public func addDelegate(_ delegate: RecentsListServiceDelegate) {
        multicastDelegate.addDelegate(delegate)
    }
    
    public func removeDelegate(_ delegate: RecentsListServiceDelegate) {
        multicastDelegate.removeDelegate(delegate)
    }
    
    public func removeAllDelegates() {
        multicastDelegate.removeAllDelegates()
    }
    
    //  MARK: - Riot Settings Observer
    
    private func addRiotSettingsObserver() {
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(userDefaultsUpdated(_:)),
                                               name: .userDefaultValueUpdated,
                                               object: nil)
    }
    
    private func removeRiotSettingsObserver() {
        NotificationCenter.default.removeObserver(self,
                                                  name: .userDefaultValueUpdated,
                                                  object: nil)
    }
    
    @objc
    private func userDefaultsUpdated(_ notification: Notification) {
        guard let key = notification.object as? String else {
            return
        }
        switch key {
        case RiotSettings.UserDefaultsKeys.pinRoomsWithMissedNotificationsOnHome,
             RiotSettings.UserDefaultsKeys.pinRoomsWithUnreadMessagesOnHome:
            refresh()
        case RiotSettings.UserDefaultsKeys.showAllRoomsInHomeSpace:
            refresh()
        default:
            break
        }
    }
    
    //  MARK: - Session State Observers
    
    private func addSessionStateObserver() {
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(sessionStateUpdated(_:)),
                                               name: .mxSessionStateDidChange,
                                               object: nil)
    }
    
    private func removeSessionStateObserver() {
        NotificationCenter.default.removeObserver(self,
                                                  name: .mxSessionStateDidChange,
                                                  object: nil)
    }
    
    @objc
    private func sessionStateUpdated(_ notification: Notification) {
        guard let session = notification.object as? MXSession else {
            return
        }
        guard session == self.session else {
            return
        }
        createFetchers()
    }
    
    //  MARK: - Private
    
    private var shouldShowInvited: Bool {
        return fetcherTypesForMode[mode]?.contains(.invited) ?? false
    }
    
    private var shouldShowFavorited: Bool {
        return fetcherTypesForMode[mode]?.contains(.favorited) ?? false
    }
    
    private var shouldShowDirect: Bool {
        switch mode {
        case .home, .allChats:
            return fetcherTypesForMode[mode]?.contains(.directHome) ?? false
        case .people:
            return fetcherTypesForMode[mode]?.contains(.directPeople) ?? false
        default:
            return false
        }
    }
    
    private var shouldShowConversation: Bool {
        switch mode {
        case .home:
            return fetcherTypesForMode[mode]?.contains(.conversationHome) ?? false
        case .rooms:
            return fetcherTypesForMode[mode]?.contains(.conversationRooms) ?? false
        default:
            return false
        }
    }
    
    private var shouldShowLowPriority: Bool {
        return ((mode != .allChats) || !AllChatsLayoutSettingsManager.shared.hasAnActiveFilter) && fetcherTypesForMode[mode]?.contains(.lowPriority) ?? false
    }
    
    private var shouldShowServerNotice: Bool {
        return fetcherTypesForMode[mode]?.contains(.serverNotice) ?? false
    }
    
    private var shouldShowSuggested: Bool {
        return fetcherTypesForMode[mode]?.contains(.suggested) ?? false
    }
    
    private var shouldShowBreadcrumbs: Bool {
        return AllChatsLayoutSettingsManager.shared.allChatLayoutSettings.sections.contains(.recents) && (fetcherTypesForMode[mode]?.contains(.breadcrumbs) ?? false)
    }
    
    private var shouldShowAllChats: Bool {
        return fetcherTypesForMode[mode]?.contains(.allChats) ?? false
    }

    private func fetcher(forSection section: RecentsListServiceSection) -> MXRoomListDataFetcher? {
        switch section {
        case .invited:
            return invitedRoomListDataFetcher
        case .favorited:
            return favoritedRoomListDataFetcher
        case .people:
            return directRoomListDataFetcher
        case .conversation:
            return conversationRoomListDataFetcher
        case .lowPriority:
            return lowPriorityRoomListDataFetcher
        case .serverNotice:
            return serverNoticeRoomListDataFetcher
        case .suggested:
            return suggestedRoomListDataFetcher
        case .breadcrumbs:
            return breadcrumbsRoomListDataFetcher
        case .allChats:
            return allChatsRoomListDataFetcher
        }
    }
    
    private func section(forFetcher fetcher: MXRoomListDataFetcher) -> RecentsListServiceSection? {
        if fetcher === invitedRoomListDataFetcher {
            return .invited
        } else if fetcher === favoritedRoomListDataFetcher {
            return .favorited
        } else if fetcher === directRoomListDataFetcher {
            return .people
        } else if fetcher === conversationRoomListDataFetcher {
            return .conversation
        } else if fetcher === lowPriorityRoomListDataFetcher {
            return .lowPriority
        } else if fetcher === serverNoticeRoomListDataFetcher {
            return .serverNotice
        } else if fetcher === suggestedRoomListDataFetcher {
            return .suggested
        } else if fetcher === breadcrumbsRoomListDataFetcher {
            return .breadcrumbs
        } else if fetcher === allChatsRoomListDataFetcher {
            return .allChats
        }
        return nil
    }
    
    private func createCommonRoomListDataFetcher(withDataTypes dataTypes: MXRoomSummaryDataTypes = [],
                                                 onlySuggested: Bool = false,
                                                 onlyBreadcrumbs: Bool = false,
                                                 paginate: Bool = true,
                                                 strictMatches: Bool = false) -> MXRoomListDataFetcher {
        guard let session = session else {
            fatalError("Session deallocated")
        }
        let filterOptions = MXRoomListDataFilterOptions(dataTypes: dataTypes,
                                                        onlySuggested: onlySuggested,
                                                        onlyBreadcrumbs: onlyBreadcrumbs,
                                                        query: query,
                                                        space: space,
                                                        showAllRoomsInHomeSpace: showAllRoomsInHomeSpace,
                                                        strictMatches: strictMatches)
        
        let fetchOptions = MXRoomListDataFetchOptions(filterOptions: filterOptions,
                                                      sortOptions: onlyBreadcrumbs ? noSortOptions : sortOptions,
                                                      async: true)
        let fetcher = session.roomListDataManager.fetcher(withOptions: fetchOptions)
        if paginate {
            fetcher.addDelegate(self)
            fetcher.paginate()
        }
        return fetcher
    }

    private func createInvitedRoomListDataFetcherForPeople() -> MXRoomListDataFetcher {
        let fetcher = createCommonRoomListDataFetcher(withDataTypes: [.invited, .direct], paginate: false, strictMatches: true)
        updateInvitedFetcher(fetcher, for: .people)
        fetcher.addDelegate(self)
        fetcher.paginate()
        return fetcher
    }

    private func createInvitedRoomListDataFetcherForRooms() -> MXRoomListDataFetcher {
        let fetcher = createCommonRoomListDataFetcher(withDataTypes: [.invited], paginate: false)
        updateInvitedFetcher(fetcher, for: .rooms)
        fetcher.addDelegate(self)
        fetcher.paginate()
        return fetcher
    }
    
    private func createDirectRoomListDataFetcherForHome() -> MXRoomListDataFetcher {
        let fetcher = createCommonRoomListDataFetcher(withDataTypes: [.direct], paginate: false)
        updateDirectFetcher(fetcher, for: .home)
        fetcher.addDelegate(self)
        fetcher.paginate()
        return fetcher
    }
    
    private func createDirectRoomListDataFetcherForPeople() -> MXRoomListDataFetcher {
        let fetcher = createCommonRoomListDataFetcher(withDataTypes: [.direct], paginate: false)
        updateDirectFetcher(fetcher, for: .people)
        fetcher.addDelegate(self)
        fetcher.paginate()
        return fetcher
    }
    
    private func createConversationRoomListDataFetcherForHome() -> MXRoomListDataFetcher {
        let fetcher = createCommonRoomListDataFetcher(withDataTypes: [], paginate: false)
        updateConversationFetcher(fetcher, for: .home)
        fetcher.addDelegate(self)
        fetcher.paginate()
        return fetcher
    }
    
    private func createConversationRoomListDataFetcherForAllChats() -> MXRoomListDataFetcher {
        let fetcher = createCommonRoomListDataFetcher(withDataTypes: [], paginate: false)
        updateConversationFetcher(fetcher, for: .allChats)
        fetcher.addDelegate(self)
        fetcher.paginate()
        return fetcher
    }
    
    private func createConversationRoomListDataFetcherForRooms() -> MXRoomListDataFetcher {
        let fetcher = createCommonRoomListDataFetcher(withDataTypes: [], paginate: false)
        updateConversationFetcher(fetcher, for: .rooms)
        fetcher.addDelegate(self)
        fetcher.paginate()
        return fetcher
    }
    
    private func createFetchers() {
        guard fetchersCreated == false else {
            removeSessionStateObserver()
            return
        }
        guard let session = session else {
            return
        }
        guard session.state != .closed else {
            MXLog.debug("[RecentsListService] createFetchers cancelled on closed session")
            return
        }
        guard session.roomListDataManager != nil else {
            MXLog.debug("[RecentsListService] createFetchers cancelled on race condition (session closing in progress)")
            return
        }
        guard session.isEventStreamInitialised else {
            return
        }
        if !hideInvitedSection {
            invitedRoomListDataFetcherForHome = createCommonRoomListDataFetcher(withDataTypes: [.invited])
            invitedRoomListDataFetcherForPeople = createInvitedRoomListDataFetcherForPeople()
            invitedRoomListDataFetcherForRooms = createInvitedRoomListDataFetcherForRooms()
        }
        favoritedRoomListDataFetcher = createCommonRoomListDataFetcher(withDataTypes: [.favorited])
        directRoomListDataFetcherForHome = createDirectRoomListDataFetcherForHome()
        directRoomListDataFetcherForPeople = createDirectRoomListDataFetcherForPeople()
        conversationRoomListDataFetcherForHome = createConversationRoomListDataFetcherForHome()
        conversationRoomListDataFetcherForRooms = createConversationRoomListDataFetcherForRooms()
        lowPriorityRoomListDataFetcher = createCommonRoomListDataFetcher(withDataTypes: [.lowPriority])
        serverNoticeRoomListDataFetcher = createCommonRoomListDataFetcher(withDataTypes: [.serverNotice])
        suggestedRoomListDataFetcher = createCommonRoomListDataFetcher(onlySuggested: true)
        breadcrumbsRoomListDataFetcher = createCommonRoomListDataFetcher(onlyBreadcrumbs: true)
        allChatsRoomListDataFetcher = createConversationRoomListDataFetcherForAllChats()
        
        fetchersCreated = true
        removeSessionStateObserver()
    }
    
    private func updateDirectFetcher(_ fetcher: MXRoomListDataFetcher, for mode: RecentsDataSourceMode) {
        var notDataTypes: MXRoomSummaryDataTypes = [.hidden, .conferenceUser, .space, .invited, .lowPriority]
            switch mode {
            case .home:
                notDataTypes.insert(.favorited)
                fetcher.fetchOptions.filterOptions.notDataTypes = notDataTypes
            case .people:
                fetcher.fetchOptions.filterOptions.notDataTypes = notDataTypes
            default:
                break
            }
        }

    private func updateInvitedFetcher(_ fetcher: MXRoomListDataFetcher, for mode: RecentsDataSourceMode) {
        var notDataTypes: MXRoomSummaryDataTypes = [.hidden, .conferenceUser, .lowPriority, .serverNotice, .space]
        switch mode {
        case .people:
            fetcher.fetchOptions.filterOptions.notDataTypes = notDataTypes
        case .rooms:
            notDataTypes.insert([.direct])
            fetcher.fetchOptions.filterOptions.notDataTypes = notDataTypes
        default:
            break
        }
    }
    
    private func updateFavoritedFetcher(_ fetcher: MXRoomListDataFetcher, for mode: RecentsDataSourceMode) {
        switch mode {
        case .home:
            if fetcher.fetchOptions.filterOptions.onlyBreadcrumbs {
                fetcher.fetchOptions.sortOptions = noSortOptions
            } else {
                fetcher.fetchOptions.sortOptions = sortOptions
            }
        case .favourites:
            var newSortOptions = sortOptions
            newSortOptions.favoriteTag = true
            fetcher.fetchOptions.sortOptions = newSortOptions
        default:
            break
        }
    }
    
    private func updateConversationFetcher(_ fetcher: MXRoomListDataFetcher, for mode: RecentsDataSourceMode) {
        var notDataTypes: MXRoomSummaryDataTypes = mode == .allChats ? [.hidden, .conferenceUser, .invited, .lowPriority, .serverNotice, .space] : [.hidden, .conferenceUser, .direct, .invited, .lowPriority, .serverNotice, .space]

        switch mode {
        case .home:
            notDataTypes.insert(.favorited)
            
            fetcher.fetchOptions.filterOptions.notDataTypes = notDataTypes
        case .rooms:
            fetcher.fetchOptions.filterOptions.notDataTypes = notDataTypes
        case .allChats:
            let settingsManager = AllChatsLayoutSettingsManager.shared
            let settings = AllChatsLayoutSettingsManager.shared.allChatLayoutSettings
            if settings.sections.contains(.favourites) && !settings.filters.contains(.favourites) {
                notDataTypes.insert(.favorited)
            }
            if settings.filters.contains(.rooms) && settingsManager.activeFilters.contains(.rooms) {
                notDataTypes.insert(.direct)
            }
            fetcher.fetchOptions.filterOptions.notDataTypes = notDataTypes
            
            if settings.filters.contains(.unreads) && settingsManager.activeFilters.contains(.unreads) {
                fetcher.fetchOptions.filterOptions.dataTypes = [.unread]
                return
            }
            if settings.filters.contains(.people) && settingsManager.activeFilters.contains(.people) {
                fetcher.fetchOptions.filterOptions.dataTypes = [.direct]
                return
            }
            if settings.filters.contains(.favourites) && settingsManager.activeFilters.contains(.favourites) {
                fetcher.fetchOptions.filterOptions.dataTypes = [.favorited]
                return
            }
            fetcher.fetchOptions.filterOptions.dataTypes = []
        default:
            break
        }
    }
    
    private func notifyDataChange(on fetcher: MXRoomListDataFetcher, totalCountsChanged: Bool) {
        if let section = section(forFetcher: fetcher) {
            multicastDelegate.invoke { $0.recentsListServiceDidChangeData?(self,
                                                                           forSection: section,
                                                                           totalCountsChanged: totalCountsChanged) }
        } else {
            stopUncompletedVoiceBroadcastIfNeeded()
            multicastDelegate.invoke { $0.recentsListServiceDidChangeData?(self,
                                                                           totalCountsChanged: totalCountsChanged) }
        }
    }
    
    deinit {
        if let observer = allChatLayoutSettingsManagerObserver {
            NotificationCenter.default.removeObserver(observer)
        }
        if let observer = allChatLayoutSettingsObserver {
            NotificationCenter.default.removeObserver(observer)
        }
        stop()
    }
    
}

//  MARK: - MXRoomListDataFetcherDelegate

extension RecentsListService: MXRoomListDataFetcherDelegate {
    
    public func fetcherDidChangeData(_ fetcher: MXRoomListDataFetcher, totalCountsChanged: Bool) {
        notifyDataChange(on: fetcher, totalCountsChanged: totalCountsChanged)
    }
    
}

// MARK: - VoiceBroadcast
extension RecentsListService {
    
    private func stopUncompletedVoiceBroadcastIfNeeded() {
        guard uncompletedVoiceBroadcastCleaningDone == false,
              let breadcrumbsFetcher = breadcrumbsRoomListDataFetcher else {
            return
        }
        // We limit for the moment the uncompleted voice broadcast cleaning to the breadcrumbs rooms list
        stopUncompletedVoiceBroadcastIfNeeded(for: breadcrumbsFetcher)
        uncompletedVoiceBroadcastCleaningDone = true
    }
    
    private func stopUncompletedVoiceBroadcastIfNeeded(for fetcher: MXRoomListDataFetcher) {
        fetcher.data?.rooms.forEach({ roomSummary in
            guard let roomSummary = roomSummary as? MXRoomSummary,
                  let room = roomSummary.room else {
                return
            }
            
            room.stopUncompletedVoiceBroadcastIfNeeded()
        })
    }
}

//  MARK: - FetcherTypes

private struct FetcherTypes: OptionSet {
    typealias RawValue = Int
    let rawValue: RawValue

    init(rawValue: RawValue) {
        self.rawValue = rawValue
    }
    
    static let invited = FetcherTypes(rawValue: 1 << 0)
    static let favorited = FetcherTypes(rawValue: 1 << 1)
    static let directHome = FetcherTypes(rawValue: 1 << 2)
    static let directPeople = FetcherTypes(rawValue: 1 << 3)
    static let conversationHome = FetcherTypes(rawValue: 1 << 4)
    static let conversationRooms = FetcherTypes(rawValue: 1 << 5)
    static let lowPriority = FetcherTypes(rawValue: 1 << 6)
    static let serverNotice = FetcherTypes(rawValue: 1 << 7)
    static let suggested = FetcherTypes(rawValue: 1 << 8)
    static let breadcrumbs = FetcherTypes(rawValue: 1 << 9)
    static let allChats = FetcherTypes(rawValue: 1 << 10)

    static let none: FetcherTypes = []
    static let all: FetcherTypes = [
        .invited, .favorited, .directHome, .directPeople, .conversationHome, .conversationRooms, .lowPriority, .serverNotice, .suggested, .breadcrumbs, .allChats]
}
