'use strict';

/* global angular, $, Terminal, AttachAddon, FitAddon, ISTATES */

// create main application module
angular.module('Application', ['pascalprecht.translate', 'ngCookies', 'angular-md5', 'ui-notification']);

angular.module('Application').controller('TerminalController', ['$scope', '$translate', '$timeout', '$location', 'Client', function ($scope, $translate, $timeout, $location, Client) {
    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; }, {});

    $scope.config = Client.getConfig();
    $scope.user = Client.getUserInfo();

    $scope.apps = [];
    $scope.selected = '';
    $scope.terminal = null;
    $scope.terminalSocket = null;
    $scope.fitAddon = null;
    $scope.restartAppBusy = false;
    $scope.appBusy = false;
    $scope.selectedAppInfo = null;
    $scope.schedulerTasks = [];

    $scope.downloadFile = {
        error: '',
        filePath: '',
        busy: false,

        downloadUrl: function () {
            if (!$scope.downloadFile.filePath) return '';

            var filePath = encodeURIComponent($scope.downloadFile.filePath);

            return Client.apiOrigin + '/api/v1/apps/' + $scope.selected.value + '/download?file=' + filePath + '&access_token=' + Client.getToken();
        },

        show: function () {
            $scope.downloadFile.busy = false;
            $scope.downloadFile.error = '';
            $scope.downloadFile.filePath = '';
            $('#downloadFileModal').modal('show');
        },

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

            Client.checkDownloadableFile($scope.selected.value, $scope.downloadFile.filePath, function (error) {
                $scope.downloadFile.busy = false;

                if (error) {
                    $scope.downloadFile.error = 'The requested file does not exist.';
                    return;
                }

                // we have to click the link to make the browser do the download
                // don't know how to prevent the browsers
                $('#fileDownloadLink')[0].click();

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

    $scope.uploadProgress = {
        busy: false,
        total: 0,
        current: 0,

        show: function () {
            $scope.uploadProgress.total = 0;
            $scope.uploadProgress.current = 0;

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

        hide: function () {
            $('#uploadProgressModal').modal('hide');
        }
    };

    $scope.uploadFile = function () {
        var fileUpload = document.querySelector('#fileUpload');

        fileUpload.onchange = function (e) {
            if (e.target.files.length === 0) return;

            $scope.uploadProgress.busy = true;
            $scope.uploadProgress.show();

            Client.uploadFile($scope.selected.value, e.target.files[0], function progress(e) {
                $scope.uploadProgress.total = e.total;
                $scope.uploadProgress.current = e.loaded;
            }, function (error) {
                if (error) console.error(error);

                $scope.uploadProgress.busy = false;
                $scope.uploadProgress.hide();
            });
        };

        fileUpload.click();
    };

    $scope.usesAddon = function (addon) {
        if (!$scope.selected || !$scope.selected.addons) return false;
        return !!Object.keys($scope.selected.addons).find(function (a) { return a === addon; });
    };

    function reset() {
        if ($scope.terminal) {
            $scope.terminal.dispose();
            $scope.terminal = null;
        }

        if ($scope.terminalSocket) {
            $scope.terminalSocket = null;
        }

        $scope.selectedAppInfo = null;
    }

    $scope.restartApp = function () {
        $scope.restartAppBusy = true;
        $scope.appBusy = true;

        var appId = $scope.selected.value;

        function waitUntilRestarted(callback) {
            refreshApp(appId, function (error, result) {
                if (error) return callback(error);

                if (result.installationState === ISTATES.INSTALLED) return callback();
                setTimeout(waitUntilRestarted.bind(null, callback), 2000);
            });
        }

        Client.restartApp(appId, function (error) {
            if (error) console.error('Failed to restart app.', error);

            waitUntilRestarted(function (error) {
                if (error) console.error('Failed wait for restart.', error);

                $scope.restartAppBusy = false;
                $scope.appBusy = false;
            });
        });
    };

    function createTerminalSocket() {
        try {
            // websocket cannot use relative urls
            var url = Client.apiOrigin.replace('https', 'wss') + '/api/v1/apps/' + $scope.selected.value + '/execws?tty=true&rows=' + $scope.terminal.rows + '&columns=' + $scope.terminal.cols + '&access_token=' + Client.getToken();
            $scope.terminalSocket = new WebSocket(url);
            $scope.terminal.loadAddon(new AttachAddon.AttachAddon($scope.terminalSocket));

            $scope.terminalSocket.onclose = function () {
                // retry in one second
                $scope.terminalReconnectTimeout = setTimeout(function () {
                    showTerminal(true);
                }, 1000);
            };

        } catch (e) {
            console.error(e);
        }
    }

    function refreshApp(id, callback) {
        Client.getApp(id, function (error, result) {
            if (error) return callback(error);

            $scope.selectedAppInfo = result;

            callback(null, result);
        });
    }

    function showTerminal(retry) {
        reset();

        if (!$scope.selected) return;

        var appId = $scope.selected.value;

        refreshApp(appId, function (error) {
            if (error) return console.error(error);

            var result = $scope.selectedAppInfo;

            $scope.schedulerTasks = result.manifest.addons.scheduler ? Object.keys(result.manifest.addons.scheduler).map(function (k) { return { name: k, command: result.manifest.addons.scheduler[k].command }; }) : [];

            $scope.terminal = new Terminal();

            $scope.fitAddon = new FitAddon.FitAddon();
            $scope.terminal.loadAddon($scope.fitAddon);

            $scope.terminal.open(document.querySelector('#terminalContainer'));

            window.terminal = $scope.terminal;

            // Let the browser handle paste
            $scope.terminal.attachCustomKeyEventHandler(function (e) {
                if (e.key === 'v' && (e.ctrlKey || e.metaKey)) return false;
            });

            if (retry) $scope.terminal.writeln('Reconnecting...');
            else $scope.terminal.writeln('Connecting...');

            // we have to give it some time to setup the terminal to make it fit, there is no event unfortunately
            setTimeout(function () {
                if (!$scope.terminal) return;

                // this is here so that the text wraps correctly after the fit!
                var YELLOW = '\u001b[33m'; // https://gist.github.com/dainkaplan/4651352
                var NC = '\u001b[0m';
                $scope.terminal.writeln(YELLOW + 'If you resize the browser window, press Ctrl+D to start a new session with the current size.' + NC);

                // we have to first write something on reconnect after app restart..not sure why
                $scope.fitAddon.fit();

                // create exec container after we fit() since we cannot resize exec container post-creation
                createTerminalSocket();

                $scope.terminal.focus();
            }, 1000);
        });
    }

    $scope.terminalInject = function (addon, extra) {
        if (!$scope.terminalSocket) return;

        var cmd, manifestVersion = $scope.selected.manifest.manifestVersion;
        if (addon === 'mysql') {
            if (manifestVersion === 1) {
                cmd = 'mysql --user=${MYSQL_USERNAME} --password=${MYSQL_PASSWORD} --host=${MYSQL_HOST} ${MYSQL_DATABASE}';
            } else {
                cmd = 'mysql --user=${CLOUDRON_MYSQL_USERNAME} --password=${CLOUDRON_MYSQL_PASSWORD} --host=${CLOUDRON_MYSQL_HOST} ${CLOUDRON_MYSQL_DATABASE}';
            }
        } else if (addon === 'postgresql') {
            if (manifestVersion === 1) {
                cmd = 'PGPASSWORD=${POSTGRESQL_PASSWORD} psql -h ${POSTGRESQL_HOST} -p ${POSTGRESQL_PORT} -U ${POSTGRESQL_USERNAME} -d ${POSTGRESQL_DATABASE}';
            } else {
                cmd = 'PGPASSWORD=${CLOUDRON_POSTGRESQL_PASSWORD} psql -h ${CLOUDRON_POSTGRESQL_HOST} -p ${CLOUDRON_POSTGRESQL_PORT} -U ${CLOUDRON_POSTGRESQL_USERNAME} -d ${CLOUDRON_POSTGRESQL_DATABASE}';
            }
        } else if (addon === 'mongodb') {
            if (manifestVersion === 1) {
                cmd = 'mongo -u "${MONGODB_USERNAME}" -p "${MONGODB_PASSWORD}" ${MONGODB_HOST}:${MONGODB_PORT}/${MONGODB_DATABASE}';
            } else {
                cmd = 'mongo -u "${CLOUDRON_MONGODB_USERNAME}" -p "${CLOUDRON_MONGODB_PASSWORD}" ${CLOUDRON_MONGODB_HOST}:${CLOUDRON_MONGODB_PORT}/${CLOUDRON_MONGODB_DATABASE}';
            }
        } else if (addon === 'redis') {
            if (manifestVersion === 1) {
                cmd = 'redis-cli -h "${REDIS_HOST}" -p "${REDIS_PORT}" -a "${REDIS_PASSWORD}"';
            } else {
                cmd = 'redis-cli -h "${CLOUDRON_REDIS_HOST}" -p "${CLOUDRON_REDIS_PORT}" -a "${CLOUDRON_REDIS_PASSWORD}"';
            }
        } else if (addon === 'scheduler' && extra) {
            cmd = extra.command;
        }

        if (!cmd) return;

        cmd += ' ';

        $scope.terminalSocket.send(cmd);
        $scope.terminal.focus();
    };

    // terminal right click handling
    $scope.terminalClear = function () {
        if (!$scope.terminal) return;
        $scope.terminal.clear();
        $scope.terminal.focus();
    };

    $scope.terminalCopy = function () {
        if (!$scope.terminal) return;

        // execCommand('copy') would copy any selection from the page, so do this only if terminal has a selection
        if (!$scope.terminal.getSelection()) return;

        document.execCommand('copy');
        $scope.terminal.focus();
    };

    $('.contextMenuBackdrop').on('click', function () {
        $('#terminalContextMenu').hide();
        $('.contextMenuBackdrop').hide();

        $scope.terminal.focus();
    });

    $('#terminalContainer').on('contextmenu', function (e) {
        if (!$scope.terminal) return true;

        e.preventDefault();

        $('.contextMenuBackdrop').show();
        $('#terminalContextMenu').css({
            display: 'block',
            left: e.pageX,
            top: e.pageY
        });

        return false;
    });

    window.addEventListener('resize', function () {
        if ($scope.fitAddon) $scope.fitAddon.fit();
    });

    Client.getStatus(function (error, status) {
        if (error) return $scope.error(error);

        if (!status.activated) {
            console.log('Not activated yet, closing or redirecting', status);
            window.close();
            window.location.href = '/';
            return;
        }

        // check version and force reload if needed
        if (!localStorage.version) {
            localStorage.version = status.version;
        } else if (localStorage.version !== status.version) {
            localStorage.version = status.version;
            window.location.reload(true);
        }

        console.log('Running terminal version ', localStorage.version);

        // get user profile as the first thing. this populates the "scope" and affects subsequent API calls
        Client.refreshUserInfo(function (error) {
            if (error) return $scope.error(error);

            Client.refreshConfig(function (error) {
                if (error) return $scope.error(error);

                refreshApp(search.id, function (error, app) {
                    $scope.selected = {
                        type: 'app',
                        value: app.id,
                        name: app.fqdn + ' (' + app.manifest.title + ')',
                        addons: app.manifest.addons,
                        manifest: app.manifest
                    };

                    // now mark the Client to be ready
                    Client.setReady();

                    $scope.initialized = true;

                    showTerminal();
                });
            });
        });
    });

    $translate([ 'terminal.title' ]).then(function (tr) {
        if (tr['terminal.title'] !== 'terminal.title') window.document.title = tr['terminal.title'];
    });

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