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