import Foundation

public enum PeerReadStateSynchronizationOperation: Equatable {
    case Push(state: CombinedPeerReadState?, thenSync: Bool)
    case Validate
}

final class MessageHistorySynchronizeReadStateTable: Table {
    static func tableSpec(_ id: Int32) -> ValueBoxTable {
        return ValueBoxTable(id: id, keyType: .int64, compactValuesOnCreation: true)
    }
    
    private let sharedKey = ValueBoxKey(length: 8)
    
    private func key(peerId: PeerId) -> ValueBoxKey {
        self.sharedKey.setInt64(0, value: peerId.toInt64())
        return self.sharedKey
    }
    
    private var updatedPeerIds: [PeerId: PeerReadStateSynchronizationOperation?] = [:]
    
    private func lowerBound() -> ValueBoxKey {
        let key = ValueBoxKey(length: 8)
        key.setInt64(0, value: 0)
        return key
    }
    
    private func upperBound() -> ValueBoxKey {
        let key = ValueBoxKey(length: 8)
        memset(key.memory, 0xff, key.length)
        return key
    }
    
    func set(_ peerId: PeerId, operation: PeerReadStateSynchronizationOperation?, operations: inout [PeerId: PeerReadStateSynchronizationOperation?]) {
        self.updatedPeerIds[peerId] = operation
        operations[peerId] = operation
    }
    
    func get(getCombinedPeerReadState: (PeerId) -> CombinedPeerReadState?) -> [PeerId: PeerReadStateSynchronizationOperation] {
        self.beforeCommit()
        
        var operations: [PeerId: PeerReadStateSynchronizationOperation] = [:]
        self.valueBox.range(self.table, start: self.lowerBound(), end: self.upperBound(), values: { key, value in
            let peerId = PeerId(key.getInt64(0))
            var operationValue: Int8 = 0
            value.read(&operationValue, offset: 0, length: 1)
            
            let operation: PeerReadStateSynchronizationOperation
            if operationValue == 0 {
                var syncValue: Int8 = 0
                value.read(&syncValue, offset: 0, length: 1)
                operation = .Push(state: getCombinedPeerReadState(peerId), thenSync: syncValue != 0)
            } else {
                operation = .Validate
            }
            
            operations[peerId] = operation
            return true
        }, limit: 0)
        return operations
    }
    
    override func beforeCommit() {
        if !self.updatedPeerIds.isEmpty {
            let key = ValueBoxKey(length: 8)
            let buffer = WriteBuffer()
            for (peerId, operation) in self.updatedPeerIds {
                key.setInt64(0, value: peerId.toInt64())
                if let operation = operation {
                    buffer.reset()
                    switch operation {
                        case let .Push(_, thenSync):
                            var operationValue: Int8 = 0
                            buffer.write(&operationValue, offset: 0, length: 1)
                            var syncValue: Int8 = thenSync ? 1 : 0
                            buffer.write(&syncValue, offset: 0, length: 1)
                        case .Validate:
                            var operationValue: Int8 = 1
                            buffer.write(&operationValue, offset: 0, length: 1)
                    }
                    
                    self.valueBox.set(self.table, key: key, value: buffer)
                } else {
                    self.valueBox.remove(self.table, key: key, secure: false)
                }
            }
            self.updatedPeerIds.removeAll()
        }
    }
}