/*global CONFIG, SITE:true */
/*
  app.js
  Copyright 2016 Ubiquiti Networks, Inc. All rights reserved.
 */

define(['jquery',
    'backbone',
    'underscore',
    'msg/Msg',
    'unifi/model/CloudUser',
    'unifi/model/AdminPref',
    'unifi/service/location/locationService',
    'ubnt/GoogleAnalytics',
    'text!includes/global/headerTemplate.html',
    'ubnt/view/dialogs/ErrorDialogView',
    'ubnt/view/dialogs/AlertDialogView',
    'ubnt/view/dialogs/LostConnectionDialogView',
    'config/ManageConfig',
    'unifi/webrtc/UniFiWebRtcPeer',
    'unifi/webrtc/WebRtcHelpers',
    'unifi/util/QueryString',
    'unifi/webrtc/Logger',
    'libs/jquery.cookie'
], function (
    $,
    Backbone,
    _,
    Msg,
    CloudUser,
    AdminPref,
    locationService,
    GoogleAnalytics,
    headerTemplate,
    ErrorDialogView,
    AlertDialogView,
    LostConnectionDialogView,
    APP_CONFIG,
    UniFiWebRtcPeer,
    helpers,
    QueryString,
    logger
) {
    'use strict';

    var USE_WEBRTC = APP_CONFIG.USE_WEBRTC; //TODO: automate this flag during build, change this to true for webrtc
    var ENABLE_GOOGLE_ANALYTICS = APP_CONFIG.ENABLE_GOOGLE_ANALYTICS;
    var GOOGLE_ANALYTICS_TRACKING_ID = APP_CONFIG.GOOGLE_ANALYTICS_TRACKING_ID;

    var app = {};

    app.initialize = function(routers, collections, AppView) {
        $.cookie.defaults.path = '/';
        $(document).ajaxSend(function(event, jqxhr, settings) {
            if (settings.type == "POST") {
                jqxhr.setRequestHeader('X-Csrf-Token', $.cookie('csrf_token'));
            }
        });

        if (ENABLE_GOOGLE_ANALYTICS) {
            GoogleAnalytics.init(GOOGLE_ANALYTICS_TRACKING_ID);
            GoogleAnalytics.enable();
        }

        if (USE_WEBRTC) {
            var queryString = QueryString.getQueryStringObject();
            this.controllerId = queryString && queryString.d;

            if (this.controllerId) {
                $('#preload-message').text(Msg.I_ValidatingSSO);
                GoogleAnalytics.trackEvent('webrtc', 'init', this.controllerId);

                this.getSsoUuid()
                    .done(function(ssoUuid) {
                        $('#preload-message').text(Msg.I_ObtainingControllerInfo);
                        this.getController(this.controllerId)
                            .done(function(controller) {
                                var controllerVersionError = this.isSupportedControllerVersion(controller);
                                if (!controllerVersionError) {
                                    this.getTurnCredentials(this.controllerId)
                                        .done(function(turnCredentials) {
                                            $('#preload-message').text(Msg.I_EstablishingCommunications);
                                            this.establishWebRTCConnection(this.controllerId, turnCredentials)
                                                .done(function() {
                                                    $('#preload-message').text(Msg.I_SettingUpMessageRouting);
                                                    helpers.setWebRtcTransport();
                                                    this._initialize(routers, collections, AppView);
                                                    GoogleAnalytics.trackEvent('webrtc', 'success', this.controllerId);
                                                }.bind(this))
                                                .fail(function(errorData) {
                                                    var offlineError = _.findWhere(arguments[0], {device_id: this.controllerId});

                                                    if (!errorData || offlineError) {
                                                        this.showAlertAndRedirect(
                                                            Msg.E_DeviceOffline,
                                                            APP_CONFIG.CLOUD_DASHBOARD_HOST
                                                        );
                                                        GoogleAnalytics.trackEvent('webrtc', 'error_device_offline', this.controllerId);
                                                    }
                                                    else {
                                                        this.showAlertAndRedirect(
                                                            Msg.E_UnableToConnectWebRTC,
                                                            APP_CONFIG.CLOUD_DASHBOARD_HOST
                                                        );
                                                        GoogleAnalytics.trackEvent('webrtc', 'error_session_expired', this.controllerId);
                                                    }
                                                }.bind(this))
                                            ;
                                        }.bind(this))
                                    ;
                                }
                                else {
                                    this.showAlertAndRedirect(
                                        controllerVersionError,
                                        APP_CONFIG.CLOUD_DASHBOARD_HOST
                                    );
                                    GoogleAnalytics.trackEvent('webrtc', 'error_incompatible_version', this.controllerId);
                                }
                            }.bind(this))
                            .fail(function() {
                                this.showAlertAndRedirect(
                                    Msg.E_CannotGetListOfControllers,
                                    APP_CONFIG.CLOUD_AUTH_HOST + '/login?redirect=' + window.location.href
                                );
                            }.bind(this))
                        ;
                    }.bind(this))
                    .fail(function(){
                        this.showAlertAndRedirect(
                            Msg.E_NotLoggedIntoSSO,
                            APP_CONFIG.CLOUD_AUTH_HOST + '/login?redirect=' + window.location.href
                        );
                    }.bind(this))
                ;
            }
            else {
                this.showAlertAndRedirect(
                    Msg.E_DeviceOffline,
                    APP_CONFIG.CLOUD_DASHBOARD_HOST
                );
                GoogleAnalytics.trackEvent('webrtc', 'error_missing_controller_id');
            }
        }
        else {
            this._initialize(routers, collections, AppView);
        }
    };

    app.getTurnCredentials = function(controllerId) {
        var self = this;
        var deferred = $.Deferred();
        var xhr = new XMLHttpRequest();

        xhr.withCredentials = true;
        xhr.addEventListener('load', function(event) {
            try {
                var response = JSON.parse(event.currentTarget.response);
                deferred.resolve(response);
            }
            catch (e) {
                deferred.resolve();
            }
        }, false);
        xhr.addEventListener('error', function(jqXHR, status, error) {
            deferred.resolve();
        }, false);

        xhr.open('GET', APP_CONFIG.CLOUD_API_HOST + APP_CONFIG.TURN_CREDENTIAL_API_ENDPOINT + controllerId);
        xhr.send();

        return deferred.promise();
    },

    app.getSsoUuid = function() { // Needed to keep the sso cookie alive.
        var self = this;
        var deferred = $.Deferred();
        var xhr = new XMLHttpRequest();

        xhr.withCredentials = true;
        xhr.addEventListener('load', function(event) {
            try {
                var response = JSON.parse(event.currentTarget.response);
                if (!self.ssoUuid) { self.ssoUuid = response.uuid; }
                deferred.resolve(response.uuid);
            }
            catch (e) {
                deferred.reject();
            }
        }, false);
        xhr.addEventListener('error', function(jqXHR, status, error) {
            deferred.reject(jqXHR, status, error);
        }, false);

        xhr.open('GET', APP_CONFIG.SSO_API_HOST + APP_CONFIG.SSO_API_ENDPOINT);
        xhr.send();

        return deferred.promise();
    };

    app.establishWebRTCConnection = function(deviceId, turnCredentials) {
        var deferred = $.Deferred();

        // establish webRTC connection first, resolve after
        this.p2pConnector = new UniFiWebRtcPeer({
            app: app,
            stunHost: APP_CONFIG.STUN_HOST,
            turnCredentials: turnCredentials,
            uniFiWssHost: APP_CONFIG.UNIFI_WSS_HOST,
            uniFiWssEndpoint: APP_CONFIG.UNIFI_WSS_ENDPOINT
        });

        this.p2pConnector
            .connect({ deviceId: deviceId})
            .done(function(webRtcPeer) {
                logger.logInfo('Connected via WebRTC with API/Update Channel', webRtcPeer);
                window.webRtcPeer = webRtcPeer;
                deferred.resolve();
            }.bind(this))
            .fail(function(errors) {
                deferred.reject(errors);
            })
        ;

        return deferred.promise();
    };

    app.getControllerList = function() {
        var deferred = $.Deferred();
        var xhr = new XMLHttpRequest();

        xhr.withCredentials = true;
        xhr.addEventListener('load', function(event) {
            var response = JSON.parse(event.currentTarget.response);
            deferred.resolve(response);
        }, false);
        xhr.addEventListener('error', function(jqXHR, status, error) {
            deferred.reject(jqXHR, status, error);
        }, false);

        logger.logInfo('Get list of Controllers.');
        xhr.open('GET', APP_CONFIG.CLOUD_API_HOST + APP_CONFIG.CLOUD_API_ENDPOINT + '?page=0&page_size=' + APP_CONFIG.CLOUD_API_PAGE_SIZE);
        xhr.send();

        return deferred.promise();
    };

    app.getController = function(controllerId) {
        var deferred = $.Deferred();
        var xhr = new XMLHttpRequest();

        xhr.withCredentials = true;
        xhr.addEventListener('load', function(event) {
            var response = JSON.parse(event.currentTarget.response);
            deferred.resolve(response);
        }, false);
        xhr.addEventListener('error', function(jqXHR, status, error) {
            deferred.reject(jqXHR, status, error);
        }, false);

        logger.logInfo('Get controller');
        xhr.open('GET', APP_CONFIG.CLOUD_API_HOST + APP_CONFIG.CLOUD_API_ENDPOINT + '/' + controllerId);
        xhr.send();

        return deferred.promise();
    };

    app.isSupportedControllerVersion = function(controller) {
        var error = null,
            extractMajor = function(version) {
                var match = /^(\d+\.\d+\.\d+)(\.\d+)?$/.exec(version);
                if (match.length < 2) {
                    logger.logError('Cannot extract version:', version);
                    return null;
                }
                return match[1];
            };

        if (!controller) {
            logger.logError(Msg.G_VersionControllerNotFoundInList);
            return Msg.G_VersionControllerNotFoundInList;
        }
        else if (extractMajor(controller.version) === extractMajor(locationService.getVersion())) {
            logger.logInfo(Msg.G_VersionMatch, controller.version);
        }
        else {
            logger.logError(Msg.G_VersionIncompatible, 'Controller=' + controller.version + ', UI=' + locationService.getVersion());
            return Msg.G_VersionIncompatible;
        }
    };

    app._initialize = function(routers, collections, AppView) {
        $('#preload-message').text(Msg.I_InitializingApplication);
        $('#header').html(_.template(headerTemplate, {msg:Msg}));

        if (CONFIG.appName === 'Operator') {
            $('#header').find('fieldset.left').remove();
            $('body').addClass('isHotspot');
        }

        var self = this;

        this.routers = {};
        this.views = {};
        this.data = {};
        this.adminPref = new AdminPref();
        this.collections = collections;

        this.cloudUser = new CloudUser();
        this.cloudUser.fetch().done(function() {
            self.cloudUserCallback();

            //initialize the main application view
            self.views.app = new AppView({
                config: CONFIG,
                app: self,
                cloudUser: self.cloudUser
            });

            //initialize a backbone router for each main section and set up a trigger to change the nav style
            _.each(routers, function(Router, key) {
                var lcase = key[0].toLowerCase() + key.substr(1)
                ;

                self.routers[lcase] = new Router({ app: self });
                self.routers[lcase].on('route', function(route) {
                    self.views.app.navigate(lcase + location.search || '');
                });
            }, self);

            $(document).ajaxError(self.ajaxError);
            self.suppressAjaxErrors = 0;

            //Initialize jQuery UI tooltips
            $(document).tooltip({
                show: false,
                hide: false,
                track: true
            });

            //load the globally used data and defer the app rendering until it's complete
            self.preload(self.collections);
        }).error(function() {
            // we errored while getting the current site information
            // something is wrong with the site context
            // let's logout and have the user login to get the correct data

            if (USE_WEBRTC) {
                app.showAlertAndRedirect(
                    Msg.E_Fatal,
                    APP_CONFIG.CLOUD_AUTH_HOST + locationService.getLoginPagePathWithRedirect()
                );
            }
            else {
                app.showAlertAndRedirect(
                    Msg.E_Fatal,
                    locationService.gotoLogoutPage()
                );
            }
        });
    };

    app.cloudUserCallback = function(){
        var self = this;
        var cloudUser = this.cloudUser;
        // set SITE configuration
        window.SITE = {
            id: cloudUser.get('site_id'),
            name: cloudUser.get('site_name')
        };

        // set USER configuration
        window.USER = {
            id: cloudUser.get('admin_id'),
            role: cloudUser.get('site_role') || 'hotspot',
            super: cloudUser.get('is_super'),
            is_local: cloudUser.get('is_local')
        };

        // is super?
        if (cloudUser.get('site_role') === 'admin' && cloudUser.get('is_super') === true) {
            $.Body.addClass('isSuper');
            self.isSuper = true;
        } else {
            $.Body.removeClass('isSuper');
            self.isSuper = false;
        }

        // is readonly?
        if (cloudUser.get('site_role') === 'readonly') {
            $.Body.addClass('isReadOnly');
            self.isReadOnly = true;
            $(document)
                .off('keyup keypress', 'form, form input[type="checkbox"],form input[type="text"]', self.ignore_enter_keypress)
                .on('keyup keypress', 'form, form input[type="checkbox"],form input[type="text"]', self.ignore_enter_keypress);
        }
        else {
            $.Body.removeClass('isReadOnly');
            self.isReadOnly = false;

            $(document).off('keyup keypress', 'form input[type="checkbox"],form input[type="text"]', self.ignore_enter_keypress);
        }

        // is local user?
        if (cloudUser.get('is_local')) {
            $.Body.addClass('isLocalUser');
            self.isLocalUser = true;
        }
        else {
            $.Body.removeClass('isLocalUser');
            self.isLocalUser = false;
        }

        if (cloudUser.get('requires_new_password')) {
            $.Body.addClass('passwordChangeRequired');
            displayChangePassword();
        } else {
            $.Body.removeClass('passwordChangeRequired');
        }
    };

    app.ignore_enter_keypress = function(e){
        if(e.keyCode == 13) {
            e.preventDefault();
            return false;
        }
    };

    app.preload = function(collections) {
        var promises = [],
            deferred = {}
        ;

        //set up a promise for each collection
        _.each(collections, function(collection, collectionName) {
            promises.push(deferred[collectionName] = $.Deferred());
        }, this);

        //defer the rendering until the collections have loaded
        $.when.apply($, promises).then(function() {
            app.render();
        }, function() {
            app.fatal();
        });

        //load each collection and resolve the promises
        _.each(collections, function(collection, collectionName) {
            this.collection(collectionName, {
                'fetch': {
                    'reload': false,
                    'deferred': deferred[collectionName]
                }
            },
            collection);
        }, this);
    };

    app.render = function() {
        this.views.app.render();

        //force initialize the views that are required by more than one section
        this.routers.devices && this.routers.devices.index();
        this.routers.clients && this.routers.clients.index();

        //start the history or if it's already been started then reset the paths
        try {
//            this.routers[CONFIG.start].route('', 'forward');
            Backbone.history.start({pushState: true, root: locationService.getRootUrl()});
        } catch (e) {
            Backbone.history.root = locationService.getRootUrl();

            var $navigation = $('#navigation');
            app.updatePushStateUrls($navigation.find('a').not('a[data-page=settings]'));
        }

        //load the URL of an empty page first to force backbone to be allowed to navigate to the right page
        //note: see line 1470 of backbone where if Backbone.history.fragment matches the new fragment then it
        //won't navigate regardless of whether Backbone.history.root has changed
        var section = Backbone.history.fragment || CONFIG.start;
        Backbone.history.loadUrl('');
        this.routers[section.split(/[/?]+/)[0]].navigate(section.replace(location.search, '') + location.search || '', {replace: false, trigger: true});
    };

    app.showAlertAndRedirect = function (msg, url) {
        this.$preload = $('#preload').detach();

        if (!app.alertDialog) {
            app.alertDialog = new AlertDialogView();
        }

        if (!app.alertDialog.$el.is(':visible')) {
            app.alertDialog.open(msg);

            _.delay(function() {
                document.location = url;
            }, 2000);
        }
    };

    app.refresh = function(manual) {
        if (app.lostConnectionDialog && app.lostConnectionDialog.$el.is(':visible')) {
            app.lostConnectionDialog.hide();
        }

        if (!this._refreshing && (this._refreshing = true)) {
            var promises = [],
                deferred = {},
                self = this,
                view = this.views.app
            ;

            //notify the app that it's refreshing
            view.refreshing();

            //set up a promise for each collection
            _.each(this.data, function(data, name) {
                promises.push(deferred[name] = $.Deferred());
            });

            //when the collections have loaded notify the app of the status
            $.when.apply($, promises).then(function() {
                if (USE_WEBRTC) {
                    self.getSsoUuid().then(function(ssoUuid) {
                        if (self.ssoUuid === ssoUuid) {
                            self._refreshing = false;
                            view.refreshing(true);
                        }
                        else {
                            self.showAlertAndRedirect(
                                Msg.E_SessionExpired,
                                APP_CONFIG.CLOUD_AUTH_HOST + locationService.getLoginPagePathWithRedirect()
                            );
                        }
                    },
                    function() {
                        self.showAlertAndRedirect(
                            Msg.E_SessionExpired,
                            APP_CONFIG.CLOUD_AUTH_HOST + locationService.getLoginPagePathWithRedirect()
                        );
                    });
                }
                else {
                    self._refreshing = false;
                    view.refreshing(true);
                }
            }, function() {
                self._refreshing = false;
                view.refreshing(false);
            });

            //re-fetch all the currently fetched data for a refresh - the views should handle data changes automatically
            _.each(this.data, function(data, name) {
                self.collection(name, {
                    'fetch': {
                        'reload': true,
                        'method': 'reload',
                        'deferred': deferred[name]
                    }
                });
            }, this);
        }
    };

    app.changeSite = function(id, route) {
        try {
            var site = id ? this.collection('Sites').get(id) : this.collection('Sites').first();
            if (site && site.id) {
                SITE = {
                    'id': site.id,
                    'name': site.get('name'),
                    'route': route || Backbone.history.fragment
                };
                this.adminPref.sync('update', this.adminPref, { 'last_site_name':SITE.name });
            } else {
                throw Msg.E_ChangeSite;
            }

            // close all property views
            var properties = this.routers.clients.views.index.properties;
            _.each(properties, function(property, id){
                property.remove();
            });

            //reset the application view
            this.views.app.reset();

            //reset the routers which in turn should reset the views
            _.each(this.routers, function(router, name) {
                router.reset && router.reset();
            }, this);

            //clear out and delete all the collections that have the bySite flag
            _.each(this.data, function(data, name) {
                var collection = data.collection;
                if (collection.bySite) {
                    collection.reset();
                    delete this.data[name];
                }
            }, this);

            var self = this;
            this.cloudUser.url = '/api/s/' + site.get('name') + '/self';
            this.cloudUser.fetch().done(function(){
                self.cloudUserCallback();
                self.preload(self.collections);
                self.views.app.cloudUser = self.cloudUser;
            });
        } catch (e) {
            this.views.app.flash('error', 'Error', e);
            return;
        }
    };

    app.collection = function(collectionName, options, Collection) {
        options = options || {};
        options.init = options.init || {};
        options.fetch = options.fetch || {};

        //if the collection can't be instantiated then reject the deferred object if it exists
        if (_.isUndefined(this.data[collectionName])) {
            try {
                if (!_.isObject(Collection)) {
                    Collection = require('unifi/collection/' + collectionName);
                }

                this.data[collectionName] = {
                    collection: new Collection(options.init.models, options.init.options),
                    state: null,
                    deferred: []
                };
            } catch (e) {
                if (options.fetch && options.fetch.deferred) {
                    options.fetch.deferred.reject();
                }
                throw 'Invalid collection: ' + collectionName;
            }
        }

        var data = this.data[collectionName];

        //fetch the collection (or optionally reload it) and resolve or reject the deferred objects
        if (_.size(options.fetch)) {
            if (options.fetch.deferred) {
                data.deferred.push(options.fetch.deferred);
            }

            if (data.state != 'fetching') {
                if (!data.state || options.fetch.reload) {
                    data.state = 'fetching';

                    data.collection[options.fetch.method || 'fetch'](_.extend(options.fetch.options || {}, {
                        'success': function() {
                            data.state = 'fetched';
                            data.deferred && _.each(data.deferred, function(deferred, key) {
                                if (deferred.state() == 'pending') {
                                    deferred.resolve(arguments);
                                    delete data.deferred[key];
                                }
                            });
                        },
                        'error': function() {
                            data.state = 'fetched';
                            data.deferred && _.each(data.deferred, function(deferred, key) {
                                if (deferred.state() == 'pending') {
                                    deferred.reject(arguments);
                                    delete data.deferred[key];
                                }
                            });
                        }
                    }));
                } else {
                    data.deferred && _.each(data.deferred, function(deferred, key) {
                        if (deferred.state() == 'pending') {
                            deferred.resolve();
                            delete data.deferred[key];
                        }
                    });
                }
            }
        }

        return data.collection;
    };

    app.fatal = function() {
        if (this.views.app) {
            this.views.app.flash('error', 'Fatal Error', Msg.E_Fatal, true);
        } else {
            (new ErrorDialogView()).open(Msg.E_Fatal);
        }
    };

    app.ajaxError = function(event, jqxhr, settings, exception) {
        if (app.suppressAjaxErrors) {
            return;
        }

        if (jqxhr.status === 401 || jqxhr.status === 403) {
            if (app.lostConnectionDialog && app.lostConnectionDialog.$el.is(':visible')) {
                app.lostConnectionDialog.hide();
            }

            if (USE_WEBRTC) {
                app.showAlertAndRedirect(
                    Msg.E_SessionExpired,
                    APP_CONFIG.CLOUD_DASHBOARD_HOST
                );
            } else {
                app.showAlertAndRedirect(
                    Msg.E_SessionExpired,
                    locationService.getRootUrl()
                );
            }

        } else if ((jqxhr.readyState === 0 && jqxhr.status === 0 && jqxhr.statusText === 'error')) {
            if (!app.lostConnectionDialog) {
                app.lostConnectionDialog = new LostConnectionDialogView({
                    buttons: {
                        'Try Again': function() {
                            app.refresh(true);
                        }
                    }
                });
            }

            if (!app.lostConnectionDialog.$el.is(':visible')) {
                app.lostConnectionDialog.show();
            }
        }
    };

    app.ajaxErrorOff = function() {
        app.suppressAjaxErrors++;
    };

    app.ajaxErrorOn = function() {
        app.suppressAjaxErrors > 0 && app.suppressAjaxErrors--;
    };

    app.updatePushStateUrls = function($links) {
        $links.each(function() {
            var $this = $(this),
                page = $this.data('page'),
                href = (Backbone.history.root || locationService.getRootUrl()) + page.toLowerCase();

            $this.attr('href', href);
        });
    };

    function displayChangePassword() {
        _.delay(function() {
            $('#ChangePassword').click();
        }, 1000);
    }

    window.googleMapsNotLoaded = function() {
        $('.google-map-container').html('<div class="googleMapsError">' + Msg.E_GoogleMapsNotLoaded + '</div>');
    };

    return app;
});
