import Foundation

// The delegate for EngineSocket
public protocol EngineSocketDelegate: class{
    // Called when the socket state is Open
    func socketOnOpen(socket: EngineSocket)
    
    // Called when the socket state is Closed
    func socketOnClose(socket: EngineSocket)
    
    // Called when the socket received packet
    func socketOnPacket(socket: EngineSocket, packet: EnginePacket)
    
    // Called when there is a message decoded
    func socketOnData(socket: EngineSocket, data: [Byte], isBinary: Bool)
    
    // Called when there is an error occured
    func socketOnError(socket: EngineSocket, error: String, description: String?)
    
    // Called when there is an error occured
    func socketDidUpgraded(socket: EngineSocket)
}

enum EngineSocketReadyState : Int, Printable{
    case Init, Open, Opening, Closing, Closed, Upgrading
    
    var description: String {
        switch self{
        case .Init: return "Init"
        case .Open: return "Open"
        case .Opening: return "Opening"
        case .Closing: return "Closing"
        case .Closed: return "Closed"
        case .Upgrading: return "Upgrading"
        }
    }
}

// A counter which counts how many EngineSocket created
var socketCount: Int = 0

public class EngineSocket: Logger, EngineTransportDelegate{
    // Whether the protocol is secure
    var secure: Bool = false
    
    // Host
    var host: String
    
    // Port
    var port: String
    
    // Path
    var path: String
    
    // Query
    var query: [String: AnyObject]
    
    // Sid generated by server, unique identifier for one socket client
    var id: String?
    
    // Headers
    public var headers: [String: String] = [:]
    
    // The running queue, create it when create a new instance
    var queue: dispatch_queue_t = {
        ++socketCount
        return dispatch_queue_create("com.menic.EngineIO-queue\(socketCount)", DISPATCH_QUEUE_SERIAL)
        }()
    
    // Available transports
    var transports: [String]
    
    // Whether the socket should upgrade
    var upgrade: Bool
    
    // Hold for available upgrades
    var upgrades: [String] = ["websocket"]
    
    // Flag indicating whether we are in the upgrading phase
    var upgrading: Bool = false
    
    // List containing all running upgrades
    var upgradingTransports: [ProbeTransportDelegate] = []
    
    // The highest transport we upgraded to, we should reconnect using it directly
    var upgradedToTransport: String?
    
    // The state of socket
    var readyState: EngineSocketReadyState = .Init
    
    // Transport instance
    public var transport: Transport?
    
    // Ping interval
    var pingInterval: Int = 30000
    
    // Ping timeout
    var pingTimeout: Int = 30000
    
    // Last ping sent time
    var pingSentAt: NSDate?
    
    // last pong received time
    var pongRecvAt: NSDate?
    
    // Last pong received time
    
    // The write queue
    var writeQueue: [EnginePacket] = []
    
    // The write calllback queue
    var writeCallbackQueue: [(()->Void)?] = []
    
    
    // The delegate
    public weak var delegate: EngineSocketDelegate?
    
    public init(host: String, port: String, path: String = "/socket.io/", secure: Bool = false, query: [String:AnyObject] = [:],
        transports: [String] = ["polling", "websocket"], upgrade: Bool = true, config: [String:AnyObject] = [:]) {
            self.host = host
            self.port = port
            self.path = path
            self.transports = transports
            self.query = query
            self.upgrade = upgrade
            self.secure = secure
    }
    
    func createTransport(transportName: String) -> Transport? {
        var transport : Transport? = nil
        if $.contains(self.transports, value: transportName){
            if transportName == "polling" {
                transport = PollingTransport(host: self.host, path: self.path, port: self.port, secure: self.secure, query: self.query)
            }
            else if transportName == "websocket" {
                transport = WebsocketTransport(host: self.host, path: self.path, port: self.port, secure: self.secure, query: self.query)
            }
        }
        
        if transport != nil && self.id != nil{
            transport!.sid = self.id
        }
        
        return transport
    }
    
    func setTransport(inout transport: Transport){
        transport.delegate = self
        self.transport = transport
    }
    
    public func open(){
        assert(transports.count != 0)
        dispatch_async(self.queue){
            self.readyState = .Opening
            
            let transportName = $.first(self.transports)!
            if var transport = self.createTransport(transportName){
                self.setTransport(&transport)
                transport.open()
            }
                
            else{
                self.debug("Not able to create transport")
            }
        }
    }
    
    public func close(){
        // close the underlying transport
        
        if self.readyState != .Closed || self.readyState != .Closing {
            dispatch_async(self.queue){
                if self.readyState != .Closed || self.readyState != .Closing {
                    self.readyState = .Closing
                    self.transport?.close()
                }
            }
        }
    }
    
    // EngineTransportDelegate
    public func transportOnPacket(transport: Transport, packet: EnginePacket) {
        if self.readyState == .Open || self.readyState == .Opening{
            info("Receive: [\(packet.description)]")
            
            self.delegate?.socketOnPacket(self, packet:packet)
            
            switch packet.type{
            case .Open:
                if let data = packet.data{
                    if let json = Converter.bytearrayToJSON(data) as? NSDictionary{
                        self.onHandshake(json)
                    }
                    else{
                        debug("Failed to parse json")
                        return
                    }
                }
                else{
                    debug("There is no data on Open packet")
                    return
                }
            case .Message:
                if let data = packet.data{
                    if let delegate = self.delegate {
                        delegate.socketOnData(self, data: data, isBinary: packet.isBinary)
                    }
                }
                else{
                    debug("No data on Message packet, ignore")
                }
            case .Pong:
                debug("Pong from server")
                self.pongRecvAt = NSDate()
            case .Error:
                if let data = packet.data {
                    self.onError("error", reason: Converter.bytearrayToNSString(data))
                }
                else{
                    self.onError("error")
                }
            default:
                debug("HITTING DEFAULT CLAUSE, CAREFUL")
                break
            }
        }
        else{
            debug("packet received with socket readyState ")
        }
    }
    
    public func transportOnError(transport: Transport, error: String, withDescription description: String) {
        debug("Tranport got error \(error) \(description)")
        self.onError(error, reason: description)
    }
    
    public func transportOnClose(transport: Transport) {
        if self.readyState == .Closing {
            debug("The transport closed as expected")
        }
        else{
            debug("The transport closed unexpected")
        }
        self.readyState = .Closed
        self.delegate?.socketOnClose(self)
    }
    
    public func transportOnOpen(transport: Transport) {
        debug("Underlying transport opened")
    }
    
    public func transportOnPause(transport: Transport) {
        // If the transport pasued, lets see whether it is in upgrading, if yes, then fire the event to the upgrade delegate to finish
        // The upgrading logic
        debug("Transport [\(transport.name)] Paused")
        
        for d in self.upgradingTransports {
            if d.prevTransport === transport {
                d.onPrevTransportPaused()
            }
        }
    }
    
    public func transportDispatchQueue(transport: Transport) -> dispatch_queue_t {
        // All transport related task should run on socket's queue
        return self.queue
    }
    
    // End of EngineTransport Delegate
    
    func onHandshake(data: NSDictionary){
        if let sid = data["sid"] as? String{
            self.id = sid
            self.transport!.sid = sid
        }
        else{
            debug("Not able to parse sid")
        }
        
        if let upgrades = data["upgrades"] as? [String]{
            self.upgrades = upgrades
        }
        
        if let pingInterval = data["pingInterval"]?.integerValue {
            self.pingInterval = pingInterval / 1000
        }
        
        if let pingTimeout = data["pingTimeout"]?.integerValue {
            self.pingTimeout = pingTimeout / 1000
        }
        
        self.onOpen()
        
        if self.readyState != .Closed{
            self.setPing()
        }
    }
    
    func onOpen(){
        debug("Socket Open")
        
        self.readyState = .Open
        
        if let delegate = self.delegate {
            delegate.socketOnOpen(self)
        }
        
        self.flush() // Flush out cached packets
        
        if self.readyState == .Open && self.upgrade && self.transport!.pausible {
            debug("Start upgrading")
            for upgrade in upgrades {
                self.probe(upgrade)
            }
        }
    }
    
    func onError(message: String, reason: String? = nil){
        debug("Close on error \(message) \(reason)")
        self.delegate?.socketOnError(self, error: message, description: reason)
        self.close()
    }
    
    func setPing(){
        if self.readyState == .Closed || self.readyState == .Closing {
            debug("Skip the ping on closed socket")
            return
        }
        
        debug("Setting up ping and reset timeout")
        
        self.delay(Double(self.pingInterval)){
            if self.readyState == .Closed || self.readyState == .Closing {
                self.debug("Skip the ping on closed socket")
                return
            }
            
            var shouldSent = true
            if let pingSent = self.pingSentAt {
                let seconds = NSDate().secondsAfterDate(pingSent)
                if seconds < self.pingInterval{
                    shouldSent = false
                }
            }
            
            if shouldSent {
                self.ping()
                self.pingSentAt = NSDate()
                
                self.setPing()
            }
            else{
                self.debug("Ping just sent no long ago, skip")
            }
        }
        
        // Reset the last pong recv time to prevent
        self.pongRecvAt = NSDate()
    }
    
    func ping(){
        debug("Sending Ping")
        self.send(.Ping)
        
        self.delay(Double(self.pingTimeout)){
            [unowned self] () -> Void in
            
            if self.pingSentAt == nil{
                // If no ping sent, no timeout
                return
            }
            else{
                if NSDate().secondsAfterDate(self.pingSentAt!) < self.pingTimeout {
                    // If ping sent, but not timeout
                    return
                }
                
                if let pongRecv = self.pongRecvAt {
                    if pongRecv.secondsAfterDate(self.pingSentAt!) >= 0 {
                        // If pong recved after ping sent
                        return
                    }
                }
            }
            
            self.onError("timeout", reason: "Ping timeout")
        }
    }
    
    /**
    send data
    */
    func send(data: NSData, callback: (()->Void)? = nil){
        self.send(.Message, data: Converter.nsdataToByteArray(data), isBinary: true, callback: callback)
    }
    
    /**
    Send text packet
    */
    func send(text: [Byte], callback: (()->Void)? = nil){
        self.send(.Message, data: text, isBinary: false, callback: callback)
    }
    
    // send packet and data with a callback
    func send(packetType: PacketType, data: [Byte]? = nil, isBinary: Bool = false, callback: (()->Void)? = nil){
        let packet = EnginePacket(data: data, type:packetType, isBinary: isBinary)
        self.packet(packet, callback: callback)
    }
    
    func flush(){
        if self.readyState != .Closed && !self.upgrading && self.transport!.writable {
            if self.writeQueue.count == 0 {
                debug("The writeQueue is empty, return")
                return
            }
            
            debug("Flushing \(self.writeQueue.count) packets")
            
            // LOCK HERE
            let packets = self.writeQueue
            self.writeQueue = []
            // UNLOCK HERE
            self.transport!.write(packets)
        }
        else{
            debug("Skip flush")
        }
    }
    
    func probe(upgrade: String){
        if var transport = self.createTransport(upgrade) {
            debug("probing: \(upgrade)")
            var probeTransportDelegate = ProbeTransportDelegate(socket: self, transportName: upgrade, transport: transport, prev: self.transport!)
            transport.delegate = probeTransportDelegate
            transport.open()
            
            self.upgradingTransports.append(probeTransportDelegate)
        }
    }
    
    func packet(packet: EnginePacket, callback: (()->Void)?){
        dispatch_async(self.queue){
            self.debug("Enqueue packet")
            self.writeQueue.append(packet)
            self.writeCallbackQueue.append(callback)
            self.flush()
        }
    }
    
    func delay(delay:Double, closure:()->()) {
        dispatch_after(
            dispatch_time(
                DISPATCH_TIME_NOW,
                Int64(delay * Double(NSEC_PER_SEC))
            ),
            self.queue, closure)
    }
    
    public override func logPrefix() -> String{
        return "[EngineSocket(\(self.id?))][\(self.readyState.description)][Upg:\(self.upgrading ? 1:0)][TW:\(self.transport!.writable ? 1:0)]"
    }
}


class ProbeTransportDelegate: Logger, EngineTransportDelegate {
    var failed = false
    var engineSocket: EngineSocket!
    var transportName: String
    // intented to use strong ref to prevent it from dealloc
    var transport: Transport
    
    // The prev Transport
    unowned var prevTransport: Transport
    
    init(socket: EngineSocket, transportName: String, transport: Transport, prev: Transport){
        self.engineSocket = socket
        self.transportName = transportName
        self.transport = transport
        self.prevTransport = prev
    }
    
    override func logPrefix() -> String {
        return "[ProbeTransportDelegate][Failed:\(self.failed)]"
    }
    
    func transportOnOpen(transport: Transport) {
        debug("Probing \(self.transportName)")
        
        transport.write([EnginePacket(string: "probe", type: .Ping)])
    }
    
    func transportOnPacket(transport: Transport, packet: EnginePacket) {
        if self.failed {
            return
        }
        
        if packet.type == .Pong {
            if let data = packet.data {
                let message = Converter.bytearrayToNSString(data)
                if message == "probe" {
                    debug("Probe transport pong")
                    self.engineSocket.upgrading = true
                    self.engineSocket.upgradedToTransport = self.transportName
                    debug("Pause current transport")
                    self.engineSocket.transport?.pause()
                    // We should wait till the pause succeed
                }
            }
        }
        else{
            debug("Probe \(self.transportName) failed")
        }
    }
    
    func onPrevTransportPaused(){
        debug("Prev transport paused")
        if self.failed || self.engineSocket.readyState == .Closed {
            debug("The upgrade failed or the socket is closed, skip the upgrading")
            return
        }
        
        debug("Changing transport and sending upgrade packet")
        self.transport.setDelegate(self.engineSocket)
        
        $.remove(self.engineSocket.upgradingTransports) { $0 === self }
        
        self.transport.write([EnginePacket(data: nil, type: .Upgrade)])
        self.engineSocket.transport = self.transport
        self.engineSocket.upgrading = false
        self.engineSocket.delegate?.socketDidUpgraded(self.engineSocket)
        self.engineSocket.flush()
        
        self.prevTransport.delegate = nil
        self.prevTransport.close()
    }
    
    // not interested in pause
    func transportOnPause(transport: Transport){}
    
    func transportOnError(transport: Transport, error: String, withDescription description: String){
        self.onError(transport)
    }
    
    // Called when the transport closed
    func transportOnClose(transport: Transport){
        self.onError(transport)
    }
    
    // Called when the dispatch queue needed
    func transportDispatchQueue(transport: Transport) -> dispatch_queue_t {
        return self.engineSocket.queue
    }
    
    func onError(transport: Transport){
        self.failed = true
        transport.setDelegate(nil)
        transport.close() 
    }
}