# EXPRESSER SOCKETS
# --------------------------------------------------------------------------
# Handles sockets communication using the module Socket.IO.
# ATTENTION! The Sockets module is started automatically by the App module.
# If you wish to disable it, set `Settings.sockets.enabled` to false.
# <!--
# @see Settings.sockets
# -->
class Sockets

    lodash = require "lodash"
    logger = require "./logger.coffee"
    settings = require "./settings.coffee"

    # @property [Array] Holds a list of current event listeners.
    currentListeners: null

    # @property [Socket.IO Object] Exposes Socket.IO object to external modules.
    io: null

    # INIT
    # --------------------------------------------------------------------------

    # Bind the Socket.IO object to the Express app. This will also set the counter
    # to increase / decrease when users connects or disconnects from the app.
    # @param [Object] options Sockets init options.
    # @option options [Object] server The Express server object to bind to.
    init: (options) =>
        options = {server: options} if not options.server?
        @currentListeners = []

        if not options.server?
            logger.error "Sockets.init", "App server is invalid. Abort!"
            return

        @io = require("socket.io") options.server

        # Listen to user connection count updates.
        @io.sockets.on "connection", (socket) =>
            @io.emit "connection-count", @getConnectionCount()
            socket.on "disconnect", @onDisconnect

            # Bind all current event listeners.
            for listener in @currentListeners
                socket.on listener.key, listener.callback if listener?

    # EVENTS
    # ----------------------------------------------------------------------

    # Emit the specified key and data to clients.
    # @param [String] key The event key.
    # @param [Object] data The JSON data to be sent out to clients.
    emit: (key, data) =>
        if @io?
            @io.emit key, data
            logger.debug "Sockets.emit", key, JSON.stringify(data).length + " bytes"
        else
            logger.debug "Sockets.emit", key, "Sockets not initiated yet, abort!"

    # Listen to a specific event. If `onlyNewClients` is true then it won't listen to that particular
    # event from currently connected clients.
    # @param [String] key The event key.
    # @param [Method] callback The callback to be called when key is triggered.
    # @param [Boolean] onlyNewClients Optional, if true, listen to event only from new clients.
    listenTo: (key, callback, onlyNewClients) =>
        return if not callback?

        onlyNewClients = false if not onlyNewClients?
        @currentListeners.push {key: key, callback: callback}

        if not onlyNewClients
            for key, socket of @io.sockets.connected
                socket.on key, callback

        logger.debug "Sockets.listenTo", key

    # Stops listening to the specified event key.
    # @param [String] key The event key.
    # @param [Object] callback The callback to stop triggering.
    stopListening: (key, callback) =>
        for socketKey, socket of @io.sockets.connected
            if callback?
                socket.removeListener key, callback
            else
                socket.removeAllListeners key

        # Remove binding from the currentListeners collection.
        for listener in @currentListeners
            if listener.key is key and (listener.callback is callback or not callback?)
                listener = null

        logger.debug "Sockets.stopListening", key

    # Remove invalid and expired event listeners.
    compact: =>
        @currentListeners = lodash.compact @currentListeners

    # HELPERS
    # ----------------------------------------------------------------------

    # Get how many users are currenly connected to the app.
    getConnectionCount: =>
        return Object.keys(@io.sockets.connected).length

    # When user disconnects, emit an event with the new connection count to all clients.
    onDisconnect: =>
        count = @getConnectionCount()
        logger.debug "Sockets.onDisconnect", "New count: #{count}."


# Singleton implementation
# --------------------------------------------------------------------------
Sockets.getInstance = ->
    @instance = new Sockets() if not @instance?
    return @instance

module.exports = exports = Sockets.getInstance()