import Foundation

public enum UnreadMessageCountsItem: Equatable {
    case total(ValueBoxKey?)
    case totalInGroup(PeerGroupId)
    case peer(id: PeerId, handleThreads: Bool)
}

private enum MutableUnreadMessageCountsItemEntry: Equatable {
    case total((ValueBoxKey, PreferencesEntry?)?, ChatListTotalUnreadState)
    case totalInGroup(PeerGroupId, ChatListTotalUnreadState)
    case peer(PeerId, Bool, CombinedPeerReadState?)

    static func ==(lhs: MutableUnreadMessageCountsItemEntry, rhs: MutableUnreadMessageCountsItemEntry) -> Bool {
        switch lhs {
        case let .total(lhsKeyAndEntry, lhsUnreadState):
            if case let .total(rhsKeyAndEntry, rhsUnreadState) = rhs {
                if lhsKeyAndEntry?.0 != rhsKeyAndEntry?.0 {
                    return false
                }
                if lhsKeyAndEntry?.1 != rhsKeyAndEntry?.1 {
                    return false
                }
                if lhsUnreadState != rhsUnreadState {
                    return false
                }
                return true
            } else {
                return false
            }
        case let .totalInGroup(groupId, state):
            if case .totalInGroup(groupId, state) = rhs {
                return true
            } else {
                return false
            }
        case let .peer(peerId, handleThreads, readState):
            if case .peer(peerId, handleThreads, readState) = rhs {
                return true
            } else {
                return false
            }
        }
    }
}

public enum UnreadMessageCountsItemEntry {
    case total(PreferencesEntry?, ChatListTotalUnreadState)
    case totalInGroup(PeerGroupId, ChatListTotalUnreadState)
    case peer(PeerId, CombinedPeerReadState?)
}

final class MutableUnreadMessageCountsView: MutablePostboxView {
    private let items: [UnreadMessageCountsItem]
    fileprivate var entries: [MutableUnreadMessageCountsItemEntry]
    
    init(postbox: PostboxImpl, items: [UnreadMessageCountsItem]) {
        self.items = items

        self.entries = items.map { item in
            switch item {
            case let .total(preferencesKey):
                return .total(preferencesKey.flatMap({ ($0, postbox.preferencesTable.get(key: $0)) }), postbox.messageHistoryMetadataTable.getTotalUnreadState(groupId: .root))
            case let .totalInGroup(groupId):
                return .totalInGroup(groupId, postbox.messageHistoryMetadataTable.getTotalUnreadState(groupId: groupId))
            case let .peer(peerId, handleThreads):
                if handleThreads, let peer = postbox.peerTable.get(peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer) {
                    var count: Int32 = 0
                    if let summary = postbox.peerThreadsSummaryTable.get(peerId: peerId) {
                        count = summary.totalUnreadCount
                    }
                    return .peer(peerId, handleThreads, CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 1, maxOutgoingReadId: 1, maxKnownId: 1, count: count, markedUnread: false))]))
                } else {
                    return .peer(peerId, handleThreads, postbox.readStateTable.getCombinedState(peerId))
                }
            }
        }
    }
    
    func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool {
        var updated = false
        
        var updatedPreferencesEntry: PreferencesEntry?
        if !transaction.currentPreferencesOperations.isEmpty {
            for i in 0 ..< self.entries.count {
                if case let .total(maybeKeyAndValue, _) = self.entries[i], let (key, _) = maybeKeyAndValue {
                    for operation in transaction.currentPreferencesOperations {
                        if case let .update(updateKey, value) = operation {
                            if key == updateKey {
                                updatedPreferencesEntry = value
                            }
                        }
                    }
                }
            }
        }
        
        if !transaction.currentUpdatedTotalUnreadStates.isEmpty || !transaction.alteredInitialPeerCombinedReadStates.isEmpty || updatedPreferencesEntry != nil {
            for i in 0 ..< self.entries.count {
                switch self.entries[i] {
                case let .total(keyAndEntry, state):
                    if let updatedState = transaction.currentUpdatedTotalUnreadStates[.root] {
                        if updatedState != state {
                            self.entries[i] = .total(keyAndEntry.flatMap({ ($0.0, updatedPreferencesEntry ?? $0.1) }), updatedState)
                            updated = true
                        }
                    }
                case let .totalInGroup(groupId, state):
                    if let updatedState = transaction.currentUpdatedTotalUnreadStates[groupId] {
                        if updatedState != state {
                            self.entries[i] = .totalInGroup(groupId, updatedState)
                            updated = true
                        }
                    }
                case let .peer(peerId, handleThreads, _):
                    if handleThreads, let peer = postbox.peerTable.get(peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer) {
                        if transaction.updatedPeerThreadsSummaries.contains(peerId) {
                            var count: Int32 = 0
                            if let summary = postbox.peerThreadsSummaryTable.get(peerId: peerId) {
                                count = summary.totalUnreadCount
                            }
                            self.entries[i] = .peer(peerId, handleThreads, CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 1, maxOutgoingReadId: 1, maxKnownId: 1, count: count, markedUnread: false))]))
                            updated = true
                        }
                    } else {
                        if transaction.alteredInitialPeerCombinedReadStates[peerId] != nil {
                            self.entries[i] = .peer(peerId, handleThreads, postbox.readStateTable.getCombinedState(peerId))
                            updated = true
                        }
                    }
                }
            }
        }
        
        return updated
    }

    func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool {
        let entries: [MutableUnreadMessageCountsItemEntry] = self.items.map { item -> MutableUnreadMessageCountsItemEntry in
            switch item {
            case let .total(preferencesKey):
                return .total(preferencesKey.flatMap({ ($0, postbox.preferencesTable.get(key: $0)) }), postbox.messageHistoryMetadataTable.getTotalUnreadState(groupId: .root))
            case let .totalInGroup(groupId):
                return .totalInGroup(groupId, postbox.messageHistoryMetadataTable.getTotalUnreadState(groupId: groupId))
            case let .peer(peerId, handleThreads):
                if handleThreads, let peer = postbox.peerTable.get(peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer) {
                    var count: Int32 = 0
                    if let summary = postbox.peerThreadsSummaryTable.get(peerId: peerId) {
                        count = summary.totalUnreadCount
                    }
                    return .peer(peerId, handleThreads, CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 1, maxOutgoingReadId: 1, maxKnownId: 1, count: count, markedUnread: false))]))
                } else {
                    return .peer(peerId, handleThreads, postbox.readStateTable.getCombinedState(peerId))
                }
            }
        }
        if self.entries != entries {
            self.entries = entries
            return true
        } else {
            return false
        }
    }
    
    func immutableView() -> PostboxView {
        return UnreadMessageCountsView(self)
    }
}

public final class UnreadMessageCountsView: PostboxView {
    public let entries: [UnreadMessageCountsItemEntry]
    
    init(_ view: MutableUnreadMessageCountsView) {
        self.entries = view.entries.map { entry in
            switch entry {
            case let .total(keyAndValue, state):
                return .total(keyAndValue?.1, state)
            case let .totalInGroup(groupId, state):
                return .totalInGroup(groupId, state)
            case let .peer(peerId, _, count):
                return .peer(peerId, count)
            }
        }
    }
    
    public func total() -> (PreferencesEntry?, ChatListTotalUnreadState)? {
        for entry in self.entries {
            switch entry {
            case let .total(preferencesEntry, state):
                return (preferencesEntry, state)
            default:
                break
            }
        }
        return nil
    }
    
    public func count(for item: UnreadMessageCountsItem) -> Int32? {
        for entry in self.entries {
            switch entry {
            case .total, .totalInGroup:
                break
            case let .peer(peerId, state):
                if case .peer(peerId, _) = item {
                    return state?.count ?? 0
                }
            }
        }
        return nil
    }
}

final class MutableCombinedReadStateView: MutablePostboxView {
    private let peerId: PeerId
    private let handleThreads: Bool
    fileprivate var state: CombinedPeerReadState?
    
    init(postbox: PostboxImpl, peerId: PeerId, handleThreads: Bool) {
        self.peerId = peerId
        self.handleThreads = handleThreads
        
        let _ = self.refreshDueToExternalTransaction(postbox: postbox)
    }
    
    func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool {
        var updated = false
        
        if transaction.alteredInitialPeerCombinedReadStates[self.peerId] != nil || transaction.updatedPeerThreadCombinedStates.contains(self.peerId) {
            if self.handleThreads, let peer = postbox.peerTable.get(self.peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer) {
                var count: Int32 = 0
                if let summary = postbox.peerThreadsSummaryTable.get(peerId: peerId) {
                    count = summary.totalUnreadCount
                }
                self.state = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 1, maxOutgoingReadId: 1, maxKnownId: 1, count: count, markedUnread: false))])
            } else {
                let state = postbox.readStateTable.getCombinedState(peerId)
                if state != self.state {
                    self.state = state
                    updated = true
                }
            }
        }
        
        return updated
    }

    func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool {
        let state: CombinedPeerReadState?
        if handleThreads, let peer = postbox.peerTable.get(peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer) {
            var count: Int32 = 0
            if let summary = postbox.peerThreadsSummaryTable.get(peerId: peerId) {
                count = summary.totalUnreadCount
            }
            state = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 1, maxOutgoingReadId: 1, maxKnownId: 1, count: count, markedUnread: false))])
        } else {
            state = postbox.readStateTable.getCombinedState(peerId)
        }
        
        if state != self.state {
            self.state = state
            return true
        } else {
            return false
        }
    }
    
    func immutableView() -> PostboxView {
        return CombinedReadStateView(self)
    }
}

public final class CombinedReadStateView: PostboxView {
    public let state: CombinedPeerReadState?
    
    init(_ view: MutableCombinedReadStateView) {
        self.state = view.state
    }
}