import Postbox
import SwiftSignalKit

public struct ChannelAdminEventLogEntry: Comparable {
    public let stableId: UInt32
    public let event: AdminLogEvent
    public let peers: [PeerId: Peer]
    
    public static func ==(lhs: ChannelAdminEventLogEntry, rhs: ChannelAdminEventLogEntry) -> Bool {
        return lhs.event == rhs.event
    }
    
    public static func <(lhs: ChannelAdminEventLogEntry, rhs: ChannelAdminEventLogEntry) -> Bool {
        return lhs.event < rhs.event
    }
}

public enum ChannelAdminEventLogUpdateType {
    case initial
    case generic
    case load
}

public struct ChannelAdminEventLogFilter: Equatable {
    public let query: String?
    public let events: AdminLogEventsFlags
    public let adminPeerIds: [PeerId]?
    
    public init(query: String? = nil, events: AdminLogEventsFlags = .all, adminPeerIds: [PeerId]? = nil) {
        self.query = query
        self.events = events
        self.adminPeerIds = adminPeerIds
    }
    
    public static func ==(lhs: ChannelAdminEventLogFilter, rhs: ChannelAdminEventLogFilter) -> Bool {
        if lhs.query != rhs.query {
            return false
        }
        if lhs.events != rhs.events {
            return false
        }
        if let lhsAdminPeerIds = lhs.adminPeerIds, let rhsAdminPeerIds = rhs.adminPeerIds {
            if lhsAdminPeerIds != rhsAdminPeerIds {
                return false
            }
        } else if (lhs.adminPeerIds != nil) != (rhs.adminPeerIds != nil) {
            return false
        }
        return true
    }
    
    public var isEmpty: Bool {
        if self.query != nil {
            return false
        }
        if self.events != .all {
            return false
        }
        if self.adminPeerIds != nil {
            return false
        }
        return true
    }
    
    public func withQuery(_ query: String?) -> ChannelAdminEventLogFilter {
        return ChannelAdminEventLogFilter(query: query, events: self.events, adminPeerIds: self.adminPeerIds)
    }
    
    public func withEvents(_ events: AdminLogEventsFlags) -> ChannelAdminEventLogFilter {
        return ChannelAdminEventLogFilter(query: self.query, events: events, adminPeerIds: self.adminPeerIds)
    }
    
    public func withAdminPeerIds(_ adminPeerIds: [PeerId]?) -> ChannelAdminEventLogFilter {
        return ChannelAdminEventLogFilter(query: self.query, events: self.events, adminPeerIds: adminPeerIds)
    }
}

public final class ChannelAdminEventLogContext {
    private let queue: Queue = Queue.mainQueue()
    
    private let postbox: Postbox
    private let network: Network
    private let peerId: PeerId
    
    private var filter: ChannelAdminEventLogFilter = ChannelAdminEventLogFilter()
    
    private var nextStableId: UInt32 = 1
    private var stableIds: [AdminLogEventId: UInt32] = [:]
    
    private var entries: ([ChannelAdminEventLogEntry], ChannelAdminEventLogFilter) = ([], ChannelAdminEventLogFilter())
    private var hasEntries: Bool = false
    private var hasEarlier: Bool = true
    private var loadingMoreEarlier: Bool = false
    
    private var subscribers = Bag<([ChannelAdminEventLogEntry], Bool, ChannelAdminEventLogUpdateType, Bool) -> Void>()
    
    private let loadMoreDisposable = MetaDisposable()
    
    public init(postbox: Postbox, network: Network, peerId: PeerId) {
        self.postbox = postbox
        self.network = network
        self.peerId = peerId
    }
    
    deinit {
        self.loadMoreDisposable.dispose()
    }
    
    public func get() -> Signal<([ChannelAdminEventLogEntry], Bool, ChannelAdminEventLogUpdateType, Bool), NoError> {
        let queue = self.queue
        return Signal { [weak self] subscriber in
            if let strongSelf = self {
                subscriber.putNext((strongSelf.entries.0, strongSelf.hasEarlier, .initial, strongSelf.hasEntries))
                
                let index = strongSelf.subscribers.add({ entries, hasEarlier, type, hasEntries in
                    subscriber.putNext((entries, hasEarlier, type, hasEntries))
                })
                
                return ActionDisposable {
                    queue.async {
                        if let strongSelf = self {
                            strongSelf.subscribers.remove(index)
                        }
                    }
                }
            } else {
                return EmptyDisposable
            }
        } |> runOn(queue)
    }
    
    public func setFilter(_ filter: ChannelAdminEventLogFilter) {
        if self.filter != filter {
            self.filter = filter
            self.loadingMoreEarlier = false
            self.hasEarlier = false
            self.hasEntries = false
            
            for subscriber in self.subscribers.copyItems() {
                subscriber(self.entries.0, self.hasEarlier, .load, self.hasEntries)
            }
            
            self.loadMoreEntries()
        }
    }
    
    public func loadMoreEntries() {
        assert(self.queue.isCurrent())
        
        if self.loadingMoreEarlier {
            return
        }
        
        let maxId: AdminLogEventId
        if self.entries.1 == self.filter, let first = self.entries.0.first {
            maxId = first.event.id
        } else {
            maxId = AdminLogEventId.max
        }
        
        self.loadingMoreEarlier = true
        self.loadMoreDisposable.set((channelAdminLogEvents(postbox: self.postbox, network: self.network, peerId: self.peerId, maxId: maxId, minId: AdminLogEventId.min, limit: 100, query: self.filter.query, filter: self.filter.events, admins: self.filter.adminPeerIds)
        |> deliverOn(self.queue)).start(next: { [weak self] result in
            if let strongSelf = self {
                var events = result.events.sorted()
                if strongSelf.entries.1 == strongSelf.filter {
                    if let first = strongSelf.entries.0.first {
                        var clipIndex = events.count
                        for i in (0 ..< events.count).reversed() {
                            if events[i] >= first.event {
                                clipIndex = i - 1
                            }
                        }
                        if clipIndex < events.count {
                            events.removeSubrange(clipIndex ..< events.count)
                        }
                    }
                    
                    var entries: [ChannelAdminEventLogEntry] = events.map { event in
                        return ChannelAdminEventLogEntry(stableId: strongSelf.stableIdForEventId(event.id), event: event, peers: result.peers)
                    }
                    entries.append(contentsOf: strongSelf.entries.0)
                    strongSelf.entries = (entries, strongSelf.filter)
                } else {
                    let entries: [ChannelAdminEventLogEntry] = events.map { event in
                        return ChannelAdminEventLogEntry(stableId: strongSelf.stableIdForEventId(event.id), event: event, peers: result.peers)
                    }
                    strongSelf.entries = (entries, strongSelf.filter)
                }
                
                strongSelf.hasEarlier = !events.isEmpty
                strongSelf.loadingMoreEarlier = false
                strongSelf.hasEntries = true
                
                for subscriber in strongSelf.subscribers.copyItems() {
                    subscriber(strongSelf.entries.0, strongSelf.hasEarlier, .load, strongSelf.hasEntries)
                }
            }
        }))
    }
    
    private func stableIdForEventId(_ id: AdminLogEventId) -> UInt32 {
        if let value = self.stableIds[id] {
            return value
        } else {
            let value = self.nextStableId
            self.nextStableId += 1
            self.stableIds[id] = value
            return value
        }
    }
}