//
//  XPSDKConnection.swift
//  XProtect
//
//  Created by Ivelin Tsankov on 1/11/17.
//  Copyright © 2017 Milestone Systems A/S. All rights reserved.
//

import Foundation

@objc public enum XPSDKConnectionState: Int {
    case idle = 0 /// set only before connecting
    case connecting = 1 /// set when we call connect and wait for response from server
    case connected = 2 /// set when we have connected and are about to send credentials
    case loggingIn = 3/// set when we exchanged keys and set login credentials
    case working = 4 /// set when we are logged in and successfully exchanging commands
    case disconnecting = 5/// set when we are disconnecting by the user
    case disconnected = 6 /// set when we have been disconnected (by the user or external factors) or failed connect/login
    case lostConnection = 7 /// set when we have experienced some sort of timeout or other connectivity error
}

enum SecondStepAuthentication: Int {
    case insufficientUserRights = 25
    case secondStepAuthenticationRequired = 34
    case onlyUsersWithTwoStepAuthenticationAreAllowed = 35
    case incorrectSecondStepAuthenticationPin = 36
}

public enum XPSDKPTZMotion {
    case XPSDKPTZMotionUp /// Move camera Up
    case XPSDKPTZMotionDown /// Move camera Down
    case XPSDKPTZMotionLeft  /// Move camera Left
    case XPSDKPTZMotionRight /// Move camera Right
    case XPSDKPTZMotionZoomIn /// Zoom camera In
    case XPSDKPTZMotionZoomOut /// Zoom camera Out
    case XPSDKPTZMotionHome /// Reset camera. Not used in the app at the moment
}

@objc public protocol XPMobileSDKConnectionDelegate {
    @objc optional func connectionDidLogIn(_ connection: XPSDKConnection)
    @objc optional func connectiondidFailLoginWithError(_ error: NSError)
    @objc optional func connectionDidFailConnectWithError(_ error: NSError)
    @objc optional func connectionWillDisconnect(_ connection: XPSDKConnection)
    @objc optional func connectionDidDisconnect(_ connection: XPSDKConnection)
    @objc optional func connectionDidConnect(_ connection: XPSDKConnection)
    @objc optional func connectionLostConnectivity(_ connection: XPSDKConnection)
    @objc optional func connectionRestoredConnectivity(_ connection: XPSDKConnection)
    
    @objc optional func connectionPopToRoot(_ connection: XPSDKConnection)
    @objc optional func connectionCodeError(_ connection: XPSDKConnection)
    @objc optional func connectionCodeRequired(_ connection: XPSDKConnection)
    
    @objc optional func wrongConnectionIdWithError(_ error: NSError)
    
    @objc optional func connectionReachabilityChanged(_ notification: Notification)
    
    @objc optional func updateState(_ state: NSNumber)
    
    @objc optional func didReceiveNotification(_ notification: NSDictionary)
    
    @objc optional func receiveChallengeForTrustWithInfo(_ info: [String: AnyObject])
    
}

@objc open class XPSDKConnection: NSObject, XPSDKRequestDelegate {
    //MARK: public properties
    open static var sharedConnection = XPSDKConnection()
    
    open let delegates = NSMutableSet()
    open internal(set) var features: NSDictionary?
    
    open var url: URL?
    open var urlEntPoint = XPSDKMilestoneXProtectEndPoint
    
    open var certificateCookie: Data?
    open var authenticationComplete = false
    
    open var state: XPSDKConnectionState = .idle
    open var dhe = DHEncryption()
    open var id: String?
    open var reachability: Reachability?
    open var serverInfo:XPSDKServerInfo?
    //TODO: remove
    open var server: AnyObject?
    open var oldConnection: AnyObject?
    
    open let raceConnections = NSMutableSet()
    
    //MARK: internal properties
    var timeout = 30
    var username: String?
    var password: String?
    var plainUsername: String?
    var plainPassword: String?
    var tempUsername: String?
    var tempPassword: String?
    var shouldSaveCredentials: Bool?
    var liveMessageTimer: Timer?
    var reachabilityTimer: Timer?
    var temporaryCode: String?
    
    var requests: NSMutableSet = NSMutableSet()
    var videoConnections: NSMutableSet = NSMutableSet()
    
    //MARK: Setup
    public func setupWithURLString(_ urlString: String) {
        let newUrlString = NSString(format: "%@", urlString)
        self.url = URL(string: newUrlString as String)
        log.info("Did set url for connection", self.url)
    }
    
    public func setupWithServerInfo(_ serInfo: XPSDKServerInfo) {
        self.serverInfo = serInfo
        self.url = serInfo.communicationURL()
        log.info("Did set server info for connection")
    }
    
    public func setupWithUsername(_ username: String, password: String, shouldSave: Bool) {
        self.username = self.dhe?.encrypt(username)
        self.password = self.dhe?.encrypt(password)
        if shouldSave {
            self.plainUsername = username
            self.plainPassword = password
        } else {
            self.tempUsername = username
            self.tempPassword = password
        }
        
        self.shouldSaveCredentials = shouldSave
        log.info("Did set encrypted username and password")
    }
    
    //MARK: Disconnect
    func disconnect() {
        log.warning("Did disconnect!")
        videoConnections.removeAllObjects()
        if self.reachabilityTimer != nil {
            self.reachabilityTimer?.invalidate()
            self.reachabilityTimer = nil
        }
        
        self.stopLiveMessageTimer()
        self.makeDelegatesPerformSelector(#selector(XPMobileSDKConnectionDelegate.connectionDidDisconnect(_:)), object: self)
    }
    
    //MARK: Live Message Methods
    func startLiveMessageTimer() {
        log.info("Did start live message")
        
        self.stopLiveMessageTimer()
        
        self.liveMessageTimer = Timer(timeInterval:Double(self.timeout) * 0.66, target: self, selector: "sendLiveMessage", userInfo: nil, repeats: true)
        
        RunLoop.main.add(self.liveMessageTimer!, forMode: RunLoopMode.commonModes)
    }
    
    func stopLiveMessageTimer() {
        log.info("Did stop live message")
        
        if self.liveMessageTimer != nil {
            self.liveMessageTimer?.invalidate()
            self.liveMessageTimer = nil
        }
    }
    
    //MARK: Reachability
    func reachabilityChanged(_ notification: Notification) {
        if self.id == nil {
            return
        }
        
        self.makeDelegatesPerformSelector(#selector(XPMobileSDKConnectionDelegate.connectionReachabilityChanged(_:)), object: notification as AnyObject)
        self.stopLiveMessageTimer()
        if reachabilityTimer != nil {
            self.reachabilityTimer?.invalidate()
            self.reachabilityTimer = nil
        }
        
        self.sendLiveMessage()
        
        let currentReachabilityStatus = self.reachability?.currentReachabilityStatus()
        
        if currentReachabilityStatus == NotReachable {
            self.makeDelegatesPerformSelector(#selector(XPMobileSDKConnectionDelegate.connectionLostConnectivity(_:)), object: self)
            
            if self.state == .working {
                self.setConnectionState(.lostConnection)
            }
        } else {
            self.recoverConnection()
        }
    }
    
    func recoverConnection() {
        self.makeDelegatesPerformSelector(#selector(XPMobileSDKConnectionDelegate.connectionRestoredConnectivity(_:)), object: self)
        self.startLiveMessageTimer()
    }
    
    public func addRaceConnection(_ raceConnection: XPSDKConnection?) {
        if raceConnection != nil {
            self.raceConnections.add(raceConnections as AnyObject)
        }
    }
    
    public func addRequset(_ request: XPSDKRequest?) {
        if request != nil {
            self.requests.add(request)
        }
    }
    
    //MARK: Delegates methods
    public func addDelegate(_ delegate: XPMobileSDKConnectionDelegate?) {
        if delegate != nil {
            self.delegates.add(delegate as AnyObject)
        }
        
        if self.raceConnections.count > 0 {
            for con in self.raceConnections {
                if con is XPSDKConnection {
                    (con as! XPSDKConnection).addDelegate(delegate)
                }
            }
        }
        
        self.oldConnection?.perform(#selector(XPSDKConnection.addDelegate(_:)), with: delegate)
    }
    
    public func removeDelegate(_ delegate: XPMobileSDKConnectionDelegate?) {
        if delegate != nil {
            self.delegates.remove(delegate as AnyObject)
        }
        
        self.oldConnection?.perform(#selector(XPSDKConnection.removeDelegate(_:)), with: delegate)
    }
    
    func makeDelegatesPerformSelector(_ selector: Selector) {
        for delegate in self.delegates {
            if (delegate as AnyObject).responds(to: selector) {
                (delegate as AnyObject).perform(selector)
            }
        }
    }
    
    func makeDelegatesPerformSelector(_ selector: Selector, object: AnyObject) {
        for delegate in self.delegates {
            if (delegate as AnyObject).responds(to: selector) {
                (delegate as AnyObject).perform(selector, with: object)
            }
        }
    }
    
    func wrongConnectionIdWithError(_ error: NSError) {
        self.setConnectionState(.lostConnection)
        self.makeDelegatesPerformSelector(#selector(XPMobileSDKConnectionDelegate.wrongConnectionIdWithError(_:)), object: error)
    }
    
    func receiveChallengeForTrust(_ challenge: URLAuthenticationChallenge, connection: XPSDKConnection?) {
        let info: [String:AnyObject] = ["challenge": challenge,
                                        "connection" : connection!]
        self.makeDelegatesPerformSelector(#selector(XPMobileSDKConnectionDelegate.receiveChallengeForTrustWithInfo(_:)), object: info as AnyObject)
    }
    
    func stopAllSessions() {
        for request in self.requests.allObjects as! [XPSDKRequest] {
            request.successHandler = nil
            request.failureHandler = nil
            request.cancelSession()
        }
        
        self.raceConnections.removeAllObjects()
        
        self.requests.removeAllObjects()
    }
    
    func setConnectionState(_ state: XPSDKConnectionState) {
        log.info("set state: ", state.rawValue)
        self.state = state
        
        let selector = #selector(XPMobileSDKConnectionDelegate.updateState(_:))
        let number = NSNumber(value: state.rawValue as Int)
        self.makeDelegatesPerformSelector(selector, object: number)
    }
    
    //TODO: here for the timeout requests, remove this ASAP
    public func explicitSetOfConnectionState (_ state: NSNumber) {
        log.info("explicit set state, you should be here only with request timeout!: ", state)
        let newState = XPSDKConnectionState(rawValue: Int(state))!
        if self.state != newState {
            self.state = newState
        }
    }
    
    public func updateNetworkIndicator () {
        var visible = false
        if (self.requests.count > 0) {
            visible = true
        } else {
            for conn in self.videoConnections {
                if let videoConnection = conn as? XPSDKVideoConnection {
                    if (videoConnection.isOpened) {
                        visible = true
                        break
                    }
                }
            }
        }
        
        UIApplication.shared.isNetworkActivityIndicatorVisible = visible
    }
    
}
