<commit_before>//
//  SocketIOClient.swift
//  Socket.IO-Client-Swift
//
//  Created by <NAME> on 11/23/14.
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.

import Foundation

public final class SocketIOClient: NSObject, SocketEngineClient {
    private let emitQueue = dispatch_queue_create("emitQueue", DISPATCH_QUEUE_SERIAL)
    private let handleQueue: dispatch_queue_t!

    public let socketURL: String

    public private(set) var engine: SocketEngine?
    public private(set) var secure = false
    public private(set) var status = SocketIOClientStatus.NotConnected
    
    public var nsp = "/"
    public var opts: [String: AnyObject]?
    public var reconnects = true
    public var reconnectWait = 10
    public var sid: String? {
        return engine?.sid
    }
    
    private let logType = "SocketIOClient"
    
    private var anyHandler: ((SocketAnyEvent) -> Void)?
    private var currentReconnectAttempt = 0
    private var handlers = ContiguousArray<SocketEventHandler>()
    private var connectParams: [String: AnyObject]?
    private var reconnectTimer: NSTimer?
    
    private let reconnectAttempts: Int!
    private var ackHandlers = SocketAckManager()
    private var currentAck = -1

    var waitingData = [SocketPacket]()
    
    /**
    Create a new SocketIOClient. opts can be omitted
    */
    public init(var socketURL: String, opts: [String: AnyObject]? = nil) {
        if socketURL["https://"].matches().count != 0 {
            self.secure = true
        }
        
        socketURL = socketURL["http://"] ~= ""
        socketURL = socketURL["https://"] ~= ""
        
        self.socketURL = socketURL
        self.opts = opts
        
        if let connectParams = opts?["connectParams"] as? [String: AnyObject] {
            self.connectParams = connectParams
        }
        
        if let logger = opts?["logger"] as? SocketLogger {
            Logger = logger
        }
        
        if let log = opts?["log"] as? Bool {
            Logger.log = log
        }
        
        if let nsp = opts?["nsp"] as? String {
            self.nsp = nsp
        }
        
        if let reconnects = opts?["reconnects"] as? Bool {
            self.reconnects = reconnects
        }
        
        if let reconnectAttempts = opts?["reconnectAttempts"] as? Int {
            self.reconnectAttempts = reconnectAttempts
        } else {
            self.reconnectAttempts = -1
        }
        
        if let reconnectWait = opts?["reconnectWait"] as? Int {
            self.reconnectWait = abs(reconnectWait)
        }
        
        if let handleQueue = opts?["handleQueue"] as? dispatch_queue_t {
            self.handleQueue = handleQueue
        } else {
            self.handleQueue = dispatch_get_main_queue()
        }
        
        super.init()
    }
    
    deinit {
        Logger.log("Client is being deinit", type: logType)
        engine?.close(fast: true)
    }
    
    private func addEngine() -> SocketEngine {
        Logger.log("Adding engine", type: logType)

        let newEngine = SocketEngine(client: self, opts: opts)

        engine = newEngine
        return newEngine
    }
    
    private func clearReconnectTimer() {
        reconnectTimer?.invalidate()
        reconnectTimer = nil
    }
    
    /**
    Closes the socket. Only reopen the same socket if you know what you're doing.
    Will turn off automatic reconnects.
    Pass true to fast if you're closing from a background task
    */
    public func close(fast fast: Bool) {
        Logger.log("Closing socket", type: logType)
        
        reconnects = false
        status = SocketIOClientStatus.Closed
        engine?.close(fast: fast)
    }
    
    /**
    Connect to the server.
    */
    public func connect() {
        connect(timeoutAfter: 0, withTimeoutHandler: nil)
    }
    
    /**
    Connect to the server. If we aren't connected after timeoutAfter, call handler
    */
    public func connect(timeoutAfter timeoutAfter:Int,
        withTimeoutHandler handler:(() -> Void)?) {
            assert(timeoutAfter >= 0, "Invalid timeout: \(timeoutAfter)")

            guard status != .Connected else {
                return
            }

            if status == .Closed {
                Logger.log("Warning! This socket was previously closed. This might be dangerous!",
                    type: logType)
            }
            
            status = SocketIOClientStatus.Connecting
            addEngine().open(connectParams)
            
            guard timeoutAfter != 0 else {
                return
            }
            
            let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeoutAfter) * Int64(NSEC_PER_SEC))

            dispatch_after(time, dispatch_get_main_queue()) {
                if self.status != .Connected {
                    self.status = .Closed
                    self.engine?.close(fast: true)
                    
                    handler?()
                }
            }
    }
    
    private func createOnAck(items: [AnyObject]) -> OnAckCallback {
        return {[weak self, ack = ++currentAck] timeout, callback in
            if let this = self {
                this.ackHandlers.addAck(ack, callback: callback)
                
                dispatch_async(this.emitQueue) {
                    this._emit(items, ack: ack)
                }
                
                if timeout != 0 {
                    let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * NSEC_PER_SEC))
                    
                    dispatch_after(time, dispatch_get_main_queue()) {
                        this.ackHandlers.timeoutAck(ack)
                    }
                }
            }
        }
    }
    
    func didConnect() {
        Logger.log("Socket connected", type: logType)
        status = .Connected
        currentReconnectAttempt = 0
        clearReconnectTimer()
        
        // Don't handle as internal because something crazy could happen where
        // we disconnect before it's handled
        handleEvent("connect", data: nil, isInternalMessage: false)
    }
    
    func didDisconnect(reason: String) {
        guard status != .Closed else {
            return
        }
        
        Logger.log("Disconnected: %@", type: logType, args: reason)
        
        status = .Closed
        
        reconnects = false
        
        // Make sure the engine is actually dead.
        engine?.close(fast: true)
        handleEvent("disconnect", data: [reason], isInternalMessage: true)
    }
    
    /// error
    public func didError(reason: AnyObject) {
        Logger.error("%@", type: logType, args: reason)
        
        handleEvent("error", data: reason as? [AnyObject] ?? [reason],
            isInternalMessage: true)
    }
    
    /**
    Same as close
    */
    public func disconnect(fast fast: Bool) {
        close(fast: fast)
    }
    
    /**
    Send a message to the server
    */
    public func emit(event: String, _ items: AnyObject...) {
        emit(event, withItems: items)
    }
    
    /**
    Same as emit, but meant for Objective-C
    */
    public func emit(event: String, withItems items: [AnyObject]) {
        guard status == .Connected else {
            return
        }

        dispatch_async(emitQueue) {
            self._emit([event] + items)
        }
    }
    
    /**
    Sends a message to the server, requesting an ack. Use the onAck method of SocketAckHandler to add
    an ack.
    */
    public func emitWithAck(event: String, _ items: AnyObject...) -> OnAckCallback {
        return emitWithAck(event, withItems: items)
    }
    
    /**
    Same as emitWithAck, but for Objective-C
    */
    public func emitWithAck(event: String, withItems items: [AnyObject]) -> OnAckCallback {
        return createOnAck([event] + items)
    }
    
    private func _emit(data: [AnyObject], ack: Int? = nil) {
        guard status == .Connected else {
            return
        }
        
        let packet = SocketPacket.packetFromEmit(data, id: ack ?? -1, nsp: nsp, ack: false)
        let str = packet.packetString
        
        Logger.log("Emitting: %@", type: logType, args: str)
        
        if packet.type == .BinaryEvent {
            engine?.send(str, withData: packet.binary)
        } else {
            engine?.send(str, withData: nil)
        }
    }
    
    // If the server wants to know that the client received data
    func emitAck(ack: Int, withItems items: [AnyObject]) {
        dispatch_async(emitQueue) {
            if self.status == .Connected {
                let packet = SocketPacket.packetFromEmit(items, id: ack ?? -1, nsp: self.nsp, ack: true)
                let str = packet.packetString
                
                Logger.log("Emitting Ack: %@", type: self.logType, args: str)
                
                if packet.type == SocketPacket.PacketType.BinaryAck {
                    self.engine?.send(str, withData: packet.binary)
                } else {
                    self.engine?.send(str, withData: nil)
                }
                
            }
        }
    }
    
    public func engineDidClose(reason: String) {
        waitingData.removeAll()
        
        if status == .Closed || !reconnects {
            didDisconnect(reason)
        } else if status != .Reconnecting {
            status = .Reconnecting
            handleEvent("reconnect", data: [reason], isInternalMessage: true)
            tryReconnect()
        }
    }
    
    // Called when the socket gets an ack for something it sent
    func handleAck(ack: Int, data: AnyObject?) {
        Logger.log("Handling ack: %@ with data: %@", type: logType, args: ack, data ?? "")
        
        ackHandlers.executeAck(ack,
            items: (data as? [AnyObject]?) ?? (data != nil ? [data!] : nil))
    }
    
    /**
    Causes an event to be handled. Only use if you know what you're doing.
    */
    public func handleEvent(event: String, data: [AnyObject]?, isInternalMessage: Bool,
        wantsAck ack: Int? = nil) {
            guard status == .Connected || isInternalMessage else {
                return
            }
            
            Logger.log("Handling event: %@ with data: %@", type: logType, args: event, data ?? "")
            
            if anyHandler != nil {
                dispatch_async(handleQueue) {
                    self.anyHandler?(SocketAnyEvent(event: event, items: data))
                }
            }
            
            for handler in handlers where handler.event == event {
                if let ack = ack {
                    dispatch_async(handleQueue) {
                        handler.executeCallback(data, withAck: ack, withSocket: self)
                    }
                } else {
                    dispatch_async(handleQueue) {
                        handler.executeCallback(data)
                    }
                }
            }
    }
    
    /**
    Leaves nsp and goes back to /
    */
    public func leaveNamespace() {
        if nsp != "/" {
            engine?.send("1\(nsp)", withData: nil)
            nsp = "/"
        }
    }
    
    /**
    Joins nsp if it is not /
    */
    public func joinNamespace() {
        Logger.log("Joining namespace", type: logType)
        
        if nsp != "/" {
            engine?.send("0\(nsp)", withData: nil)
        }
    }
    
    /**
    Joins namespace /
    */
    public func joinNamespace(namespace: String) {
        self.nsp = namespace
        joinNamespace()
    }
    
    /**
    Removes handler(s)
    */
    public func off(event: String) {
        Logger.log("Removing handler for event: %@", type: logType, args: event)
        
        handlers = ContiguousArray(handlers.filter { $0.event != event })
    }
    
    /**
    Adds a handler for an event.
    */
    public func on(event: String, callback: NormalCallback) {
        Logger.log("Adding handler for event: %@", type: logType, args: event)
        
        let handler = SocketEventHandler(event: event, callback: callback)
        handlers.append(handler)
    }
    
    /**
    Adds a handler for an event.
    */
    public func onObjectiveC(event: String, callback: NormalCallbackObjectiveC) {
        Logger.log("Adding handler for event: %@", type: logType, args: event)
        
        let handler = SocketEventHandler(event: event, callback: callback)
        handlers.append(handler)
    }
    
    /**
    Adds a single-use handler for an event.
    */
    public func once(event: String, callback: NormalCallback) {
        Logger.log("Adding once handler for event: %@", type: logType, args: event)
        
        var handler: SocketEventHandler;

        handler = SocketEventHandler(event: event) { (data, ack: AckEmitter?) in
            self.handlers = ContiguousArray(self.handlers.filter { $0 != handler })
            callback?(data, ack)
        }

        handlers.append(handler)
    }
    
    /**
    Adds a single-use handler for an event.
    */
    public func once(event event: String, callback: NormalCallbackObjectiveC) {
        Logger.log("Adding once handler for event: %@", type: logType, args: event)
        
        var handler: SocketEventHandler;

        handler = SocketEventHandler(event: event) { (data, ack: AckEmitterObjectiveC?) in
            self.handlers = ContiguousArray(self.handlers.filter { $0 != handler })
            callback?(data, ack)
        }

        handlers.append(handler)
    }
    
    /**
    Removes all handlers.
    Can be used after disconnecting to break any potential remaining retain cycles.
    */
    public func removeAllHandlers() {
        handlers.removeAll(keepCapacity: false)
    }
    
    /**
    Adds a handler that will be called on every event.
    */
    public func onAny(handler: (SocketAnyEvent) -> Void) {
        anyHandler = handler
    }<commit_msg>Add strict equality and execute "callback" as non-optional<commit_after>//
//  SocketIOClient.swift
//  Socket.IO-Client-Swift
//
//  Created by <NAME> on 11/23/14.
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.

import Foundation

public final class SocketIOClient: NSObject, SocketEngineClient {
    private let emitQueue = dispatch_queue_create("emitQueue", DISPATCH_QUEUE_SERIAL)
    private let handleQueue: dispatch_queue_t!

    public let socketURL: String

    public private(set) var engine: SocketEngine?
    public private(set) var secure = false
    public private(set) var status = SocketIOClientStatus.NotConnected
    
    public var nsp = "/"
    public var opts: [String: AnyObject]?
    public var reconnects = true
    public var reconnectWait = 10
    public var sid: String? {
        return engine?.sid
    }
    
    private let logType = "SocketIOClient"
    
    private var anyHandler: ((SocketAnyEvent) -> Void)?
    private var currentReconnectAttempt = 0
    private var handlers = ContiguousArray<SocketEventHandler>()
    private var connectParams: [String: AnyObject]?
    private var reconnectTimer: NSTimer?
    
    private let reconnectAttempts: Int!
    private var ackHandlers = SocketAckManager()
    private var currentAck = -1

    var waitingData = [SocketPacket]()
    
    /**
    Create a new SocketIOClient. opts can be omitted
    */
    public init(var socketURL: String, opts: [String: AnyObject]? = nil) {
        if socketURL["https://"].matches().count != 0 {
            self.secure = true
        }
        
        socketURL = socketURL["http://"] ~= ""
        socketURL = socketURL["https://"] ~= ""
        
        self.socketURL = socketURL
        self.opts = opts
        
        if let connectParams = opts?["connectParams"] as? [String: AnyObject] {
            self.connectParams = connectParams
        }
        
        if let logger = opts?["logger"] as? SocketLogger {
            Logger = logger
        }
        
        if let log = opts?["log"] as? Bool {
            Logger.log = log
        }
        
        if let nsp = opts?["nsp"] as? String {
            self.nsp = nsp
        }
        
        if let reconnects = opts?["reconnects"] as? Bool {
            self.reconnects = reconnects
        }
        
        if let reconnectAttempts = opts?["reconnectAttempts"] as? Int {
            self.reconnectAttempts = reconnectAttempts
        } else {
            self.reconnectAttempts = -1
        }
        
        if let reconnectWait = opts?["reconnectWait"] as? Int {
            self.reconnectWait = abs(reconnectWait)
        }
        
        if let handleQueue = opts?["handleQueue"] as? dispatch_queue_t {
            self.handleQueue = handleQueue
        } else {
            self.handleQueue = dispatch_get_main_queue()
        }
        
        super.init()
    }
    
    deinit {
        Logger.log("Client is being deinit", type: logType)
        engine?.close(fast: true)
    }
    
    private func addEngine() -> SocketEngine {
        Logger.log("Adding engine", type: logType)

        let newEngine = SocketEngine(client: self, opts: opts)

        engine = newEngine
        return newEngine
    }
    
    private func clearReconnectTimer() {
        reconnectTimer?.invalidate()
        reconnectTimer = nil
    }
    
    /**
    Closes the socket. Only reopen the same socket if you know what you're doing.
    Will turn off automatic reconnects.
    Pass true to fast if you're closing from a background task
    */
    public func close(fast fast: Bool) {
        Logger.log("Closing socket", type: logType)
        
        reconnects = false
        status = SocketIOClientStatus.Closed
        engine?.close(fast: fast)
    }
    
    /**
    Connect to the server.
    */
    public func connect() {
        connect(timeoutAfter: 0, withTimeoutHandler: nil)
    }
    
    /**
    Connect to the server. If we aren't connected after timeoutAfter, call handler
    */
    public func connect(timeoutAfter timeoutAfter:Int,
        withTimeoutHandler handler:(() -> Void)?) {
            assert(timeoutAfter >= 0, "Invalid timeout: \(timeoutAfter)")

            guard status != .Connected else {
                return
            }

            if status == .Closed {
                Logger.log("Warning! This socket was previously closed. This might be dangerous!",
                    type: logType)
            }
            
            status = SocketIOClientStatus.Connecting
            addEngine().open(connectParams)
            
            guard timeoutAfter != 0 else {
                return
            }
            
            let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeoutAfter) * Int64(NSEC_PER_SEC))

            dispatch_after(time, dispatch_get_main_queue()) {
                if self.status != .Connected {
                    self.status = .Closed
                    self.engine?.close(fast: true)
                    
                    handler?()
                }
            }
    }
    
    private func createOnAck(items: [AnyObject]) -> OnAckCallback {
        return {[weak self, ack = ++currentAck] timeout, callback in
            if let this = self {
                this.ackHandlers.addAck(ack, callback: callback)
                
                dispatch_async(this.emitQueue) {
                    this._emit(items, ack: ack)
                }
                
                if timeout != 0 {
                    let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * NSEC_PER_SEC))
                    
                    dispatch_after(time, dispatch_get_main_queue()) {
                        this.ackHandlers.timeoutAck(ack)
                    }
                }
            }
        }
    }
    
    func didConnect() {
        Logger.log("Socket connected", type: logType)
        status = .Connected
        currentReconnectAttempt = 0
        clearReconnectTimer()
        
        // Don't handle as internal because something crazy could happen where
        // we disconnect before it's handled
        handleEvent("connect", data: nil, isInternalMessage: false)
    }
    
    func didDisconnect(reason: String) {
        guard status != .Closed else {
            return
        }
        
        Logger.log("Disconnected: %@", type: logType, args: reason)
        
        status = .Closed
        
        reconnects = false
        
        // Make sure the engine is actually dead.
        engine?.close(fast: true)
        handleEvent("disconnect", data: [reason], isInternalMessage: true)
    }
    
    /// error
    public func didError(reason: AnyObject) {
        Logger.error("%@", type: logType, args: reason)
        
        handleEvent("error", data: reason as? [AnyObject] ?? [reason],
            isInternalMessage: true)
    }
    
    /**
    Same as close
    */
    public func disconnect(fast fast: Bool) {
        close(fast: fast)
    }
    
    /**
    Send a message to the server
    */
    public func emit(event: String, _ items: AnyObject...) {
        emit(event, withItems: items)
    }
    
    /**
    Same as emit, but meant for Objective-C
    */
    public func emit(event: String, withItems items: [AnyObject]) {
        guard status == .Connected else {
            return
        }

        dispatch_async(emitQueue) {
            self._emit([event] + items)
        }
    }
    
    /**
    Sends a message to the server, requesting an ack. Use the onAck method of SocketAckHandler to add
    an ack.
    */
    public func emitWithAck(event: String, _ items: AnyObject...) -> OnAckCallback {
        return emitWithAck(event, withItems: items)
    }
    
    /**
    Same as emitWithAck, but for Objective-C
    */
    public func emitWithAck(event: String, withItems items: [AnyObject]) -> OnAckCallback {
        return createOnAck([event] + items)
    }
    
    private func _emit(data: [AnyObject], ack: Int? = nil) {
        guard status == .Connected else {
            return
        }
        
        let packet = SocketPacket.packetFromEmit(data, id: ack ?? -1, nsp: nsp, ack: false)
        let str = packet.packetString
        
        Logger.log("Emitting: %@", type: logType, args: str)
        
        if packet.type == .BinaryEvent {
            engine?.send(str, withData: packet.binary)
        } else {
            engine?.send(str, withData: nil)
        }
    }
    
    // If the server wants to know that the client received data
    func emitAck(ack: Int, withItems items: [AnyObject]) {
        dispatch_async(emitQueue) {
            if self.status == .Connected {
                let packet = SocketPacket.packetFromEmit(items, id: ack ?? -1, nsp: self.nsp, ack: true)
                let str = packet.packetString
                
                Logger.log("Emitting Ack: %@", type: self.logType, args: str)
                
                if packet.type == SocketPacket.PacketType.BinaryAck {
                    self.engine?.send(str, withData: packet.binary)
                } else {
                    self.engine?.send(str, withData: nil)
                }
                
            }
        }
    }
    
    public func engineDidClose(reason: String) {
        waitingData.removeAll()
        
        if status == .Closed || !reconnects {
            didDisconnect(reason)
        } else if status != .Reconnecting {
            status = .Reconnecting
            handleEvent("reconnect", data: [reason], isInternalMessage: true)
            tryReconnect()
        }
    }
    
    // Called when the socket gets an ack for something it sent
    func handleAck(ack: Int, data: AnyObject?) {
        Logger.log("Handling ack: %@ with data: %@", type: logType, args: ack, data ?? "")
        
        ackHandlers.executeAck(ack,
            items: (data as? [AnyObject]?) ?? (data != nil ? [data!] : nil))
    }
    
    /**
    Causes an event to be handled. Only use if you know what you're doing.
    */
    public func handleEvent(event: String, data: [AnyObject]?, isInternalMessage: Bool,
        wantsAck ack: Int? = nil) {
            guard status == .Connected || isInternalMessage else {
                return
            }
            
            Logger.log("Handling event: %@ with data: %@", type: logType, args: event, data ?? "")
            
            if anyHandler != nil {
                dispatch_async(handleQueue) {
                    self.anyHandler?(SocketAnyEvent(event: event, items: data))
                }
            }
            
            for handler in handlers where handler.event == event {
                if let ack = ack {
                    dispatch_async(handleQueue) {
                        handler.executeCallback(data, withAck: ack, withSocket: self)
                    }
                } else {
                    dispatch_async(handleQueue) {
                        handler.executeCallback(data)
                    }
                }
            }
    }
    
    /**
    Leaves nsp and goes back to /
    */
    public func leaveNamespace() {
        if nsp != "/" {
            engine?.send("1\(nsp)", withData: nil)
            nsp = "/"
        }
    }
    
    /**
    Joins nsp if it is not /
    */
    public func joinNamespace() {
        Logger.log("Joining namespace", type: logType)
        
        if nsp != "/" {
            engine?.send("0\(nsp)", withData: nil)
        }
    }
    
    /**
    Joins namespace /
    */
    public func joinNamespace(namespace: String) {
        self.nsp = namespace
        joinNamespace()
    }
    
    /**
    Removes handler(s)
    */
    public func off(event: String) {
        Logger.log("Removing handler for event: %@", type: logType, args: event)
        
        handlers = ContiguousArray(handlers.filter { $0.event != event })
    }
    
    /**
    Adds a handler for an event.
    */
    public func on(event: String, callback: NormalCallback) {
        Logger.log("Adding handler for event: %@", type: logType, args: event)
        
        let handler = SocketEventHandler(event: event, callback: callback)
        handlers.append(handler)
    }
    
    /**
    Adds a handler for an event.
    */
    public func onObjectiveC(event: String, callback: NormalCallbackObjectiveC) {
        Logger.log("Adding handler for event: %@", type: logType, args: event)
        
        let handler = SocketEventHandler(event: event, callback: callback)
        handlers.append(handler)
    }
    
    /**
    Adds a single-use handler for an event.
    */
    public func once(event: String, callback: NormalCallback) {
        Logger.log("Adding once handler for event: %@", type: logType, args: event)
        
        var handler: SocketEventHandler;

        handler = SocketEventHandler(event: event) { (data, ack: AckEmitter?) in
            self.handlers = ContiguousArray(self.handlers.filter { $0 !== handler })
            callback(data, ack)
        }

        handlers.append(handler)
    }
    
    /**
    Adds a single-use handler for an event.
    */
    public func once(event event: String, callback: NormalCallbackObjectiveC) {
        Logger.log("Adding once handler for event: %@", type: logType, args: event)
        
        var handler: SocketEventHandler;

        handler = SocketEventHandler(event: event) { (data, ack: AckEmitterObjectiveC?) in
            self.handlers = ContiguousArray(self.handlers.filter { $0 !== handler })
            callback(data, ack)
        }

        handlers.append(handler)
    }
    
    /**
    Removes all handlers.
    Can be used after disconnecting to break any potential remaining retain cycles.
    */
    public func removeAllHandlers() {
        handlers.removeAll(keepCapacity: false)
    }
    
    /**
    Adds a handler that will be called on every event.
    */
    public func onAny(handler: (SocketAnyEvent) -> Void) {
        anyHandler = handler
    }
{'fraction_non_alphanumeric': 0.09705459529938819, 'fraction_numerical': 0.0020059509879308615, 'mean_word_length': 1.7942083138720224, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 23, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2636121', 'n_tokens_mistral': 8330, 'n_tokens_neox': 8094, 'n_words': 2367}