(function(context, $) {

    "use strict";

    context.JK = context.JK || {};

    context.JK.SessionLatency = function(jamClient) {

        var logger = context.JK.logger;
        var sessionPingsOut = {};
        var clientsToSessions = {};
        var sessionLatency = {};
        var subscribers = {};

        function getSortScore(sessionId) {
            return sessionLatency[sessionId].sortScore;
        }

        function ensureSessionLatencyEntry(sessionId) {
            if (!(sessionId in sessionLatency)) {
                sessionLatency[sessionId] = {
                    clientLatencies: {},
                    averageLatency: 0
                };
            }
        }

        function setInitialSortScore(session) {
            var i,
                p,
                score = 0,
                participant = null;

            // user has invitations for this session
            if ("invitations" in session) {
                score += 2;
            }
            for (i=0, p=session.participants.length; i<p; i++) {
                participant = session.participants[i];
                // this session participant is a friend
                if (participant !== undefined && participant.user !== undefined && participant.user.is_friend) {
                    score += 1;
                    break;
                }
            }
            ensureSessionLatencyEntry(session.id);
            sessionLatency[session.id].sortScore = score;
        }

        function sessionPings(session) {
            setInitialSortScore(session);
            $.each(session.participants, function(index, participant) {
                var clientID = participant.client_id;
                clientsToSessions[clientID] = session.id;
                if (!(session.id in sessionPingsOut)) {
                    sessionPingsOut[session.id] = 0;
                }
                sessionPingsOut[session.id]++;
                var jsFunction = "JK.Callbacks.clientPingResponse";
                var timeoutFunction = "JK.Callbacks.clientPingTimeout";

                jamClient.TestLatency(clientID, jsFunction, timeoutFunction);
            });
        }

        function clientPingResponse(response) {
            logger.debug("clientPingResponse");
            logger.debug(response);
            var sessionId = clientsToSessions[response.clientID];
            sessionPingsOut[sessionId]--;
            updateSessionLatency(sessionId, response);
            for (var k in subscribers) {
                if (typeof(subscribers[k]) === 'function') {
                    subscribers[k](sessionId);
                }
            }
        }

        function clientPingTimeout(clientId) {
            logger.warn("TIMEOUT during client ping:" + clientId);
            logger.warn("calling clientPingResponse directly with 1,000 MS latency");
            var response = { clientID: clientId, latency: 1000 };
            clientPingResponse(response);
        }

        function updateSessionLatency(sessionId, latencyResponse) {
            ensureSessionLatencyEntry(sessionId);
            var sl = sessionLatency[sessionId];
            sl.clientLatencies[latencyResponse.clientID] = latencyResponse.latency;
            sl.averageLatency = latencyAverage(sl.clientLatencies);
            sl.sortScore = updateSortScore(sessionId, sl.averageLatency);
        }

        function updateSortScore(sessionId, averageLatency) {
            var newScore = sessionLatency[sessionId].sortScore;
            if (!(newScore)) {
                newScore = 0;
            }
            if (!averageLatency) {
                return newScore;
            }
            var base = Math.floor(newScore);
            newScore = base + (1/averageLatency);
            return newScore;
        }

        function latencyAverage(clientLatencies) {
            var total = 0;
            var count = 0;
            $.each(clientLatencies, function(index, latency) {
                total += latency;
                count++;
            });
            return total/count;
        }

        function sessionInfo(sessionId) {
            return sessionLatency[sessionId];
        }

        function subscribe(subscriberKey, cb) {
            subscribers[subscriberKey] = cb;
        }

        this.sessionPings = sessionPings;
        this.sessionInfo = sessionInfo;
        this.getSortScore = getSortScore;
        this.subscribe = subscribe;
        this.clientPingResponse = clientPingResponse;
        this.clientPingTimeout = clientPingTimeout;

        // Register clientPingResponse on this instance as a static function
        // so that the JavascriptBridge native code can invoke it with a string.
        context.JK.Callbacks.makeStatic("clientPingResponse", this.clientPingResponse, this);
        context.JK.Callbacks.makeStatic("clientPingTimeout", this.clientPingTimeout, this);

        return this;
    };


 })(window, jQuery);