6ef254922d940632d8f12d8ce340e1e00dc83bdb
[myslice.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             /* Last lease removed for this resource -> remove the resource from the list */
242             if($scope._leases_by_resource.hasOwnProperty(other.resource) && $scope._leases_by_resource[other.resource].length == 0){
243                 /* remove resource from the list of selected resources */
244                  data_resource = {
245                     state: STATE_SET,
246                     key  : null,
247                     op   : STATE_SET_REMOVE,
248                     value: other.resource
249                 };
250                 manifold.raise_event($scope.instance.options.query_uuid, FIELD_STATE_CHANGED, data_resource);
251                
252             }
253         }
254
255         $scope.select = function(index, model_lease, model_resource)
256         {
257             var data, resource_granularity;
258
259             //resource_granularity = model_resource.granularity === undefined ? RESOURCE_DEFAULT_GRANULARITY : model_resource.granularity;
260
261             console.log("Selected", index, model_lease, model_resource);
262
263             var day_timestamp = SchedulerDateSelected.getTime() / 1000;
264             var start_time = day_timestamp + index       * model_resource.granularity; // XXX resource_granularity
265             var end_time   = day_timestamp + (index + 1) * model_resource.granularity; //
266             var start_date = new Date(start_time * 1000);
267             var end_date   = new Date(end_time   * 1000);
268
269             var lease_key = manifold.metadata.get_key('lease');
270
271             // We search for leases in the cache we previously constructed
272             var resource_leases = $scope._leases_by_resource[model_resource.urn];
273
274             switch (model_lease.status)
275             {
276                 case 'free': // out
277                 case 'pendingout':
278                     if (resource_leases) {
279                         /* Search for leases before */
280                         $.each(resource_leases, function(i, other) {
281                             if (other.end_time != start_time)
282                                 return true; // ~ continue
283         
284                             /* The lease 'other' is just before, and there should not exist
285                              * any other lease before it */
286                             start_time = other.start_time;
287         
288                             other_key = {
289                                 resource:   other.resource,
290                                 start_time: other.start_time,
291                                 end_time:   other.end_time
292                             }
293                             // This is needed to create a hashable object
294                             other_key.hashCode = manifold.record_hashcode(lease_key.sort());
295                             other_key.equals   = manifold.record_equals(lease_key);
296         
297                             data = {
298                                 state: STATE_SET,
299                                 key  : null,
300                                 op   : STATE_SET_REMOVE,
301                                 value: other_key
302                             }
303                             manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);
304                             /* Remove from local cache also, unless we listen to events from outside */
305                             $scope._leases_by_resource[model_resource.urn] = $.grep($scope._leases_by_resource[model_resource.urn], function(x) { return x != other; });
306                             return false; // ~ break
307                         });
308         
309                         /* Search for leases after */
310                         $.each(resource_leases, function(i, other) {
311                             if (other.start_time != end_time)
312                                 return true; // ~ continue
313         
314                             /* The lease 'other' is just after, and there should not exist
315                              * any other lease after it */
316                             end_time = other.end_time;
317                             other_key = {
318                                 resource:   other.resource,
319                                 start_time: other.start_time,
320                                 end_time:   other.end_time
321                             }
322                             // This is needed to create a hashable object
323                             other_key.hashCode = manifold.record_hashcode(lease_key.sort());
324                             other_key.equals   = manifold.record_equals(lease_key);
325         
326                             data = {
327                                 state: STATE_SET,
328                                 key  : null,
329                                 op   : STATE_SET_REMOVE,
330                                 value: other_key
331                             }
332                             manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);
333                             /* Remove from local cache also, unless we listen to events from outside */
334                             $scope._leases_by_resource[model_resource.urn] = $.grep($scope._leases_by_resource[model_resource.urn], function(x) { return x != other; });
335                             return false; // ~ break
336                         });
337                     }
338         
339                     $scope._create_new_lease(model_resource.urn, start_time, end_time);
340                     model_lease.status = (model_lease.status == 'free') ? 'pendingin' : 'selected';
341                     // unless the exact same lease already existed (pending_out status for the lease, not the cell !!)
342
343                     break;
344
345                 case 'selected':
346                 case 'pendingin':
347                     // We remove the cell
348
349                     /* We search for leases including this cell. Either 0, 1 or 2.
350                      * 0 : NOT POSSIBLE, should be checked.
351                      * 1 : either IN or OUT, we have make no change in the session
352                      * 2 : both will be pending, since we have made a change in the session
353                     * /!\ need to properly remove pending_in leases when removed again
354                      */
355                     if (resource_leases) {
356                         $.each(resource_leases, function(i, other) {
357                             if ((other.start_time <= start_time) && (other.end_time >= end_time)) {
358                                 // The cell is part of this lease.
359
360                                 // If the cell is not at the beginning of the lease, we recreate a lease with cells before
361                                 if (start_time > other.start_time) {
362                                     $scope._create_new_lease(model_resource.urn, other.start_time, start_time);
363                                 }
364
365                                 // If the cell is not at the end of the lease, we recreate a lease with cells after
366                                 if (end_time < other.end_time) {
367                                     $scope._create_new_lease(model_resource.urn, end_time, other.end_time);
368                                 }
369                                 
370                                 // The other lease will be removed
371                                 $scope._remove_lease(other);
372                             }
373                             // NOTE: We can interrupt the search if we know that there is a single lease (depending on the status).
374                         });
375                     }
376                 
377                     // cf comment in previous switch case
378                     model_lease.status = (model_lease.status == 'selected') ? 'pendingout' : 'free';
379
380                     break;
381
382                 case 'reserved':
383                 case 'maintainance':
384                     // Do nothing
385                     break;
386             }
387             
388
389             //$scope._dump_leases();
390         };
391   
392         $scope._dump_leases = function()
393         {
394             // DEBUG: display all leases and their status in the log
395             var leases = manifold.query_store.get_records($scope.instance.options.query_lease_uuid);
396             console.log("--------------------");
397             $.each(leases, function(i, lease) {
398                 var key = manifold.metadata.get_key('lease');
399                 var lease_key = manifold.record_get_value(lease, key);
400                 var state = manifold.query_store.get_record_state($scope.instance.options.query_lease_uuid, lease_key, STATE_SET);
401                 var state_str;
402                 switch(state) {
403                     case STATE_SET_IN:
404                         state_str = 'STATE_SET_IN';
405                         break;
406                     case STATE_SET_OUT:
407                         state_str = 'STATE_SET_OUT';
408                         break;
409                     case STATE_SET_IN_PENDING:
410                         state_str = 'STATE_SET_IN_PENDING';
411                         break;
412                     case STATE_SET_OUT_PENDING:
413                         state_str = 'STATE_SET_OUT_PENDING';
414                         break;
415                     case STATE_SET_IN_SUCCESS:
416                         state_str = 'STATE_SET_IN_SUCCESS';
417                         break;
418                     case STATE_SET_OUT_SUCCESS:
419                         state_str = 'STATE_SET_OUT_SUCCESS';
420                         break;
421                     case STATE_SET_IN_FAILURE:
422                         state_str = 'STATE_SET_IN_FAILURE';
423                         break;
424                     case STATE_SET_OUT_FAILURE:
425                         state_str = 'STATE_SET_OUT_FAILURE';
426                         break;
427                 }
428                 console.log("LEASE", new Date(lease.start_time * 1000), new Date(lease.end_time * 1000), lease.resource, state_str);
429             });
430         };
431
432         // Return this object reference.
433         return (this);
434
435     }
436
437     // Define the Controller as the constructor function.
438     app.controller("SchedulerCtrl", Controller);
439
440 })(angular, ManifoldApp);
441
442 /******************************************************************************
443  *                              MANIFOLD PLUGIN                               *
444  ******************************************************************************/
445
446 (function($) {
447         scheduler2 = Plugin.extend({
448
449             /** XXX to check
450          * @brief Plugin constructor
451          * @param options : an associative array of setting values
452          * @param element : 
453          * @return : a jQuery collection of objects on which the plugin is
454          *     applied, which allows to maintain chainability of calls
455          */
456             init: function(options, element) {
457                 // Call the parent constructor, see FAQ when forgotten
458                 this._super(options, element);
459
460                 var scope = this._get_scope()
461                 scope.instance = this;
462
463                 // XXX not needed
464                 scheduler2Instance = this;
465
466                 // We need to remember the active filter for datatables filtering
467                 // XXX not needed
468                 this.filters = Array();
469
470                 // XXX BETTER !!!!
471                 $(window).delegate('*', 'keypress', function (evt){
472                         alert("erm");
473                       });
474
475                 $(window).keydown(function(evt) {
476                     if (evt.which == 17) { // ctrl
477                         schedulerCtrlPressed = true;
478                     }
479                 }).keyup(function(evt) {
480                     if (evt.which == 17) { // ctrl
481                         schedulerCtrlPressed = false;
482                     }
483                 });
484
485                 // XXX naming
486                 //$("#" + schedulerTblId).on('mousedown', 'td', rangeMouseDown).on('mouseup', 'td', rangeMouseUp).on('mousemove', 'td', rangeMouseMove);
487
488                 this._resources_received = false;
489                 this._leases_received = false;
490                 
491                 scope._leases_by_resource = {};
492
493                 /* Listening to queries */
494                 this.listen_query(options.query_uuid, 'resources');
495                 this.listen_query(options.query_lease_uuid, 'leases');
496
497                 this.elmt().on('show', this, this.on_show);
498                 this.elmt().on('shown.bs.tab', this, this.on_show);
499                 this.elmt().on('resize', this, this.on_resize);
500
501                 /* Generate slots according to the default granularity. Should
502                  * be updated when resources arrive.  Should be the pgcd in fact XXX */
503                 this._granularity = DEFAULT_GRANULARITY;
504                 scope.granularity = this._granularity;
505                 this.scope_resources_by_key = {};
506
507                 this.do_resize();
508     
509                 scope.from = 0;
510
511                 this._initUI();
512
513             },
514
515             do_resize: function()
516             {
517                 var scope = this._get_scope();
518                 var num_hidden_cells, new_max, lcm;
519
520                 // do_resize has to be called when the window is resized, or one parameter changes
521                 // e.g. when new resources have been received
522                 //
523                 this.resource_granularities = [3600, 1800]; //, 2400]; /* s */
524
525                 /* Compute the slot length to accommodate all resources. This
526                  * is the GCD of all resource granularities. */
527                 this._slot_length = this._gcdn(this.resource_granularities);
528
529                 $('#' + schedulerTblId + ' thead tr th:eq(0)').css("width", SCHEDULER_FIRST_COLWIDTH);
530                 //self get width might need fix depending on the template 
531                 var tblwidth = $('#scheduler-reservation-table').parent().outerWidth();
532
533                 /* Number of visible cells...*/
534                 this._num_visible_cells = parseInt((tblwidth - SCHEDULER_FIRST_COLWIDTH) / SCHEDULER_COLWIDTH);
535
536                 /* ...should be a multiple of the lcm of all encountered granularities. */
537                 lcm = this._lcmn(this.resource_granularities) / this._slot_length;
538                 this._num_visible_cells = this._num_visible_cells - this._num_visible_cells % lcm;
539
540                 // A list of {id, time} dictionaries representing the slots for the given day
541                 this._all_slots = this._generate_all_slots();
542
543                 /* scope also needs this value */
544                 scope.slots = this._all_slots;
545                 scope.slot_length = this._slot_length;
546                 scope.num_visible_cells = this._num_visible_cells;
547                 scope.lcm_colspan = this._lcmn(this.resource_granularities); // XXX WHY ?
548
549                 /* Redraw... */
550                 this._scope_clear_leases();
551                 this._set_all_lease_slots();
552
553                 // Slider max value
554                 if ($('#tblSlider').data('slider') != undefined) {
555                     num_hidden_cells = this._all_slots.length - this._num_visible_cells;
556
557                     $('#tblSlider').slider('setAttribute', 'max', num_hidden_cells);
558                     $('#tblSlider').slider('setValue', scope.from, true);
559                 }
560                 this._get_scope().$apply();
561
562
563             },
564
565             on_show: function(e)
566             {
567                 var self = e.data;
568                 self.do_resize();
569                 self._get_scope().$apply();
570             },
571
572             on_resize: function(e)
573             {
574                 var self = e.data;
575                 self.do_resize();
576                 self._get_scope().$apply();
577             },
578
579             /* Handlers */
580
581             _get_scope : function()
582             {
583                 return angular.element(document.getElementById('SchedulerCtrl')).scope();
584             },
585             
586             _scope_set_resources : function()
587             {
588                 var self = this;
589                 var scope = this._get_scope();
590
591                 var records = manifold.query_store.get_records(this.options.query_uuid);
592
593                 scope.resources = [];
594
595                 $.each(records, function(i, record) {
596                     if (!record.exclusive)
597                         return true; // ~ continue
598
599                     // copy not to modify original record
600                     var resource = jQuery.extend(true, {}, record);
601
602                     // Fix granularity
603                     //resource_granularity = ((resource.granularity === undefined) || (typeof(resource.granularity) != "number")) ? RESOURCE_DEFAULT_GRANULARITY : resource.granularity;
604                     if (typeof(resource.granularity) != "number")
605                         resource.granularity = RESOURCE_DEFAULT_GRANULARITY;
606                     resource.leases = []; // a list of occupied timeslots
607
608                     self.scope_resources_by_key[resource['urn']] = resource;
609                     scope.resources.push(resource);
610                 });
611             },
612
613             _scope_clear_leases: function()
614             {
615                 var time, now;
616                 var self = this;
617                 var scope = this._get_scope();
618
619                 now = new Date().getTime();
620
621                 // Setup leases with a default free status...
622                 $.each(this.scope_resources_by_key, function(resource_key, resource) {
623                     resource.leases = [];
624                     var colspan_lease = resource.granularity / self._slot_length; //eg. 3600 / 1800 => 2 cells
625                     time = SchedulerDateSelected.getTime();
626                     for (i=0; i < self._all_slots.length / colspan_lease; i++) { // divide by granularity
627                         resource.leases.push({
628                             id:     'coucou',
629                             status: (time < now) ? 'disabled':  'free', // 'selected', 'reserved', 'maintenance' XXX pending ??
630                         });
631                         time += resource.granularity * 1000;
632                     }
633                 });
634
635             },
636
637             _scope_set_leases: function()
638             {
639                     var status;
640                 var self = this;
641                 var scope = this._get_scope();
642             
643                 manifold.query_store.iter_records(this.options.query_lease_uuid, function(lease_key, lease) {
644                     //console.log("SET LEASES", lease.resource, new Date(lease.start_time* 1000), new Date(lease.end_time* 1000));
645                     // XXX We should ensure leases are correctly merged, otherwise our algorithm won't work
646
647                     // Populate leases by resource array: this will help us merging leases later
648
649                     // let's only put _our_ leases
650                     lease_status = manifold.query_store.get_record_state(self.options.query_lease_uuid, lease_key, STATE_SET);
651                     if (lease_status != STATE_SET_IN)
652                         return true; // ~continue
653                     if (!(lease.resource in scope._leases_by_resource))
654                         scope._leases_by_resource[lease.resource] = [];
655                     scope._leases_by_resource[lease.resource].push(lease);
656
657                 });
658
659                 this._set_all_lease_slots();
660             },
661
662             _set_all_lease_slots: function()
663             {
664                 var self = this;
665             
666                 manifold.query_store.iter_records(this.options.query_lease_uuid, function(lease_key, lease) {
667                     self._set_lease_slots(lease_key, lease);
668                 });
669             },
670
671             on_resources_query_done: function(data)
672             {
673                 this._resources_received = true;
674                 this._scope_set_resources();
675                 this._scope_clear_leases();
676                 if (this._leases_received)
677                     this._scope_set_leases();
678                     
679                 this._get_scope().$apply();
680             },
681
682             on_leases_query_done: function(data)
683             {
684                 this._leases_received = true;
685                 if (this._resources_received) {
686                     this._scope_set_leases();
687                     this._get_scope().$apply();
688                 }
689             },
690
691             /* Filters on resources */
692             on_resources_filter_added:   function(filter) { this._get_scope().$apply(); },
693             on_resources_filter_removed: function(filter) { this._get_scope().$apply(); },
694             on_resources_filter_clear:   function()       { this._get_scope().$apply(); },
695
696             /* Filters on leases ? */
697             on_leases_filter_added:      function(filter) { this._get_scope().$apply(); },
698             on_leases_filter_removed:    function(filter) { this._get_scope().$apply(); },
699             on_leases_filter_clear:      function()       { this._get_scope().$apply(); },
700
701             on_field_state_changed: function(data)
702             {
703                 /*
704                 this._set_lease_slots(lease_key, lease);
705
706                 switch(data.state) {
707                     case STATE_SET:
708                         switch(data.op) {
709                             case STATE_SET_IN:
710                             case STATE_SET_IN_SUCCESS:
711                             case STATE_SET_OUT_FAILURE:
712                                 this.set_checkbox_from_data(data.value, true);
713                                 this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_RESET);
714                                 break;  
715                             case STATE_SET_OUT:
716                             case STATE_SET_OUT_SUCCESS:
717                             case STATE_SET_IN_FAILURE:
718                                 this.set_checkbox_from_data(data.value, false);
719                                 this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_RESET);
720                                 break;
721                             case STATE_SET_IN_PENDING:
722                                 this.set_checkbox_from_data(data.key, true);
723                                 this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_ADDED);
724                                 break;  
725                             case STATE_SET_OUT_PENDING:
726                                 this.set_checkbox_from_data(data.key, false);
727                                 this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_REMOVED);
728                                 break;
729                         }
730                         break;
731
732                     case STATE_WARNINGS:
733                         this.change_status(data.key, data.value);
734                         break;
735                 }
736                 */
737             },
738
739
740             /* INTERNAL FUNCTIONS */
741
742             _set_lease_slots: function(lease_key, lease)
743             {
744                 var resource, lease_status, lease_class;
745                 var day_timestamp, id_start, id_end, colspan_lease;
746
747                 resource = this.scope_resources_by_key[lease.resource];
748                 day_timestamp = SchedulerDateSelected.getTime() / 1000;
749                 if(resource === undefined){
750                     console.log('resource undefined = '+lease.resource);
751                     return;
752                 }
753                 id_start = Math.floor((lease.start_time - day_timestamp) / resource.granularity);
754
755                 /* Some leases might be in the past */
756                 if (id_start < 0)
757                     id_start = 0;
758                 /* Leases in the future: ignore */
759                 if (id_start >= this._all_slots.length)
760                     return true; // ~ continue
761
762                 id_end   = Math.ceil((lease.end_time   - day_timestamp) / resource.granularity);
763                 colspan_lease = resource.granularity / this._slot_length; //eg. 3600 / 1800 => 2 cells
764                 if (id_end >= this._all_slots.length / colspan_lease) {
765                     /* Limit the display to the current day */
766                     id_end = this._all_slots.length / colspan_lease
767                 }
768                 lease_status = manifold.query_store.get_record_state(this.options.query_lease_uuid, lease_key, STATE_SET);
769                 // the same slots might be affected multiple times.
770                 // PENDING_IN + PENDING_OUT => IN 
771                 //
772                 // RESERVED vs SELECTED !
773                 //
774                 // PENDING !!
775                 switch(lease_status) {
776                     case STATE_SET_IN:
777                         lease_class = 'selected'; // my leases
778                         lease_success = '';
779                         break;
780                     case STATE_SET_IN_SUCCESS:
781                         lease_class = 'selected'; // my leases
782                         lease_success = 'success';
783                     case STATE_SET_OUT_FAILURE:
784                         lease_class = 'selected'; // my leases
785                         lease_success = 'failure';
786                         break;
787                     case STATE_SET_OUT:
788                         lease_class = 'reserved'; // other leases
789                         lease_success = '';
790                         break;
791                     case STATE_SET_OUT_SUCCESS:
792                         lease_class = 'free'; // other leases
793                         lease_success = 'success';
794                         break;
795                     case STATE_SET_IN_FAILURE:
796                         lease_class = 'free'; // other leases
797                         lease_success = 'failure';
798                         break;
799                     case STATE_SET_IN_PENDING:
800                         lease_class = 'pendingin';
801                         lease_success = '';
802                         break;
803                     case STATE_SET_OUT_PENDING:
804                         // pending_in & pending_out == IN == replacement
805                         if (resource.leases[i].status == 'pendingin')
806                             lease_class = 'pendingin'
807                         else
808                             lease_class = 'pendingout';
809                         lease_success = '';
810                         break;
811                 
812                 }
813
814                 for (i = id_start; i < id_end; i++) {
815                     resource.leases[i].status = lease_class;
816                     resource.leases[i].success = lease_success;
817                 }
818             },
819
820 /* XXX IN TEMPLATE XXX
821                 if (SchedulerDataViewData.length == 0) {
822                     $("#plugin-scheduler").hide();
823                     $("#plugin-scheduler-empty").show();
824                     tmpScope.clearStuff();
825                 } else {
826                     $("#plugin-scheduler-empty").hide();
827                     $("#plugin-scheduler").show();
828                     // initSchedulerResources
829                     tmpScope.initSchedulerResources(schedulerMaxRows < SchedulerDataViewData.length ? schedulerMaxRows : SchedulerDataViewData.length);
830                 }
831 */
832
833             /**
834              * Initialize the date picker, the table, the slider and the buttons. Once done, display scheduler.
835              */
836             _initUI: function() 
837             {
838                 var self = this;
839                 var scope = self._get_scope();
840
841                 var num_hidden_cells;
842
843                 $("#DateToRes").datepicker({
844                         dateFormat: "D, d M yy",
845                     onRender: function(date) {
846                         return date.valueOf() < now.valueOf() ? 'disabled' : '';
847                     }
848                 }).on('changeDate', function(ev) {
849                     SchedulerDateSelected = new Date(ev.date);
850                     SchedulerDateSelected.setHours(0,0,0,0);
851                     // Set slider to origin
852                     //$('#tblSlider').slider('setValue', 0); // XXX
853                     // Refresh leases
854                     self._scope_clear_leases();
855                     self._set_all_lease_slots();
856                     // Refresh display
857                     self._get_scope().$apply();
858                 }).datepicker('setValue', SchedulerDateSelected); //.data('datepicker');
859
860                 //init Slider
861                 num_hidden_cells = self._all_slots.length - self._num_visible_cells;
862                 init_cell = (new Date().getHours() - 1) * 3600 / self._granularity;
863                 if (init_cell > num_hidden_cells)
864                     init_cell = num_hidden_cells;
865
866                 $('#tblSlider').slider({
867                     min: 0,
868                     max: num_hidden_cells,
869                     value: init_cell,
870                 }).on('slide', function(ev) {
871                     var scope = self._get_scope();
872                     scope.from = ev.value;
873                     scope.$apply();
874                 });
875                 scope.from = init_cell;
876                 scope.$apply();
877
878                 $("#plugin-scheduler-loader").hide();
879                 $("#plugin-scheduler").show();
880             },
881
882         // PRIVATE METHODS
883
884         /**
885          * Greatest common divisor
886          */
887         _gcd : function(x, y)
888         {
889             return (y==0) ? x : this._gcd(y, x % y);
890         },
891
892         _gcdn : function(array)
893         {
894             var self = this;
895             return array.reduce(function(prev, cur, idx, arr) { return self._gcd(prev, cur); });
896         },
897
898         /**
899          * Least common multiple
900          */
901         _lcm : function(x, y)
902         {
903             return x * y / this._gcd(x, y);
904         },
905
906         _lcmn : function(array)
907         {
908             var self = this;
909             return array.reduce(function(prev, cur, idx, arr) { return self._lcm(prev, cur); });
910         },
911     
912         _pad_str : function(i)
913         {
914             return (i < 10) ? "0" + i : "" + i;
915         },
916
917         /**
918          * Member variables used:
919          *   _granularity
920          * 
921          * Returns:
922          *   A list of {id, time} dictionaries.
923          */
924         _generate_all_slots: function()
925         {
926             var slots = [];
927             // Start with a random date (a first of a month), only time will matter
928             var d = new Date(2014, 1, 1, 0, 0, 0, 0);
929             var i = 0;
930             // Loop until we change the day
931             while (d.getDate() == 1) {
932                 // Nicely format the time...
933                 var tmpTime = this._pad_str(d.getHours()) + ':' + this._pad_str(d.getMinutes());
934                 /// ...and add the slot to the list of results
935                 slots.push({ id: i, time: tmpTime });
936                 // Increment the date with the granularity
937                 d = new Date(d.getTime() + this._slot_length * 1000);
938                 i++;
939             }
940             return slots;
941
942         },
943     });
944
945     /* Plugin registration */
946     $.plugin('Scheduler2', scheduler2);
947
948 })(jQuery);
949
950
951