// The core module used for the OpenSlides site and the projector
angular.module('OpenSlidesApp.core', [])

.config(function(DSProvider) {
    // Reloads everything after 5 minutes.
    // TODO: * find a way only to reload things that are still needed
    DSProvider.defaults.maxAge = 5 * 60 * 1000;  // 5 minutes
    DSProvider.defaults.reapAction = 'none';
    DSProvider.defaults.afterReap = function(model, items) {
        if (items.length > 5) {
            model.findAll({}, {bypassCache: true});
        } else {
            _.forEach(items, function (item) {
                model.refresh(item[model.idAttribute]);
            });
        }
    };
})

.run(function(DS, autoupdate) {
    autoupdate.on_message(function(data) {
        // TODO: when MODEL.find() is called after this
        //       a new request is fired. This could be a bug in DS

        // TODO: Do not send the status code to the client, but make the decission
        //       on the server side. It is an implementation detail, that tornado
        //       sends request to wsgi, which should not concern the client.
        console.log("Received object: " + data.collection + ", " + data.id);
        if (data.status_code == 200) {
            DS.inject(data.collection, data.data);
        } else if (data.status_code == 404) {
            DS.eject(data.collection, data.id);
        }
        // TODO: handle other statuscodes
    });
})

.run(function($rootScope, Config, Projector) {
    // Puts the config object into each scope.
    // TODO: maybe rootscope.config has to set before findAll() is finished
    Config.findAll().then(function() {
        $rootScope.config = function(key) {
            try {
                return Config.get(key).value;
            }
            catch(err) {
                console.log("Unkown config key: " + key);
                return ''
            }
        }
    });

    // Loads all projector data
    Projector.findAll();
})

.factory('autoupdate', function() {
    var url = location.origin + "/sockjs";

    var Autoupdate = {
        socket: null,
        message_receivers: [],
        connect: function() {
            var autoupdate = this;
            this.socket = new SockJS(url);

            this.socket.onmessage = function(event) {
                _.forEach(autoupdate.message_receivers, function(receiver) {
                    receiver(event.data);
                });
            }

            this.socket.onclose = function() {
                setTimeout(autoupdate.connect, 5000);
            }
        },
        on_message: function(receiver) {
            this.message_receivers.push(receiver);
        }
    };
    Autoupdate.connect();
    return Autoupdate;
})

.factory('jsDataModel', function($http, Projector) {
    var BaseModel = function() {};
    BaseModel.prototype.project = function() {
        return $http.post(
            '/rest/core/projector/1/prune_elements/',
            [{name: this.getResourceName(), id: this.id}]
        );
    }
    BaseModel.prototype.isProjected = function() {
        // Returns true if there is a projector element with the same
        // name and the same id.
        var projector = Projector.get(id=1);
        if (typeof projector === 'undefined') return false;
        var self = this;
        return _.findIndex(projector.elements, function(element) {
            return element.name == self.getResourceName() &&
                   typeof(element.context) !== 'undefined' &&
                   typeof(element.context.id) !== 'undefined' &&
                   element.context.id == self.id;
        }) > -1;
    }
    return BaseModel;
})

.factory('Customslide', function(DS, jsDataModel) {
    var name = 'core/customslide'
    return DS.defineResource({
        name: name,
        endpoint: '/rest/core/customslide/',
        useClass: jsDataModel,
        methods: {
            getResourceName: function () {
                return name;
            },
        },
    });
})

.factory('Tag', function(DS) {
    return DS.defineResource({
        name: 'core/tag',
        endpoint: '/rest/core/tag/'
    });
})

.factory('Config', function(DS) {
    return DS.defineResource({
        name: 'core/config',
        idAttribute: 'key',
        endpoint: '/rest/core/config/'
    });
})

.factory('Projector', function(DS) {
    return DS.defineResource({
        name: 'core/projector',
        endpoint: '/rest/core/projector/',
    });
})

.run(function(Projector, Config, Tag, Customslide){});


// The core module for the OpenSlides site
angular.module('OpenSlidesApp.core.site', ['OpenSlidesApp.core'])

.config(function($stateProvider, $urlMatcherFactoryProvider) {
    // Make the trailing slash optional
    $urlMatcherFactoryProvider.strictMode(false)

    // Use stateProvider.decorator to give default values to our states
    $stateProvider.decorator('views', function(state, parent) {
        var result = {},
            views = parent(state);

        if (state.abstract || state.data && state.data.extern) {
            return views;
        }

        angular.forEach(views, function(config, name) {

            // Sets default values for templateUrl
            var patterns = state.name.split('.'),
                templateUrl,
                controller,
                defaultControllers = {
                    create: 'CreateCtrl',
                    update: 'UpdateCtrl',
                    list: 'ListCtrl',
                    detail: 'DetailCtrl',
                };

            // templateUrl
            if (_.last(patterns).match(/(create|update)/)) {
                // When state_patterns is in the form "app.module.create" or
                // "app.module.update", use the form template.
                templateUrl = 'static/templates/' + patterns[0] + '/' + patterns[1] + '-form.html'
            } else {
                // Replaces the first point through a slash (the app name)
                var appName = state.name.replace('.', '/');
                // Replaces any folowing points though a -
                templateUrl = 'static/templates/' + appName.replace(/\./g, '-') + '.html';
            }
            config.templateUrl = config.templateUrl || templateUrl;

            // controller
            if (patterns.length >= 3) {
                controller = _.capitalize(patterns[1]) + defaultControllers[_.last(patterns)];
                config.controller = config.controller || controller;
            }
            result[name] = config;
        });
        return result;
    })

    .decorator('url', function(state, parent) {
        var defaultUrl;

        if (state.abstract) {
            defaultUrl = '';
        } else {
            var patterns = state.name.split('.'),
                defaultUrls = {
                    create: '/new',
                    update: '/edit',
                    list: '',
                    // The id is expected to be an integer, if not, the url has to
                    // be defined manually
                    detail: '/{id:int}',
                };

            defaultUrl = defaultUrls[_.last(patterns)];
        }

        state.url = state.url || defaultUrl;
        return parent(state)
    });
})

.config(function($stateProvider, $locationProvider) {
    // Core urls
    $stateProvider
        .state('dashboard', {
            url: '/',
            templateUrl: 'static/templates/dashboard.html'
        })
        .state('projector', {
            url: '/projector',
            data: {extern: true},
            onEnter: function($window) {
                $window.location.href = this.url;
            }
        })
        .state('core', {
            url: '/core',
            abstract: true,
            template: "<ui-view/>",
        })
        // version
        .state('version', {
            url: '/version',
            controller: 'VersionCtrl',
        })
        //config
        .state('config', {
            url: '/config',
            controller: 'ConfigCtrl',
            resolve: {
                configOption: function($http) {
                    return $http({ 'method': 'OPTIONS', 'url': '/rest/core/config/' });
                }
            }
        })
        // customslide
        .state('core.customslide', {
            url: '/customslide',
            abstract: true,
            template: "<ui-view/>",
        })
        .state('core.customslide.list', {
            resolve: {
                customslides: function(Customslide) {
                    return Customslide.findAll();
                }
            }
        })
        .state('core.customslide.create', {})
        .state('core.customslide.detail', {
            resolve: {
                customslide: function(Customslide, $stateParams) {
                    return Customslide.find($stateParams.id);
                }
            }
        })
        .state('core.customslide.detail.update', {
            views: {
                '@core.customslide': {}
            }
        })
        // tag
        .state('core.tag', {
            url: '/tag',
            abstract: true,
            template: "<ui-view/>",
        })
        .state('core.tag.list', {
            resolve: {
                tags: function(Tag) {
                    return Tag.findAll();
                }
            }
        })
        .state('core.tag.create', {})
        .state('core.tag.detail.update', {
            views: {
                '@core.tag': {}
            }
        });

    $locationProvider.html5Mode(true);
})

// config for ng-fab-form
.config(function(ngFabFormProvider) {
    ngFabFormProvider.extendConfig({
        setAsteriskForRequiredLabel: true
    });
})

// Helper to add ui.router states at runtime.
// Needed for the django url_patterns.
.provider('runtimeStates', function($stateProvider) {
  this.$get = function($q, $timeout, $state) {
    return {
      addState: function(name, state) {
        $stateProvider.state(name, state);
      }
    }
  }
})

// Load the django url patterns
.run(function(runtimeStates, $http) {
    $http.get('/core/url_patterns/').then(function(data) {
        for (var pattern in data.data) {
            runtimeStates.addState(pattern, {
                'url': data.data[pattern],
                data: {extern: true},
                onEnter: function($window) {
                    $window.location.href = this.url;
                }
            });
        }
    });
})

// options for angular-xeditable
.run(function(editableOptions) {
    editableOptions.theme = 'bs3';
})


// html-tag os-form-field to generate generic from fields
// TODO: make it possible to use other fields then config fields
.directive('osFormField', function($parse, Config) {
    function getHtmlType(type) {
        return {
            string: 'text',
            integer: 'number',
            boolean: 'checkbox',
            choice: 'choice',
        }[type];
    }

    return {
        restrict: 'E',
        scope: true,
        templateUrl: '/static/templates/config-form-field.html',
        link: function ($scope, iElement, iAttrs, controller, transcludeFn) {
            var field = $parse(iAttrs.field)($scope);
            var config = Config.get(field.key);
            $scope.type = getHtmlType(field.input_type);
            if ($scope.type == 'choice') {
                $scope.choices = field.choices;
            }
            $scope.label = field.label;
            $scope.key = 'field-' + field.key;
            $scope.value = config.value;
            $scope.help_text = field.help_text;
        }
    }
})

.controller("LanguageCtrl", function ($scope, gettextCatalog) {
    // controller to switch app language
    // TODO: detect browser language for default language
    gettextCatalog.setCurrentLanguage('en');
    //TODO: for debug only! (helps to find untranslated strings by adding "[MISSING]:")
    gettextCatalog.debug = true;
    $scope.switchLanguage = function (lang) {
        gettextCatalog.setCurrentLanguage(lang);
        if (lang != 'en') {
            gettextCatalog.loadRemote("static/i18n/" + lang + ".json");
        }
    }
})

.controller("LoginFormCtrl", function ($scope, $modal) {
    $scope.open = function () {
        var modalInstance = $modal.open({
            animation: true,
            templateUrl: 'LoginForm.html',
            controller: 'LoginFormModalCtrl',
            size: 'sm',
        });
    }
})

.controller('LoginFormModalCtrl', function ($scope, $modalInstance, $http, operator) {
    $scope.login = function () {
        $http.post(
            '/users/login/',
            {'username': $scope.username, 'password': $scope.password}
        ).success(function(data) {
            if (data.success) {
                operator.setUser(data.user_id);
                $scope.loginFailed = false;
                $modalInstance.close();
            } else {
                $scope.loginFailed = true;
            }
        });
    };
    $scope.guest = function () {
        $modalInstance.dismiss('cancel');
    };
    $scope.cancel = function () {
        $modalInstance.dismiss('cancel');
    };
})

// Version Controller
.controller('VersionCtrl', function($scope, $http) {
    $http.get('/core/version/').success(function(data) {
        $scope.core_version = data.openslides_version;
        $scope.plugins = data.plugins;
    });
})

// Config Controller
.controller('ConfigCtrl', function($scope, Config, configOption) {
    Config.bindAll({}, $scope, 'configs');
    $scope.configGroups = configOption.data.config_groups;

    // save changed config value
    $scope.save = function(key, value) {
        Config.get(key).value = value;
        Config.save(key);
    }
})

// Customslide Controller
.controller('CustomslideListCtrl', function($scope, Customslide) {
    Customslide.bindAll({}, $scope, 'customslides');

    // setup table sorting
    $scope.sortColumn = 'title';
    $scope.reverse = false;
    // function to sort by clicked column
    $scope.toggleSort = function ( column ) {
        if ( $scope.sortColumn === column ) {
            $scope.reverse = !$scope.reverse;
        }
        $scope.sortColumn = column;
    };

    // save changed customslide
    $scope.save = function (customslide) {
        Customslide.save(customslide);
    };
    $scope.delete = function (customslide) {
        //TODO: add confirm message
        Customslide.destroy(customslide.id).then(
            function(success) {
                //TODO: success message
            }
        );
    };
})

.controller('CustomslideDetailCtrl', function($scope, Customslide, customslide) {
    Customslide.bindOne(customslide.id, $scope, 'customslide');
})

.controller('CustomslideCreateCtrl', function($scope, $state, Customslide) {
    $scope.customslide = {};
    $scope.save = function (customslide) {
        Customslide.create(customslide).then(
            function(success) {
                $state.go('core.customslide.list');
            }
        );
    };
})

.controller('CustomslideUpdateCtrl', function($scope, $state, Customslide, customslide) {
    $scope.customslide = customslide;
    $scope.save = function (customslide) {
        Customslide.save(customslide).then(
            function(success) {
                $state.go('core.customslide.list');
            }
        );
    };
})

// Tag Controller
.controller('TagListCtrl', function($scope, Tag) {
    Tag.bindAll({}, $scope, 'tags');

    // setup table sorting
    $scope.sortColumn = 'name';
    $scope.reverse = false;
    // function to sort by clicked column
    $scope.toggleSort = function ( column ) {
        if ( $scope.sortColumn === column ) {
            $scope.reverse = !$scope.reverse;
        }
        $scope.sortColumn = column;
    };

    // save changed tag
    $scope.save = function (tag) {
        Tag.save(tag);
    };
    $scope.delete = function (tag) {
        //TODO: add confirm message
        Tag.destroy(tag.id).then(
            function(success) {
                //TODO: success message
            }
        );
    };
})

.controller('TagCreateCtrl', function($scope, $state, Tag) {
    $scope.tag = {};
    $scope.save = function (tag) {
        Tag.create(tag).then(
            function(success) {
                $state.go('core.tag.list');
            }
        );
    };
})

.controller('TagUpdateCtrl', function($scope, $state, Tag, tag) {
    $scope.tag = tag;
    $scope.save = function (tag) {
        Tag.save(tag).then(
            function(success) {
                $state.go('core.tag.list');
            }
        );
    };
})

.directive('osFocusMe', function ($timeout) {
    return {
        link: function (scope, element, attrs, model) {
            $timeout(function () {
                element[0].focus();
            });
        }
    };
});


// The core module for the OpenSlides projector
angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])

// Provider to register slides in a .config() statement.
.provider('slides', function() {
    var slidesMap = {};

    this.registerSlide = function(name, config) {
        slidesMap[name] = config;
        return this;
    };

    this.$get = function($templateRequest, $q) {
        var self = this;
        return {
            getElements: function(projector) {
                var elements = [];
                var factory = this;
                _.forEach(projector.elements, function(element) {
                    if (element.name in slidesMap) {
                        element.template = slidesMap[element.name].template;
                        elements.push(element);
                    } else {
                        console.log("Unknown slide: " + element.name);
                    }
                });
                return elements;
            }
        }
    };
})

.config(function(slidesProvider) {
    slidesProvider.registerSlide('core/customslide', {
        template: 'static/templates/core/slide_customslide.html',
    });

    slidesProvider.registerSlide('core/clock', {
        template: 'static/templates/core/slide_clock.html',
    });
})

.filter('osServertime',function() {
    return function(serverOffset) {
        var date = new Date();
        return date.setTime(date.getTime() - serverOffset);
    };
})

.controller('ProjectorCtrl', function($scope, Projector, slides) {
    Projector.find(1).then(function() {
        $scope.$watch(function () {
            return Projector.lastModified(1);
        }, function () {
            $scope.elements = [];
            _.forEach(slides.getElements(Projector.get(1)), function(element) {
                if (!element.error) {
                    $scope.elements.push(element);
                } else {
                    console.error("Error for slide " + element.name + ": " + element.error)
                }
            });
        });
    });
})

.controller('SlideCustomSlideCtrl', function($scope, Customslide) {
    // Attention! Each object that is used here has to be dealt on server side.
    // Add it to the coresponding get_requirements method of the ProjectorElement
    // class.
    var id = $scope.element.context.id;
    Customslide.find(id);
    Customslide.bindOne(id, $scope, 'customslide');
})

.controller('SlideClockCtrl', function($scope) {
    // Attention! Each object that is used here has to be dealt on server side.
    // Add it to the coresponding get_requirements method of the ProjectorElement
    // class.
    $scope.serverOffset = Date.parse(new Date().toUTCString()) - $scope.element.context.server_time;
});
