import Foundation

enum ItemCollectionInfosOperation {
    case replaceInfos(ItemCollectionId.Namespace)
}

final class ItemCollectionInfoTable: Table {
    static func tableSpec(_ id: Int32) -> ValueBoxTable {
        return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: false)
    }
    
    private let sharedKey = ValueBoxKey(length: 4 + 4 + 8)
    
    private var cachedInfos: [ItemCollectionId.Namespace: [(Int, ItemCollectionId, ItemCollectionInfo)]] = [:]
    
    private func key(collectionId: ItemCollectionId, index: Int32) -> ValueBoxKey {
        self.sharedKey.setInt32(0, value: collectionId.namespace)
        self.sharedKey.setInt32(4, value: index)
        self.sharedKey.setInt64(4 + 4, value: collectionId.id)
        return self.sharedKey
    }
    
    private func lowerBound(namespace: ItemCollectionId.Namespace) -> ValueBoxKey {
        let key = ValueBoxKey(length: 4)
        key.setInt32(0, value: namespace)
        return key
    }
    
    private func upperBound(namespace: ItemCollectionId.Namespace) -> ValueBoxKey {
        let key = ValueBoxKey(length: 4)
        key.setInt32(0, value: namespace)
        return key.successor
    }
    
    
    func getInfos(namespace: ItemCollectionId.Namespace) -> [(Int, ItemCollectionId, ItemCollectionInfo)] {
        if let cachedInfo = self.cachedInfos[namespace] {
            return cachedInfo
        } else {
            var infos: [(Int, ItemCollectionId, ItemCollectionInfo)] = []
            self.valueBox.range(self.table, start: self.lowerBound(namespace: namespace), end: self.upperBound(namespace: namespace), values: { key, value in
                if let info = PostboxDecoder(buffer: value).decodeRootObject() as? ItemCollectionInfo {
                    infos.append((Int(key.getInt32(4)), ItemCollectionId(namespace: namespace, id: key.getInt64(4 + 4)), info))
                }
                return true
            }, limit: 0)
            self.cachedInfos[namespace] = infos
            return infos
        }
    }
    
    func getIds(namespace: ItemCollectionId.Namespace) -> [ItemCollectionId] {
        if let cachedInfo = self.cachedInfos[namespace] {
            return cachedInfo.map { $0.1 }
        } else {
            var ids: [ItemCollectionId] = []
            self.valueBox.range(self.table, start: self.lowerBound(namespace: namespace), end: self.upperBound(namespace: namespace), keys: { key in
                ids.append(ItemCollectionId(namespace: namespace, id: key.getInt64(4 + 4)))
                return true
            }, limit: 0)
            return ids
        }
    }
    
    func getIndex(id: ItemCollectionId) -> Int32? {
        var index: Int32?
        self.valueBox.range(self.table, start: self.lowerBound(namespace: id.namespace), end: self.upperBound(namespace: id.namespace), keys: { key in
            let keyId = ItemCollectionId(namespace: id.namespace, id: key.getInt64(4 + 4))
            if keyId == id {
                index = key.getInt32(4)
                return false
            }
            return true
        }, limit: 0)
        return index
    }
    
    func getInfo(id: ItemCollectionId) -> ItemCollectionInfo? {
        var infoKey: ValueBoxKey?
        self.valueBox.range(self.table, start: self.lowerBound(namespace: id.namespace), end: self.upperBound(namespace: id.namespace), keys: { key in
            let keyId = ItemCollectionId(namespace: id.namespace, id: key.getInt64(4 + 4))
            if keyId == id {
                infoKey = key
                return false
            }
            return true
        }, limit: 0)
        if let infoKey = infoKey, let value = self.valueBox.get(self.table, key: infoKey), let info = PostboxDecoder(buffer: value).decodeRootObject() as? ItemCollectionInfo  {
            return info
        } else {
            return nil
        }
    }
    
    func lowerCollectionId(namespaceList: [ItemCollectionId.Namespace], collectionId: ItemCollectionId, index: Int32) -> (ItemCollectionId, Int32)? {
        var currentNamespace = collectionId.namespace
        var currentKey = self.key(collectionId: collectionId, index: index)
        while true {
            var resultCollectionIdAndIndex: (ItemCollectionId, Int32)?
            self.valueBox.range(self.table, start: currentKey, end: self.lowerBound(namespace: currentNamespace), keys: { key in
                resultCollectionIdAndIndex = (ItemCollectionId(namespace: currentNamespace, id: key.getInt64(4 + 4)), key.getInt32(4))
                return true
            }, limit: 1)
            if let resultCollectionIdAndIndex = resultCollectionIdAndIndex {
                return resultCollectionIdAndIndex
            } else {
                let index = namespaceList.firstIndex(of: currentNamespace)!
                if index == 0 {
                    return nil
                } else {
                    currentNamespace = namespaceList[index - 1]
                    currentKey = self.upperBound(namespace: currentNamespace)
                }
            }
        }
    }
    
    func higherCollectionId(namespaceList: [ItemCollectionId.Namespace], collectionId: ItemCollectionId, index: Int32) -> (ItemCollectionId, Int32)? {
        var currentNamespace = collectionId.namespace
        var currentKey = self.key(collectionId: collectionId, index: index)
        while true {
            var resultCollectionIdAndIndex: (ItemCollectionId, Int32)?
            self.valueBox.range(self.table, start: currentKey, end: self.upperBound(namespace: currentNamespace), keys: { key in
                resultCollectionIdAndIndex = (ItemCollectionId(namespace: currentNamespace, id: key.getInt64(4 + 4)), key.getInt32(4))
                return true
            }, limit: 1)
            if let resultCollectionIdAndIndex = resultCollectionIdAndIndex {
                return resultCollectionIdAndIndex
            } else {
                let index = namespaceList.firstIndex(of: currentNamespace)!
                if index == namespaceList.count - 1 {
                    return nil
                } else {
                    currentNamespace = namespaceList[index + 1]
                    currentKey = self.lowerBound(namespace: currentNamespace)
                }
            }
        }
    }
    
    func replaceInfos(namespace: ItemCollectionId.Namespace, infos: [(ItemCollectionId, ItemCollectionInfo)]) {
        self.cachedInfos.removeAll()
        
        var currentCollectionKeys: [ValueBoxKey] = []
        self.valueBox.range(self.table, start: self.lowerBound(namespace: namespace), end: self.upperBound(namespace: namespace), keys: { key in
            currentCollectionKeys.append(key)
            return true
        }, limit: 0)
        
        for key in currentCollectionKeys {
            self.valueBox.remove(self.table, key: key, secure: false)
        }
        
        var index: Int32 = 0
        let sharedEncoder = PostboxEncoder()
        for (id, info) in infos {
            sharedEncoder.reset()
            sharedEncoder.encodeRootObject(info)
            withExtendedLifetime(sharedEncoder, {
                self.valueBox.set(self.table, key: self.key(collectionId: id, index: index), value: sharedEncoder.readBufferNoCopy())
            })
            index += 1
        }
    }
    
    override func clearMemoryCache() {
        self.cachedInfos.removeAll()
    }
    
    override func beforeCommit() {
    }
}