import Foundation
import ValueBox
import PostboxDataTypes
import Table
import PostboxCoding

public protocol ReverseAssociatedPeerTable {
    func removeReverseAssociation(target: PeerId, from: PeerId)
    func addReverseAssociation(target: PeerId, from: PeerId)
}

public final class PeerTable: Table {
    public static func tableSpec(_ id: Int32) -> ValueBoxTable {
        return ValueBoxTable(id: id, keyType: .int64, compactValuesOnCreation: false)
    }
    
    private let reverseAssociatedTable: ReverseAssociatedPeerTable?
    
    private let sharedEncoder = PostboxEncoder()
    private let sharedKey = ValueBoxKey(length: 8)
    
    private var cachedPeers: [PeerId: Peer] = [:]
    private var updatedInitialPeers: [PeerId: Peer?] = [:]
    
    public init(valueBox: ValueBox, table: ValueBoxTable, reverseAssociatedTable: ReverseAssociatedPeerTable?) {
        self.reverseAssociatedTable = reverseAssociatedTable
        
        super.init(valueBox: valueBox, table: table)
    }
    
    private func key(_ id: PeerId) -> ValueBoxKey {
        self.sharedKey.setInt64(0, value: id.toInt64())
        return self.sharedKey
    }
    
    public func set(_ peer: Peer) {
        let previous = self.get(peer.id)
        self.cachedPeers[peer.id] = peer
        if self.updatedInitialPeers[peer.id] == nil {
            self.updatedInitialPeers[peer.id] = previous
        }
    }
    
    public func get(_ id: PeerId) -> Peer? {
        if let peer = self.cachedPeers[id] {
            return peer
        }
        if let value = self.valueBox.get(self.table, key: self.key(id)) {
            if let peer = PostboxDecoder(buffer: value).decodeRootObject() as? Peer {
                self.cachedPeers[id] = peer
                return peer
            }
        }
        return nil
    }
    
    override public func clearMemoryCache() {
        self.cachedPeers.removeAll()
        assert(self.updatedInitialPeers.isEmpty)
    }
    
    public func transactionUpdatedPeers() -> [(Peer?, Peer)] {
        var result: [(Peer?, Peer)] = []
        for (peerId, initialPeer) in self.updatedInitialPeers {
            if let peer = self.get(peerId) {
                result.append((initialPeer, peer))
            } else {
                assertionFailure()
            }
        }
        return result
    }
    
    override public func beforeCommit() {
        if !self.updatedInitialPeers.isEmpty {
            for (peerId, previousPeer) in self.updatedInitialPeers {
                if let peer = self.cachedPeers[peerId] {
                    self.sharedEncoder.reset()
                    self.sharedEncoder.encodeRootObject(peer)
                    
                    self.valueBox.set(self.table, key: self.key(peerId), value: self.sharedEncoder.readBufferNoCopy())
                    
                    let previousAssociation = previousPeer?.associatedPeerId
                    if previousAssociation != peer.associatedPeerId {
                        if let previousAssociation = previousAssociation {
                            self.reverseAssociatedTable?.removeReverseAssociation(target: previousAssociation, from: peerId)
                        }
                        if let associatedPeerId = peer.associatedPeerId {
                            self.reverseAssociatedTable?.addReverseAssociation(target: associatedPeerId, from: peerId)
                        }
                    }
                } else {
                    assertionFailure()
                }
            }
            
            self.updatedInitialPeers.removeAll()
        }
    }
}