import Foundation

public struct HolesViewMedia: Comparable {
    public let media: Media
    public let peer: Peer
    public let authorIsContact: Bool
    public let index: MessageIndex
    
    public static func ==(lhs: HolesViewMedia, rhs: HolesViewMedia) -> Bool {
        return lhs.index == rhs.index && (lhs.media === rhs.media || lhs.media.isEqual(to: rhs.media)) && lhs.peer.isEqual(rhs.peer) && lhs.authorIsContact == rhs.authorIsContact
    }
    
    public static func <(lhs: HolesViewMedia, rhs: HolesViewMedia) -> Bool {
        return lhs.index < rhs.index
    }
}

public struct MessageOfInterestHole: Hashable, Equatable, CustomStringConvertible {
    public let hole: MessageHistoryViewHole
    public let direction: MessageHistoryViewRelativeHoleDirection
    
    public var description: String {
        return "hole: \(self.hole), direction: \(self.direction)"
    }
}

public enum MessageOfInterestViewLocation: Hashable {
    case peer(PeerId)
}

final class MutableMessageOfInterestHolesView: MutablePostboxView {
    private let location: MessageOfInterestViewLocation
    private let count: Int
    private var anchor: HistoryViewInputAnchor
    private var wrappedView: MutableMessageHistoryView
    private var peerIds: MessageHistoryViewInput
    
    fileprivate var closestHole: MessageOfInterestHole?
    fileprivate var closestLaterMedia: [HolesViewMedia] = []
    
    init(postbox: Postbox, location: MessageOfInterestViewLocation, namespace: MessageId.Namespace, count: Int) {
        self.location = location
        self.count = count
        
        let mainPeerId: PeerId
        let peerIds: MessageHistoryViewInput
        switch self.location {
        case let .peer(id):
            mainPeerId = id
            peerIds = postbox.peerIdsForLocation(.peer(id), ignoreRelatedChats: false)
        }
        self.peerIds = peerIds
        var anchor: HistoryViewInputAnchor = .upperBound
        if let combinedState = postbox.readStateTable.getCombinedState(mainPeerId), let state = combinedState.states.first, state.1.count != 0 {
            switch state.1 {
            case let .idBased(maxIncomingReadId, _, _, _, _):
                anchor = .message(MessageId(peerId: mainPeerId, namespace: state.0, id: maxIncomingReadId))
            case let .indexBased(maxIncomingReadIndex, _, _, _):
                anchor = .index(maxIncomingReadIndex)
            }
        }
        self.anchor = anchor
        self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
        let _ = self.updateFromView()
    }
    
    private func updateFromView() -> Bool {
        let closestHole: MessageOfInterestHole?
        if let (hole, direction, _) = self.wrappedView.firstHole() {
            closestHole = MessageOfInterestHole(hole: hole, direction: direction)
        } else {
            closestHole = nil
        }
        
        var closestLaterMedia: [HolesViewMedia] = []
        switch self.wrappedView.sampledState {
        case .loading:
            break
        case let .loaded(sample):
            switch sample.anchor {
            case .index:
                let anchorIndex = binaryIndexOrLower(sample.entries, sample.anchor)
                loop: for i in max(0, anchorIndex) ..< sample.entries.count {
                    let message = sample.entries[i].message
                    if !message.media.isEmpty, let peer = message.peers[message.id.peerId] {
                        for media in message.media {
                            closestLaterMedia.append(HolesViewMedia(media: media, peer: peer, authorIsContact: sample.entries[i].attributes.authorIsContact, index: message.index))
                        }
                    }
                    if closestLaterMedia.count >= 3 {
                        break loop
                    }
                }
            case .lowerBound, .upperBound:
                break
            }
        }
        
        if self.closestHole != closestHole || self.closestLaterMedia != closestLaterMedia {
            self.closestHole = closestHole
            self.closestLaterMedia = closestLaterMedia
            return true
        } else {
            return false
        }
    }
    
    func replay(postbox: Postbox, transaction: PostboxTransaction) -> Bool {
        var peerId: PeerId
        switch self.location {
            case let .peer(id):
                peerId = id
        }
        var anchor: HistoryViewInputAnchor = self.anchor
        if transaction.alteredInitialPeerCombinedReadStates[peerId] != nil {
            var updatedAnchor: HistoryViewInputAnchor = .upperBound
            if let combinedState = postbox.readStateTable.getCombinedState(peerId), let state = combinedState.states.first, state.1.count != 0 {
                switch state.1 {
                case let .idBased(maxIncomingReadId, _, _, _, _):
                    anchor = .message(MessageId(peerId: peerId, namespace: state.0, id: maxIncomingReadId))
                case let .indexBased(maxIncomingReadIndex, _, _, _):
                    anchor = .index(maxIncomingReadIndex)
                }
            }
            anchor = updatedAnchor
        }
        
        if self.anchor != anchor {
            self.anchor = anchor
            let peerIds: MessageHistoryViewInput
            switch self.location {
            case let .peer(id):
                peerIds = postbox.peerIdsForLocation(.peer(id), ignoreRelatedChats: false)
            }
            self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
            return self.updateFromView()
        } else if self.wrappedView.replay(postbox: postbox, transaction: transaction) {
            var reloadView = false
            if !transaction.currentPeerHoleOperations.isEmpty {
                var allPeerIds: [PeerId]
                switch peerIds {
                case let .single(peerId):
                    allPeerIds = [peerId]
                case let .associated(peerId, attachedMessageId):
                    allPeerIds = [peerId]
                    if let attachedMessageId = attachedMessageId {
                        allPeerIds.append(attachedMessageId.peerId)
                    }
                case .external:
                    allPeerIds = []
                    break
                }
                for (key, _) in transaction.currentPeerHoleOperations {
                    if allPeerIds.contains(key.peerId) {
                        reloadView = true
                        break
                    }
                }
            }
            if reloadView {
                let peerIds: MessageHistoryViewInput
                switch self.location {
                case let .peer(id):
                    peerIds = postbox.peerIdsForLocation(.peer(id), ignoreRelatedChats: false)
                }
                self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
            }
            
            return self.updateFromView()
        } else {
            return false
        }
    }
    
    func immutableView() -> PostboxView {
        return MessageOfInterestHolesView(self)
    }
}

public final class MessageOfInterestHolesView: PostboxView {
    public let closestHole: MessageOfInterestHole?
    public let closestLaterMedia: [HolesViewMedia]
    
    init(_ view: MutableMessageOfInterestHolesView) {
        self.closestHole = view.closestHole
        self.closestLaterMedia = view.closestLaterMedia
    }
}
