source : toolCtrl.js

(function (angular) {
    'use strict';
    /**
     * @memberof spApp
     * @ngdoc controller
     * @name ToolCtrl
     * @description
     *   Display and run a client side or spatial-service tool.
     */
    angular.module('tool-ctrl', ['map-service', 'biocache-service', 'layers-service'])
        .controller('ToolCtrl', ['$scope', 'MapService', '$timeout', 'LayoutService', '$uibModalInstance',
            'BiocacheService', '$http', 'LayersService', 'data', 'LoggerService', 'ToolsService',
            function ($scope, MapService, $timeout, LayoutService, $uibModalInstance, BiocacheService, $http,
                      LayersService, inputData, LoggerService, ToolsService) {
                $scope.stepNames = [$i18n(378, "select process")];
                $scope.values = [];
                LayoutService.addToSave($scope);
                $scope.stage = inputData && inputData.stage || 'input';
                $scope.taskId = inputData && inputData.taskId;
                $scope.downloadImmediately = !(inputData && inputData.downloadImmediately !== undefined && !inputData.downloadImmediately);
                $scope.status = '';
                $scope.statusRunning = false;
                $scope.spec = null;
                $scope.cancelled = false;
                $scope.continous = true;
                $scope.injectDateRange = false;
                // mandatory to provide inputData.processName
                $scope.toolName = inputData !== undefined ? inputData.processName : '';
                $scope.init = function () {
                    ToolsService.init($scope.toolName).then(function (specList) {
                        // overrideValues is set from Quick links or elsewhere
                        var k = $scope.toolName;
                        if (inputData && inputData.overrideValues && inputData.overrideValues[k]) {
                            if (inputData.overrideValues[k].input && $.isArray(specList[k].input)) {
                                // tool*Service .input is an array not a map.
                                var input = inputData.overrideValues[k].input;
                                delete inputData.overrideValues[k].input;
                                $scope.spec = angular.merge({}, specList[k], inputData.overrideValues[k]);
                                // merge input
                                if (input) {
                                    for (i in $scope.spec.input) {
                                        var name = $scope.spec.input[i].name;
                                        if (input[name]) {
                                            $scope.spec.input[i] = angular.merge($scope.spec.input[i], input[name])
                                        }
                                        // needed when 'name' is numeric
                                        if (input[i]) {
                                            $scope.spec.input[i] = angular.merge($scope.spec.input[i], input[i])
                                        }
                                    }
                                }
                            } else {
                                $scope.spec = angular.merge({}, specList[k], inputData.overrideValues[k])
                            }
                            //apply override step
                            if (inputData.overrideValues.stage !== undefined) {
                                $scope.stage = inputData.overrideValues.stage
                            }
                        } else {
                            $scope.spec = angular.merge({}, specList[k])
                        }
                        $scope.initSpec();
                        $scope.initValues();
                        $scope.buildViews();
                        if ($scope.stage === 'edit') {
                            inputData.newValue = $scope.getInputs()
                        } else if ($scope.stage === 'execute') {
                            $scope.finish();
                        } else if ($scope.stage === 'output') {
                            $scope.statusUrl = $SH.layersServiceUrl + '/tasks/status/' + $scope.taskId;
                            ToolsService.checkStatus($scope)
                        }
                    });
                };
                $scope.buildViews = function () {
                    //build stepView
                    $scope.stepView = {};
                    var order = 1;
                    // if View-config.json is configured for the selected capability, use that, otherwise, use spec
                    // TODO: can this be separated from downloadImmediately and overrideValues, and moved to ToolsService?
                    var toolViewConfig = ToolsService.getViewConfig($scope.toolName)
                    if (toolViewConfig && toolViewConfig.view) {
                        toolViewConfig.view.forEach(function (v) {
                            $scope.stepView[order] = {name: v.name, inputArr: v.inputs};
                            order++;
                        })
                    } else {
                        for (var i in $scope.spec.input) {
                            if ($scope.spec.input.hasOwnProperty(i)) {
                                if ($scope.spec.input[i].type !== "auto") {
                                    var view = [i];
                                    $scope.stepView[order] = {
                                        name: $scope.spec.input[i].description,
                                        inputArr: view
                                    };
                                    order++;
                                }
                            }
                        }
                    }
                };
                $scope.initSpec = function () {
                    var c = $scope.spec.input;
                    var k;
                    var value;
                    for (k in c) {
                        if (c.hasOwnProperty(k)) {
                            value = c[k];
                            if (value.constraints === undefined) value.constraints = {};
                            var v;
                            if (value.type === 'area') {
                                if (value.constraints['defaultAreas'] === undefined) value.constraints['defaultAreas'] = true;
                                if (value.constraints['defaultToWorld'] === undefined) value.constraints['defaultToWorld'] = true;
                                if (value.constraints['max'] === undefined) value.constraints['max'] = 1000;
                            } else if (value.type === 'species') {
                                if (value.constraints['areaIncludes'] === undefined) value.constraints['areaIncludes'] = false;
                                if (value.constraints['spatialValidity'] === undefined) value.constraints['spatialValidity'] = true;
                                if (value.constraints['absentOption'] === undefined) value.constraints['absentOption'] = true;
                                if (value.constraints['canAddSpecies'] === undefined) value.constraints['canAddSpecies'] = false;
                                if (value.constraints['dateRangeOption'] === undefined) value.constraints['dateRangeOption'] = true;
                                if (value.constraints['lifeforms'] === undefined) value.constraints['lifeforms'] = true;
                                if (value.constraints['importList'] === undefined) value.constraints['importList'] = true;
                                if (value.constraints['importPoints'] === undefined) value.constraints['importPoints'] = true;
                                if (value.constraints['searchSpecies'] === undefined) value.constraints['searchSpecies'] = true;
                                if (value.constraints['allSpecies'] === undefined) value.constraints['allSpecies'] = true;
                                // } else if (value.type === 'date') {
                                // } else if (value.type === 'layer') {
                                // } else if (value.type === 'boolean') {
                                // } else if (value.type === 'int') {
                                // } else if (value.type === 'double') {
                            } else if (value.type === 'list') {
                                // convert to array of labels and values
                                value.constraints._list = [];
                                if (value.constraints.labels === undefined) {
                                    value.constraints.labels = value.constraints.content;
                                }
                                for (var i in value.constraints.content) {
                                    var label = value.constraints.content[i]
                                    if (value.constraints.labels) {
                                        label = value.constraints.labels[i]
                                    }
                                    value.constraints._list.push({
                                        value: value.constraints.content[i],
                                        label: label,
                                        selected: false
                                    })
                                }
                                // } else if (value.type === 'phylogeneticTree') {
                                // } else if (value.type === 'text') {
                            } else if (value.type === 'speciesOptions') {
                                if (value.constraints['areaIncludes'] === undefined) value.constraints['areaIncludes'] = false;
                                if (value.constraints['kosherIncludes'] === undefined) value.constraints['kosherIncludes'] = true;
                                if (value.constraints['endemicIncludes'] === undefined) value.constraints['endemicIncludes'] = false;
                                // } else if (value.type === 'facet') {
                                // } else {
                            }
                        }
                    }
                }
                $scope.initValues = function () {
                    //no need for initValues when $scope.values is populated from LayoutService.addToSave
                    if ($scope.values.length > 0) return;
                    if (inputData.values) {
                        $scope.values = inputData.values;
                        return;
                    }
                    //check for previously entered value in LayoutService
                    $scope.values = LayoutService.getValue($scope.componentName, 'values', $scope.values);
                    //defaults
                    var c = $scope.spec.input;
                    var k;
                    var value;
                    if ($scope.injectDateRange) {
                        for (k in c) {
                            if (c.hasOwnProperty(k)) {
                                value = c[k];
                                if (value.type === 'species') {
                                    c.splice(parseInt(k) + 1, 0, {
                                        type: "date",
                                        description: "Select date range",
                                        name: '_date'
                                    })
                                }
                            }
                        }
                    }
                    for (k in c) {
                        if (c.hasOwnProperty(k)) {
                            value = c[k];
                            if (value.constraints === undefined) value.constraints = {};
                            var v;
                            if (value.type === 'area') {
                                if (value.constraints['defaultAreas'] === undefined) value.constraints['defaultAreas'] = true;
                                if (value.constraints['defaultToWorld'] === undefined) value.constraints['defaultToWorld'] = true;
                                if (value.constraints['max'] === undefined) value.constraints['max'] = 1000;
                                if (value.constraints['default'] !== undefined) {
                                    // getInputs returns the .area array which is inconsistent with the value
                                    if (value.constraints['default'] instanceof Array) {
                                        v = {area: value.constraints['default']}
                                    } else {
                                        v = value.constraints['default']
                                    }
                                } else {
                                    v = {area: []}
                                }
                            } else if (value.type === 'species') {
                                if (value.constraints['areaIncludes'] === undefined) value.constraints['areaIncludes'] = false;
                                if (value.constraints['spatialValidity'] === undefined) value.constraints['spatialValidity'] = true;
                                if (value.constraints['absentOption'] === undefined) value.constraints['absentOption'] = true;
                                if (value.constraints['canAddSpecies'] === undefined) value.constraints['canAddSpecies'] = false;
                                if (value.constraints['dateRangeOption'] === undefined) value.constraints['dateRangeOption'] = true;
                                if (value.constraints['lifeforms'] === undefined) value.constraints['lifeforms'] = true;
                                if (value.constraints['importList'] === undefined) value.constraints['importList'] = true;
                                if (value.constraints['importPoints'] === undefined) value.constraints['importPoints'] = true;
                                if (value.constraints['searchSpecies'] === undefined) value.constraints['searchSpecies'] = true;
                                if (value.constraints['allSpecies'] === undefined) value.constraints['allSpecies'] = true;
                                if (value.constraints['default'] !== undefined) v = value.constraints['default'];
                                else if (value.constraints['speciesOption'] === 'allSpecies') {
                                    //specify allSpecies default
                                    v = {
                                        q: ["*:*"],
                                        name: 'All species',
                                        bs: $SH.biocacheServiceUrl,
                                        ws: $SH.biocacheUrl
                                    }
                                }
                                else v = {q: [], name: '', bs: '', ws: ''};
                                // use array as the species value when constraints.max>1
                                if (value.constraints['max'] > 1) {
                                    v = []
                                }
                            } else if (value.type === 'date') {
                                v = {fq: []}
                            } else if (value.type === 'layer') {
                                if (value.constraints['default'] !== undefined) {
                                    // getInputs returns the .area array which is inconsistent with the value
                                    if (value.constraints['default'] instanceof Array) {
                                        v = {layers: []};
                                        $.map(value.constraints['default'], function (layer) {
                                            v.layers.push(LayersService.getLayer(layer))
                                        })
                                    } else {
                                        v = LayersService.getLayer(value.constraints['default']);
                                    }
                                }
                                else v = {layers: []}
                            } else if (value.type === 'boolean') {
                                v = value.constraints['default']
                            } else if (value.type === 'int') {
                                v = value.constraints['default']
                            } else if (value.type === 'double') {
                                v = value.constraints['default']
                            } else if (value.type === 'list') {
                                if (value.constraints.selection !== 'single') {
                                    v = value.constraints['default'];
                                    if (v == undefined) {
                                        v = [];
                                    }
                                } else {
                                    v = value.constraints['default'];
                                }
                            } else if (value.type === 'phylogeneticTree') {
                                if (value.constraints['default'] !== undefined) v = value.constraints['default'];
                                else v = []
                            } else if (value.type === 'text') {
                                v = value.constraints['default']
                            } else if (value.type === 'speciesOptions') {
                                if (value.constraints['areaIncludes'] === undefined) value.constraints['areaIncludes'] = false;
                                if (value.constraints['kosherIncludes'] === undefined) value.constraints['kosherIncludes'] = true;
                                if (value.constraints['endemicIncludes'] === undefined) value.constraints['endemicIncludes'] = false;
                                if (value.constraints['default'] !== undefined) v = value.constraints['default'];
                                else v = {}
                            } else if (value.type === 'facet') {
                                if (value.constraints['default'] !== undefined) v = value.constraints['default'];
                                else v = []
                            } else if (value.constraints !== undefined && value.constraints['default'] !== undefined) {
                                v = value.constraints['default']
                            } else {
                                v = null
                            }
                            if ($scope.values[k] === undefined) {
                                $scope.values[k] = v;
                            }
                        }
                    }
                };
                $scope.finish = function () {
                    if ($scope.stage === 'edit') {
                        inputData.newValue = $scope.getInputs()
                        $scope.close()
                    } else {
                        $scope.stage = 'execute';
                        var response = $scope.execute();
                    }
                };
                $scope.finished = false;
                $scope.finishedData = {};
                $scope.downloadUrl = null;
                $scope.metadataUrl = null;
                $scope.log = {};
                $scope.logText = '';
                $scope.last = 0;
                $scope.checkStatusTimeout = null;
                $scope.getInputChecks = function (i) {
                    var value = $scope.spec.input[i];
                    if (value.constraints === undefined) value.constraints = {};
                    if (value.constraints.optional) {
                        return false
                    } else if (value.type === 'area') {
                        return $scope.values[i].area.length === 0
                    } else if (value.type === 'species') {
                        if ($scope.values[i] instanceof Array) {
                            return !(value.constraints.min === 0 || $scope.values[i].length !== 0);
                        } else {
                            return !(value.constraints.min === 0 || $scope.values[i].q.length !== 0);
                        }
                    } else if (value.type === 'layer') {
                        return $scope.values[i].layers.length < value.constraints.min || $scope.values[i].layers.length > value.constraints.max
                    } else if (value.type === 'boolean') {
                        return false
                    } else if (value.type === 'int') {
                        return $scope.values[i] < value.constraints.min || $scope.values[i] > value.constraints.max
                    } else if (value.type === 'double') {
                        return $scope.values[i] < value.constraints.min || $scope.values[i] > value.constraints.max
                    } else if (value.type === 'list' && value.constraints.selection === 'single') {
                        return $scope.values[i] === undefined
                    } else if (value.type === 'list' && value.constraints.selection !== 'single') {
                        return $scope.values[i].length === 0
                    } else if (value.type === 'phylogeneticTree') {
                        return $scope.values[i].length === 0
                    } else if (value.type === 'text') {
                        return $scope.values[i] < value.constraints.min || $scope.values[i] > value.constraints.max
                    } else if (value.type === 'speciesOptions') {
                        return false
                    } else if (value.type === 'facet') {
                        return $scope.values[i].length === 0
                    } else if (value.type === 'annotation') {
                        var value = $scope.values[i];
                        return !value || value.invalid();
                    } else {
                        return false
                    }
                };
                $scope.isDisabled = function () {
                    var iList;
                    var i;
                    var inputs = $scope.getInputs();
                    ToolsService.refresh($scope, $scope.toolName, inputs);
                    if ($scope.stage === '') {
                        return $scope.toolName.length === 0
                    } else if ($scope.stage === 'execute') {
                        return !$scope.finished
                    } else {
                        //defaults
                        if ($scope.continous) {
                            for (var sv in $scope.stepView) {
                                //Get the input list from step view
                                iList = $scope.stepView[sv].inputArr;
                                for (i in iList) {
                                    if (iList.hasOwnProperty(i)) {
                                        if ($scope.getInputChecks(iList[i])) {
                                            return true;
                                        }
                                    }
                                }
                            }
                        } else {
                            //Get the input list from step view
                            iList = $scope.stepView[$scope.step].inputArr;
                            for (i in iList) {
                                if (iList.hasOwnProperty(i)) {
                                    if ($scope.getInputChecks(iList[i])) {
                                        return true;
                                    }
                                }
                            }
                        }
                        return false;
                    }
                };
                $scope.close = function () {
                    $scope.cancelled = true;
                    $scope.$close();
                };
                $scope.execute = function () {
                    // save control state for retrying after http errors
                    LayoutService.saveValues();
                    $scope.status = 'starting...';
                    $scope.statusRunning = true;
                    //format inputs
                    var inputs = $scope.getInputs();
                    return ToolsService.execute($scope, $scope.toolName, inputs);
                };
                $scope.getInputs = function () {
                    var c = $scope.spec.input;
                    var inputs = {};
                    var k;
                    var j;
                    var ignored = 0;    // array 'inputs' ('c') and 'scope.values' are not always aligned
                    var kvalue
                    for (kvalue in c) {
                        var k = kvalue
                        // include array offset when c is an array
                        if (!isNaN(kvalue) && $.isArray(c)) {
                            k = parseInt(kvalue) + ignored
                        }
                        if (c.hasOwnProperty(kvalue)) {
                            if ($scope.values[k] !== undefined && $scope.values[k] !== null) {
                                if (c[kvalue].type == 'list' && c[kvalue].constraints.selection !== 'single') {
                                    inputs[kvalue] = []
                                    var list = c[kvalue].constraints._list
                                    for (var idx in list) {
                                        if (list[idx].selected) {
                                            inputs[kvalue].push(list[idx].value)
                                        }
                                    }
                                } else if ($scope.values[k].area !== undefined) {
                                    inputs[kvalue] = [];
                                    for (j in $scope.values[k].area) {
                                        if ($scope.values[k].area.hasOwnProperty(j)) {
                                            var a = $scope.values[k].area[j];
                                            var b = a.bbox;
                                            if ((a.bbox + '').match(/^POLYGON/g) != null) {
                                                //convert POLYGON box to bounds
                                                var split = a.bbox.split(',');
                                                var p1 = split[1].split(' ');
                                                var p2 = split[3].split(' ');
                                                b = [Math.min(p1[0], p2[0]), Math.min(p1[1], p2[1]), Math.max(p1[0], p2[0]), Math.max(p1[1], p2[1])];
                                            }
                                            inputs[kvalue].push({
                                                q: a.q ? a.q : "",
                                                name: a.name,
                                                bbox: b,
                                                area_km: a.area_km,
                                                pid: a.pid,
                                                wkt: a.wkt
                                            })
                                        }
                                    }
                                } else if ($scope.values[k].q !== undefined) {
                                    inputs[kvalue] = $.extend({}, $scope.values[k]);
                                    // additional date range fq
                                    if ($scope.injectDateRange) {
                                        inputs[kvalue].q = inputs[kvalue].q.concat($scope.values[k + 1].fq)
                                        ignored = ignored + 1
                                    }
                                } else if ($scope.values[k].layers !== undefined) {
                                    var layers = [];
                                    for (j in $scope.values[k].layers) {
                                        if ($scope.values[k].layers.hasOwnProperty(j)) {
                                            layers.push($scope.values[k].layers[j].id)
                                        }
                                    }
                                    inputs[kvalue] = layers
                                } else {
                                    inputs[kvalue] = $scope.values[k]
                                }
                            }
                        }
                    }
                    return inputs
                };
                $scope.openUrl = function (url) {
                    if (url.indexOf($SH.layersServiceUrl) != 0) {
                        // Always open in a new window when not from spatial-service
                        Util.download(url);
                    } else {
                        // open in an iframe
                        LayoutService.openIframe(url, false);
                    }
                };
                $scope.init();
                $scope.isLocalTask = function () {
                    return ToolsService.isLocalTask($scope.toolName)
                }
            }])
}(angular));