'use strict';

/* global angular:false */
/* global moment:false */
/* global $:false */
/* global ERROR,ISTATES,HSTATES,RSTATES */

// deal with accessToken in the query, this is passed for example on password reset and account setup upon invite
var search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
if (search.accessToken) {
    localStorage.token = search.accessToken;

    // strip the accessToken and expiresAt, then preserve the rest
    delete search.accessToken;
    delete search.expiresAt;

    // this will reload the page as this is not a hash change
    window.location.search = encodeURIComponent(Object.keys(search).map(function (key) { return key + '=' + search[key]; }).join('&'));
}

// create main application module
var app = angular.module('Application', ['pascalprecht.translate', 'ngCookies', 'ngFitText', 'ngRoute', 'ngAnimate', 'ngSanitize', 'angular-md5', 'base64', 'slick', 'ui-notification', 'ui.bootstrap', 'ui.bootstrap-slider', 'ngTld', 'ui.multiselect']);

app.config(['NotificationProvider', function (NotificationProvider) {
    NotificationProvider.setOptions({
        delay: 5000,
        startTop: 60,
        positionX: 'left',
        templateUrl: 'notification.html'
    });
}]);

// configure resourceUrlWhitelist https://code.angularjs.org/1.5.8/docs/api/ng/provider/$sceDelegateProvider#resourceUrlWhitelist
app.config(function ($sceDelegateProvider) {
    $sceDelegateProvider.resourceUrlWhitelist([
        // Allow same origin resource loads.
        'self',
        // Allow loading from our assets domain.
        'https://cloudron.io/**',
        'https://staging.cloudron.io/**',
        'https://dev.cloudron.io/**',
        // Allow local development against the appstore pages
        'http://localhost:5000/**'
    ]);
});

// setup all major application routes
app.config(['$routeProvider', function ($routeProvider) {
    $routeProvider.when('/', {
        redirectTo: '/apps'
    }).when('/users', {
        controller: 'UsersController',
        templateUrl: 'views/users.html?<%= revision %>'
    }).when('/app/:appId/:view?', {
        controller: 'AppController',
        templateUrl: 'views/app.html?<%= revision %>'
    }).when('/appstore', {
        controller: 'AppStoreController',
        templateUrl: 'views/appstore.html?<%= revision %>'
    }).when('/appstore/:appId', {
        controller: 'AppStoreController',
        templateUrl: 'views/appstore.html?<%= revision %>'
    }).when('/apps', {
        controller: 'AppsController',
        templateUrl: 'views/apps.html?<%= revision %>'
    }).when('/profile', {
        controller: 'ProfileController',
        templateUrl: 'views/profile.html?<%= revision %>'
    }).when('/backups', {
        controller: 'BackupsController',
        templateUrl: 'views/backups.html?<%= revision %>'
    }).when('/branding', {
        controller: 'BrandingController',
        templateUrl: 'views/branding.html?<%= revision %>'
    }).when('/network', {
        controller: 'NetworkController',
        templateUrl: 'views/network.html?<%= revision %>'
    }).when('/domains', {
        controller: 'DomainsController',
        templateUrl: 'views/domains.html?<%= revision %>'
    }).when('/email', {
        controller: 'EmailsController',
        templateUrl: 'views/emails.html?<%= revision %>'
    }).when('/email/:domain', {
        controller: 'EmailController',
        templateUrl: 'views/email.html?<%= revision %>'
    }).when('/notifications', {
        controller: 'NotificationsController',
        templateUrl: 'views/notifications.html?<%= revision %>'
    }).when('/settings', {
        controller: 'SettingsController',
        templateUrl: 'views/settings.html?<%= revision %>'
    }).when('/activity', {
        controller: 'ActivityController',
        templateUrl: 'views/activity.html?<%= revision %>'
    }).when('/support', {
        controller: 'SupportController',
        templateUrl: 'views/support.html?<%= revision %>'
    }).when('/system', {
        controller: 'SystemController',
        templateUrl: 'views/system.html?<%= revision %>'
    }).when('/services', {
        controller: 'ServicesController',
        templateUrl: 'views/services.html?<%= revision %>'
    }).when('/volumes', {
        controller: 'VolumesController',
        templateUrl: 'views/volumes.html?<%= revision %>'
    }).otherwise({ redirectTo: '/'});
}]);

app.filter('installError', function () {
    return function (app) {
        if (!app) return false;
        if (app.installationState === ISTATES.ERROR) return true;
        if (app.installationState === ISTATES.INSTALLED) {
            // app.health can also be null to indicate insufficient data
            if (app.health === HSTATES.UNHEALTHY || app.health === HSTATES.ERROR || app.health === HSTATES.DEAD) return true;
        }

        return false;
    };
});

app.filter('activeTask', function () {
    return function (app) {
        if (!app) return false;
        return app.taskId !== null;
    };
});

app.filter('installSuccess', function () {
    return function (app) {
        if (!app) return false;
        return app.installationState === ISTATES.INSTALLED;
    };
});

app.filter('appIsInstalledAndHealthy', function () {
    return function (app) {
        if (!app) return false;
        return (app.installationState === ISTATES.INSTALLED && app.health === HSTATES.HEALTHY && app.runState === RSTATES.RUNNING);
    };
});

app.filter('applicationLink', function() {
    return function(app) {
        if (!app) return '';

        if (app.installationState === ISTATES.INSTALLED && app.health === HSTATES.HEALTHY && app.runState === RSTATES.RUNNING && !app.pendingPostInstallConfirmation) {
            return 'https://' + app.fqdn;
        } else {
            return '';
        }
    };
});

// this appears when an item in app grid is clicked
app.filter('prettyAppErrorMessage', function () {
    return function (app) {
        if (!app) return '';

        if (app.installationState === ISTATES.INSTALLED) {
            // app.health can also be null to indicate insufficient data
            if (app.health === HSTATES.UNHEALTHY) return 'The app is not responding to health checks. Check the logs for any error messages.';
        }

        if (app.error.reason === 'Access Denied') {
            if (app.error.domain) return 'The DNS record for this location is not setup correctly. Please verify your DNS settings and repair this app.';
        } else if (app.error.reason === 'Already Exists') {
            if (app.error.domain) return 'The DNS record for this location already exists. Cloudron does not remove existing DNS records. Manually remove the DNS record and then click on repair.';
        }

        return app.error.message;
    };
});

// this appears as tool tip in app grid
app.filter('appProgressMessage', function () {
    return function (app) {
        var message = app.message || (app.error ? app.error.message : '');
        return message;
    };
});

// see apps.js $scope.states
app.filter('selectedStateFilter', function () {
    return function selectedStateFilter(apps, selectedState) {
        return apps.filter(function (app) {
            if (!selectedState || !selectedState.state) return true;

            if (selectedState.state === 'running') return app.runState === 'running' && app.health === 'healthy' && app.installationState === 'installed';
            if (selectedState.state === 'stopped') return app.runState === 'stopped';

            return app.runState === 'running' && (app.health !== 'healthy' || app.installationState !== 'installed'); // not responding
        });
    };
});

app.filter('selectedGroupAccessFilter', function () {
    return function selectedGroupAccessFilter(apps, group) {
        return apps.filter(function (app) {
            if (!group.id) return true; // case for no filter entry
            if (!app.accessRestriction) return true;

            if (!app.accessRestriction.groups) return false;

            if (app.accessRestriction.groups.indexOf(group.id) !== -1) return true;

            return false;
        });
    };
});

app.filter('selectedTagFilter', function () {
    return function selectedTagFilter(apps, selectedTags) {
        return apps.filter(function (app) {
            if (selectedTags.length === 0) return true;
            if (!app.tags) return false;

            for (var i = 0; i < selectedTags.length; i++) {
                if (app.tags.indexOf(selectedTags[i]) === -1) return false;
            }

            return true;
        });
    };
});

app.filter('selectedDomainFilter', function () {
    return function selectedDomainFilter(apps, selectedDomain) {
        return apps.filter(function (app) {
            if (!selectedDomain) return true;
            if (selectedDomain._alldomains) return true;  // magic domain for single select, see apps.js ALL_DOMAINS_DOMAIN

            if (selectedDomain.domain === app.domain) return true;
            if (app.aliasDomains.find(function (ad) { return ad.domain === selectedDomain.domain; })) return true;
            if (app.alternateDomains.find(function (ad) { return ad.domain === selectedDomain.domain; })) return true;

            return false;
        });
    };
});

app.filter('appSearchFilter', function () {
    return function appSearchFilter(apps, appSearch) {
        return apps.filter(function (app) {
            if (!appSearch) return true;
            appSearch = appSearch.toLowerCase();
            return app.fqdn.indexOf(appSearch) !== -1
                || (app.label && app.label.toLowerCase().indexOf(appSearch) !== -1)
                || (app.manifest.title && app.manifest.title.toLowerCase().indexOf(appSearch) !== -1)
                || (appSearch.length >=6 && app.id.indexOf(appSearch) !== -1);
        });
    };
});

app.filter('prettyDomains', function () {
    return function prettyDomains(domains) {
        return domains.map(function (d) { return d.domain; }).join(', ');
    };
});

app.filter('installationActive', function () {
    return function (app) {
        if (app.installationState === ISTATES.ERROR) return false;
        if (app.installationState === ISTATES.INSTALLED) return false;
        return true;
    };
});

// this appears in the app grid
app.filter('installationStateLabel', function () {
    return function(app) {
        if (!app) return '';

        var waiting = app.progress === 0 ? ' (Queued)' : '';

        switch (app.installationState) {
        case ISTATES.PENDING_INSTALL:
            return 'Installing' + waiting;
        case ISTATES.PENDING_CLONE:
            return 'Cloning' + waiting;
        case ISTATES.PENDING_LOCATION_CHANGE:
        case ISTATES.PENDING_CONFIGURE:
        case ISTATES.PENDING_RECREATE_CONTAINER:
        case ISTATES.PENDING_DEBUG:
            return 'Configuring' + waiting;
        case ISTATES.PENDING_RESIZE:
            return 'Resizing' + waiting;
        case ISTATES.PENDING_DATA_DIR_MIGRATION:
            return 'Migrating data' + waiting;
        case ISTATES.PENDING_UNINSTALL: return 'Uninstalling' + waiting;
        case ISTATES.PENDING_RESTORE: return 'Restoring' + waiting;
        case ISTATES.PENDING_UPDATE: return 'Updating' + waiting;
        case ISTATES.PENDING_BACKUP: return 'Backing up' + waiting;
        case ISTATES.PENDING_START: return 'Starting' + waiting;
        case ISTATES.PENDING_STOP: return 'Stopping' + waiting;
        case ISTATES.PENDING_RESTART: return 'Restarting' + waiting;
        case ISTATES.ERROR: {
            if (app.error && app.error.message === 'ETRYAGAIN') return 'DNS Error';
            return 'Error';
        }
        case ISTATES.INSTALLED: {
            if (app.debugMode) {
                return 'Recovery Mode';
            } else if (app.runState === 'running') {
                if (!app.health) return 'Starting...'; // no data yet
                if (app.health === HSTATES.HEALTHY) return 'Running';
                return 'Not responding'; // dead/exit/unhealthy
            } else if (app.runState === 'stopped') return 'Stopped';
            else return app.runState;
        }
        default: return app.installationState;
        }
    };
});

app.filter('taskName', function () {
    return function(installationState) {
        switch (installationState) {
        case ISTATES.PENDING_INSTALL: return 'install';
        case ISTATES.PENDING_CLONE: return 'clone';
        case ISTATES.PENDING_LOCATION_CHANGE: return 'location change';
        case ISTATES.PENDING_CONFIGURE: return 'configure';
        case ISTATES.PENDING_RECREATE_CONTAINER: return 'create container';
        case ISTATES.PENDING_DEBUG: return 'debug';
        case ISTATES.PENDING_RESIZE: return 'resize';
        case ISTATES.PENDING_DATA_DIR_MIGRATION: return 'data migration';
        case ISTATES.PENDING_UNINSTALL: return 'uninstall';
        case ISTATES.PENDING_RESTORE: return 'restore';
        case ISTATES.PENDING_UPDATE: return 'update';
        case ISTATES.PENDING_BACKUP: return 'backup';
        case ISTATES.PENDING_START: return 'start app';
        case ISTATES.PENDING_STOP: return 'stop app';
        case ISTATES.PENDING_RESTART: return 'restart app';
        default: return installationState || '';
        }
    };
});

app.filter('errorSuggestion', function () {
    return function (error) {
        if (!error) return '';

        switch (error.reason) {
        case ERROR.ACCESS_DENIED:
            if (error.domain) return 'Check the DNS credentials of ' + error.domain.domain + ' in the Domains & Certs view';
            return '';
        case ERROR.COLLECTD_ERROR: return 'Check if collectd is running on the server';
        case ERROR.DATABASE_ERROR: return 'Check if MySQL database is running on the server';
        case ERROR.DOCKER_ERROR: return 'Check if docker is running on the server';
        case ERROR.DNS_ERROR: return 'Check if the DNS service of the domain is running';
        case ERROR.LOGROTATE_ERROR: return 'Check if logrotate is running on the server';
        case ERROR.NETWORK_ERROR: return 'Check if there are any network issues on the server';
        case ERROR.REVERSEPROXY_ERROR: return 'Check if nginx is running on the server';
        default: return '';
        }
    };
});

app.filter('readyToUpdate', function () {
    return function (apps) {
        return apps.every(function (app) {
            return (app.installationState === ISTATES.ERROR) || (app.installationState === ISTATES.INSTALLED);
        });
    };
});

app.filter('inProgressApps', function () {
    return function (apps) {
        return apps.filter(function (app) {
            return app.installationState !== ISTATES.ERROR && app.installationState !== ISTATES.INSTALLED;
        });
    };
});

app.filter('prettyHref', function () {
    return function (input) {
        if (!input) return input;
        if (input.indexOf('http://') === 0) return input.slice('http://'.length);
        if (input.indexOf('https://') === 0) return input.slice('https://'.length);
        return input;
    };
});

app.filter('prettyEmailAddresses', function () {
    return function prettyEmailAddresses(addresses) {
        if (!addresses || addresses === '<>') return '<>';
        if (Array.isArray(addresses)) return addresses.map(function (a) { return a.slice(1, -1); }).join(', ');
        return addresses.slice(1, -1);
    };
});

// custom directive for dynamic names in forms
// See http://stackoverflow.com/questions/23616578/issue-registering-form-control-with-interpolated-name#answer-23617401
app.directive('laterName', function () {                   // (2)
    return {
        restrict: 'A',
        require: ['?ngModel', '^?form'],                   // (3)
        link: function postLink(scope, elem, attrs, ctrls) {
            attrs.$set('name', attrs.laterName);

            var modelCtrl = ctrls[0];                      // (3)
            var formCtrl  = ctrls[1];                      // (3)
            if (modelCtrl && formCtrl) {
                modelCtrl.$name = attrs.name;              // (4)
                formCtrl.$addControl(modelCtrl);           // (2)
                scope.$on('$destroy', function () {
                    formCtrl.$removeControl(modelCtrl);    // (5)
                });
            }
        }
    };
});

app.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
    var original = $location.path;
    $location.path = function (path, reload) {
        if (reload === false) {
            var lastRoute = $route.current;
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
                un();
            });
        }
        return original.apply($location, [path]);
    };
}]);

app.directive('ngClickSelect', function () {
    return {
        restrict: 'AC',
        link: function (scope, element/*, attrs */) {
            element.bind('click', function () {
                var selection = window.getSelection();
                var range = document.createRange();
                range.selectNodeContents(this);
                selection.removeAllRanges();
                selection.addRange(range);
            });
        }
    };
});

app.directive('ngClickReveal', function () {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            element.addClass('hand');

            var value = '';

            scope.$watch(attrs.ngClickReveal, function (newValue, oldValue) {
                if (newValue !== oldValue) {
                    element.html('<i>hidden</i>');
                    value = newValue;
                }
            });

            element.bind('click', function () {
                element.text(value);
            });
        }
    };
});

// https://codepen.io/webmatze/pen/isuHh
app.directive('tagInput', function () {
    return {
        restrict: 'E',
        scope: {
            inputTags: '=taglist'
        },
        require: '^form',
        link: function ($scope, element, attrs, formCtrl) {
            $scope.defaultWidth = 200;
            $scope.tagText = ''; // current tag being edited
            $scope.placeholder = attrs.placeholder;
            $scope.tagArray = function () {
                if ($scope.inputTags === undefined) {
                    return [];
                }
                return $scope.inputTags.split(' ').filter(function (tag) {
                    return tag !== '';
                });
            };
            $scope.addTag = function () {
                var tagArray = $scope.tagArray();

                // prevent adding empty or existing items
                if ($scope.tagText.length === 0 || tagArray.indexOf($scope.tagText) !== -1) {
                    return $scope.tagText = '';
                }

                tagArray.push($scope.tagText);
                $scope.inputTags = tagArray.join(' ');
                return $scope.tagText = '';
            };
            $scope.deleteTag = function (key) {
                var tagArray;
                tagArray = $scope.tagArray();
                if (tagArray.length > 0 && $scope.tagText.length === 0 && key === undefined) {
                    tagArray.pop();
                } else {
                    if (key !== undefined) {
                        tagArray.splice(key, 1);
                    }
                }
                formCtrl.$setDirty();
                return $scope.inputTags = tagArray.join(' ');
            };
            $scope.$watch('tagText', function (newVal, oldVal) {
                var tempEl;
                if (!(newVal === oldVal && newVal === undefined)) {
                    tempEl = $('<span>' + newVal + '</span>').appendTo('body');
                    $scope.inputWidth = tempEl.width() + 5;
                    if ($scope.inputWidth < $scope.defaultWidth) {
                        $scope.inputWidth = $scope.defaultWidth;
                    }
                    return tempEl.remove();
                }
            });
            element.bind('click', function () {
                element[0].firstChild.lastChild.focus();
            });
            element.bind('keydown', function (e) {
                var key = e.which;
                if (key === 9 || key === 13) {
                    e.preventDefault();
                }
                if (key === 8) {
                    return $scope.$apply('deleteTag()');
                }
            });
            element.bind('keyup', function (e) {
                var key = e.which;
                if (key === 9 || key === 13 || key === 32) {
                    e.preventDefault();
                    return $scope.$apply('addTag()');
                }
            });
        },
        template:
            '<div class="tag-input-container">' +
                '<div class="btn-group input-tag" data-ng-repeat="tag in tagArray()">' +
                    '<button type="button" class="btn btn-xs btn-primary" disabled>{{ tag }}</button>' +
                    '<button type="button" class="btn btn-xs btn-primary" data-ng-click="deleteTag($index)">&times;</button>' +
                '</div>' +
                '<input type="text" data-ng-model="tagText" ng-blur="addTag()" placeholder="{{placeholder}}"/>' +
            '</div>'
    };
});

app.config(['fitTextConfigProvider', function (fitTextConfigProvider) {
    fitTextConfigProvider.config = {
        loadDelay: 250,
        compressor: 0.9,
        min: 8,
        max: 24
    };
}]);
