import VivochaSDK

extension Notification {
}

@objc(RCTVivocha)
class RCTVivocha: RCTEventEmitter {
    
    public static var emitter: RCTVivocha!
    
    private var events: [String: Int] = [
        "agentdata": 0,
        "persistence": 0,
        "agentpresence": 0,
        "agenttyping": 0,
        "chatackreceived": 0,
        "chatacksent": 0,
        "closeremote": 0,
        "actionsent": 0,
        "messagereceived": 0,
        "messagesent": 0,
        "attachmentreceived": 0,
        "attachmentsent": 0,
        "screenshotsession": 0,
        "terminate": 0,
        "transferred": 0,
        "terminatebutton": 0,
        "chatviewminimized": 0
    ]
    
    private var tappedUrls: [String: Bool] = [:]
    
    override init() {
        super.init()
        print("Vivocha init")
        RCTVivocha.emitter = self
        registerCallbacks()
    }
    
    open override func supportedEvents() -> [String]! {
        [
            "agentdata",
            "persistence",
            "agentpresence",
            "agenttyping",
            "chatackreceived",
            "chatacksent",
            "closeremote",
            "actionsent",
            "messagereceived",
            "messagesent",
            "attachmentreceived",
            "attachmentsent",
            "screenshotsession",
            "terminate",
            "transferred",
            "terminatebutton",
            "onAction",
            "urlTapped",
            "chatviewminimized"
        ]
    }
    
    private func emit(name: String, data: Any) {
        RCTVivocha.emitter.sendEvent(withName: name, body: data)
    }
    
    private func registerCallbacks() {
        NotificationCenter.default.addObserver(
            forName: NSNotification.Name(VivochaSDK_Notif_AvailableAgents),
            object: nil,
            queue: nil,
            using: { note in
                print("VivochaSDK_Notif_AvailableAgents event")
                if (self.events["agentdata"]! > 0) {
                    let vNote = note.userInfo?["VivochaNotificationObject"] as! VivochaNotification
                    self.emit(name: "agentdata", data: ["available": true, "data": vNote.payload])
                }
            })
        NotificationCenter.default.addObserver(
            forName: NSNotification.Name(VivochaSDK_Notif_UnavailableAgents),
            object: nil,
            queue: nil,
            using: { note in
                print("VivochaSDK_Notif_UnavailableAgents event")
                if (self.events["agentdata"]! > 0) {
                    let vNote = note.userInfo?["VivochaNotificationObject"] as! VivochaNotification
                    self.emit(name: "agentdata", data: ["available": false, "data": vNote.payload])
                }
            })
        NotificationCenter.default.addObserver(
            forName: NSNotification.Name(VivochaSDK_Notif_OnPersistence),
            object: nil,
            queue: nil,
            using: { note in
                print("VivochaSDK_Notif_OnPersistence event")
                if (self.events["persistence"]! > 0) {
                    let vNote = note.userInfo?["VivochaNotificationObject"] as! VivochaNotification
                    let payload = vNote.payload as! [ String : Any ]
                    self.emit(name: "persistence", data: [
                        "conversation": payload["conversation"],
                        "currentcontact": (payload["currentContact"] as! VivochaContact?)?.toCordovaJSON(),
                        "didresumecontactfromconversation": payload["didResumeFromConversation"]
                    ])
                }
            })
        NotificationCenter.default.addObserver(
            forName: NSNotification.Name(VivochaSDK_Notif_Contact_ReceivedAgentPresence),
            object: nil,
            queue: nil,
            using: { note in
                print("VivochaSDK_Notif_Contact_ReceivedAgentPresence event")
                if (self.events["agentpresence"]! > 0) {
                    let vNote = note.userInfo?["VivochaNotificationObject"] as! VivochaNotification
                    let presence = vNote.payload as! VivochaChatPresence
                    self.emit(name: "agentpresence", data: [ "presence": Utils.presenceToObj(presence: presence) ])
                }
            })
        NotificationCenter.default.addObserver(
            forName: NSNotification.Name(VivochaSDK_Notif_Contact_ReceivedTextMessage),
            object: nil,
            queue: nil,
            using: { note in
                print("VivochaSDK_Notif_Contact_ReceivedTextMessage event")
                if (self.events["messagereceived"]! > 0) {
                    let vNote = note.userInfo?["VivochaNotificationObject"] as! VivochaNotification
                    let message = vNote.payload as! VivochaMessage
                    self.emit(name: "messagereceived", data: Utils.messageToObj(message: message))
                }
            })
        NotificationCenter.default.addObserver(
            forName: NSNotification.Name(VivochaSDK_Notif_Contact_ReceivedAckMessage),
            object: nil,
            queue: nil,
            using: { note in
                print("VivochaSDK_Notif_Contact_ReceivedAckMessage event")
                if (self.events["chatackreceived"]! > 0) {
                    let vNote = note.userInfo?["VivochaNotificationObject"] as! VivochaNotification
                    let ack = vNote.payload as! VivochaChatAck
                    self.emit(name: "chatackreceived", data: [ "ack": Utils.ackToObj(ack: ack) ])
                }
            })
        NotificationCenter.default.addObserver(
            forName: NSNotification.Name(VivochaSDK_Notif_Contact_SentTextMessageResult),
            object: nil,
            queue: nil,
            using: { note in
                print("VivochaSDK_Notif_Contact_SentTextMessageResult event")
                if (self.events["messagesent"]! > 0) {
                    let vNote = note.userInfo?["VivochaNotificationObject"] as! VivochaNotification
                    let message = vNote.payload as! VivochaMessage
                    self.emit(name: "messagesent", data: [ "message": Utils.messageToObj(message: message) ])
                }
            })
        NotificationCenter.default.addObserver(
            forName: NSNotification.Name(VivochaSDK_Notif_Contact_SentAckMessageResult),
            object: nil,
            queue: nil,
            using: { note in
                print("VivochaSDK_Notif_Contact_SentAckMessageResult event")
                if (self.events["chatacksent"]! > 0) {
                    let vNote = note.userInfo?["VivochaNotificationObject"] as! VivochaNotification
                    let ack = vNote.payload as! VivochaChatAck
                    self.emit(name: "chatacksent", data: [ "ack": Utils.ackToObj(ack: ack) ])
                }
            })
        NotificationCenter.default.addObserver(
            forName: NSNotification.Name(VivochaSDK_Notif_Contact_DidSendCustomActionResult),
            object: nil,
            queue: nil,
            using: { note in
                print("VivochaSDK_Notif_Contact_DidSendCustomActionResult event")
                if (self.events["actionsent"]! > 0) {
                    let vNote = note.userInfo?["VivochaNotificationObject"] as! VivochaNotification
                    let action = vNote.payload as! VivochaCustomAction
                    self.emit(name: "actionsent", data: [
                        "name": action.actionName,
                        "id": action.actionID,
                        "data": action.actionData
                    ])
                }
            })
        NotificationCenter.default.addObserver(
            forName: NSNotification.Name(VivochaSDK_Notif_Contact_AgentIsTyping),
            object: nil,
            queue: nil,
            using: { note in
                print("VivochaSDK_Notif_Contact_AgentIsTyping event")
                if (self.events["agenttyping"]! > 0) {
                    self.emit(name: "agenttyping", data: [
                        "isTyping": true
                    ])
                }
            })
        NotificationCenter.default.addObserver(
            forName: NSNotification.Name(VivochaSDK_Notif_Contact_UserDidTerminateChatView),
            object: nil,
            queue: nil,
            using: { note in
                print("VivochaSDK_Notif_Contact_UserDidTerminateChatView event")
                if (self.events["terminatebutton"]! > 0) {
                    let vNote = note.userInfo?["VivochaNotificationObject"] as! VivochaNotification
                    let contactId = vNote.payload as! NSString
                    self.emit(name: "terminatebutton", data: [
                        "contactId": contactId
                    ])
                }
            })

        NotificationCenter.default.addObserver(
            forName: NSNotification.Name(VivochaSDK_Notif_Contact_ChatViewMinimized),
            object: nil,
            queue: nil,
            using: { note in
                print("VivochaSDK_Notif_Contact_ChatViewMinimized event")
                if (self.events["chatviewminimized"]! > 0) {
                    self.emit(name: "chatviewminimized", data: [])
                }
            })

        NotificationCenter.default.addObserver(
            forName: NSNotification.Name(VivochaSDK_Notif_Contact_ReceivedTerminationEventFromOtherPeer),
            object: nil,
            queue: nil,
            using: { note in
                print("VivochaSDK_Notif_Contact_ReceivedTerminationEventFromOtherPeer event")
                if (self.events["closeremote"]! > 0) {
                    let vNote = note.userInfo?["VivochaNotificationObject"] as! VivochaNotification
                    let contactId = vNote.payload as! NSString
                    self.emit(name: "closeremote", data: [
                        "contactId": contactId
                    ])
                }
            })
        NotificationCenter.default.addObserver(
            forName: NSNotification.Name(VivochaSDK_Notif_Contact_Terminated),
            object: nil,
            queue: nil,
            using: { note in
                print("VivochaSDK_Notif_Contact_Terminated event")
                if (self.events["terminate"]! > 0) {
                    let vNote = note.userInfo?["VivochaNotificationObject"] as! VivochaNotification
                    let contactId = vNote.payload as! NSString
                    self.emit(name: "terminate", data: [
                        "contactId": contactId
                    ])
                }
            })
        NotificationCenter.default.addObserver(
            forName: NSNotification.Name(VivochaSDK_Notif_Contact_Screenshot_Authorized),
            object: nil,
            queue: nil,
            using: { note in
                print("VivochaSDK_Notif_Contact_Screenshot_Authorized event")
                if (self.events["screenshotsession"]! > 0) {
                    self.emit(name: "screenshotsession", data: [
                        "authorized": true
                    ])
                }
            })
        NotificationCenter.default.addObserver(
            forName: NSNotification.Name(VivochaSDK_Notif_Contact_Screenshot_Stopped),
            object: nil,
            queue: nil,
            using: { note in
                print("VivochaSDK_Notif_Contact_Screenshot_Stopped event")
                if (self.events["screenshotsession"]! > 0) {
                    self.emit(name: "screenshotsession", data: [
                        "authorized": false
                    ])
                }
            })
    }
    
    @objc(registerEvent:)
    func registerEvent(event: String) -> Any {
        if (events[event] != nil) {
            events[event]! += 1;
            return true;
        }
        return false;
    }
    
    @objc(unregisterEvent:)
    func unregisterEvent(event: String) -> Any {
        if (events[event]! > 0) {
            events[event]! -= 1;
            return true;
        }
        return false;
    }

    @objc(start:withServId:withOptions:withResolver:withRejecter:)
    func start(acctId: NSString,
               servId: NSString,
               options: NSDictionary?,
               resolve: @escaping RCTPromiseResolveBlock,
               reject: @escaping RCTPromiseRejectBlock) -> Void {
        Vivocha.setStartCallback({ success, code, reason, data in
            if (success) {
                print("Vivocha startup: success")
                resolve(data)
            } else {
                print("Vivocha startup: failure")
                reject(String(code), String(reason.rawValue), NSError(domain: "com.vivocha", code: code))
            }
        })
        VivochaSDK.Vivocha.start(
                withAccountID: acctId as String,
                andServID: servId as String,
                andOptions: Utils.objToOptions(obj: options)
        )
    }

    @objc(stop)
    func stop() -> Bool {
        return(VivochaSDK.Vivocha.stop())
    }

    @objc(getDeveloperMode)
    func getDeveloperMode() -> Any {
        return(Vivocha.getDeveloperMode)
    }

    @objc(setDeveloperMode:)
    func setDeveloperMode(mode: Bool) -> Any {
        return(Vivocha.setDeveloperMode(mode))
    }

    @objc(getSideTab)
    func getSideTab() -> Any {
        return(Vivocha.isShowingSideTab())
    }

    @objc(setSideTab:)
    func setSideTab(show: Bool) -> Any {
        if (Vivocha.isShowingSideTab() != show) {
            if (show) {
                Vivocha.showChatButton()
            } else {
                Vivocha.hideChatButton()
            }
        }
        return(true)
    }

    @objc(registerAction:)
    func registerAction(code: NSString) -> Any {
        Vivocha.manager().bindAction(code as String) { action in
            let data = [
                "code": code,
                "name": action?.actionName,
                "id": action?.actionID,
                "data": action?.actionData
            ]
            self.emit(name: "onAction", data: data)
        }
        return true;
    }

    @objc(setDataCollection:)
    func setDataCollection(data: NSArray) -> Any {
        Vivocha.setDataCollection(Utils.arrayToDataCollection(data: data))
        return true;
    }

    @objc(getDataCollection)
    func getDataCollection() -> Any {
        return(Utils.dataCollectionToArray(dataCollection: Vivocha.getDataCollection()))
    }

    @objc(setCustomerToken:)
    func setCustomerToken(jwt: NSString) -> Any {
        Vivocha.setCustomerToken(jwt as String)
        return(true)
    }

    @objc(unsetCustomerToken)
    func unsetCustomerToken() -> Any {
        Vivocha.unsetCustomerToken()
        return(true)
    }

    @objc(showView:)
    func showView(animated: Bool) -> Any {
        Vivocha.contact()?.showView(animated)
        return(true)
    }

    @objc(hideView:)
    func hideView(animated: Bool) -> Any {
        Vivocha.contact()?.hideView(animated)
        return(true)
    }

    @objc(terminate:)
    func terminate(hideView: Bool) -> Any {
        Vivocha.contact()?.terminateHidingView(hideView)
        return(true)
    }

    @objc(setPushToken:)
    func setPushToken(token: NSString) -> Any {
        Vivocha.setPushTokenWith(token as String)
        return(true)
    }

    @objc(createContact:withType:withParams:withResolver:withRejecter:)
    func createContact(data: NSArray, type: NSString, params: NSDictionary?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
        Vivocha.createContact(
            with: Utils.arrayToDataCollection(data: data),
            andType: type as String,
            andParams: params as? [AnyHashable : Any]
        ) { contact in
            if (contact == nil) {
                reject("Error", "Cannot create contact", NSError(domain: "com.vivocha", code: 0))
            } else {
                resolve(contact?.toCordovaJSON())
            }
        }
    }

    @objc(createChat:withResolver:withRejecter:)
    func createChat(data: NSArray?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
        Vivocha.createChat(with: Utils.arrayToDataCollection(data: data)) { contact in
            if (contact == nil) {
                reject("Error", "Cannot create contact", NSError(domain: "com.vivocha", code: 0))
            } else {
                resolve(contact?.toCordovaJSON())
            }
        }
    }

    @objc(getUnreadMessageCount)
    func getUnreadMessageCount() -> Any {
        let contact = Vivocha.contact()
        if (contact != nil) {
            return contact?.getUnreadMessagesCount()
        }
        
        let conversation = Vivocha.conversation()
        if (conversation != nil) {
            return conversation?.getVisitorUnreadMessagesForPersistenceWidget()
        }
        
        return(0)
    }

    @objc(getContact)
    func getContact() -> Any? {
        return Vivocha.contact()?.toCordovaJSON()
    }

    @objc(getConversation)
    func getConversation() -> Any? {
        return Utils.conversationToDictionary(conversation: Vivocha.conversation())
    }
    
    @objc(stopScreenshotSession)
    func stopScreenshotSession() -> Any {
        Vivocha.contact()?.stopScreenshotSession()
        return true
    }
    
    @objc(isScreenshotSessionAuthorized)
    func isScreenshotSessionAuthorized() -> Any {
        return Vivocha.contact()?.hasUserAuthorizedScreenshotSession() == true
    }
    
    @objc(setLanguage:)
    func setLanguage(language: NSString) -> Any {
        Vivocha.manager().overrideLanguage(language as String)
        return true
    }
    
    @objc(getAgent)
    func getAgent() -> Any? {
        return Utils.agentToDictionary(agent: Vivocha.contact()?.agent())
    }
    
    @objc(getVVCU)
    func getVVCU() -> Any? {
        return Vivocha.getVVCU()
    }
    
    @objc(getVVCT)
    func getVVCT() -> Any? {
        return Vivocha.getVVCT()
    }
    
    @objc(storeSurvey:withData:withResolver:withRejecter:)
    func storeSurvey(contactId: String, data: NSArray, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
        Vivocha.storeSurvey(withContactId: contactId, data: Utils.arrayToDataCollection(data: data)) { returnContactId, success in
            if (success) {
                resolve(returnContactId)
            } else {
                reject("Error", "Cannot store survey", NSError(domain: "com.vivocha", code: 0))
            }
        }
    }
    
    @objc(createVideoChat:withResolver:withRejecter:)
    func createVideoChat(data: NSArray?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
        Vivocha.createVideoChat(with: Utils.arrayToDataCollection(data: data)) { contact in
            if (contact == nil) {
                reject("Error", "Cannot create video chat", NSError(domain: "com.vivocha", code: 0))
            } else {
                resolve(contact?.toCordovaJSON())
            }
        }
    }
    
    @objc(createCallBackNow:withData:withResolver:withRejecter:)
    func createCallBackNow(phoneNumber: NSString, data: NSArray?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
        Vivocha.createCallBackNow(with: Utils.arrayToDataCollection(data: data), andPhoneNumber: phoneNumber as String) { contact in
            if (contact == nil) {
                reject("Error", "Cannot create video chat", NSError(domain: "com.vivocha", code: 0))
            } else {
                resolve(contact?.toCordovaJSON())
            }
        }
    }
    
    @objc(addLocalization:withLocalization:)
    func addLocalization(language: NSString, localization: NSDictionary) -> Any {
        Vivocha.add(Utils.objToLocalization(language: language, localization: localization))
        return true
    }
    
    @objc(getTheme)
    func getTheme() -> Any? {
        return Utils.themeToObj(theme: Vivocha.getTheme())
    }
    
    @objc(setTheme:)
    func setTheme(theme: NSDictionary) -> Any {
        Vivocha.setTheme(Utils.objToTheme(obj: theme))
        return true
    }
    
    @objc(getMedia)
    func getMedia() -> Any? {
        return Utils.mediaToObj(media: Vivocha.contact()?.getMedia())
    }
    
    @objc(sendAction:withId:withData:)
    func sendAction(name: NSString, id: NSString, data: NSArray?) -> Any {
        let action = VivochaCustomAction()
        action.actionName = name as String
        action.actionID = id as String
        if (data != nil) {
            action.actionData = data
        }
        Vivocha.contact()?.send(action)
        return true
    }
    
    @objc(sendMessage:withType:withPayload:)
    func sendMessage(body: NSString, type: NSString, payload: NSString) -> Any {
        Vivocha.contact()?.sendMessage(body as String, type: type as String, payload: payload as String)
        return true
    }
    
    @objc(sendAttachment:withReferenceId:withTitle:withMimetype:withDescription:withSize:)
    func sendAttachment(url: NSString, referenceId: NSString, title: NSString, mimetype: NSString, description: NSString, size: NSNumber) -> Any {
        Vivocha.contact()?.sendAttachment(withUrl: url as String, referenceId: referenceId as String, title: title as String, mimetype: mimetype as String, description: description as String, fileSize: Int32(size))
        return true
    }
    
    private func waitForUrlTapped(id: String, timeout: Int) -> Bool {
        let time = NSDate()
        while tappedUrls[id] == nil {
            usleep(100000)
            if (Int(NSDate().timeIntervalSince(time as Date) * 1000) > timeout) {
                tappedUrls[id] = false
            }
        }
        let ret = tappedUrls[id] ?? false
        tappedUrls.removeValue(forKey: id)
        return ret
    }
    
    @objc(registerUrlTapped)
    func registerUrlTapped() {
        Vivocha.manager().setTappedURLHandler { url in
            let id = UUID().uuidString
            self.emit(
                name: "urlTapped",
                data: [
                    "url": url,
                    "id": id
                ]
            )
            return self.waitForUrlTapped(id: id, timeout: 2000)
        }
    }
    
    @objc(urlTapped:withHandled:)
    func urlTapped(id: NSString, handled: Bool) -> Any {
        tappedUrls[id as String] = handled
        return true
    }
    
    @objc(getSdkVersion)
    func getSdkVersion() -> String {
        return Vivocha.getSDKVersion()
    }
    
    @objc(getSdkName)
    func getSdkName() -> String {
        return "Vivocha-iOS"
    }
    
    @objc(getSdkBaseUrl)
    func getSdkBaseUrl() -> String {
        return "https://www.vivocha.com"
    }
}
