many cosmetic changes
[myslice.git] / plugins / scheduler2 / static / js / scheduler2.js
index cd84cd8..6b55b92 100755 (executable)
 #\r
 */\r
 \r
+// XXX groupid = all slots those that go with a min granularity\r
+\r
 /* some params */\r
+var scheduler2;\r
+var scheduler2Instance;\r
 //is ctrl keyboard button pressed\r
 var schedulerCtrlPressed = false;\r
 //table Id\r
 var schedulerTblId = "scheduler-reservation-table";\r
-var schedulerTblFirstColWidth = 150;\r
-//Some Data\r
+var SCHEDULER_FIRST_COLWIDTH = 200;\r
+\r
+\r
+/* Number of scheduler slots per hour. Used to define granularity. Should be inferred from resources XXX */\r
 var schedulerSlotsPerHour = 6;\r
-var schedulerMaxRows = 50;\r
+var RESOURCE_DEFAULT_GRANULARITY    = 1800 /* s */; // should be computed automatically from resource information\r
+var DEFAULT_GRANULARITY             = 1800 /* s */; // should be computed automatically from resource information. Test with 600\r
+var DEFAULT_PAGE_RANGE = 5;\r
+\r
+var schedulerMaxRows = 12;\r
+\r
+/* All resources */\r
 var SchedulerData = [];\r
+\r
+/* ??? */\r
 var SchedulerSlots = [];\r
+\r
+var SchedulerDateSelected = new Date();\r
+// Round to midnight\r
+SchedulerDateSelected.setHours(0,0,0,0);\r
+\r
+/* Filtered resources */\r
 var SchedulerDataViewData = [];\r
+\r
 var SchedulerSlotsViewData = [];\r
-var SchedulerTotalCells;\r
-var SchedulerTotalVisibleCells;\r
 //Help Variables\r
 var _schedulerCurrentCellPosition = 0;\r
-var _leasesDone = false;\r
-var _resourcesDone = false;\r
 //Enable Debug\r
-var schedulerDebug = false;\r
+var schedulerDebug = true;\r
 //tmp to delete\r
 var tmpSchedulerLeases = [];\r
 \r
-(function ($) {\r
-    var scheduler2 = Plugin.extend({\r
+var SCHEDULER_COLWIDTH = 50;\r
+\r
+\r
+/******************************************************************************\r
+ *                             ANGULAR CONTROLLER                             *\r
+ ******************************************************************************/\r
+\r
+// Create a private execution space for our controller. When\r
+// executing this function expression, we're going to pass in\r
+// the Angular reference and our application module.\r
+(function (ng, app) {\r
+\r
+    // Define our Controller constructor.\r
+    function Controller($scope) {\r
+\r
+        // Store the scope so we can reference it in our\r
+        // class methods\r
+        this.scope = $scope;\r
+\r
+        // Set up the default scope value.\r
+        this.scope.errorMessage = null;\r
+        this.scope.name = "";\r
+\r
+        //Pagin\r
+        $scope.current_page = 1;\r
+        this.scope.items_per_page = 10;\r
+        $scope.from = 0; // JORDAN\r
+\r
+        $scope.instance = null;\r
+        $scope.resources = new Array();\r
+        $scope.slots = SchedulerSlotsViewData;\r
+        $scope.granularity = DEFAULT_GRANULARITY; /* Will be setup */\r
+        //$scope.msg = "hello";\r
+\r
+        angular.element(document).ready(function() {\r
+            //console.log('Hello World');\r
+            //alert('Hello World');\r
+            //afterAngularRendered();\r
+        });\r
+\r
+        // Pagination\r
+\r
+        $scope.range = function() {\r
+            var range_size = $scope.page_count() > DEFAULT_PAGE_RANGE ? DEFAULT_PAGE_RANGE : $scope.page_count();\r
+            var ret = [];\r
+            var start;\r
+\r
+            start = $scope.current_page;\r
+            if ( start > $scope.page_count()-range_size ) {\r
+              start = $scope.page_count()-range_size+1;\r
+            }\r
+\r
+            for (var i=start; i<start+range_size; i++) {\r
+              ret.push(i);\r
+            }\r
+            return ret;\r
+        };\r
+\r
+        $scope.prevPage = function() {\r
+          if ($scope.current_page > 1) {\r
+            $scope.current_page--;\r
+          }\r
+        };\r
+\r
+        $scope.prevPageDisabled = function() {\r
+          return $scope.current_page === 1 ? "disabled" : "";\r
+        };\r
+  \r
+        $scope.page_count = function()\r
+        {\r
+            // XXX need visible resources only\r
+            var query_ext, visible_resources_length;\r
+            if (!$scope.instance)\r
+                return 0;\r
+            query_ext = manifold.query_store.find_analyzed_query_ext($scope.instance.options.query_uuid);\r
+            var visible_resources_length = 0;\r
+            query_ext.state.each(function(i, state) {\r
+                if (state[STATE_VISIBLE])\r
+                    visible_resources_length++;\r
+            });\r
+            return Math.ceil(visible_resources_length/$scope.items_per_page);\r
+        };\r
+  \r
+        $scope.nextPage = function() {\r
+          if ($scope.current_page < $scope.page_count()) {\r
+            $scope.current_page++;\r
+          }\r
+        };\r
+  \r
+        $scope.nextPageDisabled = function() {\r
+          return $scope.current_page === $scope.page_count() ? "disabled" : "";\r
+        }; \r
+\r
+        $scope.setPage = function(n) {\r
+            $scope.current_page = n;\r
+        };\r
+        // END pagination\r
+\r
+        // FILTER\r
+\r
+        $scope.filter_visible = function(resource)\r
+        {\r
+            return manifold.query_store.get_record_state($scope.instance.options.query_uuid, resource['urn'], STATE_VISIBLE);\r
+        };\r
+\r
+        // SELECTION\r
+\r
+        $scope._create_new_lease = function(resource_urn, start_time, end_time)\r
+        {\r
+            var lease_key, new_lease, data;\r
+\r
+            lease_key = manifold.metadata.get_key('lease');\r
+\r
+            new_lease = {\r
+                resource:   resource_urn,\r
+                start_time: start_time,\r
+                end_time:   end_time,\r
+            };\r
+\r
+            // This is needed to create a hashable object\r
+            new_lease.hashCode = manifold.record_hashcode(lease_key.sort());\r
+            new_lease.equals   = manifold.record_equals(lease_key);\r
+\r
+            data = {\r
+                state: STATE_SET,\r
+                key  : null,\r
+                op   : STATE_SET_ADD,\r
+                value: new_lease\r
+            }\r
+            manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);\r
+            /* Add to local cache also, unless we listen to events from outside */\r
+            if (!(resource_urn in $scope._leases_by_resource))\r
+                $scope._leases_by_resource[resource_urn] = [];\r
+            $scope._leases_by_resource[resource_urn].push(new_lease);\r
+        }\r
+\r
+        $scope._remove_lease = function(other)\r
+        {\r
+            var lease_key, other_key, data;\r
+\r
+            lease_key = manifold.metadata.get_key('lease');\r
+\r
+            // XXX This could be a manifold.record_get_value\r
+            other_key = {\r
+                resource:   other.resource,\r
+                start_time: other.start_time,\r
+                end_time:   other.end_time\r
+            }\r
+            other_key.hashCode = manifold.record_hashcode(lease_key.sort());\r
+            other_key.equals   = manifold.record_equals(lease_key);\r
+\r
+            data = {\r
+                state: STATE_SET,\r
+                key  : null,\r
+                op   : STATE_SET_REMOVE,\r
+                value: other_key\r
+            }\r
+            manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);\r
+            /* Remove from local cache also, unless we listen to events from outside */\r
+            $scope._leases_by_resource[other.resource] = $.grep($scope._leases_by_resource[other.resource], function(x) { return x != other; });\r
+\r
+        }\r
+\r
+        $scope.select = function(index, model_lease, model_resource)\r
+        {\r
+            var data, resource_granularity;\r
 \r
-        /** XXX to check\r
+            //resource_granularity = model_resource.granularity === undefined ? RESOURCE_DEFAULT_GRANULARITY : model_resource.granularity;\r
+\r
+            console.log("Selected", index, model_lease, model_resource);\r
+\r
+            var day_timestamp = SchedulerDateSelected.getTime() / 1000;\r
+            var start_time = day_timestamp + index       * model_resource.granularity; // XXX resource_granularity\r
+            var end_time   = day_timestamp + (index + 1) * model_resource.granularity; //\r
+            var start_date = new Date(start_time * 1000);\r
+            var end_date   = new Date(end_time   * 1000);\r
+\r
+            var lease_key = manifold.metadata.get_key('lease');\r
+\r
+            // We search for leases in the cache we previously constructed\r
+            var resource_leases = $scope._leases_by_resource[model_resource.urn];\r
+\r
+            switch (model_lease.status)\r
+            {\r
+                case 'free': // out\r
+                case 'pendingout':\r
+                    if (resource_leases) {\r
+                        /* Search for leases before */\r
+                        $.each(resource_leases, function(i, other) {\r
+                            if (other.end_time != start_time)\r
+                                return true; // ~ continue\r
+        \r
+                            /* The lease 'other' is just before, and there should not exist\r
+                             * any other lease before it */\r
+                            start_time = other.start_time;\r
+        \r
+                            other_key = {\r
+                                resource:   other.resource,\r
+                                start_time: other.start_time,\r
+                                end_time:   other.end_time\r
+                            }\r
+                            // This is needed to create a hashable object\r
+                            other_key.hashCode = manifold.record_hashcode(lease_key.sort());\r
+                            other_key.equals   = manifold.record_equals(lease_key);\r
+        \r
+                            data = {\r
+                                state: STATE_SET,\r
+                                key  : null,\r
+                                op   : STATE_SET_REMOVE,\r
+                                value: other_key\r
+                            }\r
+                            manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);\r
+                            /* Remove from local cache also, unless we listen to events from outside */\r
+                            $scope._leases_by_resource[model_resource.urn] = $.grep($scope._leases_by_resource[model_resource.urn], function(x) { return x != other; });\r
+                            return false; // ~ break\r
+                        });\r
+        \r
+                        /* Search for leases after */\r
+                        $.each(resource_leases, function(i, other) {\r
+                            if (other.start_time != end_time)\r
+                                return true; // ~ continue\r
+        \r
+                            /* The lease 'other' is just after, and there should not exist\r
+                             * any other lease after it */\r
+                            end_time = other.end_time;\r
+                            other_key = {\r
+                                resource:   other.resource,\r
+                                start_time: other.start_time,\r
+                                end_time:   other.end_time\r
+                            }\r
+                            // This is needed to create a hashable object\r
+                            other_key.hashCode = manifold.record_hashcode(lease_key.sort());\r
+                            other_key.equals   = manifold.record_equals(lease_key);\r
+        \r
+                            data = {\r
+                                state: STATE_SET,\r
+                                key  : null,\r
+                                op   : STATE_SET_REMOVE,\r
+                                value: other_key\r
+                            }\r
+                            manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);\r
+                            /* Remove from local cache also, unless we listen to events from outside */\r
+                            $scope._leases_by_resource[model_resource.urn] = $.grep($scope._leases_by_resource[model_resource.urn], function(x) { return x != other; });\r
+                            return false; // ~ break\r
+                        });\r
+                    }\r
+        \r
+                    $scope._create_new_lease(model_resource.urn, start_time, end_time);\r
+                    model_lease.status = (model_lease.status == 'free') ? 'pendingin' : 'selected';\r
+                    // unless the exact same lease already existed (pending_out status for the lease, not the cell !!)\r
+\r
+                    break;\r
+\r
+                case 'selected':\r
+                case 'pendingin':\r
+                    // We remove the cell\r
+\r
+                    /* We search for leases including this cell. Either 0, 1 or 2.\r
+                     * 0 : NOT POSSIBLE, should be checked.\r
+                     * 1 : either IN or OUT, we have make no change in the session\r
+                     * 2 : both will be pending, since we have made a change in the session\r
+                    * /!\ need to properly remove pending_in leases when removed again\r
+                     */\r
+                    if (resource_leases) {\r
+                        $.each(resource_leases, function(i, other) {\r
+                            if ((other.start_time <= start_time) && (other.end_time >= end_time)) {\r
+                                // The cell is part of this lease.\r
+\r
+                                // If the cell is not at the beginning of the lease, we recreate a lease with cells before\r
+                                if (start_time > other.start_time) {\r
+                                    $scope._create_new_lease(model_resource.urn, other.start_time, start_time);\r
+                                }\r
+\r
+                                // If the cell is not at the end of the lease, we recreate a lease with cells after\r
+                                if (end_time < other.end_time) {\r
+                                    $scope._create_new_lease(model_resource.urn, end_time, other.end_time);\r
+                                }\r
+                                \r
+                                // The other lease will be removed\r
+                                $scope._remove_lease(other);\r
+                            }\r
+                            // NOTE: We can interrupt the search if we know that there is a single lease (depending on the status).\r
+                        });\r
+                    }\r
+                \r
+                    // cf comment in previous switch case\r
+                    model_lease.status = (model_lease.status == 'selected') ? 'pendingout' : 'free';\r
+\r
+                    break;\r
+\r
+                case 'reserved':\r
+                case 'maintainance':\r
+                    // Do nothing\r
+                    break;\r
+            }\r
+            \r
+\r
+            $scope._dump_leases();\r
+        };\r
+  \r
+        $scope._dump_leases = function()\r
+        {\r
+            // DEBUG: display all leases and their status in the log\r
+            var leases = manifold.query_store.get_records($scope.instance.options.query_lease_uuid);\r
+            console.log("--------------------");\r
+            $.each(leases, function(i, lease) {\r
+                var key = manifold.metadata.get_key('lease');\r
+                var lease_key = manifold.record_get_value(lease, key);\r
+                var state = manifold.query_store.get_record_state($scope.instance.options.query_lease_uuid, lease_key, STATE_SET);\r
+                var state_str;\r
+                switch(state) {\r
+                    case STATE_SET_IN:\r
+                        state_str = 'STATE_SET_IN';\r
+                        break;\r
+                    case STATE_SET_OUT:\r
+                        state_str = 'STATE_SET_OUT';\r
+                        break;\r
+                    case STATE_SET_IN_PENDING:\r
+                        state_str = 'STATE_SET_IN_PENDING';\r
+                        break;\r
+                    case STATE_SET_OUT_PENDING:\r
+                        state_str = 'STATE_SET_OUT_PENDING';\r
+                        break;\r
+                    case STATE_SET_IN_SUCCESS:\r
+                        state_str = 'STATE_SET_IN_SUCCESS';\r
+                        break;\r
+                    case STATE_SET_OUT_SUCCESS:\r
+                        state_str = 'STATE_SET_OUT_SUCCESS';\r
+                        break;\r
+                    case STATE_SET_IN_FAILURE:\r
+                        state_str = 'STATE_SET_IN_FAILURE';\r
+                        break;\r
+                    case STATE_SET_OUT_FAILURE:\r
+                        state_str = 'STATE_SET_OUT_FAILURE';\r
+                        break;\r
+                }\r
+                console.log("LEASE", new Date(lease.start_time * 1000), new Date(lease.end_time * 1000), lease.resource, state_str);\r
+            });\r
+        };\r
+\r
+        // Return this object reference.\r
+        return (this);\r
+\r
+    }\r
+\r
+    // Define the Controller as the constructor function.\r
+    app.controller("SchedulerCtrl", Controller);\r
+\r
+})(angular, ManifoldApp);\r
+\r
+/******************************************************************************\r
+ *                              MANIFOLD PLUGIN                               *\r
+ ******************************************************************************/\r
+\r
+(function($) {\r
+        scheduler2 = Plugin.extend({\r
+\r
+            /** XXX to check\r
          * @brief Plugin constructor\r
          * @param options : an associative array of setting values\r
          * @param element : \r
          * @return : a jQuery collection of objects on which the plugin is\r
          *     applied, which allows to maintain chainability of calls\r
          */\r
-        init: function (options, element) {\r
-            this.classname="scheduler2";\r
-            // Call the parent constructor, see FAQ when forgotten\r
-            this._super(options, element);\r
-\r
-            SchedulerSlots = schedulerGetSlots(60/schedulerSlotsPerHour);\r
-            //selection from table \r
-            $(window).keydown(function (evt) {\r
-                if (evt.which == 17) { // ctrl\r
-                    schedulerCtrlPressed = true;\r
-                }\r
-            }).keyup(function (evt) {\r
-                if (evt.which == 17) { // ctrl\r
-                    schedulerCtrlPressed = false;\r
+            init: function(options, element) {\r
+                // Call the parent constructor, see FAQ when forgotten\r
+                this._super(options, element);\r
+\r
+                var scope = this._get_scope()\r
+                scope.instance = this;\r
+\r
+                // XXX not needed\r
+                scheduler2Instance = this;\r
+\r
+                // We need to remember the active filter for datatables filtering\r
+                // XXX not needed\r
+                this.filters = Array();\r
+\r
+                // XXX BETTER !!!!\r
+                $(window).delegate('*', 'keypress', function (evt){\r
+                        alert("erm");\r
+                      });\r
+\r
+                $(window).keydown(function(evt) {\r
+                    if (evt.which == 17) { // ctrl\r
+                        schedulerCtrlPressed = true;\r
+                    }\r
+                }).keyup(function(evt) {\r
+                    if (evt.which == 17) { // ctrl\r
+                        schedulerCtrlPressed = false;\r
+                    }\r
+                });\r
+\r
+                // XXX naming\r
+                //$("#" + schedulerTblId).on('mousedown', 'td', rangeMouseDown).on('mouseup', 'td', rangeMouseUp).on('mousemove', 'td', rangeMouseMove);\r
+\r
+                this._resources_received = false;\r
+                this._leases_received = false;\r
+                \r
+                scope._leases_by_resource = {};\r
+\r
+                /* Listening to queries */\r
+                this.listen_query(options.query_uuid, 'resources');\r
+                this.listen_query(options.query_lease_uuid, 'leases');\r
+\r
+                this.elmt().on('show', this, this.on_show);\r
+                this.elmt().on('shown.bs.tab', this, this.on_show);\r
+                this.elmt().on('resize', this, this.on_resize);\r
+\r
+                /* Generate slots according to the default granularity. Should\r
+                 * be updated when resources arrive.  Should be the pgcd in fact XXX */\r
+                this._granularity = DEFAULT_GRANULARITY;\r
+                scope.granularity = this._granularity;\r
+                this.scope_resources_by_key = {};\r
+\r
+                this.do_resize();\r
+    \r
+                scope.from = 0;\r
+\r
+                this._initUI();\r
+\r
+            },\r
+\r
+            do_resize: function()\r
+            {\r
+                var scope = this._get_scope();\r
+                var num_hidden_cells, new_max, lcm;\r
+\r
+                // do_resize has to be called when the window is resized, or one parameter changes\r
+                // e.g. when new resources have been received\r
+                //\r
+                this.resource_granularities = [3600, 1800]; //, 2400]; /* s */\r
+\r
+                /* Compute the slot length to accommodate all resources. This\r
+                 * is the GCD of all resource granularities. */\r
+                this._slot_length = this._gcdn(this.resource_granularities);\r
+\r
+                $('#' + schedulerTblId + ' thead tr th:eq(0)').css("width", SCHEDULER_FIRST_COLWIDTH);\r
+                //self get width might need fix depending on the template \r
+                var tblwidth = $('#scheduler-reservation-table').parent().outerWidth();\r
+\r
+                /* Number of visible cells...*/\r
+                this._num_visible_cells = parseInt((tblwidth - SCHEDULER_FIRST_COLWIDTH) / SCHEDULER_COLWIDTH);\r
+\r
+                /* ...should be a multiple of the lcm of all encountered granularities. */\r
+                lcm = this._lcmn(this.resource_granularities) / this._slot_length;\r
+                this._num_visible_cells = this._num_visible_cells - this._num_visible_cells % lcm;\r
+\r
+                // A list of {id, time} dictionaries representing the slots for the given day\r
+                this._all_slots = this._generate_all_slots();\r
+\r
+                /* scope also needs this value */\r
+                scope.slots = this._all_slots;\r
+                scope.slot_length = this._slot_length;\r
+                scope.num_visible_cells = this._num_visible_cells;\r
+                scope.lcm_colspan = this._lcmn(this.resource_granularities); // XXX WHY ?\r
+\r
+                /* Redraw... */\r
+                this._scope_clear_leases();\r
+                this._set_all_lease_slots();\r
+\r
+                // Slider max value\r
+                if ($('#tblSlider').data('slider') != undefined) {\r
+                    num_hidden_cells = this._all_slots.length - this._num_visible_cells;\r
+\r
+                    $('#tblSlider').slider('setAttribute', 'max', num_hidden_cells);\r
+                    $('#tblSlider').slider('setValue', scope.from, true);\r
                 }\r
-            });\r
-            $("#" + schedulerTblId).on('mousedown', 'td', rangeMouseDown).on('mouseup', 'td', rangeMouseUp).on('mousemove', 'td', rangeMouseMove);\r
+                this._get_scope().$apply();\r
 \r
-            // Explain this will allow query events to be handled\r
-            // What happens when we don't define some events ?\r
-            // Some can be less efficient\r
 \r
-            if (schedulerDebug) console.time("Listening_to_queries");\r
-            /* Listening to queries */\r
-            this.listen_query(options.query_uuid, 'all_ev');\r
-            this.listen_query(options.query_all_resources_uuid, 'all_resources');\r
-            this.listen_query(options.query_lease_uuid, 'lease');\r
-            //this.listen_query(options.query_lease_uuid, 'lease');\r
-            if (schedulerDebug) console.timeEnd("Listening_to_queries");\r
+            },\r
 \r
-        },\r
+            on_show: function(e)\r
+            {\r
+                var self = e.data;\r
+                self.do_resize();\r
+                self._get_scope().$apply();\r
+            },\r
 \r
-        /* Handlers */\r
+            on_resize: function(e)\r
+            {\r
+                var self = e.data;\r
+                self.do_resize();\r
+                self._get_scope().$apply();\r
+            },\r
 \r
-        /* all_ev QUERY HANDLERS Start */\r
-        on_all_ev_clear_records: function (data) {\r
-            //alert('all_ev clear_records');\r
-        },\r
-        on_all_ev_query_in_progress: function (data) {\r
-           // alert('all_ev query_in_progress');\r
-        },\r
-        on_all_ev_new_record: function (data) {\r
-            //alert('all_ev new_record');\r
-        },\r
-        on_all_ev_query_done: function (data) {\r
-            //alert('all_ev query_done');\r
-        },\r
-        //another plugin has modified something, that requires you to update your display. \r
-        on_all_ev_field_state_changed: function (data) {\r
-            //alert('all_ev query_done');\r
-        },\r
-        /* all_ev QUERY HANDLERS End */\r
-        /* all_resources QUERY HANDLERS Start */\r
-        on_all_resources_clear_records: function (data) {\r
-            //data is empty on load\r
-        },\r
-        on_all_resources_query_in_progress: function (data) {\r
-            //data is empty on load\r
-        },\r
-        on_all_resources_new_record: function (data) {\r
-            //alert(data.toSource());\r
-            if (SchedulerData.length < schedulerMaxRows)\r
-                SchedulerData.push({ id: data.urn, name: data.hrn, leases: schedulerGetLeases(60 / schedulerSlotsPerHour), type: data.type });\r
-            //alert(data.toSource());\r
-        },\r
-        on_all_resources_query_done: function (data) {\r
-            _resourcesDone = true;\r
-            this._initScheduler();\r
-        },\r
-        //another plugin has modified something, that requires you to update your display. \r
-        on_all_resources_field_state_changed: function (data) {\r
-            //alert('all_resources query_done');\r
-        },\r
-        /* all_resources QUERY HANDLERS End */\r
-        /* lease QUERY HANDLERS Start */\r
-        on_lease_clear_records: function (data) { console.log('clear_records'); },\r
-        on_lease_query_in_progress: function (data) { console.log('lease_query_in_progress'); },\r
-        on_lease_new_record: function (data) {\r
-            tmpSchedulerLeases.push({\r
-                id: schedulerGetSlotId(data.start_time, data.duration, data.granularity),\r
-                slice: data.slice,\r
-                status: 'reserved',\r
-                resource: data.resource,\r
-                network: data.network,\r
-                start_time: data.start_time,\r
-                lease_type: data.lease_type,\r
-                granularity: data.granularity,\r
-                duration: data.duration\r
-            });\r
-            //console.log(data.toSource()); console.log('lease_new_record');\r
-        },\r
-        on_lease_query_done: function (data) {\r
-            _leasesDone = true;\r
-            this._initScheduler();\r
-            // console.log('lease_query_done');\r
-        },\r
-        //another plugin has modified something, that requires you to update your display. \r
-        on_lease_field_state_changed: function (data) { console.log('lease_field_state_changed'); },\r
-        /* lease QUERY HANDLERS End */\r
+            /* Handlers */\r
 \r
+            _get_scope : function()\r
+            {\r
+                return angular.element(document.getElementById('SchedulerCtrl')).scope();\r
+            },\r
+            \r
+            _scope_set_resources : function()\r
+            {\r
+                var self = this;\r
+                var scope = this._get_scope();\r
 \r
-        // no prefix\r
+                var records = manifold.query_store.get_records(this.options.query_uuid);\r
 \r
-        on_filter_added: function (filter) {\r
+                scope.resources = [];\r
 \r
-        },\r
+                $.each(records, function(i, record) {\r
+                    if (!record.exclusive)\r
+                        return true; // ~ continue\r
 \r
-        // ... be sure to list all events here\r
+                    // copy not to modify original record\r
+                    var resource = jQuery.extend(true, {}, record);\r
 \r
-        /* RECORD HANDLERS */\r
-        on_all_new_record: function (record) {\r
-            //alert('on_all_new_record');\r
-        },\r
+                    // Fix granularity\r
+                    //resource_granularity = ((resource.granularity === undefined) || (typeof(resource.granularity) != "number")) ? RESOURCE_DEFAULT_GRANULARITY : resource.granularity;\r
+                    if (typeof(resource.granularity) != "number")\r
+                        resource.granularity = RESOURCE_DEFAULT_GRANULARITY;\r
+                    resource.leases = []; // a list of occupied timeslots\r
 \r
-        debug : function (logTxt) {\r
-            if (typeof window.console != 'undefined') {\r
-                console.debug(logTxt);\r
-            }\r
-        },\r
+                    self.scope_resources_by_key[resource['urn']] = resource;\r
+                    scope.resources.push(resource);\r
+                });\r
+            },\r
 \r
-        /* INTERNAL FUNCTIONS */\r
-        _initScheduler: function () {\r
-            if (_resourcesDone && _leasesDone)\r
+            _scope_clear_leases: function()\r
             {\r
-                /* GUI setup and event binding */\r
-                this._FixLeases();\r
-                this._initUI();\r
-            }\r
-        },\r
+                var time, now;\r
+                var self = this;\r
+                var scope = this._get_scope();\r
+\r
+                now = new Date().getTime();\r
+\r
+                // Setup leases with a default free status...\r
+                $.each(this.scope_resources_by_key, function(resource_key, resource) {\r
+                    resource.leases = [];\r
+                    var colspan_lease = resource.granularity / self._slot_length; //eg. 3600 / 1800 => 2 cells\r
+                    time = SchedulerDateSelected.getTime();\r
+                    for (i=0; i < self._all_slots.length / colspan_lease; i++) { // divide by granularity\r
+                        resource.leases.push({\r
+                            id:     'coucou',\r
+                            status: (time < now) ? 'disabled':  'free', // 'selected', 'reserved', 'maintenance' XXX pending ??\r
+                        });\r
+                        time += resource.granularity * 1000;\r
+                    }\r
+                });\r
+\r
+            },\r
+\r
+            _scope_set_leases: function()\r
+            {\r
+                    var status;\r
+                var self = this;\r
+                var scope = this._get_scope();\r
+            \r
+                manifold.query_store.iter_records(this.options.query_lease_uuid, function(lease_key, lease) {\r
+                    console.log("SET LEASES", lease.resource, new Date(lease.start_time* 1000), new Date(lease.end_time* 1000));\r
+                    // XXX We should ensure leases are correctly merged, otherwise our algorithm won't work\r
 \r
-        _initUI : function () {\r
-            if (schedulerDebug) console.time("_initUI");\r
-            //init DatePicker Start\r
-            $("#DateToRes").datepicker({\r
-                dateFormat: "yy-mm-dd",\r
-                minDate: 0,\r
-                numberOfMonths: 3\r
-            }).change(function () {\r
-                //Scheduler2.loadWithDate();\r
-            }).click(function () {\r
-                $("#ui-datepicker-div").css("z-index", 5);\r
-            });\r
-            //End init DatePicker\r
+                    // Populate leases by resource array: this will help us merging leases later\r
+\r
+                    // let's only put _our_ leases\r
+                    lease_status = manifold.query_store.get_record_state(self.options.query_lease_uuid, lease_key, STATE_SET);\r
+                    if (lease_status != STATE_SET_IN)\r
+                        return true; // ~continue\r
+                    if (!(lease.resource in scope._leases_by_resource))\r
+                        scope._leases_by_resource[lease.resource] = [];\r
+                    scope._leases_by_resource[lease.resource].push(lease);\r
+\r
+                });\r
+\r
+                this._set_all_lease_slots();\r
+            },\r
+\r
+            _set_all_lease_slots: function()\r
+            {\r
+                var self = this;\r
             \r
-            //init Table\r
-            this._FixTable();\r
-            //End init Table\r
-\r
-            //init Slider\r
-            $('#tblSlider').slider({\r
-                min: 0,\r
-                max: SchedulerTotalCells - SchedulerTotalVisibleCells - 1,\r
-                value: 0,\r
-                slide: function (event, ui) {\r
-                    //$("#amount").val("$" + ui.values[0] + " - $" + ui.values[1]);\r
-                    //console.log(ui.value);\r
-                    if (_schedulerCurrentCellPosition > ui.value)\r
-                        angular.element(document.getElementById('SchedulerCtrl')).scope().moveBackSlot(ui.value, ui.value + SchedulerTotalVisibleCells);\r
-                    else if (_schedulerCurrentCellPosition < ui.value)\r
-                        angular.element(document.getElementById('SchedulerCtrl')).scope().moveFrontSlot(ui.value, ui.value + SchedulerTotalVisibleCells);\r
-                    _schedulerCurrentCellPosition = ui.value;\r
+                manifold.query_store.iter_records(this.options.query_lease_uuid, function(lease_key, lease) {\r
+                    self._set_lease_slots(lease_key, lease);\r
+                });\r
+            },\r
+\r
+            on_resources_query_done: function(data)\r
+            {\r
+                this._resources_received = true;\r
+                this._scope_set_resources();\r
+                this._scope_clear_leases();\r
+                if (this._leases_received)\r
+                    this._scope_set_leases();\r
+                    \r
+                this._get_scope().$apply();\r
+            },\r
+\r
+            on_leases_query_done: function(data)\r
+            {\r
+                this._leases_received = true;\r
+                if (this._resources_received) {\r
+                    this._scope_set_leases();\r
+                    this._get_scope().$apply();\r
                 }\r
-            });\r
-            //End init Slider\r
+            },\r
 \r
-            //other stuff\r
-            $("#plugin-scheduler-loader").hide();\r
-            $("#plugin-scheduler").show();\r
+            /* Filters on resources */\r
+            on_resources_filter_added:   function(filter) { this._get_scope().$apply(); },\r
+            on_resources_filter_removed: function(filter) { this._get_scope().$apply(); },\r
+            on_resources_filter_clear:   function()       { this._get_scope().$apply(); },\r
 \r
-            //fixOddEvenClasses();\r
-            //$("#" + schedulerTblId + " td:not([class])").addClass("free");\r
-            if (schedulerDebug) console.timeEnd("_initUI");\r
-        },\r
+            /* Filters on leases ? */\r
+            on_leases_filter_added:      function(filter) { this._get_scope().$apply(); },\r
+            on_leases_filter_removed:    function(filter) { this._get_scope().$apply(); },\r
+            on_leases_filter_clear:      function()       { this._get_scope().$apply(); },\r
 \r
-        _FixLeases  : function () {\r
-            for (var i = 0; i < tmpSchedulerLeases.length; i++) {\r
-                var tmpLea = tmpSchedulerLeases[i];\r
-                var tmpRes = schedulerFindResourceById(SchedulerData, tmpLea.resource);\r
-                if (tmpRes != null) {\r
-                    //alert(tmpLea.id + '-' + tmpLea.start_time);\r
-                    tmpRes.leases[tmpLea.id] = tmpLea;\r
+            on_field_state_changed: function(data)\r
+            {\r
+                /*\r
+                this._set_lease_slots(lease_key, lease);\r
+\r
+                switch(data.state) {\r
+                    case STATE_SET:\r
+                        switch(data.op) {\r
+                            case STATE_SET_IN:\r
+                            case STATE_SET_IN_SUCCESS:\r
+                            case STATE_SET_OUT_FAILURE:\r
+                                this.set_checkbox_from_data(data.value, true);\r
+                                this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_RESET);\r
+                                break;  \r
+                            case STATE_SET_OUT:\r
+                            case STATE_SET_OUT_SUCCESS:\r
+                            case STATE_SET_IN_FAILURE:\r
+                                this.set_checkbox_from_data(data.value, false);\r
+                                this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_RESET);\r
+                                break;\r
+                            case STATE_SET_IN_PENDING:\r
+                                this.set_checkbox_from_data(data.key, true);\r
+                                this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_ADDED);\r
+                                break;  \r
+                            case STATE_SET_OUT_PENDING:\r
+                                this.set_checkbox_from_data(data.key, false);\r
+                                this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_REMOVED);\r
+                                break;\r
+                        }\r
+                        break;\r
+\r
+                    case STATE_WARNINGS:\r
+                        this.change_status(data.key, data.value);\r
+                        break;\r
                 }\r
-            }\r
+                */\r
+            },\r
+\r
+\r
+            /* INTERNAL FUNCTIONS */\r
+\r
+            _set_lease_slots: function(lease_key, lease)\r
+            {\r
+                var resource, lease_status, lease_class;\r
+                var day_timestamp, id_start, id_end, colspan_lease;\r
+\r
+                resource = this.scope_resources_by_key[lease.resource];\r
+                day_timestamp = SchedulerDateSelected.getTime() / 1000;\r
+                id_start = Math.floor((lease.start_time - day_timestamp) / resource.granularity);\r
+\r
+                /* Some leases might be in the past */\r
+                if (id_start < 0)\r
+                    id_start = 0;\r
+                /* Leases in the future: ignore */\r
+                if (id_start >= this._all_slots.length)\r
+                    return true; // ~ continue\r
+\r
+                id_end   = Math.ceil((lease.end_time   - day_timestamp) / resource.granularity);\r
+                colspan_lease = resource.granularity / this._slot_length; //eg. 3600 / 1800 => 2 cells\r
+                if (id_end >= this._all_slots.length / colspan_lease) {\r
+                    /* Limit the display to the current day */\r
+                    id_end = this._all_slots.length / colspan_lease\r
+                }\r
+                lease_status = manifold.query_store.get_record_state(this.options.query_lease_uuid, lease_key, STATE_SET);\r
+                // the same slots might be affected multiple times.\r
+                // PENDING_IN + PENDING_OUT => IN \r
+                //\r
+                // RESERVED vs SELECTED !\r
+                //\r
+                // PENDING !!\r
+                switch(lease_status) {\r
+                    case STATE_SET_IN:\r
+                        lease_class = 'selected'; // my leases\r
+                        lease_success = '';\r
+                        break;\r
+                    case STATE_SET_IN_SUCCESS:\r
+                        lease_class = 'selected'; // my leases\r
+                        lease_success = 'success';\r
+                    case STATE_SET_OUT_FAILURE:\r
+                        lease_class = 'selected'; // my leases\r
+                        lease_success = 'failure';\r
+                        break;\r
+                    case STATE_SET_OUT:\r
+                        lease_class = 'reserved'; // other leases\r
+                        lease_success = '';\r
+                        break;\r
+                    case STATE_SET_OUT_SUCCESS:\r
+                        lease_class = 'free'; // other leases\r
+                        lease_success = 'success';\r
+                        break;\r
+                    case STATE_SET_IN_FAILURE:\r
+                        lease_class = 'free'; // other leases\r
+                        lease_success = 'failure';\r
+                        break;\r
+                    case STATE_SET_IN_PENDING:\r
+                        lease_class = 'pendingin';\r
+                        lease_success = '';\r
+                        break;\r
+                    case STATE_SET_OUT_PENDING:\r
+                        // pending_in & pending_out == IN == replacement\r
+                        if (resource.leases[i].status == 'pendingin')\r
+                            lease_class = 'pendingin'\r
+                        else\r
+                            lease_class = 'pendingout';\r
+                        lease_success = '';\r
+                        break;\r
+                \r
+                }\r
+\r
+                for (i = id_start; i < id_end; i++) {\r
+                    resource.leases[i].status = lease_class;\r
+                    resource.leases[i].success = lease_success;\r
+                }\r
+            },\r
+\r
+/* XXX IN TEMPLATE XXX\r
+                if (SchedulerDataViewData.length == 0) {\r
+                    $("#plugin-scheduler").hide();\r
+                    $("#plugin-scheduler-empty").show();\r
+                    tmpScope.clearStuff();\r
+                } else {\r
+                    $("#plugin-scheduler-empty").hide();\r
+                    $("#plugin-scheduler").show();\r
+                    // initSchedulerResources\r
+                    tmpScope.initSchedulerResources(schedulerMaxRows < SchedulerDataViewData.length ? schedulerMaxRows : SchedulerDataViewData.length);\r
+                }\r
+*/\r
+\r
+            /**\r
+             * Initialize the date picker, the table, the slider and the buttons. Once done, display scheduler.\r
+             */\r
+            _initUI: function() \r
+            {\r
+                var self = this;\r
+                var scope = self._get_scope();\r
+\r
+                var num_hidden_cells;\r
+\r
+                $("#DateToRes").datepicker({\r
+                       dateFormat: "D, d M yy",\r
+                    onRender: function(date) {\r
+                        return date.valueOf() < now.valueOf() ? 'disabled' : '';\r
+                    }\r
+                }).on('changeDate', function(ev) {\r
+                    SchedulerDateSelected = new Date(ev.date);\r
+                    SchedulerDateSelected.setHours(0,0,0,0);\r
+                    // Set slider to origin\r
+                    //$('#tblSlider').slider('setValue', 0); // XXX\r
+                    // Refresh leases\r
+                    self._scope_clear_leases();\r
+                    self._set_all_lease_slots();\r
+                    // Refresh display\r
+                    self._get_scope().$apply();\r
+                }).datepicker('setValue', SchedulerDateSelected); //.data('datepicker');\r
+\r
+                //init Slider\r
+                num_hidden_cells = self._all_slots.length - self._num_visible_cells;\r
+                init_cell = (new Date().getHours() - 1) * 3600 / self._granularity;\r
+                if (init_cell > num_hidden_cells)\r
+                    init_cell = num_hidden_cells;\r
+\r
+                $('#tblSlider').slider({\r
+                    min: 0,\r
+                    max: num_hidden_cells,\r
+                    value: init_cell,\r
+                }).on('slide', function(ev) {\r
+                    var scope = self._get_scope();\r
+                    scope.from = ev.value;\r
+                    scope.$apply();\r
+                });\r
+                scope.from = init_cell;\r
+                scope.$apply();\r
+\r
+                $("#plugin-scheduler-loader").hide();\r
+                $("#plugin-scheduler").show();\r
+            },\r
+\r
+        // PRIVATE METHODS\r
+\r
+        /**\r
+         * Greatest common divisor\r
+         */\r
+        _gcd : function(x, y)\r
+        {\r
+            return (y==0) ? x : this._gcd(y, x % y);\r
         },\r
 \r
-        _FixTable: function () {\r
-            var colWidth = 50;\r
-            SchedulerTotalCells = SchedulerSlots.length;\r
-            $('#' + schedulerTblId + ' thead tr th:eq(0)').css("width", schedulerTblFirstColWidth);\r
-            //this get width might need fix depending on the template \r
-            var tblwidth = $('#scheduler-tab').parent().outerWidth();\r
-            SchedulerTotalVisibleCells = parseInt((tblwidth - schedulerTblFirstColWidth) / colWidth);\r
-\r
-            //if (SchedulerData.length == 0) {\r
-            //    //puth some test data\r
-            //    SchedulerData.push({ name: 'xyz+aaa', leases: schedulerGetLeases(60 / schedulerSlotsPerHour), urn: 'xyz+aaa', type: 'node' });\r
-            //    SchedulerData.push({ name: 'xyz+bbb', leases: schedulerGetLeases(60 / schedulerSlotsPerHour), urn: 'xyz+bbb', type: 'node' });\r
-            //    SchedulerData.push({ name: 'xyz+ccc', leases: schedulerGetLeases(60 / schedulerSlotsPerHour), urn: 'xyz+ccc', type: 'node' });\r
-            //    SchedulerData.push({ name: 'nitos1', leases: schedulerGetLeases(60 / schedulerSlotsPerHour), urn: 'nitos1', type: 'node' });\r
-            //}\r
-            angular.element(document.getElementById('SchedulerCtrl')).scope().initSlots(0, SchedulerTotalVisibleCells);\r
+        _gcdn : function(array)\r
+        {\r
+            var self = this;\r
+            return array.reduce(function(prev, cur, idx, arr) { return self._gcd(prev, cur); });\r
         },\r
-        _SetPeriodInPage: function (start, end) {\r
-        }\r
-    });\r
 \r
-    //Sched2 = new Scheduler2();\r
+        /**\r
+         * Least common multiple\r
+         */\r
+        _lcm : function(x, y)\r
+        {\r
+            return x * y / this._gcd(x, y);\r
+        },\r
 \r
-    /* Plugin registration */\r
-    $.plugin('Scheduler2', scheduler2);\r
+        _lcmn : function(array)\r
+        {\r
+            var self = this;\r
+            return array.reduce(function(prev, cur, idx, arr) { return self._lcm(prev, cur); });\r
+        },\r
+    \r
+        _pad_str : function(i)\r
+        {\r
+            return (i < 10) ? "0" + i : "" + i;\r
+        },\r
+\r
+        /**\r
+         * Member variables used:\r
+         *   _granularity\r
+         * \r
+         * Returns:\r
+         *   A list of {id, time} dictionaries.\r
+         */\r
+        _generate_all_slots: function()\r
+        {\r
+            var slots = [];\r
+            // Start with a random date (a first of a month), only time will matter\r
+            var d = new Date(2014, 1, 1, 0, 0, 0, 0);\r
+            var i = 0;\r
+            // Loop until we change the day\r
+            while (d.getDate() == 1) {\r
+                // Nicely format the time...\r
+                var tmpTime = this._pad_str(d.getHours()) + ':' + this._pad_str(d.getMinutes());\r
+                /// ...and add the slot to the list of results\r
+                slots.push({ id: i, time: tmpTime });\r
+                // Increment the date with the granularity\r
+                d = new Date(d.getTime() + this._slot_length * 1000);\r
+                i++;\r
+            }\r
+            return slots;\r
 \r
-    // TODO Here use cases for instanciating plugins in different ways like in the pastie.\r
+        },\r
+    });\r
 \r
+    /* Plugin registration */\r
+    $.plugin('Scheduler2', scheduler2);\r
 \r
 })(jQuery);\r
 \r