'use strict';

/* global angular */
/* global $ */
/* global asyncSeries */
/* global asyncForEach */
/* global RSTATES */
/* global ISTATES */
/* global ERROR */

angular.module('Application').controller('AppController', ['$scope', '$location', '$timeout', '$interval', '$route', '$routeParams', 'Client', function ($scope, $location, $timeout, $interval, $route, $routeParams, Client) {
    Client.onReady(function () { if (!Client.getUserInfo().admin) $location.path('/'); });

    // Avoid full reload on path change
    // https://stackoverflow.com/a/22614334
    // reloadOnUrl: false in $routeProvider did not work!
    var lastRoute = $route.current;
    $scope.$on('$locationChangeSuccess', function (/* event */) {
        if (lastRoute.$$route.originalPath === $route.current.$$route.originalPath) {
            $route.current = lastRoute;
        }
    });

    var appId = $routeParams.appId;
    if (!appId) return $location.path('/apps');

    $scope.view = '';
    $scope.app = null;
    $scope.config = Client.getConfig();
    $scope.user = Client.getUserInfo();
    $scope.domains = [];
    $scope.groups = [];
    $scope.users = [];

    $scope.HOST_PORT_MIN = 1024;
    $scope.HOST_PORT_MAX = 65535;
    $scope.ROBOTS_DISABLE_INDEXING_TEMPLATE = '# Disable search engine indexing\n\nUser-agent: *\nDisallow: /';

    $scope.setView = function (view) {
        if ($scope.view === view) return;

        // on error only allow uninstall or debug view
        if ($scope.app.error && view !== 'uninstall') view = 'debug';

        $route.updateParams({ view: view });
        $scope[view].show();
        $scope.view = view;
    };

    $scope.postInstallMessage = {
        confirmed: false,
        openApp: false,

        show: function (openApp) {
            $scope.postInstallMessage.confirmed = false;
            $scope.postInstallMessage.openApp = !!openApp;

            if (!$scope.app.manifest.postInstallMessage) return;
            $('#postInstallModal').modal('show');
        },

        submit: function () {
            if (!$scope.postInstallMessage.confirmed) return;

            $scope.app.pendingPostInstallConfirmation = false;
            delete localStorage['confirmPostInstall_' + $scope.app.id];

            $('#postInstallModal').modal('hide');
        }
    };

    $scope.display = {
        busy: false,
        error: {},
        success: false,

        tags: '',
        label: '',
        icon: { data: null },

        iconUrl: function () {
            if (!$scope.app) return '';

            if ($scope.display.icon.data === '__original__') { // user clicked reset
                return $scope.app.iconUrl + '&original=true';
            } else if ($scope.display.icon.data) { // user uploaded icon
                return $scope.display.icon.data;
            } else { // current icon
                return $scope.app.iconUrl;
            }
        },

        resetCustomIcon: function () {
            $scope.display.icon.data = '__original__';
        },

        showCustomIconSelector: function () {
            $('#iconFileInput').click();
        },

        show: function () {
            var app = $scope.app;

            $scope.display.error = {};

            // translate for tag-input
            $scope.display.tags = app.tags ? app.tags.join(',') : '';

            $scope.display.label = $scope.app.label || '';
            $scope.display.icon = { data: null };
        },

        submit: function () {
            $scope.display.busy = true;
            $scope.display.error = {};

            function done(error) {
                if (error) Client.error(error);

                refreshApp($scope.display.show);

                $timeout(function () {
                    $scope.display.busy = false;
                    $scope.display.success = true;
                    $scope.displayForm.$setPristine();
                }, 1000);
            }

            var NOOP = function (next) { return next(); };
            var configureLabel = $scope.display.label === $scope.app.label ? NOOP : Client.configureApp.bind(null, $scope.app.id, 'label', { label: $scope.display.label });

            configureLabel(function (error) {
                if (error) return done(error);

                var tags = $scope.display.tags.split(',').map(function (t) { return t.trim(); }).filter(function (t) { return !!t; });

                var configureTags = angular.equals(tags, $scope.app.tags) ? NOOP : Client.configureApp.bind(null, $scope.app.id, 'tags', { tags: tags });

                configureTags(function (error) {
                    if (error) return done(error);

                    // skip if icon is unchanged
                    if ($scope.display.icon.data === null) return done();

                    var icon;
                    if ($scope.display.icon.data === '__original__') { // user reset the icon
                        icon = '';
                    } else if ($scope.display.icon.data) { // user loaded custom icon
                        icon = $scope.display.icon.data.replace(/^data:image\/[a-z]+;base64,/, '');
                    }

                    Client.configureApp($scope.app.id, 'icon', { icon: icon }, function (error) {
                        if (error) return done(error);
                        done();
                    });
                });
            });
        }
    };

    $scope.location = {
        busy: false,
        error: {},
        domainCollisions: [],

        domain: null,
        location: '',
        alternateDomains: [],
        portBindings: {},
        portBindingsEnabled: {},
        portBindingsInfo: {},

        addAlternateDomain: function (event) {
            event.preventDefault();
            $scope.location.alternateDomains.push({
                domain: $scope.domains[0],
                subdomain: ''
            });
        },

        delAlternateDomain: function (event, index) {
            event.preventDefault();
            $scope.location.alternateDomains.splice(index, 1);
        },

        show: function () {
            var app = $scope.app;

            $scope.location.error = {};
            $scope.location.domainCollisions = [];
            $scope.location.location = app.location;
            $scope.location.domain = $scope.domains.filter(function (d) { return d.domain === app.domain; })[0];
            $scope.location.portBindingsInfo = angular.extend({}, app.manifest.tcpPorts, app.manifest.udpPorts); // Portbinding map only for information
            $scope.location.alternateDomains = app.alternateDomains.map(function (a) { return { subdomain: a.subdomain, domain: $scope.domains.filter(function (d) { return d.domain === a.domain; })[0] };});

            // fill the portBinding structures. There might be holes in the app.portBindings, which signalizes a disabled port
            for (var env in $scope.location.portBindingsInfo) {
                if (app.portBindings && app.portBindings[env]) {
                    $scope.location.portBindings[env] = app.portBindings[env];
                    $scope.location.portBindingsEnabled[env] = true;
                } else {
                    $scope.location.portBindings[env] = $scope.location.portBindingsInfo[env].defaultValue || 0;
                    $scope.location.portBindingsEnabled[env] = false;
                }
            }
        },

        submit: function (overwriteDns) {
            $('#domainCollisionsModal').modal('hide');

            $scope.location.busy = true;
            $scope.location.error = {};
            $scope.location.domainCollisions = [];

            // only use enabled ports from portBindings
            var portBindings = {};
            for (var env in $scope.location.portBindings) {
                if ($scope.location.portBindingsEnabled[env]) {
                    portBindings[env] = $scope.location.portBindings[env];
                }
            }

            var data = {
                overwriteDns: !!overwriteDns,
                location: $scope.location.location,
                domain: $scope.location.domain.domain,
                portBindings: portBindings,
                alternateDomains: $scope.location.alternateDomains.map(function (a) { return { subdomain: a.subdomain, domain: a.domain.domain };})
            };

            // pre-flight only for changed domains
            var domains = [];
            if ($scope.app.domain !== data.domain || $scope.app.location !== data.location) domains.push({ subdomain: data.location, domain: data.domain });
            data.alternateDomains.forEach(function (a) {
                if ($scope.app.alternateDomains.some(function (d) { return d.domain === a.domain && d.subdomain === a.subdomain; })) return;
                domains.push({ subdomain: a.subdomain, domain: a.domain });
            });

            asyncForEach(domains, function (domain, callback) {
                if (overwriteDns) return callback();

                Client.checkDNSRecords(domain.domain, domain.subdomain, function (error, result) {
                    if (error) return callback(error);
                    if (result.error) {
                        if (data.domain === domain.domain && data.location === domain.subdomain) {
                            $scope.location.error.location = domain.domain + ' ' + result.error.message;
                        } else {
                            $scope.location.error.alternateDomains = domain.domain + ' ' + result.error.message;
                        }
                        $scope.location.busy = false;
                        return;
                    }

                    if (result.needsOverwrite) $scope.location.domainCollisions.push(domain);

                    callback();
                });
            }, function (error) {
                if (error) {
                    $scope.location.busy = false;
                    return Client.error(error);
                }

                if ($scope.location.domainCollisions.length) {
                    $scope.location.busy = false;
                    return $('#domainCollisionsModal').modal('show');
                }

                Client.configureApp($scope.app.id, 'location', data, function (error) {
                    if (error && (error.statusCode === 409 || error.statusCode === 400)) {
                        if ((error.subdomain && error.domain) || error.field === 'location') {
                            if (data.domain === error.domain && data.location === error.subdomain) { // the primary
                                $scope.location.error.location = error.message;
                                $scope.locationForm.$setPristine();
                            } else {
                                $scope.location.error.alternateDomains = error.message;
                            }
                        } else if (error.portName || error.field === 'portBindings') {
                            $scope.location.error.port = error.message;
                        }

                        $scope.location.busy = false;
                        return;
                    }
                    if (error) return Client.error(error);

                    $scope.locationForm.$setPristine();
                    $scope.location.busy = false;

                    refreshApp();
                });
            });
        }
    };

    $scope.access = {
        busy: false,
        error: {},
        success: false,

        ftp: false,
        ssoAuth: false,
        accessRestrictionOption: 'any',
        accessRestriction: { users: [], groups: [] },

        isAccessRestrictionValid: function () {
            var tmp = $scope.access.accessRestriction;
            return !!(tmp.users.length || tmp.groups.length);
        },

        show: function () {
            var app = $scope.app;

            $scope.access.error = {};
            $scope.access.ftp = app.manifest.addons.localstorage && app.manifest.addons.localstorage.ftp;
            $scope.access.ssoAuth = (app.manifest.addons['ldap'] || app.manifest.addons['oauth']) && app.sso;
            $scope.access.accessRestrictionOption = app.accessRestriction ? 'groups' : 'any';
            $scope.access.accessRestriction = { users: [], groups: [] };

            if (app.accessRestriction) {
                var userSet = { };
                app.accessRestriction.users.forEach(function (uid) { userSet[uid] = true; });
                $scope.users.forEach(function (u) { if (userSet[u.id] === true) $scope.access.accessRestriction.users.push(u); });

                var groupSet = { };
                app.accessRestriction.groups.forEach(function (gid) { groupSet[gid] = true; });
                $scope.groups.forEach(function (g) { if (groupSet[g.id] === true) $scope.access.accessRestriction.groups.push(g); });
            }
        },

        submit: function () {
            $scope.access.busy = true;
            $scope.access.error = {};

            var accessRestriction = null;
            if ($scope.access.accessRestrictionOption === 'groups') {
                accessRestriction = { users: [], groups: [] };
                accessRestriction.users = $scope.access.accessRestriction.users.map(function (u) { return u.id; });
                accessRestriction.groups = $scope.access.accessRestriction.groups.map(function (g) { return g.id; });
            }

            Client.configureApp($scope.app.id, 'access_restriction', { accessRestriction: accessRestriction }, function (error) {
                if (error) return Client.error(error);

                $timeout(function () {
                    $scope.access.success = true;
                    $scope.access.busy = false;
                }, 1000);
            });
        }
    };

    $scope.resources = {
        busy: false,
        busyDataDir: false,
        error: {},

        currentMemoryLimit: 0,
        memoryLimit: 0,
        memoryTicks: [],
        dataDir: null,

        show: function () {
            var app = $scope.app;

            $scope.resources.error = {};
            $scope.resources.currentMemoryLimit = app.memoryLimit || app.manifest.memoryLimit || (256 * 1024 * 1024);
            $scope.resources.memoryLimit = $scope.resources.currentMemoryLimit;
            $scope.resources.dataDir = app.dataDir;

            Client.memory(function (error, memory) {
                if (error) return console.error(error);

                // create ticks starting from manifest memory limit. the memory limit here is currently split into ram+swap (and thus *2 below)
                // TODO: the *2 will overallocate since 4GB is max swap that cloudron itself allocates
                $scope.resources.memoryTicks = [];
                var npow2 = Math.pow(2, Math.ceil(Math.log(memory.memory)/Math.log(2)));
                for (var i = 256; i <= (npow2*2/1024/1024); i *= 2) {
                    if (i >= (app.manifest.memoryLimit/1024/1024 || 0)) $scope.resources.memoryTicks.push(i * 1024 * 1024);
                }
                if (app.manifest.memoryLimit && $scope.resources.memoryTicks[0] !== app.manifest.memoryLimit) {
                    $scope.resources.memoryTicks.unshift(app.manifest.memoryLimit);
                }
            });
        },

        submitMemoryLimit: function () {
            $scope.resources.busy = true;
            $scope.resources.error = {};

            var memoryLimit = $scope.resources.memoryLimit === $scope.resources.memoryTicks[0] ? 0 : $scope.resources.memoryLimit;
            Client.configureApp($scope.app.id, 'memory_limit', { memoryLimit: memoryLimit }, function (error) {
                if (error) return Client.error(error);

                $scope.resources.currentMemoryLimit = $scope.resources.memoryLimit;
                $scope.resources.busy = false;

                refreshApp();
            });
        },

        submitDataDir: function () {
            $scope.resources.busyDataDir = true;
            $scope.resources.error = {};

            Client.configureApp($scope.app.id, 'data_dir', { dataDir: $scope.resources.dataDir || null }, function (error) {
                if (error && error.statusCode === 400) {
                    $scope.resources.error.dataDir = error.message;
                    $scope.resources.busyDataDir = false;
                    return;
                }
                if (error) return Client.error(error);

                $scope.resourcesDataDirForm.$setPristine();
                $scope.resources.busyDataDir = false;

                refreshApp();
            });
        }
    };

    $scope.email = {
        busy: false,
        error: {},

        mailboxName: '',
        mailboxDomain: '',

        show: function () {
            var app = $scope.app;

            $scope.emailForm.$setPristine();
            $scope.email.error = {};
            $scope.email.mailboxName = app.mailboxName || '';
            $scope.email.mailboxDomain = $scope.domains.filter(function (d) { return d.domain === app.mailboxDomain; })[0];
        },

        submit: function () {
            $scope.email.error = {};
            $scope.email.busy = true;

            Client.configureApp($scope.app.id, 'mailbox', { mailboxName: $scope.email.mailboxName || null, mailboxDomain: $scope.email.mailboxDomain.domain }, function (error) {
                if (error && error.statusCode === 400) {
                    $scope.email.busy = false;
                    $scope.email.error.mailboxName = error.message;
                    $scope.emailForm.$setPristine();
                    return;
                }
                if (error) return Client.error(error);

                $scope.emailForm.$setPristine();

                $scope.email.busy = false;

                refreshApp(function (error) {
                    if (error) return;
                    // when the mailboxName is 'reset', this will fill it up with the default again
                    $scope.email.mailboxName = $scope.app.mailboxName || '';
                    $scope.email.mailboxDomain = $scope.domains.filter(function (d) { return d.domain === $scope.app.mailboxDomain; })[0];
                });
            });
        }
    };

    $scope.security = {
        busy: false,
        error: {},
        success: false,

        robotsTxt: '',
        csp: '',

        show: function () {
            $scope.security.error = {};
            $scope.security.robotsTxt = $scope.app.reverseProxyConfig.robotsTxt || '';
            $scope.security.csp = $scope.app.reverseProxyConfig.csp || '';
        },

        submit: function () {
            $scope.security.busy = true;
            $scope.security.error = {};

            var reverseProxyConfig = {
                robotsTxt: $scope.security.robotsTxt || null, // empty string resets
                csp: $scope.security.csp || null // empty string resets
            };

            Client.configureApp($scope.app.id, 'reverse_proxy', reverseProxyConfig, function (error) {
                if (error) return Client.error(error);

                $timeout(function () {
                    $scope.security.success = true;
                    $scope.security.busy = false;
                }, 1000);
            });
        }
    };

    $scope.updates = {
        busy: false,
        busyCheck: false,
        busyUpdate: false,
        skipBackup: false,

        enableAutomaticUpdate: false,

        show: function () {
            var app = $scope.app;

            $scope.updates.enableAutomaticUpdate = app.enableAutomaticUpdate;
        },

        toggleAutomaticUpdates: function () {
            $scope.updates.busy = true;

            Client.configureApp($scope.app.id, 'automatic_update', { enable: !$scope.updates.enableAutomaticUpdate }, function (error) {
                if (error) return Client.error(error);

                $timeout(function () {
                    $scope.updates.enableAutomaticUpdate = !$scope.updates.enableAutomaticUpdate;
                    $scope.updates.busy = false;
                }, 1000);
            });
        },

        check: function () {
            $scope.updates.busyCheck = true;

            Client.checkForUpdates(function (error) {
                if (error) Client.error(error);

                $scope.updates.busyCheck = false;
            });
        },

        askUpdate: function () {
            $scope.updates.busyUpdate = false;
            $('#updateModal').modal('show');
        },

        confirmUpdate: function () {
            $scope.updates.busyUpdate = true;

            Client.updateApp($scope.app.id, $scope.config.update.apps[$scope.app.id].manifest, { skipBackup: $scope.updates.skipBackup }, function (error) {
                $scope.updates.busyUpdate = false;
                if (error) return Client.error(error);

                $('#updateModal').modal('hide');
                refreshApp();
            });
        }
    };

    $scope.backups = {
        busy: false,
        busyCreate: false,
        error: {},
        copyBackupIdDone: false,

        enableBackup: false,
        backups: [],

        copyBackupId: function (backup) {
            var copyText = document.getElementById('backupIdHelper');
            copyText.value = backup.id;
            copyText.select();
            document.execCommand('copy');

            $scope.backups.copyBackupIdDone = true;

            // reset after 2.5sec
            $timeout(function () { $scope.backups.copyBackupIdDone = false; }, 2500);
        },

        trackBackupTask: function () {
            $scope.backups.busyCreate = true;

            refreshApp(function (error) {
                if (error) Client.error(error);

                $scope.backups.busyCreate = false;

                waitForAppTask(function (error) {
                    if (error) return Client.error(error);
                    $scope.backups.show();
                });
            });
        },

        createBackup: function () {
            $scope.backups.busyCreate = true;

            Client.backupApp($scope.app.id, function (error) {
                if (error) Client.error(error);

                $scope.backups.trackBackupTask();
            });
        },

        show: function () {
            var app = $scope.app;

            $scope.backups.error = {};
            $scope.backups.enableBackup = app.enableBackup;

            Client.getAppBackups(app.id, function (error, backups) {
                if (error) return Client.error(error);

                $scope.backups.backups = backups;
            });
        },

        toggleAutomaticBackups: function () {
            $scope.backups.busy = true;
            $scope.backups.error = {};

            Client.configureApp($scope.app.id, 'automatic_backup', { enable: !$scope.backups.enableBackup }, function (error) {
                if (error) return Client.error(error);

                $timeout(function () {
                    $scope.backups.enableBackup = !$scope.backups.enableBackup;
                    $scope.backups.busy = false;
                }, 1000);
            });
        }
    };

    $scope.uninstall = {
        busy: false,
        error: {},

        show: function () {
            $scope.uninstall.error = {};
        },

        ask: function () {
            $('#uninstallModal').modal('show');
        },

        submit: function () {
            $scope.uninstall.busy = true;

            var NOOP = function (next) { return next(); };
            var stopAppTask = $scope.app.taskId ? Client.stopTask.bind(null, $scope.app.taskId) : NOOP;

            stopAppTask(function () { // ignore error
                Client.uninstallApp($scope.app.id, function (error) {
                    if (error && error.statusCode === 402) { // unpurchase failed
                        Client.error('Relogin to Cloudron App Store');
                    } else if (error) {
                        Client.error(error);
                    } else {
                        $('#uninstallModal').modal('hide');
                        Client.refreshAppCache($scope.app.id, function() {}); // reflect the new app state immediately
                        $location.path('/apps');
                    }

                    $scope.uninstall.busy = false;
                });
            });
        }
    };

    $scope.debug = {
        show: function () {},
        busyRunState: false,

        stopAppTask: function (taskId) {
            Client.stopTask(taskId, function (error) {
                // we can ignore a call trying to cancel an already done task
                if (error && error.statusCode !== 409) Client.error(error);
            });
        },

        toggleRunState: function () {
            var func  = $scope.app.runState === RSTATES.STOPPED ? Client.startApp : Client.stopApp;
            $scope.debug.busyRunState = true;

            func($scope.app.id, function (error) {
                $scope.debug.busyRunState = false;
                if (error) return Client.error(error);

                refreshApp();
            });
        }
    };

    $scope.restore = {
        busy: false,
        error: {},

        backup: null,

        show: function (backup) {
            $scope.restore.error = {};
            $scope.restore.backup = backup;

            $('#restoreModal').modal('show');
        },

        submit: function () {
            $scope.restore.busy = true;

            Client.restoreApp($scope.app.id, $scope.restore.backup.id, function (error) {
                if (error) {
                    Client.error(error);
                    $scope.restore.busy = false;
                    return;
                }

                $('#restoreModal').modal('hide');

                refreshApp();
            });
        }
    };

    $scope.clone = {
        busy: false,
        error: {},

        backup: null,
        location: '',
        domain: null,
        portBindings: {},
        portBindingsInfo: {},
        portBindingsEnabled: {},

        show: function (backup) {
            var app = $scope.app;

            $scope.clone.error = {};
            $scope.clone.backup = backup;
            $scope.clone.domain = $scope.domains.find(function (d) { return app.domain === d.domain; }); // pre-select the app's domain
            $scope.clone.portBindingsInfo = angular.extend({}, app.manifest.tcpPorts, app.manifest.udpPorts);   // Portbinding map only for information
            // set default ports
            for (var env in $scope.clone.portBindingsInfo) {
                $scope.clone.portBindings[env] = $scope.clone.portBindingsInfo[env].defaultValue || 0;
                $scope.clone.portBindingsEnabled[env] = true;
            }

            $('#cloneModal').modal('show');
        },

        submit: function () {
            $scope.clone.busy = true;

            // only use enabled ports from portBindings
            var finalPortBindings = {};
            for (var env in $scope.clone.portBindings) {
                if ($scope.clone.portBindingsEnabled[env]) {
                    finalPortBindings[env] = $scope.clone.portBindings[env];
                }
            }

            var data = {
                location: $scope.clone.location,
                domain: $scope.clone.domain.domain,
                portBindings: finalPortBindings,
                backupId: $scope.clone.backup.id
            };

            Client.checkDNSRecords(data.domain, data.location, function (error, result) {
                if (error) {
                    Client.error(error);
                    $scope.clone.busy = false;
                    return;
                }
                if (result.error) {
                    if (result.error.reason === ERROR.ACCESS_DENIED) {
                        $scope.clone.error.location = 'DNS credentials for ' + data.domain + ' are invalid. Update it in Domains & Certs view';
                    } else {
                        $scope.clone.error.location = result.error.message;
                    }
                    $scope.clone.needsOverwrite = true;
                    $scope.clone.busy = false;
                    return;
                }
                if (result.needsOverwrite) {
                    $scope.clone.error.location = 'DNS Record already exists. Confirm that the domain is not in use for services external to Cloudron';
                    $scope.clone.needsOverwrite = true;
                    $scope.clone.busy = false;
                    return;
                }

                Client.cloneApp($scope.app.id, data, function (error/*, clonedApp */) {
                    $scope.clone.busy = false;

                    if (error) {
                        if (error.statusCode === 409) {
                            if (error.portName) {
                                $scope.clone.error.port = error.message;
                            } else if (error.domain) {
                                $scope.clone.error.location = 'This location is already taken.';
                                $('#cloneLocationInput').focus();
                            } else {
                                Client.error(error);
                            }
                        } else {
                            Client.error(error);
                        }
                        return;
                    }

                    $('#cloneModal').modal('hide');

                    $location.path('/apps');
                });
            });
        }
    };

    $scope.repair = {
        busy: false,
        error: {},

        location: null,
        domain: null,
        alternateDomains: [],
        backups: [],

        backupId: '',

        // this prepares the repair dialog with whatever is required for repair action
        show: function () {
            $scope.repair.error = {};
            $scope.repair.busy = false;
            $scope.repair.location = null;
            $scope.repair.domain = null;
            $scope.repair.alternateDomains = [];
            $scope.repair.backupId = '';

            var app = $scope.app;

            var errorState = ($scope.app.error && $scope.app.error.installationState) || ISTATES.PENDING_CONFIGURE;

            if (errorState === ISTATES.PENDING_LOCATION_CHANGE) {
                $scope.repair.location = app.location;
                $scope.repair.domain = $scope.domains.filter(function (d) { return d.domain === app.domain; })[0];
                $scope.repair.alternateDomains = $scope.app.alternateDomains;
                $scope.repair.alternateDomains = $scope.app.alternateDomains.map(function (altDomain) {
                    return {
                        subdomain: altDomain.subdomain,
                        enabled: true,
                        domain: $scope.domains.filter(function (d) { return d.domain === altDomain.domain; })[0]
                    };
                });
            }

            if (errorState === ISTATES.PENDING_RESTORE) {
                Client.getAppBackups($scope.app.id, function (error, backups) {
                    if (error) return Client.error(error);

                    $scope.repair.backups = backups;
                    $scope.repair.backupId = '';

                    $('#repairModal').modal('show');
                });
                return;
            }

            $('#repairModal').modal('show');
        },

        submit: function () {
            $scope.repair.error = {};
            $scope.repair.busy = true;

            var errorState = ($scope.app.error && $scope.app.error.installationState) || ISTATES.PENDING_CONFIGURE;
            var data = {};
            var repairFunc;

            switch (errorState) {
            case ISTATES.PENDING_INSTALL:
            case ISTATES.PENDING_CLONE: // if manifest or bad image, use CLI to provide new manifest
                repairFunc = Client.repairApp.bind(null, $scope.app.id, {}); // this will trigger a re-install
                break;

            case ISTATES.PENDING_LOCATION_CHANGE:
                data.location = $scope.repair.location;
                data.domain = $scope.repair.domain.domain;
                data.alternateDomains = $scope.repair.alternateDomains.filter(function (a) { return a.enabled; })
                    .map(function (d) { return { subdomain: d.subdomain, domain: d.domain.domain }; });
                data.overwriteDns = true; // always overwriteDns. user can anyway check and uncheck above
                repairFunc = Client.configureApp.bind(null, $scope.app.id, 'location', data);
                break;

            case ISTATES.PENDING_DATA_DIR_MIGRATION:
                repairFunc = Client.configureApp.bind(null, $scope.app.id, 'data_dir', { dataDir: null });
                break;

            // this also happens for import faliures. this UI can only show backup listing. use CLI for arbit id/config
            case ISTATES.PENDING_RESTORE:
                repairFunc = Client.restoreApp.bind(null, $scope.app.id, $scope.repair.backupId);
                break;

            case ISTATES.PENDING_UNINSTALL:
                repairFunc = Client.uninstallApp.bind(null, $scope.app.id);
                break;

            case ISTATES.PENDING_START:
            case ISTATES.PENDING_STOP:
            case ISTATES.PENDING_RESIZE:
            case ISTATES.PENDING_DEBUG:
            case ISTATES.PENDING_RECREATE_CONTAINER:
            case ISTATES.PENDING_CONFIGURE:
            case ISTATES.PENDING_BACKUP: // only here for completeness, should never happen as this only set task error and not app erro
            case ISTATES.PENDING_UPDATE: // when update failed, just bring it back to current state and user can click update again
            default:
                repairFunc = Client.repairApp.bind(null, $scope.app.id, {});
                break;
            }

            repairFunc(function (error) {
                if (error) return Client.error(error);

                $scope.repair.busy = false;
                $('#repairModal').modal('hide');
            });
        }
    };

    $scope.postInstallConfirm = {
        message: '',
        confirmed: false,

        show: function () {
            $scope.postInstallConfirm.message = $scope.app.manifest.postInstallMessage;
            $scope.postInstallConfirm.confirmed = false;

            $('#postInstallConfirmModal').modal('show');

            return false; // prevent propagation and default
        },

        submit: function () {
            if (!$scope.postInstallConfirm.confirmed) return;

            $scope.app.pendingPostInstallConfirmation = false;
            delete localStorage['confirmPostInstall_' + $scope.app.id];

            $('#postInstallConfirmModal').modal('hide');
        }
    };

    function fetchUsers(callback) {
        Client.getUsers(function (error, users) {
            if (error) return callback(error);

            // ensure we have something to work with in the access restriction dropdowns
            users.forEach(function (user) { user.display = user.username || user.email; });

            $scope.users = users;

            callback();
        });
    }

    function fetchGroups(callback) {
        Client.getGroups(function (error, groups) {
            if (error) return callback(error);

            $scope.groups = groups;

            callback();
        });
    }

    function getDomains(callback) {
        Client.getDomains(function (error, result) {
            if (error) return callback(error);

            $scope.domains = result;

            callback();
        });
    }

    function getBackupConfig(callback) {
        Client.getBackupConfig(function (error, backupConfig) {
            if (error) return callback(error);

            $scope.backupEnabled = backupConfig.provider !== 'noop';

            callback();
        });
    }

    function refreshApp(callback) {
        callback = callback || function () {};

        Client.getApp($scope.app.id, function (error, app) {
            if (error && error.statusCode === 404) return $location.path('/apps');
            if (error) return callback(error);

            // Immediately move to debug view on error
            if (app.error && !($scope.view === 'debug' || $scope.view === 'uninstall')) $scope.setView('debug');

            // ensure we have amended progress properties set before copy
            app.taskProgress = $scope.app.taskProgress;
            app.taskProgressMessage = $scope.app.taskProgressMessage;

            $scope.app = app;

            if (app.taskId) {
                Client.getTask(app.taskId, function (error, task) {
                    if (error) return callback(error);

                    $scope.app.taskProgress = task && task.percent ? task.percent : 5; // start with 5 to avoid empty progress bar
                    $scope.app.taskProgressMessage = task ? task.message : '';

                    callback();
                });
            } else {
                $scope.app.taskProgress = 0;
                $scope.app.taskProgressMessage = '';

                callback();
            }
        });
    }

    function waitForAppTask(callback) {
        callback = callback || function () {};

        if (!$scope.app.taskId) return callback();

        // app will be refreshed on interval
        $timeout(waitForAppTask.bind(null, callback), 2000); // not yet done
    }

    Client.onReady(function () {
        Client.getApp(appId, function (error, app) {
            if (error && error.statusCode === 404) return $location.path('/apps');
            if (error) return Client.error(error);

            $scope.app = app;

            $scope.setView($routeParams.view || 'display');

            // track on page load backup if active
            if (app.installationState === ISTATES.PENDING_BACKUP) $scope.backups.trackBackupTask();

            asyncSeries([
                fetchUsers,
                fetchGroups,
                getDomains,
                getBackupConfig
            ], function (error) {
                if (error) return Client.error(error);

                $scope.display.show();
                $scope.location.show();
                $scope.resources.show();
                $scope.access.show();
                $scope.email.show();
                $scope.security.show();
                $scope.backups.show();
                $scope.updates.show();

                var refreshTimer = $interval(function () { refreshApp(); }, 2000); // call with inline function to avoid iteration argument passed see $interval docs
                $scope.$on('$destroy', function () {
                    $interval.cancel(refreshTimer);
                });
            });
        });
    });

    $('#iconFileInput').get(0).onchange = function (event) {
        var fr = new FileReader();
        fr.onload = function () {
            $scope.$apply(function () {
                // var file = event.target.files[0];
                $scope.display.icon.data = fr.result;
            });
        };
        fr.readAsDataURL(event.target.files[0]);
    };

    // setup all the dialog focus handling
    ['appUninstallModal', 'appUpdateModal', 'appRestoreModal'].forEach(function (id) {
        $('#' + id).on('shown.bs.modal', function () {
            $(this).find('[autofocus]:first').focus();
        });
    });

    $('.modal-backdrop').remove();
}]);
