"use strict"
// Copyright (c) Alex Ellis 2017. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

var app = angular.module('faasGateway', ['ngMaterial', 'ngMessages', 'faasGateway.funcStore']);

app.controller("home", ['$scope', '$log', '$http', '$location', '$interval', '$filter', '$mdDialog', '$mdToast', '$mdSidenav',
    function($scope, $log, $http, $location, $interval, $filter, $mdDialog, $mdToast, $mdSidenav) {
        var FUNCSTORE_DEPLOY_TAB_INDEX = 0;
        var CUSTOM_DEPLOY_TAB_INDEX = 1;

        var newFuncTabIdx = FUNCSTORE_DEPLOY_TAB_INDEX;
        $scope.functions = [];
        $scope.invocationInProgress = false;
        $scope.invocationRequest = "";
        $scope.invocationResponse = "";
        $scope.invocationStatus = "";
        $scope.invocationStart = new Date().getTime();
        $scope.roundTripDuration = "";
        $scope.selectedNamespace = "";
        $scope.allNamespaces = [""];
        $scope.invocation = {
            contentType: "text"
        };

        $scope.baseUrl = $location.absUrl().replace(/\ui\/$/, '');

        try {
            $scope.canCopyToClipboard = document.queryCommandSupported('copy');
        } catch (err) {
            console.error(err);
            $scope.canCopyToClipboard = false;
        }
        $scope.copyClicked = function(e) {
            e.target.parentElement.querySelector('input').select()
            var copySuccessful = false;
            try {
                copySuccessful = document.execCommand('copy');
            } catch (err) {
                console.error(err);
            }
            var msg = copySuccessful ? 'Copied to Clipboard' : 'Copy failed. Please copy it manually';
            showPostInvokedToast(msg);
        }



        $scope.toggleSideNav = function() {
            $mdSidenav('left').toggle();
        };

        $scope.functionTemplate = {
            image: "",
            envProcess: "",
            network: "",
            service: "",
            envVars: {},
            labels: {},
            annotations: {},
            secrets: [],
            namespace: "",
        };

        $scope.invocation.request = "";

        var fetchFunctionsDelay = 3500;
        var queryFunctionDelay = 2500;




        $http.get("../system/namespaces")
            .then(function(response) {
                $scope.allNamespaces = response.data;
                if ($scope.selectedNamespace === "" && response.data && response.data[0] && response.data[0] !== "") {
                    $scope.selectedNamespace = response.data[0]
                    refreshData($scope.selectedNamespace)
                }
            })


        var fetchFunctionsInterval = $interval(function() {
            refreshData($scope.selectedNamespace);
        }, fetchFunctionsDelay);

        var queryFunctionInterval = $interval(function() {
            if($scope.selectedFunction && $scope.selectedFunction.name) {
                refreshFunction($scope.selectedFunction);
            }
        }, queryFunctionDelay);

        $scope.setNamespace = function(namespace) {
            $scope.selectedNamespace = namespace;
            $scope.selectedFunction = undefined;
            $scope.functions = [];
            refreshData($scope.selectedNamespace)
        }

        var refreshFunction = function(functionInstance) {
            const url = "/system/function/" + functionInstance.name;
            $http.get(buildNamespaceAwareURL(url, $scope.selectedNamespace))
            .then(function(response) {
               $scope.selectedFunction.ready = (response.data && response.data.availableReplicas && response.data.availableReplicas > 0);
            })
            .catch(function(err) {
                console.error(err);
            });
        };

        var showPostInvokedToast = function(message, duration) {
            $mdToast.show(
                $mdToast.simple()
                .textContent(message)
                .position("top right")
                .hideDelay(duration || 500)
            );
        };

        $scope.fireRequest = function() {
            var requestContentType = $scope.invocation.contentType == "json" ? "application/json" : "text/plain";
            if ($scope.invocation.contentType == "binary") {
                requestContentType = "binary/octet-stream";
            }

            var fnNamespace = ($scope.selectedNamespace && $scope.selectedNamespace.length > 0) ? "." + $scope.selectedNamespace : "";
            var options = {
                url: "../function/" + $scope.selectedFunction.name + fnNamespace,
                data: $scope.invocation.request,
                method: "POST",
                headers: { "Content-Type": requestContentType },
                responseType: $scope.invocation.contentType == "binary" ? "arraybuffer" : $scope.invocation.contentType
            };

            $scope.invocationInProgress = true;
            $scope.invocationResponse = "";
            $scope.invocationStatus = null;
            $scope.roundTripDuration = "";
            $scope.invocationStart = new Date().getTime()


            var tryDownload = function(data, filename) {
                var caught;

                try {
                    var blob = new Blob([data], { type: "binary/octet-stream" });

                    if (window.navigator.msSaveBlob) { // // IE hack; see http://msdn.microsoft.com/en-us/library/ie/hh779016.aspx
                        window.navigator.msSaveOrOpenBlob(blob, filename);
                    }
                    else {
                        var linkElement = window.document.createElement("a");
                        linkElement.href = window.URL.createObjectURL(blob);
                        linkElement.download = filename;
                        document.body.appendChild(linkElement);
                        linkElement.click();
                        document.body.removeChild(linkElement);
                    }

                } catch (ex) {
                    caught = ex;
                }
                return caught;
            }

            $http(options)
                .then(function (response) {
                    var data = response.data;
                    var status = response.status;

                    if($scope.invocation.contentType == "binary") {
                        var filename = uuidv4();

                        if($scope.selectedFunction.labels) {
                            var ext = $scope.selectedFunction.labels["com.openfaas.ui.ext"];
                            if(ext && ext.length > 0 ) {
                                filename = filename + "." + ext;
                            }
                        }

                        var caught = tryDownload(data, filename);
                        if(caught) {
                            console.log(caught);
                            $scope.invocationResponse = caught
                        } else {
                            $scope.invocationResponse = data.byteLength + " byte(s) received";
                        }

                    } else {

                        if (typeof data == 'object') {
                            $scope.invocationResponse = JSON.stringify(data, null, 2);
                        } else {
                            $scope.invocationResponse = data;
                        }
                    }

                    $scope.invocationInProgress = false;
                    $scope.invocationStatus = status;
                    var now = new Date().getTime();
                    $scope.roundTripDuration = (now - $scope.invocationStart) / 1000;
                    showPostInvokedToast("Success");

                }).catch(function(error1) {
                    $scope.invocationInProgress = false;
                    $scope.invocationResponse = error1.statusText + "\n" + error1.data;
                    $scope.invocationStatus = error1.status;
                    var now = new Date().getTime();
                    $scope.roundTripDuration = (now - $scope.invocationStart) / 1000;

                    showPostInvokedToast("Error");
                });
        };

        var refreshData = function (selectedNamespace) {
            $http.get(buildNamespaceAwareURL("/system/functions", selectedNamespace)).then(function (response) {
                const curNamespace = ($scope.functions.length > 0 && $scope.functions[0].namespace && $scope.functions[0].namespace) ? $scope.functions[0].namespace : "";
                const  newNamespace = (response.data && response.data[0] && response.data[0].namespace) ? response.data[0].namespace : "";

                if (response && response.data && (curNamespace !== newNamespace || $scope.functions.length != response.data.length)) {
                  $scope.functions = response.data;
                  if (!$scope.functions.indexOf($scope.selectedFunction )) {
                      $scope.selectedFunction = undefined;
                  }
                }

                if ($scope.selectedFunction) {
                    response.data.some(function(entry) {
                        if (entry.name === $scope.selectedFunction.name) {
                            $scope.selectedFunction.invocationCount = entry.invocationCount
                            return true
                        }
                    });
                }

            });
        };

        $scope.showFunction = function(fn) {
            if ($scope.selectedFunction != fn) {
                $scope.selectedFunction = fn;
                $scope.invocation.request = "";
                $scope.invocationResponse = "";
                $scope.invocationStatus = "";
                $scope.invocationInProgress = false;
                if (fn.labels && fn.labels['com.openfaas.ui.ext']) {
                  $scope.invocation.contentType = "binary";
                } else {
                  $scope.invocation.contentType = "text";
                }
                $scope.invocation.roundTripDuration = "";
            }
        };

        var showDialog = function($event) {
            var parentEl = angular.element(document.body);
            $mdDialog.show({
                parent: parentEl,
                targetEvent: $event,
                templateUrl: "templates/newfunction.html",
                locals: {
                    item: $scope.functionTemplate,
                    selectedNamespace: $scope.selectedNamespace
                },
                controller: DialogController,
            });
        };

        var DialogController = function($scope, $mdDialog, item, selectedNamespace) {
            var fetchNamespaces = function () {
                $http.get("../system/namespaces")
                    .then(function(response) {
                        $scope.allNamespaces = response.data;
                    })
            }
            $scope.selectedTabIdx = newFuncTabIdx;
            $scope.item = {};
            $scope.selectedFunc = null;
            $scope.envFieldsVisible = false;
            $scope.envVarInputs = [{key: "", value: ""}];

            $scope.secretFieldsVisible = false;
            $scope.secretInputs = [{name: ""}];

            $scope.labelFieldsVisible = false;
            $scope.labelInputs = [{key: "", value: ""}];

            $scope.annotationFieldsVisible = false;
            $scope.annotationInputs = [{key: "", value: ""}];
            $scope.namespaceSelect = selectedNamespace;
            fetchNamespaces();


            $scope.closeDialog = function() {
                $mdDialog.hide();
            };

            $scope.onFuncSelected = function(func) {
                $scope.item.image = func.image;
                $scope.item.service = func.name;
                $scope.item.envProcess = func.fprocess;
                $scope.item.network = func.network;
                $scope.envVarsToEnvVarInputs(func.environment);
                $scope.labelsToLabelInputs(func.labels);
                $scope.annotationsToAnnotationInputs(func.annotations);
                $scope.secretsToSecretInputs(func.secrets);
                $scope.item.namespace = $scope.namespaceSelected();


                $scope.selectedFunc = func;
            }

            $scope.onTabSelect = function(idx) {
                newFuncTabIdx = idx;
            }

            $scope.onStoreTabDeselect = function() {
                $scope.selectedFunc = null;
            }

            $scope.onCustomTabDeselect = function() {
                $scope.item = {};
            }

            $scope.createFunc = function() {
                $scope.item.envVars = $scope.envVarInputsToEnvVars();
                $scope.item.secrets = $scope.secretInputsToSecrets();
                $scope.item.labels = $scope.labelInputsToLabels();
                $scope.item.annotations = $scope.annotationInputsToAnnotations();
                $scope.item.namespace = $scope.namespaceSelected();

                var options = {
                    url: "../system/functions",
                    data: $scope.item,
                    method: "POST",
                    headers: { "Content-Type": "application/json" },
                    responseType: "text"
                };

                $http(options)
                    .then(function(response) {
                        item.image = "";
                        item.service = "";
                        item.envProcess = "";
                        item.network = "";
                        item.envVars = {};
                        item.labels = {};
                        item.annotations = {};
                        item.secrets = [];
                        item.namespace = "";

                        $scope.validationError = "";
                        $scope.closeDialog();
                        showPostInvokedToast("Function created");
                    }).catch(function(error1) {
                        showPostInvokedToast("Error");
                        $scope.selectedTabIdx = CUSTOM_DEPLOY_TAB_INDEX;
                        $scope.validationError = error1.data;
                    });
            };

            $scope.fnNamespaceSelected = function(inputNamespace) {
                $scope.namespaceSelect = inputNamespace;
            }

            $scope.onEnvInputExpand = function() {
                $scope.envFieldsVisible = !$scope.envFieldsVisible;
            }

            $scope.addEnvVar = function(index) {
                var newInput = {key: "", value: ""};
                $scope.envVarInputs.splice(index + 1, 0, newInput);
            }

            $scope.removeEnvVar = function($event, envVar) {
                var index = $scope.envVarInputs.indexOf(envVar);
                if ($event.which == 1) {
                    $scope.envVarInputs.splice(index, 1);
                }
            }

            $scope.onSecretInputExpand = function() {
                $scope.secretFieldsVisible = !$scope.secretFieldsVisible;
            }

            $scope.addSecret = function(index) {
                var newInput = {name: ""};
                $scope.secretInputs.splice(index + 1, 0, newInput);
            }

            $scope.removeSecret = function($event, secret) {
                var index = $scope.secretInputs.indexOf(secret);
                if ($event.which == 1) {
                    $scope.secretInputs.splice(index, 1);
                }
            }

            $scope.onLabelInputExpand = function() {
                $scope.labelFieldsVisible = !$scope.labelFieldsVisible;
            }

            $scope.addLabel = function(index) {
                var newInput = {key: "", value: ""};
                $scope.labelInputs.splice(index + 1, 0, newInput);
            }

            $scope.removeLabel = function($event, label) {
                var index = $scope.labelInputs.indexOf(label);
                if ($event.which == 1) {
                    $scope.labelInputs.splice(index, 1);
                }
            }

            $scope.onAnnotationInputExpand = function() {
                $scope.annotationFieldsVisible = !$scope.annotationFieldsVisible;
            }

            $scope.addAnnotation = function(index) {
                var newInput = {key: "", value: ""};
                $scope.annotationInputs.splice(index + 1, 0, newInput);
            }

            $scope.removeAnnotation = function($event, annotation) {
                var index = $scope.annotationInputs.indexOf(annotation);
                if ($event.which == 1) {
                    $scope.annotationInputs.splice(index, 1);
                }
            }

            $scope.envVarInputsToEnvVars = function() {
                var self = this;
                var result = {};
                for(var i = 0; i < self.envVarInputs.length; i++) {
                    if (self.envVarInputs[i].key && self.envVarInputs[i].value) {
                        result[self.envVarInputs[i].key] = self.envVarInputs[i].value;
                    }
                }

                return result;
            }

            $scope.envVarsToEnvVarInputs = function(envVars) {
                var result = [];
                for (var e in envVars) {
                    result.push({key: e, value: envVars[e]});
                }

                if (result.length > 0) {
                    // make the fields visible if deploying from store with values
                    $scope.envFieldsVisible = true;
                } else {
                    result.push({key: "", value: ""});
                    $scope.envFieldsVisible = false;
                }

                $scope.envVarInputs = result;
            }

            $scope.secretInputsToSecrets = function() {
                var self = this;
                var result = [];
                for(var i = 0; i < self.secretInputs.length; i++) {
                    if (self.secretInputs[i].name) {
                        result.push(self.secretInputs[i].name);
                    }
                }

                return result;
            }

            $scope.secretsToSecretInputs = function(secrets) {
                var result = [];
                for (var secret in secrets) {
                    result.push({name: secret});
                }

                if (result.length > 0) {
                    // make the fields visible if deploying from store with values
                    $scope.secretFieldsVisible = true;
                } else {
                    result.push({name: ""});
                    $scope.secretFieldsVisible = false;
                }

                $scope.secretInputs = result;
            }

            $scope.labelInputsToLabels = function() {
                var self = this;
                var result = {};
                for(var i = 0; i < self.labelInputs.length; i++) {
                    if (self.labelInputs[i].key && self.labelInputs[i].value) {
                        result[self.labelInputs[i].key] = self.labelInputs[i].value;
                    }
                }

                return result;
            }

            $scope.labelsToLabelInputs = function(labels) {
                var result = [];
                for (var l in labels) {
                    result.push({key: l, value: labels[l]});
                }

                if (result.length > 0) {
                    // make the fields visible if deploying from store with values
                    $scope.labelFieldsVisible = true;
                } else {
                    result.push({key: "", value: ""});
                    $scope.labelFieldsVisible = false;
                }

                $scope.labelInputs = result;
            }

            $scope.annotationInputsToAnnotations = function() {
                var self = this;
                var result = {};
                for(var i = 0; i < self.annotationInputs.length; i++) {
                    if (self.annotationInputs[i].key && self.annotationInputs[i].value) {
                        result[self.annotationInputs[i].key] = self.annotationInputs[i].value;
                    }
                }

                return result;
            }

            $scope.namespaceSelected = function() {
                var self = this;
                return self.namespaceSelect;
            }

            $scope.annotationsToAnnotationInputs = function(annotations) {
                var result = [];
                for (var a in annotations) {
                    result.push({key: a, value: annotations[a]});
                }

                if (result.length > 0) {
                    // make the fields visible if deploying from store with values
                    $scope.annotationFieldsVisible = true;
                } else {
                    result.push({key: "", value: ""});
                    $scope.annotationFieldsVisible = false;
                }

                $scope.annotationInputs = result;
            }
        };

        $scope.newFunction = function() {
            showDialog();
        };

        $scope.deleteFunction = function($event) {
            var confirm = $mdDialog.confirm()
                .title('Delete Function')
                .textContent('Are you sure you want to delete ' + $scope.selectedFunction.name + '?')
                .ariaLabel('Delete function')
                .targetEvent($event)
                .ok('OK')
                .cancel('Cancel');

            $mdDialog.show(confirm)
                .then(function() {
                    var options = {

                        url: buildNamespaceAwareURL("/system/functions", $scope.selectedNamespace),
                        data: {
                            functionName: $scope.selectedFunction.name
                        },
                        method: "DELETE",
                        headers: { "Content-Type": "application/json" },
                        responseType: "json"
                    };

                    return $http(options);
                }).then(function() {
                    showPostInvokedToast("Success");
                }).catch(function(err) {
                    if (err) {
                        // show error toast only if there actually is an err.
                        // because hitting 'Cancel' also rejects the promise.
                        showPostInvokedToast("Error");
                    }
                });
        };
    }
]);

function uuidv4() {
    var cryptoInstance = window.crypto || window.msCrypto; // for IE11
    return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, function(c) {
        return (c ^ cryptoInstance.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
    })
}

function buildNamespaceAwareURL(path, namespace) {
    let newUrl = path.startsWith("/")? ".." + path: "../" + path;

    if (namespace && namespace.length > 0){
        newUrl +=  "?namespace=" +  namespace
    }
    return newUrl
}