// $Id: controller.js 32761 2015-11-02 11:50:21Z schaap $

var gridControllerMod = angular.module('GridControllerMod', ['ui.grid',
                                                             'ui.grid.edit',
                                                             'ui.grid.selection',
                                                             'ui.grid.resizeColumns',
                                                             'ui.grid.autoResize']);

gridControllerMod.controller('GridController', ['$scope', '$window', 'dataService', 'uiGridConstants', function($scope, $window, dataService, uiGridConstants) {

    var self = this;

    $scope.dataService = dataService;

    $scope.sanitize_url = function(in_url) {
        var split_url = in_url.split("://");
        return split_url[0] + "://" + split_url[1].replace(/\/+/g, "/");
    }

    $scope.selectBlockingPredecessors = function(in_blocking_predecessors) {
        $scope.$parent.$parent.loadTasksSelectAndJumpIntoView(in_blocking_predecessors);
    };

    $scope.openLtaLocation = function(in_ingest_tasks) {
        var ingest_tasks = Array.isArray(in_ingest_tasks) ? in_ingest_tasks : [in_ingest_tasks];

        //example: http://lofar.target.rug.nl/Lofar?mode=query_result_page_user&product=AveragingPipeline&ObservationId=544965&project=LC6_015
        //map task.sub_type to url product parameter
        var project2project2product2tasksDict = {};

        for(var t of ingest_tasks) {
            var lta_product;
            switch(t.sub_type) {
                case 'averaging_pipeline': lta_product = 'AveragingPipeline'; break;
                case 'calibration_pipeline': lta_product = 'CalibrationPipeline'; break;
                case 'pulsar_pipeline': lta_product = 'PulsarPipeline'; break;
                case 'lofar_imaging_pipeline': lta_product = 'ImagingPipeline'; break;
                case 'imaging_pipeline_msss': lta_product = 'ImagingPipeline'; break;
                case 'long_baseline_pipeline': lta_product = 'LongBaselinePipeline'; break;
                case 'lofar_observation': lta_product = 'Observation'; break;
            }
            if(lta_product && (t.ingest_status != undefined) ) {
                if(!project2project2product2tasksDict.hasOwnProperty(t.project_name)) {
                    project2project2product2tasksDict[t.project_name] = {};
                }
                if(!project2project2product2tasksDict[t.project_name].hasOwnProperty(lta_product)) {
                    project2project2product2tasksDict[t.project_name][lta_product] = [];
                }
                project2project2product2tasksDict[t.project_name][lta_product].push(t);
            }
        }

        var window_cntr = 0;
        for(var project in project2project2product2tasksDict) {
            for(var product in project2project2product2tasksDict[project]) {
                var product_tasks = project2project2product2tasksDict[project][product];
                var otdb_ids = product_tasks.map(function(pt) { return pt.otdb_id; });
                var otdb_ids_string = otdb_ids.join(',');
                var url = dataService.config.lta_base_url + '/Lofar?mode=query_result_page_user&product=' + product + '&ObservationId=' + otdb_ids_string + '&project=' + project;
                url = $scope.sanitize_url(url);
                setTimeout(function(url_arg) {
                    $window.open(url_arg, '_blank');
                }, window_cntr*250, url);
                window_cntr += 1;
            }
        }
    }

    $scope.columns = [
    { field: 'name',
        enableCellEdit: false,
        cellTooltip: function(row, col) { return row.entity.description; },
        width: '*',
        minWidth: '100',
    },
    { field: 'project_name',
        displayName:'Project',
        enableCellEdit: false,
        cellTemplate:'<div style=\'padding-top:5px;\'>' +
                     '<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.project_mom2object_id}}"' +
                         ' title="{{row.grid.appScope.dataService.momProjectsDict[row.entity.project_mom_id].description}}"' +
                     '>{{row.entity[col.field]}}' +
                     '</a></div>',
        width: '*',
        minWidth: '80',
        filter: {
            type: uiGridConstants.filter.SELECT,
            selectOptions: []
        }
    },
    { field: 'starttime',
        displayName: 'Start',
        width: '120',
        type: 'date',
        enableCellEdit: false,
        enableCellEditOnFocus: false,
        cellTemplate:'<div style=\'text-align:center; padding-top:5px;\'>{{row.entity[col.field] | date:\'yyyy-MM-dd HH:mm:ss\'}}</div>',
        sort: { direction: uiGridConstants.ASC, priority: 3 }
    },
    { field: 'endtime',
        displayName: 'End',
        width: '120',
        type: 'date',
        enableCellEdit: false,
        enableCellEditOnFocus: false,
        cellTemplate:'<div style=\'text-align:center; padding-top:5px;\'>{{row.entity[col.field] | date:\'yyyy-MM-dd HH:mm:ss\'}}</div>'
    },
    { field: 'duration',
        displayName: 'Duration',
        width: '70',
        type: 'number',
        enableFiltering: false,
        enableCellEdit: false,
        enableCellEditOnFocus: false,
        cellTemplate:'<div style=\'text-align:center; padding-top:5px;\'>{{row.entity[col.field] | secondsToHHmmss}}</div>'
    },
    { field: 'status',
        enableCellEdit: false,
        width: '70',
        filter: {
            condition: uiGridConstants.filter.EXACT,
            type: uiGridConstants.filter.SELECT,
            selectOptions: []
        },
        cellClass: function(grid, row, col, rowRenderIndex, colRenderIndex) {
            return "grid-status-" + grid.getCellValue(row,col);
        }
        // Supposedly [1], select-box items can be formatted using:
        // headerCellFilter: 'statusFormatter'
        //
        // [1] http://stackoverflow.com/questions/37286945/ui-grid-setting-template-for-filter-options
    },
    {   field: 'info',
        displayName: 'Info',
        enableCellEdit: false,
        width: '45',
        filter: {
            condition: function(searchTerm, cellValue, row, column) {
                var do_include = false;
                switch(searchTerm) {
                    case 0: do_include = (row.entity.blocked_by_ids.length > 0);    break;
                    case 1: do_include = (row.entity.ingest_status=="ingesting");   break;
                    case 2: do_include = (row.entity.ingest_status=="ingested");    break;
                    case 3: do_include = (row.entity.ingest_status=="failed");      break;
                    case 4: do_include = row.entity.data_pinned;                    break;
                    default: break;
                };
                return do_include;
            },
            type: uiGridConstants.filter.SELECT,
            selectOptions: []
        },
        editableCellTemplate: 'ui-grid/dropdownEditor',
        editDropdownOptionsArray: [],
        headerTooltip: "Additional status information",
        cellTemplate:   '<div style="text-align: center" class="ui-grid-cell-contents">' +
                            '<span ng-if="row.entity.blocked_by_ids.length > 0"><img ng-click="row.grid.appScope.selectBlockingPredecessors(row.entity.blocked_by_ids)" ng-src="static/icons/blocked.png" title="Blocked by {{row.entity.blocked_by_ids.length.toString()}} predecessor(s) - Click to select" /></span>' +
                            '<span ng-if="row.entity.ingest_status==\'ingesting\'"><img ng-click="row.grid.appScope.openLtaLocation(row.entity)" ng-src="static/icons/ingest_in_progress.png" title="Ingest in progress - Click to open LTA catalog" /></span>' +
                            '<span ng-if="row.entity.ingest_status==\'ingested\'"><img ng-click="row.grid.appScope.openLtaLocation(row.entity)" ng-src="static/icons/ingest_successful.png" title="Ingest successful - Click to open LTA catalog" /></span>' +
                            '<span ng-if="row.entity.ingest_status==\'failed\'"><img ng-click="row.grid.appScope.openLtaLocation(row.entity)" ng-src="static/icons/ingest_failed.png" title="Ingest failed - Click to open LTA catalog" /></span>' +
                            '<span ng-if="row.entity.data_pinned"><img ng-src="static/icons/pinned.png" title="data is pinned and will not be deleted by (auto) cleanup service" /></span>' +
                        '</div>'
    },
    { field: 'type',
        enableCellEdit: false,
        width: '80',
        filter: {
            condition: uiGridConstants.filter.EXACT,
            type: uiGridConstants.filter.SELECT,
            selectOptions: []
        },
        sort: { direction: uiGridConstants.ASC, priority: 2 }
    },
    { field: 'disk_usage',
        displayName: 'Size',
        type: 'number',
        enableCellEdit: false,
        cellTemplate:'<div style=\'text-align:right; padding-top: 5px;\'>{{row.entity.disk_usage_readable}}</div>',
        width: '80',
        filter: {
            type: uiGridConstants.filter.SELECT,
            condition: uiGridConstants.filter.GREATER_THAN,
            selectOptions: [{ value:0, label: '> 0'}, { value:1e6, label: '> 1M'}, { value:1e9, label: '> 1G'}, { value:1e10, label: '> 10G'}, { value:1e11, label: '> 100G'}, { value:1e12, label: '> 1T'} ]
        }
    },
    { field: 'mom_object_group_id',
        displayName: 'Group ID',
        enableCellEdit: false,
        cellTemplate:'<div style=\'text-align: center; padding-top:5px;\'>' +
                        '<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom_object_group_mom2object_id}}"' +
                            'title="' +
                                'Group name: ' +           '{{row.entity.mom_object_group_name}}\n' +
                                'Parent group name: ' +    '{{row.entity.mom_object_parent_group_name}}\n' +
                                'Parent group ID: ' +      '{{row.entity.mom_object_parent_group_id}}' +
                            '">{{row.entity.mom_object_group_id}}</a></div>',
        width: '80',
        filter: {
            condition: uiGridConstants.filter.EXACT,
            type: uiGridConstants.filter.SELECT,
            selectOptions: []
        }
    },
    { field: 'mom_id',
        displayName: 'MoM ID',
        enableCellEdit: false,
        cellTemplate:'<div style=\'text-align: center; padding-top:5px;\'>' +
                        '<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom2object_id}}"' +
                            'title="' +
                                'Project description: ' +  '{{row.grid.appScope.dataService.momProjectsDict[row.entity.project_mom_id].description}}\n' +
                                'Task description: ' +     '{{row.entity.description}}\n' +
                                'Group name: ' +           '{{row.entity.mom_object_group_name}}\n' +
                                'Group ID: ' +             '{{row.entity.mom_object_group_id}}\n' +
                                'Parent group name: ' +    '{{row.entity.mom_object_parent_group_name}}\n' +
                                'Parent group ID: ' +      '{{row.entity.mom_object_parent_group_id}}' +
                            '">{{row.entity[col.field]}} </a></div>',
        width: '65'
    },
    { field: 'otdb_id',
        displayName: 'SAS ID',
        enableCellEdit: false,
        cellTemplate:'<div style=\'text-align:center; padding-top:5px;\'>{{row.entity.otdb_id}}</div>',
        width: '65'
    },
    { field: 'id',
        displayName: 'RADB ID',
        enableCellEdit: false,
        cellTemplate:'<div style=\'text-align:center; padding-top:5px;\'><a target="_blank" href="tasks/{{row.entity.id}}.html">{{row.entity[col.field]}}</a></div>',
        width: '72'
    },
    { field: 'cluster',
        displayName: 'Cluster',
        enableCellEdit: false,
        width: '75',
        filter: {
            condition: uiGridConstants.filter.EXACT,
            type: uiGridConstants.filter.SELECT,
            selectOptions: []
        },
        cellClass: function(grid, row, col, rowRenderIndex, colRenderIndex) {
            var value = grid.getCellValue(row,col);
            return "grid-cluster-" + value;
        },
        sort: { direction: uiGridConstants.ASC, priority: 1 }
    }];

    if($scope.dataService.projectMode) {
        $scope.columns.splice(1, 1);
    }

    $scope.gridOptions = {
        enableGridMenu: false,
        enableSorting: true,
        enableFiltering: true,
        enableCellEdit: false,
        enableColumnResize: true,
        enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER,
        enableRowSelection: true,
        enableRowHeaderSelection: true,
        enableFullRowSelection: false,
        modifierKeysToMultiSelect: true,
        multiSelect:true,
        enableSelectionBatchEvent:false,
        gridMenuShowHideColumns: false,
        columnDefs: $scope.columns,
        data: [],
//         rowTemplate: "<div ng-repeat=\"(colRenderIndex, col) in colContainer.renderedColumns track by col.uid\" ui-grid-one-bind-id-grid=\"rowRenderIndex + '-' + col.uid + '-cell'\" class=\"ui-grid-cell\" ng-class=\"{ 'ui-grid-row-header-cell': col.isRowHeader }\" role=\"{{col.isRowHeader ? 'rowheader' : 'gridcell'}}\" ui-grid-cell></div>"
        rowTemplate: "<div ng-repeat=\"(colRenderIndex, col) in colContainer.renderedColumns track by col.uid\" ui-grid-one-bind-id-grid=\"rowRenderIndex + '-' + col.uid + '-cell'\" class=\"ui-grid-cell\" ng-class=\"{ 'ui-grid-row-header-cell': col.isRowHeader }\" role=\"{{col.isRowHeader ? 'rowheader' : 'gridcell'}}\" ui-grid-cell context-menu>",
        onRegisterApi: function(gridApi){
            $scope.gridApi = gridApi;

            $scope.gridApi.core.on.rowsRendered($scope, function() {
                //on.rowsRendered is called whenever the data/filtering of the grid changed
                //update the filteredTasks in the dataService from the resulting new grid rows
                $scope.$evalAsync(function() {
                    var taskDict = $scope.dataService.taskDict;
                    $scope.dataService.filteredTasks = [];
                    var rows = $scope.gridApi.core.getVisibleRows(grid);
                    var numRows = rows.length;
                    for(var i = 0; i < numRows; i++) {
                        var row = rows[i];
                        if(row.visible)
                        {
                            var task_id = row.entity.id;
                            var task = taskDict[task_id];
                            if(task) {
                                $scope.dataService.filteredTasks.push(task);
                            }

                            row.setSelected($scope.dataService.selected_task_ids.indexOf(task_id) != -1);
                        }
                    }

                    $scope.dataService.filteredTaskChangeCntr++;

                    if($scope.dataService.filteredTasks.length == 0) {
                        var otdb_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'otdb_id'; });
                        if(otdb_col && otdb_col.filters.length && otdb_col.filters[0].hasOwnProperty('term')) {
                            var otdb_id = otdb_col.filters[0].term;
                            $scope.$parent.$parent.loadTaskByOTDBIdSelectAndJumpIntoView(otdb_id).then(function(loadedTask) {
                                if(loadedTask) {
                                    otdb_col.filters[0].term = null;
                                }
                            });
                        } else {
                            var mom_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'mom_id'; });

                            if(mom_col && mom_col.filters.length && mom_col.filters[0].hasOwnProperty('term')) {
                                var mom_id = mom_col.filters[0].term;
                                $scope.$parent.$parent.loadTaskByMoMIdSelectAndJumpIntoView(mom_id).then(function(task) {
                                    mom_col.filters[0].term = null;
                                    if(task == undefined) {
                                        //getting the task by mom_id did not find a task
                                        //maybe the entered id was a mom group_id?
                                        //let's try to loadTasksByMoMGroupIdSelectAndJumpIntoView
                                        $scope.$parent.$parent.loadTasksByMoMGroupIdSelectAndJumpIntoView(mom_id).then(function(tasks) {
                                            if(tasks == undefined || tasks.length == 0) {
                                                //getting the tasks by mom group id did not find any tasks
                                                //maybe the entered id was a mom parent group_id?
                                                //let's try to loadTasksByMoMParentGroupIdSelectAndJumpIntoView
                                                $scope.$parent.$parent.loadTasksByMoMParentGroupIdSelectAndJumpIntoView(mom_id).then(function(tasks) {
                                                    //pass
                                                });
                                            }
                                        });
                                    }
                                });
                            }
                        }
                    }
                });
            });

            gridApi.edit.on.afterCellEdit($scope,function(rowEntity, colDef, newValue, oldValue){
                var task = $scope.dataService.taskDict[rowEntity.id];
                var newTask = { id: task.id, status: task.status };
                $scope.dataService.putTask(newTask);
            });

            gridApi.selection.on.rowSelectionChanged($scope,function(row){
                if(row.entity.id) {
                    if(row.isSelected) {
                        $scope.dataService.addSelectedTaskId(row.entity.id);
                    } else if(!row.isSelected) {
                        $scope.dataService.removeSelectedTaskId(row.entity.id);
                    }
                }
            });
        }
    };

    function fillColumFilterSelectOptions(options, columnDef) {
        if (columnDef == undefined)
            return;

        var columnSelectOptions = [];
        if(options) {
            for(var i = 0; i < options.length; i++) {
                var option = options[i];
                if(option.hasOwnProperty('value') && option.hasOwnProperty('label')) {
                    columnSelectOptions.push({ value: option.value, label: option.label })
                }
                else {
                    columnSelectOptions.push({ value: option, label: option })
                }
            }
        }

        columnDef.filter.selectOptions = columnSelectOptions;
    };

    function populateListAsync() {
        $scope.$evalAsync(populateList);
    };

    function populateList() {
        if('tasks' in $scope.dataService && $scope.dataService.tasks.length > 0) {
            var viewFrom = $scope.dataService.viewTimeSpan.from;
            var viewTo = $scope.dataService.viewTimeSpan.to;

            var gridTasks = [];

            for(var task of $scope.dataService.tasks) {
                if(task.endtime >= viewFrom && task.starttime <= viewTo) {

                    var gridTask = {
                        blocked_by_ids: task.blocked_by_ids,
                        cluster: task.cluster,
                        description: task.description,
                        disk_usage: task.disk_usage,
                        disk_usage_readable: task.disk_usage_readable,
                        duration: task.duration,
                        endtime: task.endtime,
                        id: task.id,
                        ingest_status: task.ingest_status,
                        mom2object_id: task.mom2object_id,
                        mom_id: task.mom_id,
                        mom_object_group_id: task.mom_object_group_id,
                        mom_object_group_mom2object_id: task.mom_object_group_mom2object_id,
                        mom_object_group_name: task.mom_object_group_name,
                        mom_object_parent_group_id: task.mom_object_parent_group_id,
                        name: task.name,
                        nr_of_dataproducts: task.nr_of_dataproducts,
                        otdb_id: task.otdb_id,
                        predecessor_ids: task.predecessor_ids,
                        project_mom2object_id: task.project_mom2object_id,
                        project_mom_id: task.project_mom_id,
                        project_name: task.project_name,
                        specification_id: task.specification_id,
                        starttime: task.starttime,
                        status: task.status,
                        status_id: task.status_id,
                        sub_type: task.sub_type,
                        successor_ids: task.successor_ids,
                        type: task.type,
                        type_id: task.type_id,
                        data_pinned: task.data_pinned
                    };



                    gridTasks.push(task);
                }
            }

            $scope.gridOptions.data = gridTasks;
        } else
            $scope.gridOptions.data = []

        fillProjectsColumFilterSelectOptions()
        fillGroupsColumFilterSelectOptions();
    };

    function jumpToSelectedTaskRows() {
        var rowIndices = dataService.selected_task_ids.map(function(t_id) { return $scope.gridOptions.data.findIndex(function(row) {return row.id == t_id; } ); });
        rowIndices = rowIndices.filter(function(idx) {return idx > -1;}).sort();

        for(var rowIndex of rowIndices) {
            $scope.gridApi.core.scrollTo($scope.gridOptions.data[rowIndex], null);
        }
    };

    function onSelectedTaskIdsChanged() {
        var selected_task_ids = $scope.dataService.selected_task_ids;
        var rows = $scope.gridApi.grid.rows;

        for(var row of rows) {
            row.setSelected(selected_task_ids.indexOf(row.entity.id) != -1);
        }

        //find out if we have selected all tasks in a single mom group
        var selected_tasks = $scope.dataService.selected_task_ids.map(function(t_id) { return $scope.dataService.taskDict[t_id]; }).filter(function(t) { return t != undefined;});

        if(selected_tasks && selected_tasks.length > 1) {
            var selected_task_group_ids = selected_tasks.map(function(t) { return t.mom_object_group_id; });
            selected_task_group_ids = selected_task_group_ids.unique();

            if(selected_task_group_ids.length == 1) {
                //we have selected tasks in a single mom group
                //find out if we have selected all tasks within this mom group
                var mom_object_group_id = selected_task_group_ids[0];
                var all_group_tasks = $scope.dataService.tasks.filter(function(t) { return t.mom_object_group_id == mom_object_group_id; });

                if(all_group_tasks.length == selected_tasks.length) {
                    //we have selected all tasks in a single mom group
                    //apply filter on group column to see only tasks within this group
                    var group_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'mom_object_group_id'; });
                    if(group_col) {
                        var mom_object_group_name = all_group_tasks[0].mom_object_group_name;
                        var label = mom_object_group_id + ' ' + mom_object_group_name;

                        var groupSelectOptions = [ { value: mom_object_group_id, label: label} ];

                        fillColumFilterSelectOptions(groupSelectOptions, $scope.columns.find(function(c) {return c.field == 'mom_object_group_id'; }));
                        group_col.filters[0].term = mom_object_group_id;
                    }
                }
            }
        }


        $scope.$evalAsync(jumpToSelectedTaskRows);
    };

    $scope.$watch('dataService.taskChangeCntr', function() { populateListAsync(); });
    $scope.$watch('dataService.claimChangeCntr', function() { populateListAsync(); });
    $scope.$watch('dataService.viewTimeSpan', function() {
        populateListAsync();
        $scope.$evalAsync(jumpToSelectedTaskRows);
    }, true);

    function fillFilterSelectOptions() {
        if(dataService.initialLoadComplete) {
            fillStatusColumFilterSelectOptions();
            fillInfoColumFilterSelectOptions();
            fillTypeColumFilterSelectOptions();
            fillProjectsColumFilterSelectOptions();
            fillGroupsColumFilterSelectOptions();
            fillColumFilterSelectOptions(['CEP2', 'CEP4', 'DRAGNET'], $scope.columns.find(function(c) {return c.field == 'cluster'; }));
        }
    };

    $scope.$watch('dataService.filteredTaskChangeCntr', function()  { $scope.$evalAsync(fillFilterSelectOptions()); });
    $scope.$watch('dataService.initialLoadComplete',    function()  {
        populateListAsync();
        $scope.$evalAsync(fillFilterSelectOptions()); });

    function fillProjectsColumFilterSelectOptions() {
        var projectNames = [];
        var momProjectsDict = $scope.dataService.momProjectsDict;
        var tasks = $scope.dataService.filteredTasks;

        var project_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'project_name'; });
        if(project_col && project_col.filters.length && project_col.filters[0].term) {
            tasks = $scope.dataService.tasks;
        }

        //get unique projectIds from tasks
        var task_project_ids = tasks.map(function(t) { return t.project_mom_id; });
        task_project_ids = task_project_ids.unique();

        for(var project_id of task_project_ids) {
            if(momProjectsDict.hasOwnProperty(project_id)) {
                var projectName = momProjectsDict[project_id].name;
                if(!(projectName in projectNames)) {
                    projectNames.push(projectName);
                }
            }
        }
        projectNames.sort();
        fillColumFilterSelectOptions(projectNames, $scope.columns.find(function(c) {return c.field == 'project_name'; }));
    };

    function fillStatusColumFilterSelectOptions() {
        var tasks = $scope.dataService.filteredTasks;

        var status_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'status'; });
        if(status_col && status_col.filters.length && status_col.filters[0].term) {
            tasks = $scope.dataService.tasks;
        }

        //get unique statuses from tasks
        var task_statuses = tasks.map(function(t) { return t.status; });
        task_statuses = task_statuses.unique();
        task_statuses.sort();

        fillColumFilterSelectOptions(task_statuses, $scope.columns.find(function(c) {return c.field == 'status'; }));
    };

    function fillTypeColumFilterSelectOptions() {
        var tasks = $scope.dataService.filteredTasks;

        var type_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'type'; });
        if(type_col && type_col.filters.length && type_col.filters[0].term) {
            tasks = $scope.dataService.tasks;
        }

        //get unique types from tasks
        var task_types = tasks.map(function(t) { return t.type; });
        task_types = task_types.unique();
        task_types.sort();

        fillColumFilterSelectOptions(task_types, $scope.columns.find(function(c) {return c.field == 'type'; }));
    };

    function fillGroupsColumFilterSelectOptions() {
        var group_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'mom_object_group_id'; });
        if(!group_col || group_col.filter.term) {
            return;
        }

        var tasks = $scope.dataService.filteredTasks;

        //get unique groupNames from tasks
        var groupId2Name = {};
        var groupIds = [];

        for(var task of tasks) {
            if(task.mom_object_group_id) {
                if(!groupId2Name.hasOwnProperty(task.mom_object_group_id)) {
                    groupId2Name[task.mom_object_group_id] = task.mom_object_group_name;
                    groupIds.push(task.mom_object_group_id);
                }
            }
        }

        groupIds.sort();

        fillColumFilterSelectOptions(groupIds, $scope.columns.find(function(c) {return c.field == 'mom_object_group_id'; }));
    };

    function fillInfoColumFilterSelectOptions() {
        var tasks = $scope.dataService.filteredTasks;

        var info_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'info'; });
        if(info_col && info_col.filters.length && info_col.filters[0].term) {
            tasks = $scope.dataService.tasks;
        }

        // Generate a list of unique information items
        var task_info = [];
        var info_bit_flags = 0x00;
        for(var task of tasks) {
            if((task.blocked_by_ids.length > 0) && !(info_bit_flags & 0x01)) {
                task_info.push({ value: 0, label: 'Blocked tasks' });
                info_bit_flags |= 0x01;
            }

            if((task.ingest_status === 'ingesting') && !(info_bit_flags & 0x02)) {
                task_info.push({ value: 1, label: 'Ingests in progress' });
                info_bit_flags |= 0x02;
            }

            if((task.ingest_status === 'ingested') && !(info_bit_flags & 0x04)) {
                task_info.push({ value: 2, label: 'Successful ingests' });
                info_bit_flags |= 0x04;
            }

            if((task.ingest_status === 'failed') && !(info_bit_flags & 0x08)) {
                task_info.push({ value: 3, label: 'Failed ingests' });
                info_bit_flags |= 0x08;
            }

            if(task.data_pinned && !(info_bit_flags & 0x10)) {
                task_info.push({ value: 4, label: 'Pinned' });
                info_bit_flags |= 0x10;
            }
        };

        // sort on key values
        function keysrt(key,desc) {
          return function(a,b){
           return desc ? ~~(a[key] < b[key]) : ~~(a[key] > b[key]);
          }
        }

        task_info.sort(keysrt('value'));
        fillColumFilterSelectOptions(task_info, $scope.columns.find(function(c) {return c.field == 'info'; }));
    };

    $scope.$watch('dataService.selected_task_ids', onSelectedTaskIdsChanged, true);
    $scope.$watch('dataService.selected_project_id', function() {
        fillProjectsColumFilterSelectOptions();

        var project_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'project_name'; });
        if(project_col && project_col.filters.length) {
            if(dataService.selected_project_id != undefined) {
                var projectName = dataService.momProjectsDict[dataService.selected_project_id].name;
                if(projectName != undefined) {
                    var project_names = project_col.filter.selectOptions.map(function(so) { return so.value;});
                    if(project_names.includes(projectName)) {
                        project_col.filters[0].term = projectName;
                    }
                }
            }
        }
    });
}]);

gridControllerMod.directive('contextMenu', ['$document', '$window', function($document, $window) {
    return {
      restrict: 'A',
      scope: {
      },
      link: function($scope, $element, $attrs) {
        function handleContextMenuEvent(event) {
            //pragmatic 'hard-coded' way of getting the dataService and the rowEntity via scope tree.
            var dataService = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.dataService;
            var cleanupCtrl = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.cleanupCtrl;
            var dataCtrlScope = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent;
            var row = $scope.$parent.$parent.$parent.row;
            var rowEntity = row.entity;

            if(!dataService || !rowEntity)
                return true;

            var taskId = rowEntity.id;
            var task = dataService.taskDict[taskId];
            if(!task)
                return true;

            if(!dataService.isTaskIdSelected(taskId)) {
                dataService.setSelectedTaskId(taskId);
            }

            var docElement = angular.element($document);

            //search for already existing contextmenu element
            while($document.find('#grid-context-menu').length) {
                //found, remove it, so we can create a fresh one
                $document.find('#grid-context-menu')[0].remove();

                //unbind document close event handlers
                docElement.unbind('click', closeContextMenu);
                docElement.unbind('contextmenu', closeContextMenu);
            }

            //create contextmenu element
            //with list of menu items,
            //each with it's own action
            var contextmenuElement = angular.element('<div id="grid-context-menu"></div>');
            var ulElement = angular.element('<ul class="dropdown-menu" role="menu" style="left:' + event.clientX + 'px; top:' + event.clientY + 'px; z-index: 100000; display:block;"></ul>');
            contextmenuElement.append(ulElement);

            var selected_tasks = dataService.selected_task_ids.map(function(t_id) { return dataService.taskDict[t_id]; });
            selected_tasks = selected_tasks.filter(function(t) { return t != undefined; });
            var selected_cep4_tasks = selected_tasks.filter(function(t) { return t['cluster'] == 'CEP4'; });

//                             var liElement = angular.element('<li><a href="#">Copy Task</a></li>');
//                             ulElement.append(liElement);
//                             liElement.on('click', function() {
//                                 closeContextMenu();
//                                 //TODO: remove link to dataService in this generic plugin
//                                 dataService.copyTask(task);
//                             });

            var liElement = angular.element('<li><a href="#">Select group</a></li>');
            ulElement.append(liElement);
            liElement.on('click', function() {
                closeContextMenu();
                dataCtrlScope.loadTasksByMoMGroupIdSelectAndJumpIntoView(task.mom_object_group_id);
            });

            var liElement = angular.element('<li><a href="#">Select parent group</a></li>');
            ulElement.append(liElement);
            liElement.on('click', function() {
                closeContextMenu();
                dataCtrlScope.loadTasksByMoMParentGroupIdSelectAndJumpIntoView(task.mom_object_parent_group_id);
            });

            var blocked_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return (t.blocked_by_ids.length > 0); });

            if(blocked_selected_cep4_tasks.length > 0) {
                var liContent = '<li><a href="#">Select blocking predecessor(s)</a></li>'
                var liElement = angular.element(liContent);
                ulElement.append(liElement);
                liElement.on('click', function() {
                    closeContextMenu();

                    var blocking_predecessors = []
                    for(var task of blocked_selected_cep4_tasks) {
                        blocking_predecessors = blocking_predecessors.concat(task.blocked_by_ids);
                    }
                    row.grid.appScope.selectBlockingPredecessors(blocking_predecessors);
                });
            }

            var completed_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.status == 'finished' || t.status == 'aborted'; });
            var completed_selected_cep4_observations = completed_selected_cep4_tasks.filter(function(t) { return t.type == 'observation'; });

            if(completed_selected_cep4_observations.length > 0 && dataService.config.inspection_plots_base_url) {
                var liElement = angular.element('<li><a href="#">Inspection Plots</a></li>');
                ulElement.append(liElement);
                liElement.on('click', function() {
                    closeContextMenu();

                    var window_cntr = 0;
                    for(var obs of completed_selected_cep4_observations) {
                        var url = dataService.config.inspection_plots_base_url + '/' + obs.otdb_id;
                        url = row.grid.appScope.sanitize_url(url);
                        setTimeout(function(url_arg) {
                            $window.open(url_arg, '_blank');
                        }, window_cntr*750, url);
                        window_cntr += 1;
                    }
                });
            }

            var ingest_tasks = selected_tasks.filter(function(t) { return t.ingest_status != undefined; });

            if(ingest_tasks.length > 0 && dataService.config.lta_base_url) {
                var liElement = angular.element('<li><a href="#">Open in LTA catalogue</a></li>');
                ulElement.append(liElement);
                liElement.on('click', function() {
                    closeContextMenu();
                    row.grid.appScope.openLtaLocation(ingest_tasks);
                });
            }

            var liContent = selected_tasks.length > 0 ? '<li><a href="#">Show parset</a></li>' : '<li><a href="#" style="color:#aaaaaa">Show parset</a></li>'
            var liElement = angular.element(liContent);
            ulElement.append(liElement);
            if(selected_tasks.length > 0) {
                liElement.on('click', function() {
                    closeContextMenu();

                    var window_cntr = 0;
                    for(var selected_task of selected_tasks) {
                        var url = 'rest/tasks/otdb/' + selected_task.otdb_id + '/parset';
                        setTimeout(function(url_arg) {
                            $window.open(url_arg, '_blank');
                        }, window_cntr*750, url);
                        window_cntr += 1;
                    }

                });
            }

            var liContent = selected_cep4_tasks.length > 0 ? '<li><a href="#">Show disk usage</a></li>' : '<li><a href="#" style="color:#aaaaaa">Show disk usage</a></li>'
            var liElement = angular.element(liContent);
            ulElement.append(liElement);
            if(selected_cep4_tasks.length > 0) {
                liElement.on('click', function() {
                    closeContextMenu();
                    if(selected_cep4_tasks.length == 1) {
                        cleanupCtrl.showTaskDiskUsage(task);
                    } else {
                        cleanupCtrl.showTaskDiskUsage(completed_selected_cep4_tasks);
                    }
                });
            }

            var unpinned_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return !t.data_pinned; });

            var liContent = unpinned_selected_cep4_tasks.length > 0 ? '<li><a href="#">Delete data</a></li>' : '<li><a href="#" style="color:#aaaaaa">Delete data</a></li>'
            var liElement = angular.element(liContent);
            ulElement.append(liElement);
            if(unpinned_selected_cep4_tasks.length > 0) {
                liElement.on('click', function() {
                    closeContextMenu();
                    cleanupCtrl.deleteTasksDataWithConfirmation(unpinned_selected_cep4_tasks);
                });
            }

            if(unpinned_selected_cep4_tasks.length > 0) {
                var liContent = '<li><a href="#">Pin task data against (auto) cleanup</a></li>';
                var liElement = angular.element(liContent);
                ulElement.append(liElement);
                liElement.on('click', function() {
                    closeContextMenu();

                    for(var t of unpinned_selected_cep4_tasks) {
                        var newTask = { id: t.id, data_pinned: true };
                        dataService.putTask(newTask);
                    }
                });
            }

            var pinned_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.data_pinned; });

            if(pinned_selected_cep4_tasks.length > 0) {
                var liContent = '<li><a href="#">Unpin task data (allow (auto) cleanup to delete)</a></li>';
                var liElement = angular.element(liContent);
                ulElement.append(liElement);
                liElement.on('click', function() {
                    closeContextMenu();

                    for(var t of pinned_selected_cep4_tasks) {
                        var newTask = { id: t.id, data_pinned: false };
                        dataService.putTask(newTask);
                    }
                });
            }

            var schedulable_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.status == 'approved' || t.status == 'conflict' || t.status == 'error'; });

            if(schedulable_cep4_tasks.length > 0) {
                var liContent = '<li><a href="#">Schedule CEP4 task(s)</a></li>'
                var liElement = angular.element(liContent);
                ulElement.append(liElement);
                liElement.on('click', function() {
                    closeContextMenu();
                    for(var pl of schedulable_cep4_tasks) {
                        var newTask = { id: pl.id, status: 'prescheduled' };
                        dataService.putTask(newTask);
                    }
                });
            }

            var unschedulable_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.status == 'prescheduled' || t.status == 'scheduled' || t.status == 'queued' || t.status == 'error'; });

            if(unschedulable_selected_cep4_tasks.length > 0) {
                var liContent = '<li><a href="#">Unschedule (pre)scheduled/queued/error tasks</a></li>'
                var liElement = angular.element(liContent);
                ulElement.append(liElement);
                liElement.on('click', function() {
                    closeContextMenu();
                    for(var pl of unschedulable_selected_cep4_tasks) {
                        if(pl.status == 'queued') {
                            var newTask = { id: pl.id, status: 'aborted' };
                            dataService.putTask(newTask);
                        }

                        var newTask = { id: pl.id, status: 'approved' };
                        dataService.putTask(newTask);
                    }
                });
            }

            var active_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return (t.status == 'active' || t.status == 'completing') && t.type == 'pipeline'; });

            if(active_selected_cep4_pipelines.length > 0) {
                var liContent = '<li><a href="#">Abort active CEP4 pipelines</a></li>'
                var liElement = angular.element(liContent);
                ulElement.append(liElement);
                liElement.on('click', function() {
                    closeContextMenu();
                    for(var pl of active_selected_cep4_pipelines) {
                        var newTask = { id: pl.id, status: 'aborted' };
                        dataService.putTask(newTask);
                    }
                });
            }

            var aborted_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return (t.status == 'aborted' || t.status == 'error') && t.type == 'pipeline'; });

            if(aborted_selected_cep4_pipelines.length > 0) {
                var liContent = '<li><a href="#">Reschedule aborted/error CEP4 pipelines</a></li>'
                var liElement = angular.element(liContent);
                ulElement.append(liElement);
                liElement.on('click', function() {
                    closeContextMenu();
                    for(var pl of aborted_selected_cep4_pipelines) {
                        var newTask = { id: pl.id, status: 'prescheduled' };
                        dataService.putTask(newTask);
                    }
                });
            }

            var closeContextMenu = function(cme) {
                contextmenuElement.remove();

                //unbind document close event handlers
                docElement.unbind('click', closeContextMenu);
                docElement.unbind('contextmenu', closeContextMenu);
            };

            //click anywhere to remove the contextmenu
            docElement.bind('click', closeContextMenu);
            docElement.bind('contextmenu', closeContextMenu);

            //add contextmenu to body
            var body = $document.find('body');
            body.append(contextmenuElement);

            //prevent bubbling event upwards
            return false;
        }

        $element.bind('contextmenu', handleContextMenuEvent);

        $scope.$on('$destroy', function() {
            $element.unbind('contextmenu', handleContextMenuEvent);
        });
      }
    };
  }]);