Add resource from the scheduler not only leases
[unfold.git] / plugins / scheduler2 / static / js / scheduler2.js
1 /*
2 #
3 # Copyright (c) 2013 NITLab, University of Thessaly, CERTH, Greece
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # of this software and associated documentation files (the "Software"), to deal
7 # in the Software without restriction, including without limitation the rights
8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 # copies of the Software, and to permit persons to whom the Software is
10 # furnished to do so, subject to the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be included in
13 # all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 # THE SOFTWARE.
22 #
23 #
24 # This is a MySlice plugin for the NITOS Scheduler
25 # Nitos Scheduler v1
26 #
27 */
28
29 // XXX groupid = all slots those that go with a min granularity
30
31 /* some params */
32 var scheduler2;
33 var scheduler2Instance;
34 //is ctrl keyboard button pressed
35 var schedulerCtrlPressed = false;
36 //table Id
37 var schedulerTblId = "scheduler-reservation-table";
38 var SCHEDULER_FIRST_COLWIDTH = 200;
39
40
41 /* Number of scheduler slots per hour. Used to define granularity. Should be inferred from resources XXX */
42 var schedulerSlotsPerHour = 6;
43 var RESOURCE_DEFAULT_GRANULARITY    = 1800 /* s */; // should be computed automatically from resource information
44 var DEFAULT_GRANULARITY             = 1800 /* s */; // should be computed automatically from resource information. Test with 600
45 var DEFAULT_PAGE_RANGE = 5;
46
47 var schedulerMaxRows = 12;
48
49 /* All resources */
50 var SchedulerData = [];
51
52 /* ??? */
53 var SchedulerSlots = [];
54
55 var SchedulerDateSelected = new Date();
56 // Round to midnight
57 SchedulerDateSelected.setHours(0,0,0,0);
58
59 /* Filtered resources */
60 var SchedulerDataViewData = [];
61
62 var SchedulerSlotsViewData = [];
63 //Help Variables
64 var _schedulerCurrentCellPosition = 0;
65 //Enable Debug
66 var schedulerDebug = true;
67 //tmp to delete
68 var tmpSchedulerLeases = [];
69
70 var SCHEDULER_COLWIDTH = 50;
71
72
73 /******************************************************************************
74  *                             ANGULAR CONTROLLER                             *
75  ******************************************************************************/
76
77 // Create a private execution space for our controller. When
78 // executing this function expression, we're going to pass in
79 // the Angular reference and our application module.
80 (function (ng, app) {
81
82     // Define our Controller constructor.
83     function Controller($scope) {
84
85         // Store the scope so we can reference it in our
86         // class methods
87         this.scope = $scope;
88
89         // Set up the default scope value.
90         this.scope.errorMessage = null;
91         this.scope.name = "";
92
93         //Pagin
94         $scope.current_page = 1;
95         this.scope.items_per_page = 10;
96         $scope.from = 0; // JORDAN
97
98         $scope.instance = null;
99         $scope.resources = new Array();
100         $scope.slots = SchedulerSlotsViewData;
101         $scope.granularity = DEFAULT_GRANULARITY; /* Will be setup */
102         //$scope.msg = "hello";
103
104         angular.element(document).ready(function() {
105             //console.log('Hello World');
106             //alert('Hello World');
107             //afterAngularRendered();
108         });
109
110         // Pagination
111
112         $scope.range = function() {
113             var range_size = $scope.page_count() > DEFAULT_PAGE_RANGE ? DEFAULT_PAGE_RANGE : $scope.page_count();
114             var ret = [];
115             var start;
116
117             start = $scope.current_page;
118             if ( start > $scope.page_count()-range_size ) {
119               start = $scope.page_count()-range_size+1;
120             }
121
122             for (var i=start; i<start+range_size; i++) {
123               ret.push(i);
124             }
125             return ret;
126         };
127
128         $scope.prevPage = function() {
129           if ($scope.current_page > 1) {
130             $scope.current_page--;
131           }
132         };
133
134         $scope.prevPageDisabled = function() {
135           return $scope.current_page === 1 ? "disabled" : "";
136         };
137   
138         $scope.page_count = function()
139         {
140             // XXX need visible resources only
141             var query_ext, visible_resources_length;
142             if (!$scope.instance)
143                 return 0;
144             query_ext = manifold.query_store.find_analyzed_query_ext($scope.instance.options.query_uuid);
145             var visible_resources_length = 0;
146             query_ext.state.each(function(i, state) {
147                 if (state[STATE_VISIBLE])
148                     visible_resources_length++;
149             });
150             return Math.ceil(visible_resources_length/$scope.items_per_page);
151         };
152   
153         $scope.nextPage = function() {
154           if ($scope.current_page < $scope.page_count()) {
155             $scope.current_page++;
156           }
157         };
158   
159         $scope.nextPageDisabled = function() {
160           return $scope.current_page === $scope.page_count() ? "disabled" : "";
161         }; 
162
163         $scope.setPage = function(n) {
164             $scope.current_page = n;
165         };
166         // END pagination
167
168         // FILTER
169
170         $scope.filter_visible = function(resource)
171         {
172             return manifold.query_store.get_record_state($scope.instance.options.query_uuid, resource['urn'], STATE_VISIBLE);
173         };
174
175         // SELECTION
176
177         $scope._create_new_lease = function(resource_urn, start_time, end_time)
178         {
179             var lease_key, new_lease, data;
180
181             lease_key = manifold.metadata.get_key('lease');
182
183             new_lease = {
184                 resource:   resource_urn,
185                 start_time: start_time,
186                 end_time:   end_time,
187             };
188
189             // This is needed to create a hashable object
190             new_lease.hashCode = manifold.record_hashcode(lease_key.sort());
191             new_lease.equals   = manifold.record_equals(lease_key);
192
193             data = {
194                 state: STATE_SET,
195                 key  : null,
196                 op   : STATE_SET_ADD,
197                 value: new_lease
198             }
199             manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);
200
201             /* Add to local cache also, unless we listen to events from outside */
202             if (!(resource_urn in $scope._leases_by_resource)){
203                 $scope._leases_by_resource[resource_urn] = [];
204                 /* Add the resource of the selected timeslot to the pending list */
205                 data_resource = {
206                     state: STATE_SET,
207                     key  : null,
208                     op   : STATE_SET_ADD,
209                     value: resource_urn
210                 };
211                 manifold.raise_event($scope.instance.options.query_uuid, FIELD_STATE_CHANGED, data_resource);
212             }
213             $scope._leases_by_resource[resource_urn].push(new_lease);
214
215         }
216
217         $scope._remove_lease = function(other)
218         {
219             var lease_key, other_key, data;
220
221             lease_key = manifold.metadata.get_key('lease');
222
223             // XXX This could be a manifold.record_get_value
224             other_key = {
225                 resource:   other.resource,
226                 start_time: other.start_time,
227                 end_time:   other.end_time
228             }
229             other_key.hashCode = manifold.record_hashcode(lease_key.sort());
230             other_key.equals   = manifold.record_equals(lease_key);
231
232             data = {
233                 state: STATE_SET,
234                 key  : null,
235                 op   : STATE_SET_REMOVE,
236                 value: other_key
237             }
238             manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);
239             /* Remove from local cache also, unless we listen to events from outside */
240             $scope._leases_by_resource[other.resource] = $.grep($scope._leases_by_resource[other.resource], function(x) { return x != other; });
241
242         }
243
244         $scope.select = function(index, model_lease, model_resource)
245         {
246             var data, resource_granularity;
247
248             //resource_granularity = model_resource.granularity === undefined ? RESOURCE_DEFAULT_GRANULARITY : model_resource.granularity;
249
250             console.log("Selected", index, model_lease, model_resource);
251
252             var day_timestamp = SchedulerDateSelected.getTime() / 1000;
253             var start_time = day_timestamp + index       * model_resource.granularity; // XXX resource_granularity
254             var end_time   = day_timestamp + (index + 1) * model_resource.granularity; //
255             var start_date = new Date(start_time * 1000);
256             var end_date   = new Date(end_time   * 1000);
257
258             var lease_key = manifold.metadata.get_key('lease');
259
260             // We search for leases in the cache we previously constructed
261             var resource_leases = $scope._leases_by_resource[model_resource.urn];
262
263             switch (model_lease.status)
264             {
265                 case 'free': // out
266                 case 'pendingout':
267                     if (resource_leases) {
268                         /* Search for leases before */
269                         $.each(resource_leases, function(i, other) {
270                             if (other.end_time != start_time)
271                                 return true; // ~ continue
272         
273                             /* The lease 'other' is just before, and there should not exist
274                              * any other lease before it */
275                             start_time = other.start_time;
276         
277                             other_key = {
278                                 resource:   other.resource,
279                                 start_time: other.start_time,
280                                 end_time:   other.end_time
281                             }
282                             // This is needed to create a hashable object
283                             other_key.hashCode = manifold.record_hashcode(lease_key.sort());
284                             other_key.equals   = manifold.record_equals(lease_key);
285         
286                             data = {
287                                 state: STATE_SET,
288                                 key  : null,
289                                 op   : STATE_SET_REMOVE,
290                                 value: other_key
291                             }
292                             manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);
293                             /* Remove from local cache also, unless we listen to events from outside */
294                             $scope._leases_by_resource[model_resource.urn] = $.grep($scope._leases_by_resource[model_resource.urn], function(x) { return x != other; });
295                             return false; // ~ break
296                         });
297         
298                         /* Search for leases after */
299                         $.each(resource_leases, function(i, other) {
300                             if (other.start_time != end_time)
301                                 return true; // ~ continue
302         
303                             /* The lease 'other' is just after, and there should not exist
304                              * any other lease after it */
305                             end_time = other.end_time;
306                             other_key = {
307                                 resource:   other.resource,
308                                 start_time: other.start_time,
309                                 end_time:   other.end_time
310                             }
311                             // This is needed to create a hashable object
312                             other_key.hashCode = manifold.record_hashcode(lease_key.sort());
313                             other_key.equals   = manifold.record_equals(lease_key);
314         
315                             data = {
316                                 state: STATE_SET,
317                                 key  : null,
318                                 op   : STATE_SET_REMOVE,
319                                 value: other_key
320                             }
321                             manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);
322                             /* Remove from local cache also, unless we listen to events from outside */
323                             $scope._leases_by_resource[model_resource.urn] = $.grep($scope._leases_by_resource[model_resource.urn], function(x) { return x != other; });
324                             return false; // ~ break
325                         });
326                     }
327         
328                     $scope._create_new_lease(model_resource.urn, start_time, end_time);
329                     model_lease.status = (model_lease.status == 'free') ? 'pendingin' : 'selected';
330                     // unless the exact same lease already existed (pending_out status for the lease, not the cell !!)
331
332                     break;
333
334                 case 'selected':
335                 case 'pendingin':
336                     // We remove the cell
337
338                     /* We search for leases including this cell. Either 0, 1 or 2.
339                      * 0 : NOT POSSIBLE, should be checked.
340                      * 1 : either IN or OUT, we have make no change in the session
341                      * 2 : both will be pending, since we have made a change in the session
342                     * /!\ need to properly remove pending_in leases when removed again
343                      */
344                     if (resource_leases) {
345                         $.each(resource_leases, function(i, other) {
346                             if ((other.start_time <= start_time) && (other.end_time >= end_time)) {
347                                 // The cell is part of this lease.
348
349                                 // If the cell is not at the beginning of the lease, we recreate a lease with cells before
350                                 if (start_time > other.start_time) {
351                                     $scope._create_new_lease(model_resource.urn, other.start_time, start_time);
352                                 }
353
354                                 // If the cell is not at the end of the lease, we recreate a lease with cells after
355                                 if (end_time < other.end_time) {
356                                     $scope._create_new_lease(model_resource.urn, end_time, other.end_time);
357                                 }
358                                 
359                                 // The other lease will be removed
360                                 $scope._remove_lease(other);
361                             }
362                             // NOTE: We can interrupt the search if we know that there is a single lease (depending on the status).
363                         });
364                     }
365                 
366                     // cf comment in previous switch case
367                     model_lease.status = (model_lease.status == 'selected') ? 'pendingout' : 'free';
368
369                     break;
370
371                 case 'reserved':
372                 case 'maintainance':
373                     // Do nothing
374                     break;
375             }
376             
377
378             //$scope._dump_leases();
379         };
380   
381         $scope._dump_leases = function()
382         {
383             // DEBUG: display all leases and their status in the log
384             var leases = manifold.query_store.get_records($scope.instance.options.query_lease_uuid);
385             console.log("--------------------");
386             $.each(leases, function(i, lease) {
387                 var key = manifold.metadata.get_key('lease');
388                 var lease_key = manifold.record_get_value(lease, key);
389                 var state = manifold.query_store.get_record_state($scope.instance.options.query_lease_uuid, lease_key, STATE_SET);
390                 var state_str;
391                 switch(state) {
392                     case STATE_SET_IN:
393                         state_str = 'STATE_SET_IN';
394                         break;
395                     case STATE_SET_OUT:
396                         state_str = 'STATE_SET_OUT';
397                         break;
398                     case STATE_SET_IN_PENDING:
399                         state_str = 'STATE_SET_IN_PENDING';
400                         break;
401                     case STATE_SET_OUT_PENDING:
402                         state_str = 'STATE_SET_OUT_PENDING';
403                         break;
404                     case STATE_SET_IN_SUCCESS:
405                         state_str = 'STATE_SET_IN_SUCCESS';
406                         break;
407                     case STATE_SET_OUT_SUCCESS:
408                         state_str = 'STATE_SET_OUT_SUCCESS';
409                         break;
410                     case STATE_SET_IN_FAILURE:
411                         state_str = 'STATE_SET_IN_FAILURE';
412                         break;
413                     case STATE_SET_OUT_FAILURE:
414                         state_str = 'STATE_SET_OUT_FAILURE';
415                         break;
416                 }
417                 console.log("LEASE", new Date(lease.start_time * 1000), new Date(lease.end_time * 1000), lease.resource, state_str);
418             });
419         };
420
421         // Return this object reference.
422         return (this);
423
424     }
425
426     // Define the Controller as the constructor function.
427     app.controller("SchedulerCtrl", Controller);
428
429 })(angular, ManifoldApp);
430
431 /******************************************************************************
432  *                              MANIFOLD PLUGIN                               *
433  ******************************************************************************/
434
435 (function($) {
436         scheduler2 = Plugin.extend({
437
438             /** XXX to check
439          * @brief Plugin constructor
440          * @param options : an associative array of setting values
441          * @param element : 
442          * @return : a jQuery collection of objects on which the plugin is
443          *     applied, which allows to maintain chainability of calls
444          */
445             init: function(options, element) {
446                 // Call the parent constructor, see FAQ when forgotten
447                 this._super(options, element);
448
449                 var scope = this._get_scope()
450                 scope.instance = this;
451
452                 // XXX not needed
453                 scheduler2Instance = this;
454
455                 // We need to remember the active filter for datatables filtering
456                 // XXX not needed
457                 this.filters = Array();
458
459                 // XXX BETTER !!!!
460                 $(window).delegate('*', 'keypress', function (evt){
461                         alert("erm");
462                       });
463
464                 $(window).keydown(function(evt) {
465                     if (evt.which == 17) { // ctrl
466                         schedulerCtrlPressed = true;
467                     }
468                 }).keyup(function(evt) {
469                     if (evt.which == 17) { // ctrl
470                         schedulerCtrlPressed = false;
471                     }
472                 });
473
474                 // XXX naming
475                 //$("#" + schedulerTblId).on('mousedown', 'td', rangeMouseDown).on('mouseup', 'td', rangeMouseUp).on('mousemove', 'td', rangeMouseMove);
476
477                 this._resources_received = false;
478                 this._leases_received = false;
479                 
480                 scope._leases_by_resource = {};
481
482                 /* Listening to queries */
483                 this.listen_query(options.query_uuid, 'resources');
484                 this.listen_query(options.query_lease_uuid, 'leases');
485
486                 this.elmt().on('show', this, this.on_show);
487                 this.elmt().on('shown.bs.tab', this, this.on_show);
488                 this.elmt().on('resize', this, this.on_resize);
489
490                 /* Generate slots according to the default granularity. Should
491                  * be updated when resources arrive.  Should be the pgcd in fact XXX */
492                 this._granularity = DEFAULT_GRANULARITY;
493                 scope.granularity = this._granularity;
494                 this.scope_resources_by_key = {};
495
496                 this.do_resize();
497     
498                 scope.from = 0;
499
500                 this._initUI();
501
502             },
503
504             do_resize: function()
505             {
506                 var scope = this._get_scope();
507                 var num_hidden_cells, new_max, lcm;
508
509                 // do_resize has to be called when the window is resized, or one parameter changes
510                 // e.g. when new resources have been received
511                 //
512                 this.resource_granularities = [3600, 1800]; //, 2400]; /* s */
513
514                 /* Compute the slot length to accommodate all resources. This
515                  * is the GCD of all resource granularities. */
516                 this._slot_length = this._gcdn(this.resource_granularities);
517
518                 $('#' + schedulerTblId + ' thead tr th:eq(0)').css("width", SCHEDULER_FIRST_COLWIDTH);
519                 //self get width might need fix depending on the template 
520                 var tblwidth = $('#scheduler-reservation-table').parent().outerWidth();
521
522                 /* Number of visible cells...*/
523                 this._num_visible_cells = parseInt((tblwidth - SCHEDULER_FIRST_COLWIDTH) / SCHEDULER_COLWIDTH);
524
525                 /* ...should be a multiple of the lcm of all encountered granularities. */
526                 lcm = this._lcmn(this.resource_granularities) / this._slot_length;
527                 this._num_visible_cells = this._num_visible_cells - this._num_visible_cells % lcm;
528
529                 // A list of {id, time} dictionaries representing the slots for the given day
530                 this._all_slots = this._generate_all_slots();
531
532                 /* scope also needs this value */
533                 scope.slots = this._all_slots;
534                 scope.slot_length = this._slot_length;
535                 scope.num_visible_cells = this._num_visible_cells;
536                 scope.lcm_colspan = this._lcmn(this.resource_granularities); // XXX WHY ?
537
538                 /* Redraw... */
539                 this._scope_clear_leases();
540                 this._set_all_lease_slots();
541
542                 // Slider max value
543                 if ($('#tblSlider').data('slider') != undefined) {
544                     num_hidden_cells = this._all_slots.length - this._num_visible_cells;
545
546                     $('#tblSlider').slider('setAttribute', 'max', num_hidden_cells);
547                     $('#tblSlider').slider('setValue', scope.from, true);
548                 }
549                 this._get_scope().$apply();
550
551
552             },
553
554             on_show: function(e)
555             {
556                 var self = e.data;
557                 self.do_resize();
558                 self._get_scope().$apply();
559             },
560
561             on_resize: function(e)
562             {
563                 var self = e.data;
564                 self.do_resize();
565                 self._get_scope().$apply();
566             },
567
568             /* Handlers */
569
570             _get_scope : function()
571             {
572                 return angular.element(document.getElementById('SchedulerCtrl')).scope();
573             },
574             
575             _scope_set_resources : function()
576             {
577                 var self = this;
578                 var scope = this._get_scope();
579
580                 var records = manifold.query_store.get_records(this.options.query_uuid);
581
582                 scope.resources = [];
583
584                 $.each(records, function(i, record) {
585                     if (!record.exclusive)
586                         return true; // ~ continue
587
588                     // copy not to modify original record
589                     var resource = jQuery.extend(true, {}, record);
590
591                     // Fix granularity
592                     //resource_granularity = ((resource.granularity === undefined) || (typeof(resource.granularity) != "number")) ? RESOURCE_DEFAULT_GRANULARITY : resource.granularity;
593                     if (typeof(resource.granularity) != "number")
594                         resource.granularity = RESOURCE_DEFAULT_GRANULARITY;
595                     resource.leases = []; // a list of occupied timeslots
596
597                     self.scope_resources_by_key[resource['urn']] = resource;
598                     scope.resources.push(resource);
599                 });
600             },
601
602             _scope_clear_leases: function()
603             {
604                 var time, now;
605                 var self = this;
606                 var scope = this._get_scope();
607
608                 now = new Date().getTime();
609
610                 // Setup leases with a default free status...
611                 $.each(this.scope_resources_by_key, function(resource_key, resource) {
612                     resource.leases = [];
613                     var colspan_lease = resource.granularity / self._slot_length; //eg. 3600 / 1800 => 2 cells
614                     time = SchedulerDateSelected.getTime();
615                     for (i=0; i < self._all_slots.length / colspan_lease; i++) { // divide by granularity
616                         resource.leases.push({
617                             id:     'coucou',
618                             status: (time < now) ? 'disabled':  'free', // 'selected', 'reserved', 'maintenance' XXX pending ??
619                         });
620                         time += resource.granularity * 1000;
621                     }
622                 });
623
624             },
625
626             _scope_set_leases: function()
627             {
628                     var status;
629                 var self = this;
630                 var scope = this._get_scope();
631             
632                 manifold.query_store.iter_records(this.options.query_lease_uuid, function(lease_key, lease) {
633                     //console.log("SET LEASES", lease.resource, new Date(lease.start_time* 1000), new Date(lease.end_time* 1000));
634                     // XXX We should ensure leases are correctly merged, otherwise our algorithm won't work
635
636                     // Populate leases by resource array: this will help us merging leases later
637
638                     // let's only put _our_ leases
639                     lease_status = manifold.query_store.get_record_state(self.options.query_lease_uuid, lease_key, STATE_SET);
640                     if (lease_status != STATE_SET_IN)
641                         return true; // ~continue
642                     if (!(lease.resource in scope._leases_by_resource))
643                         scope._leases_by_resource[lease.resource] = [];
644                     scope._leases_by_resource[lease.resource].push(lease);
645
646                 });
647
648                 this._set_all_lease_slots();
649             },
650
651             _set_all_lease_slots: function()
652             {
653                 var self = this;
654             
655                 manifold.query_store.iter_records(this.options.query_lease_uuid, function(lease_key, lease) {
656                     self._set_lease_slots(lease_key, lease);
657                 });
658             },
659
660             on_resources_query_done: function(data)
661             {
662                 this._resources_received = true;
663                 this._scope_set_resources();
664                 this._scope_clear_leases();
665                 if (this._leases_received)
666                     this._scope_set_leases();
667                     
668                 this._get_scope().$apply();
669             },
670
671             on_leases_query_done: function(data)
672             {
673                 this._leases_received = true;
674                 if (this._resources_received) {
675                     this._scope_set_leases();
676                     this._get_scope().$apply();
677                 }
678             },
679
680             /* Filters on resources */
681             on_resources_filter_added:   function(filter) { this._get_scope().$apply(); },
682             on_resources_filter_removed: function(filter) { this._get_scope().$apply(); },
683             on_resources_filter_clear:   function()       { this._get_scope().$apply(); },
684
685             /* Filters on leases ? */
686             on_leases_filter_added:      function(filter) { this._get_scope().$apply(); },
687             on_leases_filter_removed:    function(filter) { this._get_scope().$apply(); },
688             on_leases_filter_clear:      function()       { this._get_scope().$apply(); },
689
690             on_field_state_changed: function(data)
691             {
692                 /*
693                 this._set_lease_slots(lease_key, lease);
694
695                 switch(data.state) {
696                     case STATE_SET:
697                         switch(data.op) {
698                             case STATE_SET_IN:
699                             case STATE_SET_IN_SUCCESS:
700                             case STATE_SET_OUT_FAILURE:
701                                 this.set_checkbox_from_data(data.value, true);
702                                 this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_RESET);
703                                 break;  
704                             case STATE_SET_OUT:
705                             case STATE_SET_OUT_SUCCESS:
706                             case STATE_SET_IN_FAILURE:
707                                 this.set_checkbox_from_data(data.value, false);
708                                 this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_RESET);
709                                 break;
710                             case STATE_SET_IN_PENDING:
711                                 this.set_checkbox_from_data(data.key, true);
712                                 this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_ADDED);
713                                 break;  
714                             case STATE_SET_OUT_PENDING:
715                                 this.set_checkbox_from_data(data.key, false);
716                                 this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_REMOVED);
717                                 break;
718                         }
719                         break;
720
721                     case STATE_WARNINGS:
722                         this.change_status(data.key, data.value);
723                         break;
724                 }
725                 */
726             },
727
728
729             /* INTERNAL FUNCTIONS */
730
731             _set_lease_slots: function(lease_key, lease)
732             {
733                 var resource, lease_status, lease_class;
734                 var day_timestamp, id_start, id_end, colspan_lease;
735
736                 resource = this.scope_resources_by_key[lease.resource];
737                 day_timestamp = SchedulerDateSelected.getTime() / 1000;
738                 if(resource === undefined){
739                     console.log('resource undefined = '+lease.resource);
740                     return;
741                 }
742                 id_start = Math.floor((lease.start_time - day_timestamp) / resource.granularity);
743
744                 /* Some leases might be in the past */
745                 if (id_start < 0)
746                     id_start = 0;
747                 /* Leases in the future: ignore */
748                 if (id_start >= this._all_slots.length)
749                     return true; // ~ continue
750
751                 id_end   = Math.ceil((lease.end_time   - day_timestamp) / resource.granularity);
752                 colspan_lease = resource.granularity / this._slot_length; //eg. 3600 / 1800 => 2 cells
753                 if (id_end >= this._all_slots.length / colspan_lease) {
754                     /* Limit the display to the current day */
755                     id_end = this._all_slots.length / colspan_lease
756                 }
757                 lease_status = manifold.query_store.get_record_state(this.options.query_lease_uuid, lease_key, STATE_SET);
758                 // the same slots might be affected multiple times.
759                 // PENDING_IN + PENDING_OUT => IN 
760                 //
761                 // RESERVED vs SELECTED !
762                 //
763                 // PENDING !!
764                 switch(lease_status) {
765                     case STATE_SET_IN:
766                         lease_class = 'selected'; // my leases
767                         lease_success = '';
768                         break;
769                     case STATE_SET_IN_SUCCESS:
770                         lease_class = 'selected'; // my leases
771                         lease_success = 'success';
772                     case STATE_SET_OUT_FAILURE:
773                         lease_class = 'selected'; // my leases
774                         lease_success = 'failure';
775                         break;
776                     case STATE_SET_OUT:
777                         lease_class = 'reserved'; // other leases
778                         lease_success = '';
779                         break;
780                     case STATE_SET_OUT_SUCCESS:
781                         lease_class = 'free'; // other leases
782                         lease_success = 'success';
783                         break;
784                     case STATE_SET_IN_FAILURE:
785                         lease_class = 'free'; // other leases
786                         lease_success = 'failure';
787                         break;
788                     case STATE_SET_IN_PENDING:
789                         lease_class = 'pendingin';
790                         lease_success = '';
791                         break;
792                     case STATE_SET_OUT_PENDING:
793                         // pending_in & pending_out == IN == replacement
794                         if (resource.leases[i].status == 'pendingin')
795                             lease_class = 'pendingin'
796                         else
797                             lease_class = 'pendingout';
798                         lease_success = '';
799                         break;
800                 
801                 }
802
803                 for (i = id_start; i < id_end; i++) {
804                     resource.leases[i].status = lease_class;
805                     resource.leases[i].success = lease_success;
806                 }
807             },
808
809 /* XXX IN TEMPLATE XXX
810                 if (SchedulerDataViewData.length == 0) {
811                     $("#plugin-scheduler").hide();
812                     $("#plugin-scheduler-empty").show();
813                     tmpScope.clearStuff();
814                 } else {
815                     $("#plugin-scheduler-empty").hide();
816                     $("#plugin-scheduler").show();
817                     // initSchedulerResources
818                     tmpScope.initSchedulerResources(schedulerMaxRows < SchedulerDataViewData.length ? schedulerMaxRows : SchedulerDataViewData.length);
819                 }
820 */
821
822             /**
823              * Initialize the date picker, the table, the slider and the buttons. Once done, display scheduler.
824              */
825             _initUI: function() 
826             {
827                 var self = this;
828                 var scope = self._get_scope();
829
830                 var num_hidden_cells;
831
832                 $("#DateToRes").datepicker({
833                         dateFormat: "D, d M yy",
834                     onRender: function(date) {
835                         return date.valueOf() < now.valueOf() ? 'disabled' : '';
836                     }
837                 }).on('changeDate', function(ev) {
838                     SchedulerDateSelected = new Date(ev.date);
839                     SchedulerDateSelected.setHours(0,0,0,0);
840                     // Set slider to origin
841                     //$('#tblSlider').slider('setValue', 0); // XXX
842                     // Refresh leases
843                     self._scope_clear_leases();
844                     self._set_all_lease_slots();
845                     // Refresh display
846                     self._get_scope().$apply();
847                 }).datepicker('setValue', SchedulerDateSelected); //.data('datepicker');
848
849                 //init Slider
850                 num_hidden_cells = self._all_slots.length - self._num_visible_cells;
851                 init_cell = (new Date().getHours() - 1) * 3600 / self._granularity;
852                 if (init_cell > num_hidden_cells)
853                     init_cell = num_hidden_cells;
854
855                 $('#tblSlider').slider({
856                     min: 0,
857                     max: num_hidden_cells,
858                     value: init_cell,
859                 }).on('slide', function(ev) {
860                     var scope = self._get_scope();
861                     scope.from = ev.value;
862                     scope.$apply();
863                 });
864                 scope.from = init_cell;
865                 scope.$apply();
866
867                 $("#plugin-scheduler-loader").hide();
868                 $("#plugin-scheduler").show();
869             },
870
871         // PRIVATE METHODS
872
873         /**
874          * Greatest common divisor
875          */
876         _gcd : function(x, y)
877         {
878             return (y==0) ? x : this._gcd(y, x % y);
879         },
880
881         _gcdn : function(array)
882         {
883             var self = this;
884             return array.reduce(function(prev, cur, idx, arr) { return self._gcd(prev, cur); });
885         },
886
887         /**
888          * Least common multiple
889          */
890         _lcm : function(x, y)
891         {
892             return x * y / this._gcd(x, y);
893         },
894
895         _lcmn : function(array)
896         {
897             var self = this;
898             return array.reduce(function(prev, cur, idx, arr) { return self._lcm(prev, cur); });
899         },
900     
901         _pad_str : function(i)
902         {
903             return (i < 10) ? "0" + i : "" + i;
904         },
905
906         /**
907          * Member variables used:
908          *   _granularity
909          * 
910          * Returns:
911          *   A list of {id, time} dictionaries.
912          */
913         _generate_all_slots: function()
914         {
915             var slots = [];
916             // Start with a random date (a first of a month), only time will matter
917             var d = new Date(2014, 1, 1, 0, 0, 0, 0);
918             var i = 0;
919             // Loop until we change the day
920             while (d.getDate() == 1) {
921                 // Nicely format the time...
922                 var tmpTime = this._pad_str(d.getHours()) + ':' + this._pad_str(d.getMinutes());
923                 /// ...and add the slot to the list of results
924                 slots.push({ id: i, time: tmpTime });
925                 // Increment the date with the granularity
926                 d = new Date(d.getTime() + this._slot_length * 1000);
927                 i++;
928             }
929             return slots;
930
931         },
932     });
933
934     /* Plugin registration */
935     $.plugin('Scheduler2', scheduler2);
936
937 })(jQuery);
938
939
940