// The session model contains information about the music
// sessions that the current client has joined.
(function(context,$) {

    "use strict";

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

    context.JK.SessionModel = function(app, server, client) {
        var clientId = client.clientID;
        var currentSessionId = null; // Set on join, prior to setting currentSession.
        var currentSession = null;
        var subscribers = {};
        var users = {}; // User info for session participants
        var rest = context.JK.Rest();

        function id() {
            return currentSession.id;
        }

        function participants() {
            if (currentSession) {
                return currentSession.participants;
            } else {
                return [];
            }
        }

        function creatorId() {
            if(!currentSession) {
                throw "creator is not known"
            }
            return currentSession.user_id;
        }

        function alreadyInSession() {
            var inSession = false;
            $.each(participants(), function(index, participant) {
                 if(participant.user.id == context.JK.currentUserId) {
                     inSession = true;
                     return false;
                 }
            });
            return inSession;
        }

        /**
        * Join the session specified by the provided id.
        */
        function joinSession(sessionId) {
            currentSessionId = sessionId;
            logger.debug("SessionModel.joinSession(" + sessionId + ")");
            var deferred = joinSessionRest(sessionId);

            deferred
                .done(function(){
                    logger.debug("calling jamClient.JoinSession");

                    if(!alreadyInSession()) {
                        // on temporary disconnect scenarios, a user may already be in a session when they enter this path
                        // so we avoid double counting
                        context.JK.GA.trackSessionMusicians(context.JK.GA.SessionCreationTypes.join);
                    }

                    client.JoinSession({ sessionID: sessionId });
                    refreshCurrentSession();
                    server.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, refreshCurrentSession);
                    server.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, refreshCurrentSession);


                })
                .fail(function() {
                    currentSessionId = null;
                });

            return deferred;
        }

        /**
        * Leave the current session, if there is one.
        * callback: called in all conditions; either after an attempt is made to tell the server that we are leaving,
        * or immediately if there is no session
        */
        function leaveCurrentSession() {
            var deferred;

            if(currentSessionId) {
                logger.debug("SessionModel.leaveCurrentSession()");
                // TODO - sessionChanged will be called with currentSession = null
                server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, refreshCurrentSession);
                server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, refreshCurrentSession);
                // leave the session right away without waiting on REST.  Why?  If you can't contact the server, or if it takes a long
                // time, for that entire duration you'll still be sending voice data to the other users.
                // this may be bad if someone decides to badmouth others in the left-session during this time
                logger.debug("calling jamClient.LeaveSession for clientId=" + clientId);
                client.LeaveSession({ sessionID: currentSessionId });
                deferred = leaveSessionRest(currentSessionId);
                deferred.done(function() {
                    sessionChanged();
                });

                // 'unregister' for callbacks
                context.jamClient.SessionRegisterCallback("");
                context.jamClient.SessionSetAlertCallback("");
                currentSession = null;
                currentSessionId = null;
            }
            else {
                deferred = new $.Deferred();
                deferred.resolve();
            }

            return deferred;
        }

        /**
        * Refresh the current session, and participants.
        */
        function refreshCurrentSession() {
            logger.debug("SessionModel.refreshCurrentSession()");
            refreshCurrentSessionRest(function() {
                refreshCurrentSessionParticipantsRest(sessionChanged);
            });
        }

        /**
        * Subscribe for sessionChanged events. Provide a subscriberId
        * and a callback to be invoked on session changes.
        */
        function subscribe(subscriberId, sessionChangedCallback) {
            logger.debug("SessionModel.subscribe(" + subscriberId + ", [callback])");
            subscribers[subscriberId] = sessionChangedCallback;
        }

        /**
        * Notify subscribers that the current session has changed.
        */
        function sessionChanged() {
            logger.debug("SessionModel.sessionChanged()");
            for (var subscriberId in subscribers) {
                subscribers[subscriberId]();
            }
        }

        /**
        * Reload the session data from the REST server, calling
        * the provided callback when complete.
        */
        function refreshCurrentSessionRest(callback) {
            var url = "/api/sessions/" + currentSessionId;
            $.ajax({
                type: "GET",
                url: url,
                async: false,
                success: function(response) {
                    sendClientParticipantChanges(currentSession, response);
                    logger.debug("Current Session Refreshed:");
                    logger.debug(response);
                    currentSession = response;
                    callback();
                },
                error: ajaxError
            });
        }

        /**
        * Seems silly. We should just have the bridge take sessionId, clientId
        */
        function _toJamClientParticipant(participant) {
            return {
                userID : "",
                clientID : participant.client_id,
                tcpPort : 0,
                udpPort : 0,
                localIPAddress : participant.ip_address,  // ?
                globalIPAddress : participant.ip_address, // ?
                latency : 0,
                natType : ""
            };
        }

        function sendClientParticipantChanges(oldSession, newSession) {
            var joins = [], leaves = []; // Will hold JamClientParticipants

            var oldParticipants = []; // will be set to session.participants if session
            var oldParticipantIds = [];
            var newParticipants = [];
            var newParticipantIds = [];

            if (oldSession && oldSession.participants) {
                oldParticipants = oldSession.participants;
                $.each(oldParticipants, function() {
                    oldParticipantIds.push(this.client_id);
                });
            }
            if (newSession && newSession.participants) {
                newParticipants = newSession.participants;
                $.each(newParticipants, function() {
                    newParticipantIds.push(this.client_id);
                });
            }

            $.each(newParticipantIds, function(i,v) {
                if ($.inArray(v, oldParticipantIds) === -1) {
                    // new participant id that's not in old participant ids: Join
                    joins.push(_toJamClientParticipant(newParticipants[i]));
                }
            });
            $.each(oldParticipantIds, function(i,v) {
                if ($.inArray(v, newParticipantIds) === -1) {
                    // old participant id that's not in new participant ids: Leave
                    leaves.push(_toJamClientParticipant(oldParticipants[i]));
                }
            });

            $.each(joins, function(i,v) {
                if (v.client_id != clientId) {
                    client.ParticipantJoined(newSession, v);
                }
            });
            $.each(leaves, function(i,v) {
                if (v.client_id != clientId) {
                    client.ParticipantLeft(newSession, v);
                }
            });
        }

        /**
        * Ensure that we have user info for all current participants.
        */
        function refreshCurrentSessionParticipantsRest(callback) {
            var callCount = 0;
            $.each(participants(), function(index, value) {
                if (!(this.user.id in users)) {
                    var userInfoUrl = "/api/users/" + this.user.id;
                    callCount += 1;
                    $.ajax({
                        type: "GET",
                        url: userInfoUrl,
                        async: false,
                        success: function(user) {
                            callCount -= 1;
                            users[user.id] = user;
                        },
                        error: function(jqXHR, textStatus, errorThrown) {
                            callCount -= 1;
                            logger.error('Error getting user info from ' + userInfoUrl);
                        }
                    });
                }
            });
            if (!(callback)) {
                return;
            }
            context.JK.joinCalls(
                function() { return callCount === 0; }, callback, 10);
        }

        function participantForClientId(clientId) {
            var foundParticipant = null;
            $.each(currentSession.participants, function(index, participant) {
                if (participant.client_id === clientId) {
                    foundParticipant = participant;
                    return false;
                }
            });
            return foundParticipant;
        }

        function addTrack(sessionId, data) {
            logger.debug("track data = " + JSON.stringify(data));
            var url = "/api/sessions/" + sessionId + "/tracks";
            $.ajax({
                type: "POST",
                dataType: "json",
                contentType: 'application/json',
                url: url,
                async: false,
                data: JSON.stringify(data),
                processData:false,
                success: function(response) {
                    // save to the backend
                    context.jamClient.TrackSaveAssignments();
                    logger.debug("Successfully added track (" + JSON.stringify(data) + ")");
                    refreshCurrentSession();
                },
                error: ajaxError
            });
        }

        function updateTrack(sessionId, trackId, data) {
            var url = "/api/sessions/" + sessionId + "/tracks/" + trackId;
            $.ajax({
                type: "POST",
                dataType: "json",
                contentType: 'application/json',
                url: url,
                async: false,
                data: JSON.stringify(data),
                processData:false,
                success: function(response) {
                    logger.debug("Successfully updated track info (" + JSON.stringify(data) + ")");
                },
                error: ajaxError
            });
        }

        function deleteTrack(sessionId, trackId) {
            if (trackId) {
                $.ajax({
                    type: "DELETE",
                    url: "/api/sessions/" + sessionId + "/tracks/" + trackId,
                    async: false,
                    success: function(response) {
                        // TODO: if in recording, more cleanup to do???

                        // update backend client (for now, only the second track can be removed)
                        client.TrackSetCount(1);
                        client.TrackSaveAssignments();

                        // refresh Session screen
                        refreshCurrentSession();
                    },
                    error: function(jqXHR, textStatus, errorThrown) {
                        logger.error("Error deleting track " + trackId);
                    }
                });
            }
        }

        /**
        * Make the server calls to join the current user to
        * the session provided.
        */
        function joinSessionRest(sessionId) {
            var tracks = context.JK.TrackHelpers.getUserTracks(context.jamClient);
            var data = {
                client_id: clientId,
                ip_address: server.publicIP,
                as_musician: true,
                tracks: tracks
            };
            var url = "/api/sessions/" + sessionId + "/participants";
            return $.ajax({
                type: "POST",
                dataType: "json",
                contentType: 'application/json',
                url: url,
                data: JSON.stringify(data),
                processData:false
            });
        }

        function leaveSessionRest(sessionId) {
            var url = "/api/participants/" + clientId;
            return $.ajax({
                type: "DELETE",
                url: url,
                async: true
            });
        }

        function reconnect() {
            context.JK.CurrentSessionModel.leaveCurrentSession()
                .always(function() {
                    window.location.reload();
                });
        }

        function registerReconnect(content) {
            $('a.disconnected-reconnect', content).click(function() {

                var template = $('#template-reconnecting').html();
                var templateHtml = context.JK.fillTemplate(template, null);
                var content = context.JK.Banner.show({
                    html : template
                });

                rest.serverHealthCheck()
                    .done(function() {
                        reconnect();
                    })
                    .fail(function(xhr, textStatus, errorThrown) {

                        if(xhr && xhr.status >= 100) {
                            // we could connect to the server, and it's alive
                            reconnect();
                        }
                        else {
                            var template = $('#template-could-not-reconnect').html();
                            var templateHtml = context.JK.fillTemplate(template, null);
                            var content = context.JK.Banner.show({
                                html : template
                            });

                            registerReconnect(content);
                        }
                    });

                return false;
            });
        }

        function onWebsocketDisconnected(in_error) {

            if(app.clientUpdating) {
                // we don't want to do a 'cover the whole screen' dialog
                // because the client update is already showing.
                return;
            }

            // kill the streaming of the session immediately
            logger.debug("calling jamClient.LeaveSession for clientId=" + clientId);
            client.LeaveSession({ sessionID: currentSessionId });

            if(in_error) {
                var template = $('#template-disconnected').html();
                var templateHtml = context.JK.fillTemplate(template, null);
                var content = context.JK.Banner.show({
                    html : template
                }) ;
                registerReconnect(content);
            }
        }

        function ajaxError(jqXHR, textStatus, errorMessage) {
            logger.error("Unexpected ajax error: " + textStatus);
        }

        // Public interface
        this.id = id;
        this.participants = participants;
        this.joinSession = joinSession;
        this.leaveCurrentSession = leaveCurrentSession;
        this.refreshCurrentSession = refreshCurrentSession;
        this.subscribe = subscribe;
        this.participantForClientId = participantForClientId;
        this.addTrack = addTrack;
        this.updateTrack = updateTrack;
        this.deleteTrack = deleteTrack;
        this.onWebsocketDisconnected = onWebsocketDisconnected;
        this.getCurrentSession = function() {
            return currentSession;
        };
    };

 })(window,jQuery);