import Foundation

private func collectionId(_ peerId: PeerId) -> String {
    return "p\(UInt64(bitPattern: peerId.toInt64()))"
}

private func itemId(_ messageId: MessageId) -> String {
    return "p\(UInt64(bitPattern: messageId.peerId.toInt64()))a\(UInt32(bitPattern: messageId.namespace))b\(UInt32(bitPattern: messageId.id))"
}

private func messageTags(_ tags: MessageTags) -> String {
    var result = ""
    for tag in tags {
        if !result.isEmpty {
            result += " "
        }
        result += "t\(tag.rawValue)"
    }
    return result
}

private func parseMessageId(_ value: String) -> MessageId? {
    if !value.hasPrefix("p") {
        return nil
    }
    guard let aRange = value.range(of: "a") else {
        return nil
    }
    guard let bRange = value.range(of: "b") else {
        return nil
    }
    let pString = value[value.index(value.startIndex, offsetBy: 1) ..< aRange.lowerBound]
    let nString = value[aRange.upperBound ..< bRange.lowerBound]
    let iString = value[bRange.upperBound ..< value.endIndex]
    
    guard let pValue = UInt64(pString) else {
        return nil
    }
    guard let nValue = UInt32(nString) else {
        return nil
    }
    guard let iValue = UInt32(iString) else {
        return nil
    }
    
    return MessageId(peerId: PeerId(Int64(bitPattern: pValue)), namespace: Int32(bitPattern: nValue), id: Int32(bitPattern: iValue))
}

private let alphanumerics = CharacterSet.alphanumerics

final class MessageHistoryTextIndexTable {
    static func tableSpec(_ id: Int32) -> ValueBoxFullTextTable {
        return ValueBoxFullTextTable(id: id)
    }
    
    private let valueBox: ValueBox
    private let table: ValueBoxFullTextTable
    
    init(valueBox: ValueBox, table: ValueBoxFullTextTable) {
        self.valueBox = valueBox
        self.table = table
    }
    
    func add(messageId: MessageId, text: String, tags: MessageTags) {
        self.valueBox.fullTextSet(self.table, collectionId: collectionId(messageId.peerId), itemId: itemId(messageId), contents: text, tags: messageTags(tags))
    }
    
    func remove(messageId: MessageId) {
        self.valueBox.fullTextRemove(self.table, itemId: itemId(messageId), secure: true)
    }
    
    func search(peerId: PeerId?, text: String, tags: MessageTags?) -> [MessageId] {
        var escapedText = String(text.map({ c in
            var codeUnits: [UnicodeScalar] = []
            for codeUnit in String(c).unicodeScalars {
                codeUnits.append(codeUnit)
            }
            for codeUnit in codeUnits {
                if !alphanumerics.contains(codeUnit) {
                    return " "
                }
            }
            return c
        }))
        if !escapedText.isEmpty {
            escapedText += "*"
        }
        var result: [MessageId] = []
        self.valueBox.fullTextMatch(self.table, collectionId: peerId.flatMap { collectionId($0) }, query: escapedText, tags: tags.flatMap(messageTags), values: { _, itemId in
            if let messageId = parseMessageId(itemId) {
                result.append(messageId)
            } else {
                assertionFailure()
            }
            return true
        })
        return result
    }
}