//
//  Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//

import Foundation
import SignalServiceKit

@objc
public class SearchResult: NSObject {
    @objc
    public let thread: ThreadViewModel

    init(thread: ThreadViewModel) {
        self.thread = thread
    }
}

public class SearchResultSet {
    public let conversations: [SearchResult]
    public let contacts: [SearchResult]
    public let messages: [SearchResult]

    public init(conversations: [SearchResult], contacts: [SearchResult], messages: [SearchResult]) {
        self.conversations = conversations
        self.contacts = contacts
        self.messages = messages
    }

    public class var empty: SearchResultSet {
        return SearchResultSet(conversations: [], contacts: [], messages: [])
    }
}

@objc
public class ConversationSearcher: NSObject {

    private let finder: FullTextSearchFinder

    @objc
    public static let shared: ConversationSearcher = ConversationSearcher()
    override private init() {
        finder = FullTextSearchFinder()
        super.init()
    }

    public func results(searchText: String, transaction: YapDatabaseReadTransaction) -> SearchResultSet {

        // TODO limit results, prioritize conversations, then contacts, then messages.
        var conversations: [SearchResult] = []
        var contacts: [SearchResult] = []
        var messages: [SearchResult] = []

        self.finder.enumerateObjects(searchText: searchText, transaction: transaction) { (match: Any) in
            if let thread = match as? TSThread {
                let threadViewModel = ThreadViewModel(thread: thread, transaction: transaction)
                let searchResult = SearchResult(thread: threadViewModel)

                conversations.append(searchResult)
            } else {
                Logger.debug("\(self.logTag) in \(#function) unhandled item: \(match)")
            }
        }

        return SearchResultSet(conversations: conversations, contacts: contacts, messages: messages)
    }

    @objc(filterThreads:withSearchText:)
    public func filterThreads(_ threads: [TSThread], searchText: String) -> [TSThread] {
        guard searchText.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 else {
            return threads
        }

        return threads.filter { thread in
            switch thread {
            case let groupThread as TSGroupThread:
                return self.groupThreadSearcher.matches(item: groupThread, query: searchText)
            case let contactThread as TSContactThread:
                return self.contactThreadSearcher.matches(item: contactThread, query: searchText)
            default:
                owsFail("Unexpected thread type: \(thread)")
                return false
            }
        }
    }

    @objc(filterGroupThreads:withSearchText:)
    public func filterGroupThreads(_ groupThreads: [TSGroupThread], searchText: String) -> [TSGroupThread] {
        guard searchText.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 else {
            return groupThreads
        }

        return groupThreads.filter { groupThread in
            return self.groupThreadSearcher.matches(item: groupThread, query: searchText)
        }
    }

    @objc(filterSignalAccounts:withSearchText:)
    public func filterSignalAccounts(_ signalAccounts: [SignalAccount], searchText: String) -> [SignalAccount] {
        guard searchText.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 else {
            return signalAccounts
        }

        return signalAccounts.filter { signalAccount in
            self.signalAccountSearcher.matches(item: signalAccount, query: searchText)
        }
    }

    // MARK: - Helpers

    // MARK: Searchers

    private lazy var groupThreadSearcher: Searcher<TSGroupThread> = Searcher { (groupThread: TSGroupThread) in
        let groupName = groupThread.groupModel.groupName
        let memberStrings = groupThread.groupModel.groupMemberIds.map { recipientId in
            self.indexingString(recipientId: recipientId)
            }.joined(separator: " ")

        return "\(memberStrings) \(groupName ?? "")"
    }

    private lazy var contactThreadSearcher: Searcher<TSContactThread> = Searcher { (contactThread: TSContactThread) in
        let recipientId = contactThread.contactIdentifier()
        return self.indexingString(recipientId: recipientId)
    }

    private lazy var signalAccountSearcher: Searcher<SignalAccount> = Searcher { (signalAccount: SignalAccount) in
        let recipientId = signalAccount.recipientId
        return self.indexingString(recipientId: recipientId)
    }

    private var contactsManager: OWSContactsManager {
        return Environment.current().contactsManager
    }

    private func indexingString(recipientId: String) -> String {
        let contactName = contactsManager.displayName(forPhoneIdentifier: recipientId)
        let profileName = contactsManager.profileName(forRecipientId: recipientId)

        return "\(recipientId) \(contactName) \(profileName ?? "")"
    }
}

//public class ConversationFullTextSearchFinder {
//
//    public func enumerateObjects(searchText: String, transaction: YapDatabaseReadTransaction, block: @escaping (Any) -> Void) {
//        guard let ext = ext(transaction: transaction) else {
//            owsFail("ext was unexpectedly nil")
//            return
//        }
//
//        ext.enumerateKeysAndObjects(matching: searchText) { (_, _, object, _) in
//            block(object)
//        }
//    }
//
//    private func ext(transaction: YapDatabaseReadTransaction) -> YapDatabaseFullTextSearchTransaction? {
//        return transaction.ext(ConversationFullTextSearchFinder.dbExtensionName) as? YapDatabaseFullTextSearchTransaction
//    }
//
//    // MARK: - Extension Registration
//
//    static let dbExtensionName: String = "ConversationFullTextSearchFinderExtension1"
//
//    public class func asyncRegisterDatabaseExtension(storage: OWSStorage) {
//        storage.asyncRegister(dbExtensionConfig, withName: dbExtensionName)
//    }
//
//    // Only for testing.
//    public class func syncRegisterDatabaseExtension(storage: OWSStorage) {
//        storage.register(dbExtensionConfig, withName: dbExtensionName)
//    }
//
//    private class var dbExtensionConfig: YapDatabaseFullTextSearch {
//        let contentColumnName = "content"
//        let handler = YapDatabaseFullTextSearchHandler.withObjectBlock { (dict: NSMutableDictionary, _: String, _: String, object: Any) in
//            if let groupThread = object as? TSGroupThread {
//                dict[contentColumnName] = groupThread.groupModel.groupName
//            }
//        }
//
//        // update search index on contact name changes?
//        // update search index on message insertion?
//
//        // TODO is it worth doing faceted search, i.e. Author / Name / Content?
//        // seems unlikely that mobile users would use the "author: Alice" search syntax.
//        return YapDatabaseFullTextSearch(columnNames: ["content"], handler: handler)
//    }
//}