version = "0.2.10-dev";
build = "241M";

/**
 * From prototype.js
 */
if (!Object.extend) {
  Object.extend = function(destination, source) {
    if (!source) return;
    for (property in source) {
      destination[property] = source[property];
    }
    return destination;
  };
};

DAY_START = new Time("08:00");
DAY_END = new Time("22:00");
SERVICE_START = new Time("11:30");
SERVICE_END = new Time("13:30");
PAUSE = 0;
THRESHOLD = 300000; // in ms
LEFT = "left";
RIGHT = "right";
SPLIT = "split";
CANCEL = "cancel";
SERVICE = "service";
PRE = "pre";
POST = "post";
BREAK = "break";/*
 * Class: Time
 * Représente une heure de la journée.
 */
function Time(date) {
/*
 * Property: time
 * Représente la valeur numérique de cet objet, soit le nombre de millisecondes
 * entre le début de la journée (0) et cette heure.
 */
  if (date instanceof Time) {
    return date;
  }
  else if (typeof date == 'string') {
    var t = date.split(':');
    try { this.time = ((parseInt(t[0], 10) * 60) + parseInt(t[1], 10)) * 60000; }
    catch (ex) { this.time = 0; }
  }
  else if (typeof date == 'number' && isFinite(date)) { this.time = date; }  
  else { this.time = 0; }
  
  this.getTime = function() { return this.time; };
  this.getHours = function() { return parseInt(this.time / 3600000); };
  this.getMinutes = function() { 
    return parseInt((this.time % 3600000) / 60000);
  };
  this.toString = function() {
    function pad(n) { return (n < 10 ? '0' + n : n); }
    return pad(this.getHours()) + ':' + pad(this.getMinutes());
  };
};

Time.prototype = {
  /*
   * Method: after
   * Décale l'heure vers le futur.
   * 
   * Parameters
   * time - le décalage à appliquer. Ce paramètre peut être exprimé soit comme 
   * une instance de <Time>, soit comme une chaîne au format 'hh:mm'.
   * 
   * Returns:
   * Une instance de <Time>
   */
  after: function(time) {
    if (!time) { return this; }
    else if (time instanceof Time) {
      return new Time(this.time + time.time);
    }
    else if (typeof time == 'number' && isFinite(time)) {
      return new Time(this.time + time);
    }
  },
  
  /*
   * Method: before
   * Décale l'heure vers le passé
   * 
   * Parameters:
   * time - le décalage à appliquer. Ce paramètre peut être exprimé soit comme 
   * une instance de <Time>, soit comme une chaîne au format 'hh:mm'.
   * 
   * Returns:
   * Une instance de <Time>
   */
  before: function(time) {
    if (!time) { return this; }
    else if (time instanceof Time) {
      return new Time(this.time - time.time);
    }
    else if (typeof time =='number' && isFinite(time)) {
      return new Time(this.time - time);
    }
  },
  
  /*
   * 
   */
  valueOf: function() { return this.time; },
//  toJSONString: function() { return this.toString(); },
  
  /*
   * Method: toDecimalString
   * Affiche cette heure en présentant les minutes sous forme décimale.
   */
  toDecimalString: function() {
    return parseFloat(this.getTime() / 3600000).toFixed(2);
  }
};

/*
 * Method: toJSONString
 * Détermine l'affichage d'une instance de <Time> au format JSON. En 
 * l'occurrence, une instance de Time est affichée sous la forme d'une chaîne de
 * caractères.
 * 
 * Returns:
 * <Time.toString>
 */
Time.toJSONString = function(time) {
  return time.toString();
}
/*
 * Class: TimedEvent
 * Représente un événement ayant une heure de début et une heure de fin.
 */
function TimedEvent(data) {
  var e = Object.extend(this, data);
  return e;
};

TimedEvent.re = new RegExp("start|end");
TimedEvent.extend = function(destination, source) {
  if (!source) { return; }
  for (property in source) {
    if (property.match(TimedEvent.re)) {
      destination[property] = new Time(source[property]);
    }
    else {
      destination[property] = source[property];
    }
  }
};

TimedEvent.SCALE = 60000;

TimedEvent.prototype = {
  __dirty: false,
  isDirty: function() { return this.__dirty; },
  setDirty: function(dirty) { this.__dirty = dirty; },
  
  /*
   * Method: schedule
   * Modifie les heures de début et de fin de cet événement.
   * 
   * Parameters:
   * start - la nouvelle heure de début
   * end - la nouvelle heure de fin
   */
  schedule: function(start, end) {
    if (!start || !end || start > end ) { return; }
    this.start = start;
    this.end = end;
    this.duration = null;
    this.setDirty(true);
  },
  
  /*
   * Method: getStartTime
   * Retourne l'heure de début de cet événement
   * 
   * Returns:
   * Une instance de <Time>
   */
  getStartTime: function() { return this.start; },
  
  /*
   * Method: setStartTime
   * Met à jour l'heure de début de cet événement. Par convention, la durée 
   * <getDuration> de cet événement est conservée, et l'heure de fin <getEndTime> 
   * est mise à jour.
   * 
   * Parameters:
   * time - la nouvelle heure de début de cette événement
   */
  setStartTime: function(time) {
    if (!(time instanceof Time)) return;
    if (this.end) { this.end = time.after(this.getDurationMs()); }
    this.start = time;
    this.setDirty(true);
  },
  
  /*
   * Method: getEndTime
   * Retourne l'heure de fin de cet événement.
   * 
   * Returns:
   * Une instance de <Time>
   */
  getEndTime: function() {
    if (!this.end) {
      this.end = this.start.after(this.duration * TimedEvent.SCALE);
    }
    return this.end;
  },

  /*
   * Method: setEndTime
   * Met à jour l'heure de fin de cet événement. Par convention, l'heure de
   * début <getStartTime> de cet événement est conservée, et sa durée 
   * <getDuration> est mise à jour.
   * 
   * Parameters:
   * time - la nouvelle heure de début de cet événemen
   */
  setEndTime: function(time) {
    if (!(time instanceof Time)) return;
    this.duration = null;
    this.end = time;
    this.setDirty(true);
  },
  
  /*
   * Method: getDuration
   * Retourne la durée de cet événement en minutes.
   * 
   * Returns:
   * La durée de cet événement en minutes
   * 
   * See also:
   * <setDuration>
   * <getDurationMs>
   */
  getDuration: function() {
    return this.duration ? 
             parseInt(this.duration) :
             (this.end - this.start) / TimedEvent.SCALE;
  },
  
  /*
   * Method: setDuration
   * Met à jour la durée de cet événement. Par convention, l'heure de début
   * de cet événement (<getStartTime>) est conservée, et son heure de fin 
   * (<getEndTime>) est mise à jour.
   * 
   * Parameters:
   * duration - la nouvelle durée de cet événement en minutes.
   * 
   * See also:
   * <getDuration>
   * <getDurationMs>
   */
  setDuration: function(duration) {
    this.duration = duration;
    this.end = null;
  },
  
  /*
   * Method: getDurationMs
   * Retourne le durée de cet événement en millisecondes.
   * 
   * Returns:
   * La durée de cete événement en millisecondes.
   * 
   * See also:
   * <getDuration>
   * <setDuration>
   */
  getDurationMs: function() {
    return this.getDuration() * TimedEvent.SCALE;
  },
  
  /*
   * Method: shift
   * Décale cet événement dans le temps
   * 
   * Parameters:
   * duration - la durée de laquelle cet événement doit petre décalé
   */
  shift: function(duration) {
    this.setStartTime(this.start.after(duration));
  },
  
  /*
   * Method: compare
   * Compare cet événement à un autre événement.
   */
  compare: function(that) {
    if (!that || !(that instanceof TimedEvent)) { return null; }
    var s1 = this.start;
    var s2 = that.start;
    
    if (s1 == s2) {
      var e1 = this.end;
      var e2 = that.end;
      return e1 < e2 ? -1 : (e1 == e2 ? 0 : 1);
    }
    else { return s1 < s2 ? -1 : 1; }
  },
  
  /*
   * Method: isLocked
   * Indique si cet événement est verrouillé.
   * 
   * Returns:
   * 'true' si cet événement est verrouillé; 'false' sinon
   * 
   * See also:
   * <setLocked>
   */
  isLocked: function() { return this.locked; },
  
  /*
   * Method: setLocked
   * Met à jour le verrouillage de cet évenement.
   * 
   * Parameters:
   * locked - 'true' si cet événement doit être verrouillé; 'false' sinon
   * 
   * See also:
   * <isLocked>
   */
  setLocked: function(locked) { this.locked = locked; },
  
  /*
   * Method: isSplittable
   * Indique si cet événement peut être scindé.
   * 
   * Returns:
   * 'true' si cet événement peut être scindé; 'false' sinon
   * 
   * See also:
   * <setSplittable>
   */
  isSplittable: function() { return this.splittable; },
  
  /*
   * Method: setSplittable
   * Met à jour la faculté de cet événement à être scindé.
   * 
   * Parameters:
   * 'true' si cet événement peut être scindé; 'false' sinon
   * 
   * See also:
   * <isSplittable>
   */
  setSplittable: function(splittable) { this.splittable = splittable; },
  
  /*
   * Method: toString
   * Retourne une représentation de cet événement sous la forme d'une chaîne
   * de caractères.
   * 
   * Returns:
   * Une chaîne de caractères représentant cet événement
   */
  toString: function() { return this.description || this.start.toString(); }
};
/*
    json.js
    2006-10-05

    This file adds these methods to JavaScript:

        object.toJSONString()

            This method produces a JSON text from an object. The
            object must not contain any cyclical references.

        array.toJSONString()

            This method produces a JSON text from an array. The
            array must not contain any cyclical references.

        string.parseJSON()

            This method parses a JSON text to produce an object or
            array. It will return false if there is an error.

    It is expected that these methods will formally become part of the
    JavaScript Programming Language in the Fourth Edition of the
    ECMAScript standard.
*/
(function () {
    var m = {
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        s = {
            array: function (x) {
                var a = ['['], b, f, i, l = x.length, v;
                for (i = 0; i < l; i += 1) {
                    v = x[i];
                    f = s[typeof v];
                    if (f) {
                        v = f(v);
                        if (typeof v == 'string') {
                            if (b) {
                                a[a.length] = ',';
                            }
                            a[a.length] = v;
                            b = true;
                        }
                    }
                }
                a[a.length] = ']';
                return a.join('');
            },
            'boolean': function (x) {
                return String(x);
            },
            'null': function (x) {
                return "null";
            },
            number: function (x) {
                return isFinite(x) ? String(x) : 'null';
            },
            object: function (x) {
                if (x) {
                    if (x instanceof Array) {
                        return s.array(x);
                    }
                    if (x instanceof Time) {
                      return s.time(x);
                    }
                    var a = ['{'], b, f, i, v;
                    for (i in x) {
                      if (i.match(/^__/i)) { continue; }
                        v = x[i];
                        
                        f = s[typeof v];
                        if (f) {
                            v = f(v);
                            if (typeof v == 'string') {
                                if (b) {
                                    a[a.length] = ',';
                                }
                                a.push(s.string(i), ':', v);
                                b = true;
                            }
                        }
                    }
                    a[a.length] = '}';
                    return a.join('');
                }
                return 'null';
            },
            string: function (x) {
                if (/["\\\x00-\x1f]/.test(x)) {
                    x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) {
                        var c = m[b];
                        if (c) {
                            return c;
                        }
                        c = b.charCodeAt();
                        return '\\u00' +
                            Math.floor(c / 16).toString(16) +
                            (c % 16).toString(16);
                    });
                }
                return '"' + x + '"';
            },
            time: function(x) { return x.toString(); }
        };

    Object.prototype.toJSONString = function () {
        return s.object(this);
    };

    Array.prototype.toJSONString = function () {
        return s.array(this);
    };
})();

String.prototype.parseJSON = function () {
    try {
        return (/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(this)) &&
            eval('(' + this + ')');
    } catch (e) {
        return false;
    }
};
var i18n = {
  activity: "Activit&eacute;",
  'break': "Coupure",
  'confirm-remove': "Voulez-vous vraiment supprimer la ressource ",
  'delete': "Supprimer",
  details: "Détails",
  empty: "RAZ",
  hide: "Masquer",
  lock: "Verrouiller",
  load: "Charge",
  merge: "Fusionner",
  open: "Ouvrir:",
  pause: "Pause",
  service: "Service",
  show: "Afficher",
  split: "Scinder",
  unlock: "D&eacute;verrouiller",
  'wait-area': "Zone d'attente",
  filter: 'Filtre',
  do_filter: 'Filtrer',
  ok: 'OK',
  cancel: 'Annuler',
  yes: 'Oui',
  no: 'Non',
  save: 'Sauvegarder',
  'save-as': 'Sauvegarder sous'
};
/*
 * Class: ServiceTimedEvent
 * Représente un événement ayant une pahse de service avec une heure de début
 * et une heure de fin.
 * 
 * Extends: <TimedEvent>
 */
function ServiceTimedEvent(data) {
  TimedEvent.extend(this, data); 
};

ServiceTimedEvent.prototype = new TimedEvent();

Object.extend(ServiceTimedEvent.prototype, {
  /*
   * Method: getServiceStartTime
   * Retourne l'heure de début de la phase de service
   * 
   * Returns:
   * Une instance de <Time>
   */
  getServiceStartTime: function() {
    if (!this.serviceStartTime) {
      this.serviceStartTime = this['service-start'] ? 
                                new Time(this['service-start']) :
                                SERVICE_START;
    }
    return this.serviceStartTime;
  },

  /*
   * Method: setServiceStartTime
   * Met à jour l'heure de début de la phase de service pour cette activité.
   * 
   * Parameters:
   * time - l'heure de début de la phase de service à utiliser (une instance
   * de <Time>)
   */
  setServiceStartTime: function(time) {
    if (!(time instanceof Time)) return;
    delete this.serviceStartTime;
    this.serviceStart = time.toString();
  },
  
  /*
   * Method: getServiceEndTime
   * Retourne l'heure de fin de la phase de service de cette activité.
   * 
   * Returns:
   * Une instance de <Time>
   */
  getServiceEndTime: function() {
    if (!this.serviceEndTime) {
      this.serviceEndTime = this['service-end'] ? 
                              new Time(this['service-end']) :
                              SERVICE_END;
    }
    return this.serviceEndTime;
  },

  /*
   * Method: setServiceEndTime
   * Met à jour l'heure de fin de la phase de service pour cette activité.
   * 
   * Parameters:
   * time - l'heure de fin de service à utiliser (une instance de <Time>)
   */
  setServiceEndTime: function(time) {
    if (!(time instanceof Time)) return;
    delete this.serviceEndTime;
    this.serviceEndStart = time.toString();
  }
});
/*
 * Class: Planning
 * Représente un planning.
 * 
 * Extends: <TimedEvent>
 */
function Planning(data) {
  TimedEvent.extend(this, data);
  if (this.start) { DAY_START = this.start; } 
  if (this.end) { DAY_END = this.end; } 
  
  if (!this.prestations) this.prestations = [];
  var prestations = this.prestations;
  for (var i = prestations.length - 1; i >= 0; i--) {
    var p = new Prestation(prestations[i]);
    prestations[i] = p;
  }
  if (!this.wait_area) { 
    var that = this;
    this.wait_area = new WaitArea({ start: that.start }); 
  }
  else { this.wait_area = new WaitArea(this.wait_area); }
  /*
  this.setDefaultTime(this.start || DAY_START.toString(),
                      this.end || DAY_END.toString(),
                      this['service-start'] || SERVICE_START.toString(),
                      this['service-end'] || SERVICE_END.toString());
                      * */
};

Planning.prototype = new TimedEvent();

Object.extend(Planning.prototype, {
  /*
   * Method: empty
   * Supprime toutes les activités de toutes les ressources de toutes les
   * prestations de ce planning, à l'exception des macro-gammes de service et 
   * des activités verrouillées. Les activités supprimées sont déplacées dans 
   * la zone e stockage de ce planning.
   * 
   * See also:
   * <Prestation.empty>
   * <Resource.empty>
   */
  empty: function() {
    var prestations = this.prestations;
    if( !prestations) { return; }
    for (var i = prestations.length - 1; i >= 0; i--) {
      prestations[i].empty(this);
    }
  },

  /* deprecated */
  setDefaultTime: function(start, end, serviceStart, serviceEnd) {
    if (!this.start) this.start = start;
    if (!this.end) this.end = end;
    if (!this['service-start']) this['service-start'] = serviceStart;
    if (!this['service-end']) this['service-end'] = serviceEnd;
    for (var i = this.prestations.length - 1; i >= 0; i--) {
      this.prestations[i].setDefaultTime(start + 3600000, end + 3600000, serviceStart, serviceEnd);
    }
  }
});
/*
 * Class: Prestation
 * Représente une prestation.
 * 
 * Extends: <ServiceTimedEvent>
 */
function Prestation(data) {
  TimedEvent.extend(this, data);
  if (!this.resources) this.resources = [];
  var resources = this.resources;
  for (var i = resources.length - 1; i >= 0; i--) {
    var r = resources[i], resource;
    r.prestation = this.id;
    resource = new Resource(resources[i]);
    resource['service-start'] = this['service-start']
    resource['service-end'] = this['service-end']
    resource.prestation = this.id;
    resources[i] = resource;
  }
};

Prestation.prototype = new ServiceTimedEvent();

Object.extend(Prestation.prototype, {
  count: { cuisinant: 0, eds: 0, caisse: 0, other: 0 },
  getResources: function() {
    var resources = this.resources,
        n = resources.length,
        activities,
        i;
    for (var i = 0; i < n; i++) {
      if (resources[i].getActivities().length == 0) break;
    }
    if (i == n) { 
      this.resources.push(new Resource({ start: this.start, end: this.end,
                                         'service-start': this['service-start'], 
                                         'service-end': this['service-end'],
                                         id: 'other' + this.count.other++ }))
    }
    return this.resources;
  },

/*
 * Method: empty
 * Vide toutes les prestations de toutes les ressources de cette prestation, à
 * l'exception des macro-gammes de service et des activités verrouillées. Les
 * activités supprimées des ressoruces sont déposées dans la zone d'attente du
 * planning indiqué.
 * 
 * Parameters:
 * planning - le planning dont la zone d'attente doit récupérer les activités
 * supprimées
 */
  empty: function(planning) {
    var resources = this.resources;
    for (var i = resources.length - 1; i >= 0; i--) {
      resources[i].empty(planning);
    }
    this.setDirty(true);
  },
  
  /*
   * Method: remove
   * Supprime une ressource de cette prestation
   * 
   * Parameters:
   * resource - la ressource à supprimer
   */
  remove: function(resource) {
    this.resources.remove(resource);
    this.__dirty = true;
  },

  setDefaultTime: function(start, end, serviceStart, serviceEnd) {
    if (!this.start) this.start = start;
    if (!this.end) this.end = end;
    if (!this['service-start']) this['service-start'] = serviceStart;
    if (!this['service-end']) this['service-end'] = serviceEnd;
    for (var i = this.resources.length - 1; i >= 0; i--) {
      this.resources[i].setDefaultTime(start, end, serviceStart, serviceEnd);
    }
  },
  
  /*
   * Method: shift
   * Méthode utilitaire pur décaler toutes les activités de toutes les 
   * ressources de cette prestation.
   * 
   * Parameters:
   * time - la durée de laquelle toutes les activités de cette ressource
   * doivent être décalées (en millisecondes)
   * 
   * See also:
   * <Resource.shift>
   */
  shift: function(time) {
    var resources = this.resources,
        n = resources.length,
        i;
    for (i = 0; i < n; i++) {
      resources[i].shift(time);
    }
  },
  /*
   * Method: getLoad
   * Calcule la charge de cette prestation, soit le nombre de ressources
   * utilisées par intervalle donné.
   * 
   * Parameters:
   * interval - l'intervalle utilisé pour le calcul de charge
   * 
   * See also:
   * <Resource.getLoad>
   */
  getLoad: function(interval) {
    if (!interval) var interval = 15 * 60000;
    var intervals = parseInt((DAY_END.time - DAY_START.time) / interval),
        resources = this.resources,
        n = resources.length,
        i= 0,
        data = new Array(intervals);
        
    for (i; i < n; i++) { data = resources[i].getLoad(data); }
    return data;
  },
  getTimeWorked: function() {
    var resources = this.resources,
        n = resources.length
        time = 0;
    for (var i = n - 1; i >= 0; i--) {
      time += resources[i].getTimeWorked().time;
    }
    return new Time(time);
  },
  getTotalTime: function() {
    var resources = this.resources,
        n = resources.length
        time = 0;
    for (var i = n - 1; i >= 0; i--) {
      time += resources[i].getTotalTime().time;
    }
    return new Time(time);    
  }
});
/*
 * Class: Resource
 * Représente une ressource.
 * 
 * Extends: <ServiceTimedEvent>
 */
function Resource(data) {
  TimedEvent.extend(this, data);
  if (!this.activities) this.activities = [];
  for (var i = this.activities.length - 1; i >= 0; i--) {
    var a = this.activities[i],
        event, 
        constr = Activity.TYPES[a.type] || MacroGamme;
    event = new constr(a);
    event.resource = this.id;
    event.prestation = this.prestation;
    this.activities[i] = event;
  }
};

Resource.prototype = new ServiceTimedEvent();

Object.extend(Resource.prototype, {
  type: 'autre',
  
  /*
   * Method: getActivities
   * Retourne la liste des activités de cette ressource, triées par ordre 
   * chronologique.
   * 
   * Returns:
   * Un tableau d'instances de <Activity>.
   */
  getActivities: function() {
    return this.activities.sort(function(a,b) { return a.compare(b); });
  },  
  
  /*
   * Method: getPhases
   * Retourne les phases de ctte ressource, à savoir une liste d'événements
   * correspondant à:
   * - la phase allant de la prise de poste jusqu'au début de la coupure
   * - la coupure
   * - la phase allant de la fin de la coupure jusquu'à la fin de la journée
   */
  getPhases: function() {
    var phases = [],
      start = this.start,
      end = this.end,
      brk = this.getBreak();
        
    phases.push(new TimedEvent({ start: start, 
                                 end: brk? brk.start : end, 
                                 phase: PRE }));
    if (brk) { 
      phases.push(brk);
      phases.push(new TimedEvent({ start: brk.getEndTime(), 
                                   end: end, 
                                   phase: POST }));
    }
    this.phases = phases;
    return this.phases;
  },
    
  /*
   * Method: getAllowedPause
   * Retourne le temps de pause autorisé pour cette ressource, en minutes.
   */
  getAllowedPause: function() {
    if (this.pause) { return parseInt(this.pause); }
    else { return PAUSE; }
  },
  
  /*
   * Method: getAllowdPauseMs
   * Retourne le temps de pause autorisé pour cette ressource, en millisecondes.
   */
  getAllowedPauseMs: function() {
    var pauses = this.getAllowedPause();
    pauses = Math.ceil(pauses / 15) * 15 * TimedEvent.SCALE;
    return pauses;
  },
  
  /*
   * Method: getPausesMs
   * Retourne le temps de pause utilisé par cette ressource, en millisecondes.
   */
  getPausesMs: function() {
    var activities = this.activities;
    var pauses = 0;
    for (var i = activities.length - 1; i >= 0; i--) {
      var a = activities[i];
      if (a instanceof Pause) { pauses += a.getDurationMs(); }
    }
    return pauses;
  },

  /*
   * Method: getBreak
   * Retourne la coupure de cette ressource
   * 
   * Returns:
   * Un <TimedEvent> représentant la coupure de cette ressource
   */
  getBreak: function() {
    var start = this.start,
        end = this.end,
        breakStart = this['break-start'],
        breakEnd = this['break-end'];

    breakStart = breakStart && breakEnd && start < breakStart && breakStart < end ?
                   breakStart : 
                   null;
    breakEnd = breakStart && breakStart < breakEnd && breakEnd < end ?
                 breakEnd :
                 null;
    if(breakStart && breakEnd && breakStart < breakEnd) {
      return new TimedEvent({ start: breakStart, 
                              end: breakEnd, 
                              phase: BREAK });
    }
    else { return null; }
  },
  /*
   * Method: getBreakDurationMs
   * Retourne la durée de la coupure (en millisecondes)
   */
  getBreakDurationMs: function() {
    var brk = this.getBreak();
    return brk ? brk.getDurationMs() : 0;
    return this['break-start'] && this['break-end'] ?
            (this['break-end'].time - this['break-start'].time) :
            0;
  },
  /*
   * Method: empty
   * Supprime toutes les activités de cette ressource, à l'exception de:
   * - les macro-gammes ou pauses verrouillées
   * - les macro-gammes profilées
   */
  empty: function(planning) {
    var activities = this.activities;
    for (i = activities.length - 1; i >= 0; i--) {
      activity = activities[i];
      if (activity.locked || activity.phase == 'service') { continue; }
      this.activities = this.activities.remove(activity);
      if (!(activity instanceof Pause)) {
        planning.wait_area.activities.push(activity);
      }
    }
    this.setDirty(true);
  },
  
  /*
   * Method: getTimeWorked
   * Retourne le temps de travail effectif, soit la somme des durées des 
   * macro-gammes affectées à cette ressource.
   */
  getTimeWorked: function() {
    var activities = this.getActivities(), 
        n = activities.length,
        activity,
        i, time = 0;
    for (i = 0; i < n; i++) {
      activity = activities[i];
      if (activity instanceof Pause) continue;
      time += activity.getDurationMs();
    }
    return new Time(time);
  },

  getTotalTime: function() {
    return new Time(this.getDurationMs() - 
                      this.getPausesMs() - 
                      this.getBreakDurationMs());
  },
  getActivity: function(time) {
    var activities = this.getActivities(), 
        n = activities.length,
        t, a, i;
    if (time instanceof Time) { t = time; }
    else { 
      try { t = new Time(time); } catch (ex) {} 
    }
    if (!t) return null;
   
    for (i = n - 1; i >= 0; i--) {
      a = activities[i];
      if ( t < a.start) { continue; }
      if ( t > a.getEndTime()) { return a; }
    }
  },
  
  /*
   * Method: canAdd
   * 
   */
  canAdd: function(activity, time) {
    if (!activity || !activity.splittable) return false;
 
    if (!(time instanceof Time)) {
      try { time = new Time(time); }
      catch (ex) {} 
    }
    if (!time) var time = activity.start;
    
    var last = DAY_START,
        activities = this.getActivities(),
        n = activities.length,
        current;
        
    if (n == 0) { return true; }
    
    for (var i = 0; i < n; i++) {
      current = activities[i];
      if (time < current.getEndTime()) { break; }
      last = current.getEndTime();
    }
    var next = i == n ? DAY_END : current.start;
    return (time < next) && 
           (activity.getDurationMs() < (next - last));
  },
  /*
   * Method: addActivity
   * Ajoute une activité (<MacroGamme> ou <Pause>) à cette ressource
   * 
   * Parameters:
   * activity - l'activité à ajouter
   * options - des parramètres supplémentaires
   * 
   * Options:
   * start - heure de début de l'activité
   * time - heure 
   * action - action à prendre en cas de conflit avec les activités existantes
   * de la ressource
   * 
   * Returns:
   * true - si l'ajout s'est correctement accompli
   * false - si l'ajout ne s'est pas accompli correctement
   */
  addActivity: function(activity, options) {
    var activities = this.getActivities(),
        options = options || {},
        n = activities.length,
        time = options.time || activity.start,
        start = options.start || activity.start, // find some default if options.start not set
        end,
        duration = activity.getDurationMs(), // activity duration in ms
        previous = DAY_START,
        before = 0,
        next,
        after = 0,
        current, previous, next, i, ok = 0, choice, split,
        merged;

    if (start < DAY_START) start = DAY_START;

    if (options.action == LEFT) { return this.addAndShiftLeft(activity, start); }
    else if (options.action == RIGHT) { return this.addAndShiftRight(activity, start); }
    else if (options.action == SPLIT) { return this.addAndSplit(activity, time, options.planning); }
    
    // if no activities, ok to add
    if (n == 0) ok = 1;
    if (options.force) { ok = 1; }
    if (ok) { activity.setStartTime(start); }
    
    
    // otherwise loop through current activities
    else {
      for (i = 0; i < n; i++) {
        current = activities[i];
        if (i < n - 1) { next = activities[i + 1].start; }
        else { next = DAY_END; }
        
        // Drop (mouse position) is before activity start (or not more than threshold)
        if (previous < time && time < current.start + THRESHOLD) {
          if (activity.phase == SERVICE) {
            ok = previous <= start && start.after(duration) <= current.getStartTime();
          }
          else if (duration <= current.getStartTime() - previous) {
            if (current.start < start.after(duration) + THRESHOLD) {
              start = current.start.before(duration);
            }
            ok = true;
            break;
          }
          break;
        }
 
        // Drop (mouse position) is within activity or in the gap before the next 
        // activity 
        else if (current.start < time && time < next) {
          if (activity.phase == SERVICE) {
            ok = current.getEndTime() <= start && start.after(duration) <= next;
            break;
          }
          else if (duration <= next - current.getEndTime()) {
            if (next < start.after(duration)) {
              start = next.before(duration);
            }
            if (start < current.getEndTime().after(THRESHOLD)) {
              start = current.getEndTime();
            }
            ok = true;
            break;
          }
          break;
        }
        previous = current.getEndTime();
      }
      if (ok) {
        activity.setStartTime(start);
        /*
        if (!choice) choice = options.action;
        if (choice) {
          switch (choice) {
            case RIGHT :
              for (var j = i + 1; j < n; j++) {
                var a = activities[j];
                duration = a.getDurationMs();
                if (a.start < end) { 
                  start = end;
                  end = start.after(duration);
                  a.schedule(start, end);
                }
                else { break; }
              }
              break;
            case LEFT : 
              for (var j = i; j >= 0; j--) {
                var a = activities[j];
                if (end <= a.start) { continue; }
                if (a.getEndTime() < start) { break; }
                duration = a.getDurationMs();
                end = start;
                start = end.before(duration);
                a.schedule(start, end);
              }
              break;
            case SPLIT :
              this.split(activity, split, options.planning);
              break;
            case CANCEL : 
              ok = false;
              break;
          }
        } */
      }
    }
    if (ok) {
      this.activities.push(activity);
      activity.resource = this.id;
      activity.setDirty(true);
      this.setDirty(true);
      this.merge();
    }  
    return ok;
  },
  /*
   * Method: canShiftLeft
   * Indique si une activité (macro-gamme ou pause) peut être ajoutée à cette
   * ressource à une heure donnée en déplaçant d'autres activités vers la gauche.
   * Les macro-gammes profilées ou verrouillées ne peuvent pas être déplacées.
   * 
   * Parameters:
   * activity - l'activité à ajouter
   * time - l'heure à laquelle l'ajouter
   * 
   * Returns:
   * true - si l'activité peut être ajoutée
   * false - sinon
   */
  canShiftLeft: function(activity, time) {
    var start = time, end = time.after(activity.getDurationMs()),
        last = DAY_START, before = 0,
        activities = this.getActivities(),
        n = activities.length,
        current, i;

    for (i = 0; i < n; i++) {
      current = activities[i];
      if (end < current.start) { break; }
      if (current.start < start && (current.locked || current.phase == SERVICE)) { before = 0; }
      else { 
        before += current.start.time - last.time;
      }
      last = current.getEndTime();
      if (end < last) { break; }
    }
    return activity.getDurationMs() < before;
  },
  /*
   * Method: addAndShiftLeft
   * Ajoute une activité (macro-gamme ou pause) à cette ressource à une heure
   * donnée en déplaçant d'autrs activités vers la gauche.
   * Les macro-gammes profilées ou verrouillées ne peuvent pas être déplacées.
   * 
   * Pré-conditions:
   * Assurez-vous que <canShiftLeft> retourne la valeur trueavant d'invoquer 
   * cette méthode.
   * 
   * Parameters:
   * activitiy - l'activité (macro-gamme ou pause) à ajouter
   * time - l'heure à laquelle l'ajouter
   * 
   * Returns:
   * true
   */
  addAndShiftLeft: function(activity, time) {
    activity.setStartTime(time);
    activity.resource = this.id;
    this.activities.push(activity);
    var activities = this.getActivities(),
        a, i = 0, j = 0,
        start, duration;
    while (activities[i] != activity) { i++; }
    duration = (i + 1 < activities.length ? 
                  activities[i + 1].getStartTime() : 
                  DAY_END).time - activity.getEndTime().time; 
    if (duration < 0) { activity.shift(duration); }
    /*
      if (a.start < end)) {
        activity.shift(a.start.time - activity.getEndTime().time);
      }
    }*/
    start = activity.start;
    for (j = i - 1; j >= 0; j--) {
      a = activities[j];
      duration = start.time - a.getEndTime().time;
      if (duration >= 0) { break; }
      a.shift(duration); 
      start = a.start;
    }
    return true;
  },
  /*
   * Method: canShiftRight
   * indique si une activité (macro-gamme ou pause) peut être ajoutée à cette
   * ressource à une heure donnée en déplaçant d'autres activités vers la droite.
   * Les macro-gammes profilées ou verrouillées ne peuvent pas être déplacées.
   * 
   * Parameters:
   * activity - activité (macro-gamme ou pause) à ajouter
   * time - heure à laquelle l'ajouter
   * 
   * Returns:
   * true - si l'activité peut être ajoutée
   * false - sinon
   */
  canShiftRight: function(activity, time) {
    var start = time, end = time.after(activity.getDurationMs()), 
        last = DAY_END, after = 0,
        activities = this.getActivities(),
        n = activities.length,
        current, i;

    for (i = n - 1; i >= 0; i--) {
      current = activities[i];
      if (current.start < time) { break; }
      else if (end < current.getEndTime() && (current.locked || current.phase == SERVICE)) { after = 0; }
      else {
        after += last - current.getEndTime();
      }
      last = current.start;
      if (last < start) { break; }
    }
    //alert(new Time(after));
    return activity.getDurationMs() < after;
  },
  /*
   * Method: addAndShiftRight
   * Ajoute une activité (macro-gamme ou pause) à cette ressource une heure 
   * donnée en déplaçant les activités précédentes vers la droite.
   * Les macro-gammes profilées ou verrouillées ne peuvent pas être déplacées.
   * 
   * Pré-conditions:
   * Assurez-vous que <canShiftRight> retourne la valeur trueavant d'invoquer 
   * cette méthode.
   * 
   * Parameters:
   * activity - activité (macro-gamme ou pause) à ajouter
   * time - heure à laquelle l'ajouter
   * 
   * Returns:
   * true
   * 
   */
  addAndShiftRight: function(activity, time) {
    activity.setStartTime(time);
    this.activities.push(activity);
    activity.resource = this.id;
    var activities = this.getActivities(),
        a, i = 0, j = 0, n = activities.length,
        start, duration;
    while (activities[i] != activity) { i++; }
    if (i > 0) { 
      a = activities[i - 1];
      if (activity.start < a.getEndTime()) {
        activity.setStartTime(a.getEndTime());
      }
    }
    start = activity.getEndTime();
    for (j = i + 1; j < n; j++) {
      a = activities[j];
      if (start < a.start ) { break; }
      if (a.getEndTime() < start && (a.phase == SERVICE || a.locked)) {
        activity.setStartTime(a.getEndTime());
        start = activity.getEndTime();
      } 
      else { 
        a.setStartTime(start); 
        start = a.getEndTime();
      }
    }
    return true;    
  },
  /*
   * Method: canSplit
   * Indique si une activité peut être scindée lors de son ajout à cette 
   * ressource à une heure donnée.
   * 
   * Parameters:
   * activity - l'activité (macro-gamme ou pause) à ajouter
   * time - l'heure à laquelle l'ajouter
   * 
   * Returns:
   * true - si l'activité peut être scindée
   * false - sinon
   */
  canSplit: function(activity, time) {
    if (!activity || !activity.splittable) return false;
 
    if (!(time instanceof Time)) {
      try { time = new Time(time); }
      catch (ex) {} 
    }
    if (!time) var time = activity.start;
    
    
    var last = DAY_START,
        activities = this.getActivities(),
        n = activities.length,
        current;
    
    for (var i = 0; i < n; i++) {
      current = activities[i];
      if (time < current.getEndTime()) { break; }
      last = current.getEndTime();
    }
    return time < current.start && (current.start - last > 0);
  },
  /*
   * Method: addAndSplit
   * Ajoute une activité (macro-gamme ou pause) à cette ressource à une heure
   * donnée en la scindant.
   * 
   * Preconditions:
   * canSplit retourne true
   * 
   * Parameters:
   * activity  - l'activité à ajouter
   * time: l'heure à laquelle l'ajouter
   * 
   * Returns:
   * true
   */
  addAndSplit: function(activity, time, planning) {
    if (!activity || !activity.splittable) return false;
 
    if (!(time instanceof Time)) {
      try { time = new Time(time); }
      catch (ex) {} 
    }
    if (!time) var time = activity.start;
    
    var last = DAY_START,
        activities = this.getActivities(),
        n = activities.length,
        current;
    
    for (var i = 0; i < n; i++) {
      current = activities[i];
      if (time < current.getEndTime()) { break; }
      last = current.getEndTime();
    }
    activity.setStartTime(last);
    this.activities.push(activity);
    activity.resource = this.id;
    this.split(activity, current.start.time - last.time, planning);
    return true;
  },
  
  /*
   * Method: split
   * Scinde une activité (macro-gamme ou pause) à une heure donnée.
   * 
   * Parameters:
   * activity - l'activité (macro-gamme ou pause) à scinder
   * time - le moment où la scinder, exprimé comme l'heure depuis depuis le 
   * début de l'activité 
   * planning - la planning dans la zone d'attente duquel le reliquat doit être
   * déposé 
   */ 
  split: function(activity, time, planning) {
    if (!activity || !time || !planning) { return; }
    if (!(time instanceof Time)) {
      try { var time = new Time(time); }
      catch (ex) { return; }
    }
    var wait_area = planning.wait_area, 
        clone = activity.clone(),
        start = activity.start.after(time),
        end = activity.getEndTime();
    clone.schedule(start, end);
    end = start;
    start = activity.start;
    activity.schedule(start, end);
    wait_area.addActivity(clone, { merge: !(this instanceof WaitArea) });
    return activity;
  },  
  /*
   * Method: merge
   * Fusionne les activités fongibles de cette ressource.
   */
  merge: function() {
    var activities = this.getActivities(),
        n = activities.length,
        i, current, previous, start, end;
    if (n <= 1) { return; }
    for (i = n - 1; i > 0; i--) {
      current = activities[i];
      previous = activities[i - 1];
      if (current.locked || 
            previous.locked ||
            current.phase == SERVICE ||
            previous.phase == SERVICE) { continue; }
      if (((current.start.time - previous.getEndTime().time) < THRESHOLD) &&
           (current.canMerge(previous))) {
        start = previous.start;
        end = start.after(current.getDurationMs() + previous.getDurationMs());
        previous.schedule(start, end);
        activities.splice(i, 1);
      }
    } 
  },
  /*
   * Method: remove
   * Supprime une activité de cette ressource.
   * 
   * Parameters:
   * activity - l'activité à supprimer
   */
  removeActivity: function(activity) {
    this.activities.remove(activity);
  },
  
  /*
  canShiftBefore: function(activity, time, at) {
    if (!activity) { return false; }
 
    if (!time) var time = activity.start;
    if (!(time instanceof Time)) {
      try { time = new Time(time); }
      catch (ex) { return false; } 
    }
    if (!at) var at = time;
    if (!(at instanceof Time)) {
      try { at = new Time(at); }
      catch (ex) { return false; } 
    }
    
    var start = DAY_START, end = DAY_END, last, before = 0,
        activities = this.getActivities(),
        n = activities.length,
        current, i;

    last = start;
   
    for (i = 0; i < n; i++) {
      current = activities[i];
      if ( time < current.getEndTime()) { break; }
      else if (current.locked || current.phase == SERVICE) { before = 0; }
      else { 
        before += Math.min(current.start, at.after(activity.getDurationMs())) - last;
      }
      last = current.getEndTime();
    }
    if (current.getEndTime() < at) {
      before += at - current.getEndTime();
    }
    return activity.getDurationMs() < before;
  },
  canShiftAfter: function(activity, time, at) {
    if (!activity) { return false; }
 
    if (!time) var time = activity.start;
    if (!(time instanceof Time)) {
      try { time = new Time(time); }
      catch (ex) { return false; } 
    }
    if (!at) var at = time;
    if (!(at instanceof Time)) {
      try { at = new Time(at); }
      catch (ex) { return false; } 
    }
    
    var start = DAY_START, end = DAY_END, last, after = 0,
        activities = this.getActivities(),
        n = activities.length,
        current, i;

    last = end;
    for (i = n - 1; i >= 0; i--) {
      current = activities[i];
      if (current.start < time) { break; }
      else if (current.locked || current.phase == SERVICE) { after = 0; }
      else {
        after += (last - Math.max(current.getEndTime(), at));
      }
      last = current.start;
    }
    if (at < current.start) {
      after += current.start - at;
    }
    return activity.getDurationMs() < after;
  }, */
  setDefaultTime: function(start, end, serviceStart, serviceEnd) {
    if (!this.start) this.start = start;
    if (!this.end) this.end = end;
    if (!this['service-start']) this['service-start'] = serviceStart;
    if (!this['service-end']) this['service-end'] = serviceEnd;
  },
  /*
   * Method: shift
   * Méthode utilitaire pour décaler toutes les activités d'une ressource.
   * 
   * Parameters:
   * time - la durée de laquelle toutes les activités de cette ressource
   * doivent être décalées (en millisecondes)
   * 
   * See also:
   * <Prestation.shift>
   */
  shift: function(time) {
    var activities = this.activities,
        n = activities.length,
        activity,
        i;
    for (i = 0; i < n; i++) {
      activity = activities[i];
      activity.setStartTime(activity.start.after(time))
    }
  },
  /*
   * Method: getLoad
   * Met à jour un tableau de charge en fonction des activités de cette 
   * ressource. Cette méthode prend en entrée un tableau indiquant un nombre de
   * ressources par intervalle, et le met à jour en fonction des activités de
   * cette ressource: chaque valeur du tableau est soit inchangée si cette
   * ressource n'a aucune activité pendant l'intervalle en question, soit
   * incrémentée si cette ressoruce est occupée pendant l'intervalle.
   * 
   * Cette méthode est destinée à être utilisée en boucle pour toutes les
   * ressources d'une prestation.
   * 
   * Parameters:
   * data - un tableau de charge indiquant le nombre de ressources utilisées
   * par intervalle
   * interval - l'intervalle à utiliser pour le calcul de charge (en minutes)
   * 
   * Returns:
   * data - le tableau passé en entrée et mis à jour par la méthode
   * 
   * See also:
   * <Prestation.getLoad>
   */
  getLoad: function(data, interval) {
    if (!interval) var interval = 15 * 60000;
    var activities = this.getActivities(),
        n = activities.length,
        intervals = parseInt((DAY_END.time - DAY_START.time) / interval),
        i = 0, j = 0, k = 0, l, m, a;
    if (!data) var data = new Array(intervals);
     
    for (i; i < n; i++) {
      a = activities[i];
      k = parseInt((a.start.time - DAY_START.time) / interval);
      l = (a.getEndTime().time + interval - DAY_START.time) / interval;
      m = parseInt(l);
      l = (l == m ? m - 1 : m);
      for (j = Math.max(j, k); j < l; j++) { data[j] ? data[j]++ : data[j] = 1; }
    }
    return data;
  }
});
/*
 * Class: WaitArea
 * Représente une zone de déstockage.
 * 
 * Extends: <Resource>
 */
 function WaitArea(data) {
  TimedEvent.extend(this, data);
  this.description = i18n['wait-area'];
  if (!this.activities) this.activities = [];
  for (var i = this.activities.length - 1; i >= 0; i--) {
    var a = this.activities[i];
    var constr;
    if (a.type) { constr = Activity.TYPES[a.type]; }
    if (!constr) { constr = Activity; }
    this.activities[i] = new constr(a);
  }
};

WaitArea.prototype = new Resource();

Object.extend(WaitArea.prototype, {
  
  /*
   * Method: addActivity
   * Ajoute une activité à cette zone d'attente.
   * 
   * Parameters:
   * activity -  l'activité à ajouter
   * options - un objet Javascript contenant diverses options
   * 
   * Options:
   * merge - si ''true'', procéder à la fusion automatique éventuelle de
   * l'activité ajoutée aux activités déjà contenues dans la zone d'attente
   * 
   * See also:
   * <mergeActivity>
   */
  addActivity: function(activity, options) {
    if (options.merge == true) { this.mergeActivity(activity); }
    else { 
      this.activities.push(activity);
      activity.setDirty(true); 
      this.setDirty(true);
    }
    return true;
  },
  
  /*
   * Method: mergeActivity
   * Ajoute une activité à cette zone d'attente en la fusion si possible avec
   * une activité déjà contenue.
   * 
   * Parameters:
   * activity - l'activité à ajouter
   * 
   * See also:
   * <addActivity>
   */
  mergeActivity: function(activity) {
    var activities = this.getActivities(),
        n = activities ? activities.length : 0,
        i, a, merge = false, start, end;
        
    for (i = 0; i < n; i++) {
      a = activities[i];
      if (a.canMerge(activity)) {
        start = a > activity ? activity.start : a.start;
        end = start.after(a.getDurationMs() + activity.getDurationMs());
        a.schedule(start, end);
        merge = true;
        break;
      }        
    } 
    if (!merge) { 
      activities.push(activity); 
      activity.setDirty(true);
    } 
    this.setDirty(true);
  },
  
  /*
   * Method: merge
   * Fusionne partout où possible les activités de cette zone d'attente.
   */
  merge: function() {
    var activities = this.getActivities(),
        n = activities.length, i = 0, j, activity, a,
        start, end;
    for (i; i < n; i++) {
      activity = activities[i];
      if (activity.locked || activity.__ignore) { continue; }
      for (j = i + 1; j < n; j++) {
        a = activities[j];
        if (a.locked || a.__ignore) { continue; }
        if (activity.canMerge(a)) {
          start = activity.start;
          end = start.after(activity.getDurationMs() + a.getDurationMs());
          activity.schedule(start, end);
          a.__ignore = true;
        }
      }
    }
    for (i = n - 1; i >= 0; i--) {
      if (activities[i].__ignore == true) { 
        this.activities.splice(i, 1);
      }
    }
  }  
});

/*
 * Class: Activity
 * Classe générique servant de super-classe à toutes les classes représentant
 * un événement susceptible d'être affecté à une <Resource>.  
 * 
 * Extends: <TimedEvent>
 * 
 * See also:
 * <MacroGamme>
 * <Pause>
 */
function Activity(data) {
  TimedEvent.extend(this, data);
};

Activity.prototype = new TimedEvent();

Object.extend(Activity.prototype, {
  /**
   * Property: type
   * Indique le type de l'activité. Ce champ est utilisé lors de la restitution 
   * des données depuis une chaîne au format JSOn afin de déterminer le
   * cosntructeur adéquat.
   * 
   */
  type: null,
  
  /*
   * Property: splittable
   * Indique si l'activité peut être scindée.
   * 
   * true - l'activité peut être scindée
   * false - l'activité ne peut pas être scindée
   * 
   * Value:
   * true
   */
  splittable: true,
  
  /*
   * Retourne une copie de l'activité. Le clonage ignore les propriétés suivantes:
   * - méthodes (propriétés de type 'function' )
   * - propritétés dont le nom commence par __ (double underscore)
   */
  clone: function() {
    var data = {};
    for (var prop in this) {
      if (prop == '__tsId' || typeof this[prop] == 'function') { continue; }
      data[prop] = this[prop];
    }

    var constr = Activity.TYPES[this.type] || MacroGamme;
    return new constr(data);
  },
  /*
   * Method: canMerge
   * Indique si l'activité peut fusionner avec une autre.
   * 
   * Parameters:
   * other - une autre activité avec laquelle fusionner
   * 
   * Returns:
   * false
   */
  canMerge: function(other) { return false; }
});

Activity.prototype.toString = function() {
  var s = this.description + ' [' + this.start.toString() + '-' + 
            this.getEndTime().toString() + ']';
  return s;
};

Activity.TYPES = {
  'macro-gamme': MacroGamme,
  pause: Pause /*,
  service: Service */
};
/*
 * Class: MacroGamme
 * Représente une macro-gamme
 * 
 * Extends: <Activity>
 */
function MacroGamme(data) {
  TimedEvent.extend(this, data);
};

MacroGamme.prototype = new Activity();

Object.extend(MacroGamme.prototype, {
  /**
   * Property: type
   * Indique le type de l'activité. Ce champ est utilisé lors de la restitution 
   * des données depuis une chaîne au format JSON afin de déterminer le
   * constructeur adéquat.
   * 
   */
  type: 'macro-gamme',
  
  /*
   * Method: canMerge
   * Indique si la macro-gamme est susceptible d'être fusionnée avec une autre.
   * 
   * La fusion est possible si (1) aucune des deux macro-gammes n'est verrouillée
   * et si (2) les propriétés suivantes ont une valeur identique pour les 
   * deux macro-gammes:
   *   - module
   *   - sous-module
   *   - phase
   *   - description
   */
  canMerge: function(other) { 
    if (!other) return false;
    if (this.locked|| other.locked) { return false; }
    try {
      return ( this.module == other.module &&
               this['sous-module'] == other['sous-module'] &&
               this.description == other.description &&
               this.phase == other.phase);    
    }
    catch(ex) { return false; }
  }  
});
/*
 * Class: Pause
 * Représente une pause
 * 
 * Extends: <Activity>
 * 
 */
function Pause(data) {
  TimedEvent.extend(this, data);
};

Pause.prototype = new Activity();
Object.extend(Pause.prototype, {
  type: 'pause',
  
  description: i18n['pause'],
  
  /*
   * Method: canMerge
   * Indique si la pause est susceptible dêtre fusionnée avec une autre activité.
   * 
   * Returns:
   * false
   */
  canMerge: function(other) { return false; },

  /*
   * Property: splittable
   * Indique si l'activité peut être scindée.
   * 
   * true - l'activité peut être scindée
   * false - l'activité ne peut pas être scindée
   * 
   * Value:
   * false
   */
  splittable: false
});
WAITAREA_ID = 'wait';
css = {
  ACTIVITIES: "activities",
  EMPTY: "empty",
  FIRST: "first",
  LAST: "last",
  MAIN: "main",
  PAUSE: "pause"
}

var GATONERO = (GATONERO ? GATONERO : null);
if (GATONERO) {
  GATONERO.extend(ENTITIES, {
    '&eacute;': 'é',
    '&egrave;': 'è',
    '&ecirc;': 'ê',
    '&euml;': 'ë',
    '&agrave;': 'à',
    '&ocirc;': 'ô'
  });
  
  GATONERO.extend(GATONERO.i18n, i18n);
}

/*
 * Class: PlanningDesc
 * Un composant décrivant la représentation graphique d'un <Planning> à l'écran.
 * 
 * Extends: GATONERO.Box <http://www.gatonero.com/ria/0.5/docs/files/container/box-js.html>
 */
var PlanningDesc = GATONERO.extend(GATONERO.Box, {
  
  /**
   * A unique identifyer for all instances of this class. Also the XML tag
   * used to represent instances of this class.
   */
  classId: 'planning',
  
  /**
   * Use a vertical orientation for this container's layout
   */
  orientation: GATONERO.Constants.VERTICAL,
  
  /**
   * A custom CSS class to use for this component on the page
   */
  'css-class': 'planning',
  
  WAITAREA_ID: 'wait',
  
  itemChanged: function(itemEvent) {
    var source = itemEvent.getSource();
    this.setDirty(true);
    if (source == this.layout) { var prop = this.LAYOUT; }
    else if (source == this.model) { 
      var prop = this.VALUE; 
      this.setDirtyAll();
    }
    else { var prop = this.COMPONENTS; }
    this.fireItemEvent(prop, itemEvent);
  },
  
  /**
   * Force refresh of child components when this container is modified
   */
  setDirtyAll: function() {
    this.setDirty(true);
    var comps = this.getComponents();
    for (var i = 0, n = comps.length; i < n; i++) {
      var comp = comps[i];
      if (comp.setDirtyAll) comp.setDirtyAll();
      else comp.setDirty(true);
    }
  },
  /**
   * Returns an array of components contained in this container. 
   */
  getComponents: function() {
    if (!this.components || this.isDirty()) { this.reloadComponents(); }
    return this.components;
  },
  
  /**
   * Computes the list of this container's components based on the underlying
   * value (a Planning object).
   */
  reloadComponents: function() {
    this.components = [this.filter];
    var value = this.getValue();
    if (value) {
      var prestations = value.prestations,
          n = prestations.length;
      for (var i = 0; i < n; i++) {
        var prestation = prestations[i];
        var p = this.factory.findItem(prestation.id);
         if (!p) {
          p = this.factory.createItem(PrestationDesc, prestation.id);
          p.setValue(prestation, { silent: true });
        }
        this.components[i + 1] = p;
      }
    } 
    this.components.push(this.getWaitArea());
  },
  getWaitArea: function() {
    var planning, id, wait;
    desc = this.waitarea;
    if (!this.desc) { 
      desc = this.factory.findItem(WAITAREA_ID);
      if (!desc) { desc = this.factory.createItem(WaitAreaDesc, WAITAREA_ID); }
      this.waitarea = desc;
    }
    planning = this.getValue();
    if (planning && planning.wait_area) {
      desc.setValue(planning.wait_area, { silent: true });
    }
    return desc;
  },
  loadChild: function(item) {
    if (item instanceof GATONERO.Toolbar && !this.toolbar) {
      this.toolbar = item;
    }
    else if (item.id = 'filter' && !this.filter) {
      this.filter = item;
      this.filter.addItemListener(this);
    }
  },
  empty: function() { 
    var value = this.getValue();
    if (value) { 
      value.empty(); 
      this.setDirtyAll(true);
      this.fireItemEvent(GATONERO.Item.Value, null, value);
    }
  }
});

/**
 * Binds this class to its classId. This allows the class's classId 'planning'
 * to be used in XML UI descriptions.
 */
GATONERO.ItemFactory.bind(PlanningDesc);


var PlanningRenderer = GATONERO.extend(GATONERO.PanelRenderer, {
  defaultClassName: 'panel', 
  createToolbar: function(elt, desc, parent) {
    var r = this.renderer
        toolbar_desc = desc.toolbar,
        caption = r.getCaption(elt),
        toolbar = r.render(toolbar_desc, caption);
   r.setComponent(elt, r._TOOLBAR, toolbar);
  },
  /*
  doCreateToolbar: function(elt, desc, parent) {
    var r = this.renderer,
        empty = document.createElement('A'),
        filter = document.createElement('A');
        
    empty.innerHTML = i18n['empty'].entitify();
    this.addClass(empty, 'empty');
    this.attachEvent(empty, 'click', this.doEmpty);
    r.setComponent(elt, 'empty', empty);
    
    filter.innerHTML = i18n['filter'];
    this.addClass(filter, 'empty');
    this.attachEvent(filter, 'click', this.showFilter);
    r.setComponent(elt, 'filter', filter);
    
    return elt;    
  },
  doRenderToolbar: function(elt, desc, parent) {
    var r = this.renderer,
        toolbar = r.getComponent(elt, r._TOOLBAR),
        empty = r.getComponent(elt, 'empty'),
        filter = r.getComponent(elt, 'filter');
    toolbar.appendChild(empty);
    toolbar.appendChild(filter);
  }, */
  doEmpty: function(e, elt, desc) {
    var r = this.renderer,
        planning = desc.getValue();
    if (!planning) { return; }
    planning.empty();
    desc.setDirtyAll();
    desc.fireItemEvent(GATONERO.Component.prototype.VALUE, null, planning);
  },
  showFilter: function(e, elt, desc) {
    var filter = desc.factory.findItem('filter');
    filter.setVisible(!filter.isVisible());
  }
});

PlanningDesc.prototype.delegate = new PlanningRenderer();
PlanningDesc.prototype.delegate.showTimeline = false;

var PlanningValueJSON = GATONERO.extend(GATONERO.JSONValue, {
  classId: 'planning-value-json',
  'url-prefix': 'data/',
  getUrl: function() {
    return this['url-prefix'] + this.name + '.json';
  },
  parseResponse: function(response) {
    var planning, start = new Date();
    try { eval('var data = ' + response + ';' ); }
    catch (ex) { alert(ex); /* LOG */ }
    if (data) { planning = new Planning(data); } 
    return planning;
  }
});

GATONERO.addProperty(PlanningValueJSON, 'name');

GATONERO.ItemFactory.bind(PlanningValueJSON);

var PlanningValuePHP = GATONERO.extend(GATONERO.JSONValue, {
  classId: 'planning-value-php',
  'url-prefix': '../data/planning.php?id=',
  getUrl: function() {
    return this['url-prefix'] + this.id;
  },
  parseResponse: function(response) {
    var planning;
    try { eval('var data = ' + response + ';' ); }
    catch (ex) { alert(ex); }
    if (data) { planning = new Planning(data); }
    return planning;
  }
  
});

GATONERO.ItemFactory.bind(PlanningValuePHP);
/*
 * Class: PrestationDesc
 * Un composant décrivant la représentation graphique d'une <Prestation> à l'écran.
 * 
 * Extends: GATONERO.Container (<http://www.gatonero.com/ria/latest/docs/files/container/container-js.html>)
 */

var PrestationDesc = GATONERO.extend(GATONERO.Container, {
  classId: 'prestation',
  orientation: GATONERO.Constants.VERTICAL,
  'css-class': 'prestation',
  isCollapsed: function() { 
    var prestation = this.getValue();
    return prestation ? !prestation.expanded : true;
  },
  setCollapsed: function(collapsed) { 
    var old = this.isCollapsed();
    if (old == collapsed) { return; }
    var prestation = this.getValue();
    if (prestation) prestation.expanded = !collapsed;
    this.setDirty(true);
    this.fireItemEvent('collapsed', old, collapsed);    
  },
  setDirtyAll: function() {
    if (this.__rendering) { return; }
    var comps = this.getComponents(),
        n = comps.length;
    for (var i = n - 1; i >= 0; i--) {
      comps[i].setDirty(true);
    }
    this.setDirty(true);
  },
  getComponents: function() {
    if (!this.components || this.isDirty()) { this.reloadComponents(); }
    return this.components;
  },
  reloadComponents: function() {
    this.components = [];
    var value = this.getValue();
    if (value) {
      var resources = value.getResources(),
          n = resources.length, i = 0, r, resource;
      for (i; i < n; i++) {
        resource = resources[n - i - 1];
        r = this.factory.findItem(resource.id);
        if (!r) {
          r = this.factory.createItem(ResourceDesc, resource.id);
          r.setValue(resource, { silent: true });
          r.addItemListener(this);
        }
        if (!resource.name) {
          resource.name = resource.type + ' [' + (n - i) + ']';
        }
        this.components[i] = r;
      }
    }
  },
  getTimeline: function() {
    if (!this.timeline) { 
      this.timeline = this.factory.createItem(Timeline, null); 
      this.timeline.setValue(this.getPhases());
      this.timeline['show-intervals'] = false;
    }
    return this.timeline;
  },
  getPhases: function() {
    if (!this.events || this.isDirty()) {
      var prestation = this.getValue();
      if (!prestation) { return []; }
      var pre = new TimedEvent( { start: prestation.start, end: prestation['service-start'], phase: PRE }),
          service = new TimedEvent( { start: prestation['service-start'], end: prestation['service-end'], phase: SERVICE }), 
          post = new TimedEvent({ start: prestation['service-end'], end: prestation.end, phase: POST });
      this.events = [pre, service, post];
    }
    return this.events;
  },
  remove: function(resource) {
    var prestation = this.getValue();
    if (!prestation || !resource) return;
    prestation.remove(resource);
    this.setDirty(true);
    this.fireItemEvent(GATONERO.Item.VALUE, null, prestation);
  },
  getActivityIndicator: function() {
    if (!this.activity) {
      var activity =  this.factory.createItem(ActivityIndicator);
      activity.setValue(this.getValue());
      activity.setHideCaption(false);
      var self = this;
      activity.isDirty = function() { return self.isDirty(); }
      this.activity = activity;
    }
    return this.activity;
  }
});

GATONERO.addProperty(PrestationDesc, 
                     'hide-resources',
                     { isBoolean: true, default_value: false });

GATONERO.ItemFactory.bind(PrestationDesc);

var PrestationRenderer = GATONERO.extend(GATONERO.RendererDelegate, {
  tag: 'TABLE',
  useCaption: false,
  create: function(elt, desc, parent) {
    var r = this.renderer,
        thead = document.createElement('THEAD'),
        tbody = document.createElement('TBODY'),
        tfoot = document.createElement('TFOOT'),
        caption = document.createElement('TR'),
        caption_text = document.createElement('TD'),
        timeline = document.createElement('TD'),
        toolbar = document.createElement('TD'),
        empty = document.createElement('A'), 
        hide = document.createElement('A'),
        collapse = document.createElement('DIV'),
        footer = this.createFooter(elt, desc);
                
    r.setComponent(elt, 'thead', thead);
    r.setComponent(elt, 'tbody', tbody);
    r.setComponent(elt, 'tfoot', tfoot);
    r.setComponent(elt, 'caption', caption);
    this.addClass(caption_text, 'caption_text');
    r.setComponent(elt, 'caption_text', caption_text);
    r.setComponent(elt, 'timeline', timeline);
    r.setComponent(elt, 'toolbar', toolbar);
    r.setComponent(elt, 'collapse', collapse);
    
    empty.innerHTML = i18n['empty'];
    this.attachEvent(empty, 'click', this.doEmpty);
    r.setComponent(elt, 'empty', empty);
    
    hide.innerHTML = i18n['hide'];
    this.attachEvent(hide, 'click', this.hide);
    r.setComponent(elt, 'hide', hide);
    
    collapse.innerHTML = '&nbsp;';
    this.addClass(collapse, this.css.ICON);
    this.attachEvent(collapse,'click', this.collapse);
    r.setComponent(elt, 'collapse', collapse);

    r.setComponent(elt, 'footer', footer);
  },
  collapse: function(e, elt, panel) {
    panel.setCollapsed(!panel.isCollapsed());
  },
  renderCaption: function(elt, desc, parent) {},
  doRenderCaption: function(elt, desc, parent) {
    var r = this.renderer,
        tbody = r.getComponent(elt, 'tbody'),
        caption = r.getCaption(elt),
        caption_text = r.getComponent(elt, 'caption_text'),
        timeline_desc = desc.getTimeline(),
        timeline = r.getComponent(elt, 'timeline'),
        toolbar = r.getComponent(elt, 'toolbar'),
        empty = r.getComponent(elt, 'empty'),
        hide = r.getComponent(elt, 'hide'),
        collapse = r.getComponent(elt, 'collapse');
        
    this.empty(caption);
    this.empty(timeline);
    
    tbody.appendChild(caption);
    this.addClass(caption, this.css.CAPTION);
    caption.appendChild(caption_text);
    caption_text.innerHTML = desc.getCaption();
    caption.appendChild(timeline);
    caption.appendChild(toolbar);
    toolbar.appendChild(collapse);
    if (timeline_desc) {
      timeline.appendChild(r.render(timeline_desc, timeline));
    }
    
    if (desc.isCollapsed()) {
      this.addClass(caption, "collapse");
      this.removeClass(collapse, "collapse") 
      this.addClass(collapse, "expand");     
    }
    else {
      this.removeClass(caption, "collapse");
      this.removeClass(collapse, "expand");
      this.addClass(collapse, "collapse");
      
      var br = document.createElement('BR');
      caption_text.appendChild(br);
        
      empty.innerHTML = i18n['empty'];
      caption_text.appendChild(empty);

      hide.innerHTML = desc.isHideResources() ? i18n['show'] : i18n['hide'];
      caption_text.appendChild(hide);
    }
    
    return elt;    
  },
  doRender: function(table, desc, parent) {
    var r = this.renderer,
        thead = r.getComponent(table, 'thead'),
        tbody = r.getComponent(table, 'tbody'),
        tfoot = r.getComponent(table, 'tfoot');
        
    this.empty(tbody);
    table.appendChild(thead);
    table.appendChild(tbody);
    table.appendChild(tfoot);
    
    
    this.doRenderCaption(table, desc, parent);
    if (!desc.isCollapsed()) { 
      var components = desc.getComponents(),
          n = components.length,
          child, comp, i;
      for (i = 0; i < n; i++) {
        child = components[i];
        if (desc.isHideResources() && child.getValue().getActivities().length == 0) {
          continue;
        }
        comp = r.render(child, table);
        if (comp) { tbody.appendChild(comp); }
      }
      if (desc.getValue()) { tbody.appendChild(this.renderFooter(table, desc)); }
    }
    return table;
  },
  createFooter: function(elt, desc) {
    var tr = document.createElement('TR');
    for (var i = 2; i >= 0; i--) { 
      tr.appendChild(document.createElement('TD')); 
    }
    return tr;
  },
  renderFooter: function(table, desc) {
    var r = this.renderer,
        footer = r.getComponent(table, 'footer'),
        load = desc.getValue().getLoad(),
        n = load.length,
        tds = footer.getElementsByTagName('TD'),
        s = '';
        
    tds[0].innerHTML = i18n['load'];
    this.addClass(tds[0], this.css.CAPTION);
    
    for (var i = 0; i < n; i++) {
      s += '<span>' + (load[i] ? load[i] : '&nbsp;') + '</span>';
    }
    
    tds[1].innerHTML = s;
    this.empty(tds[2]);
    tds[2].appendChild(r.render(desc.getActivityIndicator(), tds[2]));
    this.addClass(footer, 'load');
    return footer;
  },
  doEmpty: function(e, elt, desc) {
    var r = this.renderer,
        prestation = desc.getValue(),
        p_desc = r.getDescriptor(elt, PlanningDesc),
        planning;
    if (!prestation || !p_desc) { return; }
    planning = p_desc.getValue();
    if (!planning) { return; }
    prestation.empty(planning);
    desc.setDirtyAll();
    desc.fireItemEvent(GATONERO.Component.prototype.VALUE, null, prestation);
    p_desc.getWaitArea().setDirty(true);
    p_desc.fireItemEvent(GATONERO.Component.prototype.VALUE, null, planning); 
  },
  hide: function(e, elt, desc) {
    desc.setHideResources(!desc.isHideResources());
  }
});

PrestationDesc.prototype.delegate = new PrestationRenderer();
var id = 0;

/*
 * Class: ResourceDesc
 * Un composant décrivant la représentation graphique d'une ressource 
 * (<Resource>) à l'écran.
 * 
 * Extends: GATONERO.Box (<http://www.gatonero.com/ria/0.5/docs/files/container/box-js.html>)
 */
var ResourceDesc = GATONERO.extend(GATONERO.Box, {
  classId: 'resource',
  orientation: GATONERO.Constants.HORIZONTAL,
  'caption-position': GATONERO.Constants.TOP,
  'css-class': 'resource',
  isDirty: function() {
    return this.__dirty || (this.getValue() && this.getValue().isDirty());
  },
  setDirty: function(dirty) {
    if (dirty == false && this.getValue()) { this.getValue().setDirty(false); }
    this.__dirty = dirty;
  },
  getCaption:  function() { 
    var value = this.getValue();
    return value ? (value.name ? value.name : value.toString()) : '';
  },
  getPhases: function() {
    if (this.isDirty() || !this.phases) {
      var phases = [], value = this.getValue(), data,
          serviceStart, serviceEnd, breakStart, breakEnd;
      if (!value) { return null; }
      serviceStart = value['service-start'];
      serviceEnd = value['service-end'];
      breakStart = value['break-start'];
      breakEnd = value['break-end'];
    
      if (breakStart > 0 && breakEnd > 0) {
        data = [value.start,
                breakStart, breakEnd,
                value.end];
      }  
      else { data = [value.start, value.end]; }
        
      var start, end, phase;
      for (var i = 0, n = data.length - 1; i < n; i++) {
        start = data[i];
        end = data[i + 1];
        phase = (!breakStart || end <= breakStart) ? 
                  PRE : 
                  ((!breakEnd || start >= breakEnd) ? POST : BREAK);
        phases[i] = new TimedEvent({ start: start,
                                     end: end,
                                     phase: phase });
      } 
      this.phases = phases;
    }    
    return this.phases;
  },
  getComponents: function() {
    if (!this.components) { 
      this.components = [this.getMainPanel(), this.getIndicators()]; 
    }
    return this.components;
  },
  getMainPanel: function() {
    if (!this.mainPanel) {
      var main = this.factory.createItem(ResourceTimeline, null);
      main.setValue(this.getValue(), { silent: true });

      var self = this;
      main.isDirty = function() { return self.isDirty(); }
      this.mainPanel = main;
    }
    return this.mainPanel;
  },
  getIndicatorsPanel: function() {
    if (!this.indicatorsPanel) {
      var indicators = this.factory.createItem(Indicators, null);
      indicators.setValue(this.getValue(), { silent: true });
      var self = this;
      indicators.isDirty = function() { return self.isDirty(); };
      this.indicatorsPanel = indicators;
    }
    return this.indicatorsPanel;
  },
  addToClipboard: function(timeslot) {
    timeslot.setSelected(true);
    this.getMainPanel().addToClipboard(timeslot);
    this.getValue().removeActivity(timeslot.getValue());
    this.setDirty(true);
    this.fireItemEvent('value', null, this.getValue());
  },
  removeFromClipboard: function(timeslot, add) {
    timeslot.setSelected(false);
    this.getMainPanel().removeFromClipboard(timeslot);
    if (add) { this.getValue().addActivity(timeslot.getValue()); }
    this.setDirty(true);
    this.fireItemEvent('value', null, this.getValue());
  }
});

GATONERO.ItemFactory.bind(ResourceDesc);

var ResourceRenderer = GATONERO.extend(GATONERO.RendererDelegate, {
  tag: 'TR',
  useCaption: false,
  create: function(elt, desc, parent) {
    var r = this.renderer,
        caption = document.createElement('TD'),
        details = document.createElement('IMG'),
        remove = document.createElement('IMG'),
        caption_text = document.createElement('DIV'),
        main_panel = document.createElement('TD'),
        indicators = document.createElement('TD');
        
    this.addClass(caption, this.css.CAPTION);

    details.src = 'css/images/info.png';
    details.alt = i18n['details'];
    this.addClass(details, [this.css.ICON, 'info']);
    this.attachEvent(details, 'click', this.showDetails);
    
    remove.src = 'css/images/delete.png';
    remove.alt = i18n['delete'];
    this.addClass(remove, [this.css.ICON, 'delete']);
    this.attachEvent(remove, 'click', this.remove);
      
    r.setComponent(elt, 'caption', caption);
    r.setComponent(elt, 'details', details);
    r.setComponent(elt, 'remove', remove);
    r.setComponent(elt, 'caption_text', caption_text);
    r.setComponent(elt, 'main_panel', main_panel);
    r.setComponent(elt, 'indicators', indicators);
  },
  renderCaption: function(elt, desc, parent) {
    var r = this.renderer,
        caption = r.getCaption(elt),
        details = r.getComponent(elt, 'details'),
        remove = r.getComponent(elt, 'remove'),
        caption_text = r.getComponent(elt, 'caption_text');
        
    caption.appendChild(details);
    caption.appendChild(remove);
    caption.appendChild(caption_text);
    caption_text.innerHTML = desc.getCaption();
    elt.appendChild(caption);
    return elt;
  },
  doRender: function(tr, desc, parent) {
    var r = this.renderer,
        main_panel = r.getComponent(tr, 'main_panel'),
        indicators = r.getComponent(tr, 'indicators'),
        remove = r.getComponent(tr, 'remove'),
        resource = desc.getValue();
        
    tr.appendChild(main_panel);
    main_panel.appendChild(r.render(desc.getMainPanel(), main_panel));
        
    tr.appendChild(indicators);
    indicators.appendChild(r.render(desc.getIndicatorsPanel(), indicators));
    
    if (resource) {
      remove.style.display = (resource.getActivities().length > 0) ? 'none' : 'inline';
    }
    
    this.doRenderClipboard(tr, desc);
    return tr;
  },
  doRenderClipboard: function(tr, desc) {
    
  },
  showDetails: function(e, elt, desc) {
    var r = this.renderer;
    var resource = desc.getValue();
    if (!resource) { return; }
    var w = desc.factory.findItem('details');
    
    r.setPosition(w.element, r.getMousePosition(e), { x: 5, y: 5 })
    w.setCaption(desc.getCaption());
      
    var name = desc.factory.findItem('name');
    name.setValue(resource.name);
    
    var tfstart = desc.factory.findItem('tf-start');
    tfstart.setValue(resource.start);
    
    var end = desc.factory.findItem('end');
    end.setValue(resource.getEndTime());
  
    var pause = desc.factory.findItem('pause');
    if (resource.pause) pause.setValue(resource.pause);
    
    var break_start = desc.factory.findItem('break-start');
    var breakStart = resource['break-start'];
    if (breakStart) { break_start.setValue(breakStart); }
      
    var break_end = desc.factory.findItem('break-end');
    var breakEnd = resource['break-end'];
    if (breakEnd) { break_end.setValue(breakEnd); }
      
    w.getAction('ok').setResourceDesc(desc);
    w.setVisible(true);
  },
  remove: function(e, elt, desc) {
    var r = this.renderer,
        resource = desc.getValue(),
        p_desc = r.getDescriptor(elt, PrestationDesc), 
        prestation;
    if (!resource || !p_desc) { return; }
    prestation = p_desc.getValue();
    if (!prestation) { return; }

    if (resource.getActivities().length > 0) { alert('verboten'); return; }
    
    var dialog = desc.factory.findItem('remove-confirm');
    r.setPosition(dialog.element, r.getMousePosition(e), { x: 5, y: 5 });
    dialog.setMessage(i18n['confirm-remove'] + desc.getCaption());
    var okAction = dialog.getAction('yes');
    okAction.setTarget(p_desc);
    okAction.setParameters([resource]);
    dialog.setVisible(true);
  }
});

ResourceDesc.prototype.delegate = new ResourceRenderer();
/*
 * Class: WaitAreaDesc
 * Un composant décrivant la représentation graphique d'une zone de déstockage 
 * (<WaitArea>) à l'écran.
 * 
 * Extends: <ResourceDesc>
 */
var WaitAreaDesc = GATONERO.extend(ResourceDesc, {
  classId: 'wait-area',
  orientation: GATONERO.Constants.HORIZONTAL,
  'css-class': 'wait',
  droppable: true,
  collapsible: true,
  draggable: false,
  pinnable: false,
  'hide-caption': false,
  'caption-position': GATONERO.Constants.TOP,
  getCaption: function() { return i18n['wait-area']; },
  isDirty: function() {
    return this.__dirty || (this.getValue() && this.getValue().isDirty());
  },
  getComponents: function() {
    var wait = this.getValue(), 
        events, n, timeslot;

    if (!this.components  || this.isDirty()) {
      this.components = []; 
    }
    if (wait) {
      events = wait.getActivities();
      for (var i = 0, n = events.length; i < n; i++ ) {
        timeslot = Timeslot.getTimeslot(events[i], this.factory);
        this.components.push(timeslot);
      }
    }
    return this.components;
  },
  getIndicatorsPanel: function() { return null; },
  __useSeparators: false,
  getTimeline: function() { return null; }
});

GATONERO.ItemFactory.bind(WaitAreaDesc);

var WaitAreaRenderer = GATONERO.extend(GATONERO.PanelRenderer, {
  doCreateToolbar: function(elt, desc, parent) {
    var r = this.renderer,
        merge = document.createElement('A');
        
    merge.innerHTML = i18n['merge'].entitify();
    this.attachEvent(merge, 'click', this.merge);
    r.setComponent(elt, 'merge', merge);
    return elt;    
  },
  doRenderToolbar: function(elt, desc, parent) {
    var r = this.renderer,
        toolbar = r.getComponent(elt, r._TOOLBAR),
        merge = r.getComponent(elt, 'merge');
    toolbar.appendChild(merge);
  },
  merge: function(e, elt, desc) {
    var waitarea = desc.getValue();
    if (!waitarea) { return; }
    waitarea.merge();
    desc.setDirty(true);
    desc.fireItemEvent(GATONERO.Item.VALUE, null, waitarea);
  },
  canDrop: function(draggable, data, source, position, offset, options) {
    return (data instanceof Activity);
  },
  drop: function(droppable, draggable, source, position, offset, options) {
    var r = this.renderer,
        delegate = this,
        wait_desc = r.getDescriptor(droppable, WaitAreaDesc),
        waitarea = wait_desc.getValue(),
        activity = source.getValue(),
        options = options || {};
    if (activity instanceof Pause || activity.phase == SERVICE) { 
      return GATONERO.DRAG_CANCELLED;
    }

    options.merge = true;

    ok = waitarea.addActivity(activity, options);
    return ok ? GATONERO.DRAG_COMPLETE : GATONERO.DRAG_CANCELLED;
  },
  dragComplete: function(droppable, draggable, source) {
    var r = this.renderer,
        waitarea = r.getDescriptor(droppable, WaitAreaDesc),
        resource_from = r.getDescriptor(source.element, ResourceDesc);
    if (resource_from) {
      resource_from.setDirty(true);
      resource_from.fireItemEvent(GATONERO.Item.VALUE, null, resource_from.getValue());
    }
    waitarea.setDirty(true);
    waitarea.fireItemEvent(GATONERO.Item.VALUE, null, waitarea.getValue());
  },
  cancelDrop: function(activity, resource) {
    if (activity instanceof Pause) { return; }
    var r = resource.getValue();
    if (!r) { return; }
    r.addActivity(activity, { force: true });
    resource.setDirty(true);
    resource.fireItemEvent(GATONERO.Item.VALUE, null, r);    
  }
});

WaitAreaDesc.prototype.delegate = new WaitAreaRenderer();
/*
 * Class: ResourceTimeline
 * Un composant décrivant la représentation à l'écran des activités d'une
 * resource (<Resource>). Cette représentation prend l'apparence d'une 
 * <Timeline> affichant les phases (<Resource.getPhases>) de la ressource, 
 * sous laquelle est affichée une <Timeline> indiquant les activités 
 * (<Resource.getActivities>) de la ressource.
 * 
 * Extends: GATONERO.Box (<http://www.gatonero.com/ria/0.5/docs/files/container/box-js.html>)
 */
var ResourceTimeline = GATONERO.extend(GATONERO.Box, {
  classId: 'resource-timeline',
  orientation: GATONERO.Constants.VERTICAL,
  getWidth: function() {
    return (DAY_END - DAY_START) / TimedEvent.SCALE;
  },
  'css-class': css.MAIN,
  droppable: true,
  getComponents: function() {
    if (!this.components) {
      this.components = [this.getTimeline(), 
                         this.getActivitiesPanel(),
                         this.getClipboard()];
    }
    return this.components;
  },
  getActivitiesPanel: function() {
    if (!this.activitiesPanel) {
      var panel = this.factory.createItem(Timeline, null);
      panel['show-intervals'] = false;
      panel.setCssClass(css.ACTIVITIES, { silent: true });
      var self = this;
      panel.isDirty = function() { return self.isDirty(); }
      panel.getValue = function() {
        var resource = self.getValue();
        return  resource ? resource.getActivities() : [];
      }
      this.activitiesPanel = panel;
    }
    return this.activitiesPanel;
  },
  getTimeline: function() {
    if (!this.timeline) {
      var timeline = this.factory.createItem(Timeline, null);
      timeline['show-intervals'] = false;
      var self = this;
      timeline.isDirty = function() { return self.isDirty(); }
      timeline.getValue = function() { 
        var resource = self.getValue();
        return resource ? resource.getPhases() : []; }
      this.timeline = timeline;
    }
    return this.timeline;
  },
  
  getClipboard: function() {
    if (!this.clipboard) { 
      this.clipboard = this.factory.createItem(Clipboard, null); 
      this.clipboard.resource = this.getValue().id;
    }
    return this.clipboard;
  },
  addToClipboard: function(timeslot) {
    this.getClipboard().addEvent(timeslot.getValue());
    this.setDirty(true);
    this.fireItemEvent('clipboard', null, timeslot.getValue());
  },
  removeFromClipboard: function(timeslot) {
    this.getClipboard().removeEvent(timeslot.getValue());
    this.setDirty(true);
    this.fireItemEvent('clipboard', null, timeslot.getValue());
  }
});

GATONERO.ItemFactory.bind(ResourceTimeline);

var ResourceTimelineRenderer = GATONERO.extend(GATONERO.PanelRenderer, {
  useCaption: false,
  canDrop: function(draggable, data, source, position, offset, options) {
    return (data instanceof Activity || source instanceof Clipboard);
  },
  drop: function(droppable, draggable, source, data, position, offset, options) {
    var r = this.renderer,
        delegate = this,
        rdesc_to = r.getDescriptor(droppable, ResourceDesc),
        resource_to = rdesc_to.getValue(),
        activity = data,
        prestation, 
        planning,
        ok = false,
        options = options || {},
        dragged = r.drag.dragged;

    if (source instanceof Clipboard) {
      var events = source.getEvents(),
          n = events.length,
          event,
          offset = DAY_START.time + 
                     ((r.getPosition(dragged).x - r.getPosition(droppable).x) * 
                        TimedEvent.SCALE) -
                     source.getStart().time,
          rdesc_from = source.factory.findItem(source.resource);
      ok = true;
      for (var i = n - 1; i >= 0; i--) {
        event = events[i];
        ok &= resource_to.canAdd(event, event.start.after(offset));
      }
      if (!ok) { return GATONERO.DRAG_CANCELLED; }
      
      for (var i = n - 1; i >= 0; i--) {
        event = events[i];
        var at = event.start.after(offset),
            timeslot = Timeslot.getTimeslot(event, source.factory);
        rdesc_from.removeFromClipboard(timeslot, false);
        resource_to.addActivity(event, { start: at, time: at, force: true });
      }
      return GATONERO.DRAG_COMPLETE;
    }
    if (activity instanceof Pause && activity.resource != resource_to.id) {
      return GATONERO.DRAG_CANCELLED;
    }
    if (options.action == 'cancel') {
      return GATONERO.DRAG_CANCELLED;
    }
    if (!options.time) {
      options.time = 
        DAY_START.after((position.x - r.getPosition(droppable).x) * TimedEvent.SCALE)
    }
    if (!options.start) {
      var d = (r.getPosition(dragged).x - r.getPosition(droppable).x) * TimedEvent.SCALE;
      if (activity.phase == SERVICE) d = Math.round(d / 900000) * 900000;
      options.start = DAY_START.after(d);
    }
    options.position = position;
    if (resource_to.activities.length == 0) { 
      var prestation = r.getDescriptor(droppable, PrestationDesc);
      prestation.setDirty(true);
    }
    ok = resource_to.addActivity(activity, options);
    
    if (ok) { return GATONERO.DRAG_COMPLETE; }
    if (activity.phase == SERVICE){ return GATONERO.DRAG_CANCELLED; }
    
    var canSplit = resource_to.canSplit(activity, options.time, options.start);
    if (canSplit) {
      planning = r.getDescriptor(droppable, PlanningDesc);
      options.planning = planning.getValue();    
    }
    var canShiftLeft = resource_to.canShiftLeft(activity, options.start);
    var canShiftRight = resource_to.canShiftRight(activity, options.start);
    if (canSplit || canShiftLeft || canShiftRight) {
      var w = rdesc_to.factory.findItem('resource');
      r.setPosition(w.element, options.position, { x: 5, y: 5 });
      if (canSplit) w.factory.findItem('choice').setSelectedIndex(0);
      else if (canShiftLeft) w.factory.findItem('choice').setSelectedIndex(1);
      else if (canShiftRight) w.factory.findItem('choice').setSelectedIndex(2);
      w.factory.findItem('split').setEnabled(canSplit);
      w.factory.findItem('left').setEnabled(canShiftLeft);
      w.factory.findItem('right').setEnabled(canShiftRight);
      w.getAction('close').doExecute = function() {
          var choice = w.getValue();
          options.action = choice;
          r.drop(droppable, draggable, source, data, position, offset, options);
          return true;
        };
      w.setVisible(true);
      return GATONERO.DROP_IN_PROGRESS;
    }
    else { return GATONERO.DRAG_CANCELLED; }
  },
  dragComplete: function(droppable, draggable, source, options) {
    var r = this.renderer,
        resource = r.getDescriptor(droppable, ResourceDesc),
        planning = r.getDescriptor(droppable, PlanningDesc),
        resource_from = r.getDescriptor(source.element, ResourceDesc);
    Timeslot.__clipboardResource = null;
    Timeslot.__clipboardCount = 0;
    resource.setDirty(true);
    if (resource_from) resource_from.setDirty(true);
    resource.fireItemEvent(GATONERO.Item.VALUE, null, resource.getValue());
    if (resource_from) resource_from.fireItemEvent(GATONERO.Item.VALUE, null, resource_from.getValue());
    if (options && options.action == SPLIT) {
      var waitarea = r.getDescriptor(droppable, PlanningDesc).getWaitArea();
      waitarea.setDirty(true);
      waitarea.fireItemEvent(GATONERO.Item.VALUE, null, waitarea.getValue());
    }
    var prestation = r.getDescriptor(droppable, PrestationDesc);
    prestation.setDirty(true);
    prestation.fireItemEvent(GATONERO.Item.VALUE, null, prestation.getValue()); 
  },
  cancelDrop: function(activity) {
    if (activity instanceof Pause) { return; }
    var rdesc = this.factory
    var r = resource.getValue();
    if (!r) { return; }
    r.addActivity(activity, { force: true });
    resource.setDirty(true);
    resource.fireItemEvent(GATONERO.Item.VALUE, null, r);    
  }
});

ResourceTimeline.prototype.delegate = new ResourceTimelineRenderer();
/*
 * Class: Indicators
 * Un composant contenant divers indicateurs pour une ressource:
 * - indicateur d'activité
 * - indicateur du temps de pause
 */
var Indicators = GATONERO.extend(GATONERO.Container, {
  classId: 'indicators',
  droppable: true,
  getComponents: function() {
    if (!this.components) { 
      this.components = [this.getActivityIndicator(), this.getPauseIndicator()];
    }
    return this.components;
  },
  getPauseIndicator: function() {
    if (!this.pause) {
      var pause = this.factory.createItem(PauseIndicator, null);
      pause.resource = this.getValue();
      var self = this;
      pause.isDirty = function() { return self.isDirty(); },
      this.pause = pause;
    }
    return this.pause;
  },
  getActivityIndicator: function() {
    if (!this.activity) {
       var activity = this.factory.createItem(ActivityIndicator, null);
       activity.setValue(this.getValue());
       var self = this;
       activity.isDirty = function() { return self.isDirty(); };
       this.activity = activity;
    } 
    return this.activity;
  }
});

GATONERO.ItemFactory.bind(Indicators);

var IndicatorsRenderer = GATONERO.extend(GATONERO.RendererDelegate, {
  tag: 'TABLE',
  useCaption: false,
  doRenderCaption: function(caption, desc) {},
  doRender: function(table, desc, parent) {
    
    this.empty(table);
    if (!table.tbody) table.tbody = document.createElement('TBODY');
    table.appendChild(table.tbody);
    this.empty(table.tbody);
    
    var children = desc.getComponents(),
        n = children.length,
        child, tr, td1, td2, comp, i;
    for (i = 0; i < n; i++) {
      child = children[i];
      var tr = document.createElement('TR');
      var td1 = document.createElement('TD');
      td1.innerHTML = child.getCaption();
      td1.className = 'caption';
      tr.appendChild(td1);
      
      child.setHideCaption(true);
      var td2 = document.createElement('TD');
      comp = this.renderer.render(child, td2);
      td2.appendChild(comp);
      tr.appendChild(td2);
      
      table.tbody.appendChild(tr);
    }
    return table;
  },
  canDrop: function(draggable, data, source, position, offset, options) {
    return (data instanceof Pause);
  },
  drop: function(droppable, draggable, source, data, position, offset, options) {
    return GATONERO.DRAG_COMPLETE;    
  },
  dragComplete: function(droppable, draggable, source, options) {
    var r = this.renderer,
        resource = r.getDescriptor(droppable, ResourceDesc);
    resource.setDirty(true);
    resource.fireItemEvent();
  }
});

Indicators.prototype.delegate = new IndicatorsRenderer();

/*
 * Class: ActivityIndicator
 * Un descripteur de composant destiné à afficher le niveau d'activité d'une 
 * <Resource>. Le niveau d'activité est affiché sous la forme:
 * (start code)
 * temps d'activité / temps disponible
 * (end code)
 * où:
 * 
 * - temps d'activité: total des durées des macro-gammes de la ressource
 * - temps disponible:  durée de la journée de travail de la ressource diminuée
 * du temps de pause de la ressource déjà planifié.
 * 
 * Extends: <https://www.gatonero.com/ria/0.5/docs/files/label-js.html>
 */
var ActivityIndicator = GATONERO.extend(GATONERO.Label, {
  classId: 'activity-indicator',
  caption: i18n['activity'],
  'css-class': 'activity',
  'caption-position': GATONERO.Constants.LEFT,
  getText: function() {
    var value = this.getValue();
    if (!value) return '0.0/0.0';
    return value.getTimeWorked().toDecimalString() + '/' +
             value.getTotalTime().toDecimalString();
  }
});

GATONERO.ItemFactory.bind(ActivityIndicator);
/*
 * Class: PauseIndicator
 * Un composant représentant un indicateur du temps de pause pour une <Resource>.
 * 
 * Extends: GATONERO.ProgressBar <http://www.gatonero.com/ria/0.5/docs/files/input/progress-bar-js-html>
 */
var PauseIndicator = GATONERO.extend(GATONERO.ProgressBar, {
  SCALE: 1,
  classId: 'pause-indicator',
  draggable: true,
  droppable: true,
  caption: i18n['pause'],
  getMax: function() { 
    return this.resource.getAllowedPauseMs() / TimedEvent.SCALE * this.SCALE; 
  },
  getWidth: function() { return this.getMax(); },
  getValue: function() { 
    return (this.resource.getAllowedPauseMs() - this.resource.getPausesMs()) / 
              TimedEvent.SCALE * this.SCALE; 
  }  
});

GATONERO.ItemFactory.bind(PauseIndicator);

var PauseIndicatorRenderer = GATONERO.extend(GATONERO.ProgressBarRenderer, {
  getDraggable: function(desc, position) {
    var r = this.renderer;
    var pause = this.getDragData(desc, position);
    var timeslot = Timeslot.getTimeslot(pause, desc.factory);
    var slot = r.render(timeslot, document.body);
    slot.style.position ='absolute';
    r.setPosition(slot, position, { x: -10, y: -10 });
    document.body.appendChild(slot);
    return slot;
  },
  getDragData: function(desc, position) {
    return new Pause({ start: "00:00", end: "00:15", resource: desc.resource.id });
  }
});

PauseIndicator.prototype.delegate = new PauseIndicatorRenderer();
/*
 * Class: Timeline
 * Custom component descriptor used to represent a timeline, that is, a 
 * set of events displayed in their order of occurrence in time.
 * The Timeline class is used throughout the code in various forms.
 * 
 * Extends:
 * GATONERO.Box
 */
var Timeline = GATONERO.extend(GATONERO.Box, {
  /*
   * Property: classId
   * A unique identifyer for all instances of this class.
   */
  classId: 'timeline',
  'css-class': 'timeline',
  
  /*
   * Property: hide-caption = true
   * Do not show the caption
   */
  'hide-caption': true,
  orientation: GATONERO.Constants.HORIZONTAL,
  interval: 60 * 60 * 1000,
  scale: 60 * 1000,
  getEvents: function() {
    if (!this.events || this.isDirty()){
      var events = this.getValue();
      this.events = events ? events.sort(function(a, b) { return a.compare(b); }) : [];
    }
    return this.events;
  },
  getDuration: function() { return this.getEnd() - this.getStart(); },
  getWidth: function() { 
    return this.getDuration() / this.scale; 
  },
  getComponents: function() {
    if (!this.components) this.components = [];
    if (this.isDirty()) { this.reloadComponents(); }
    return this.components;
  },
  reloadComponents: function() {
    var events = this.getEvents(),
        components = this.components,
        timeslots = [],
        n = events ? events.length : 0,
        e,
        start = this.getStart(),
        end = this.getEnd(),
        time = start,
        duration,
        sep,
        timeslot,
        i;
    if (!events || !(events instanceof Array)) { return; }
    this.components = [];
    for (var i = 0; i < n; i++) {
      e = events[i];
      if (time < e.start) {
        this.addIntervals(time, e.start); 
      }
      timeslot = Timeslot.getTimeslot(e, this.factory);
      this.components.push(timeslot);    
      time = e.getEndTime();
    }

    if (time < end) { this.addIntervals(time, end); }
    if (this.clipboard && this.clipboard.getEvents().length > 0) { this.components.push(this.clipboard); }

  },
  /*
   * Method: addIntervals
   * Adds 'placeholder' events to the timeline when no event is present.
   * Parameters:
   * start - beginning of the period for which to add intervals
   * end - end of the period
   */
  addIntervals: function(start, end) {
    if (!start || !end) { return; }
    var time = start, sep, duration;
    while (time < end) {
      sep = this.factory.createItem(Timeslot, null);
      duration = end - time;
      sep.setWidth(duration / TimedEvent.SCALE, { silent: true });
      time = time.after(duration);
      this.components.push(sep);
    }
  }
});

GATONERO.addProperties(Timeline, {
  'show-intervals': { isBoolean: true, default_value: false },
  scale: { default_value: 60000 },
  start: {},
  end: {}
});

GATONERO.extend(Timeline.prototype, {
  getStart: function() { return this.start || DAY_START; },
  getEnd: function() { return this.end || DAY_END; }
});


GATONERO.ItemFactory.bind(Timeline);

var TimelineRenderer = GATONERO.extend(GATONERO.PanelRenderer, {
  useCaption: false,
  renderCaption: function(elt, desc, parent) {},
  doRender: function(div, desc, parent) {
    GATONERO.PanelRenderer.prototype.doRender.call(this, div, desc, parent);
    var events = desc.getValue();
    if (!events || events.length == 0) { return div; }
    var start = DAY_START.getTime() / desc.scale;
    div.style.backgroundPosition = -(start) + 'px 100%';
    return div;
  }
});

Timeline.prototype.delegate = new TimelineRenderer();
var Timeslot = GATONERO.extend(GATONERO.Label, {
  classId: 'timeslot',
  'hide-caption': true,
  isDirty: function() {
    return this.__dirty || (this.getValue() && this.getValue().isDirty());
  },
  setDirty: function(dirty) {
    if (dirty == false && this.getValue()) { this.getValue().setDirty(false); }
    this.__dirty = dirty;    
  },
  isDraggable: function() {
    if (this.isSplitting() || this.isSelected()) { return false; }
    var event = this.getValue();
    return (event && event instanceof Activity && !event.locked);
  },
  getTooltip: function() {
    var event = this.getValue(), tooltip = '';
    if (event && event instanceof Activity) {
      tooltip += event.toString().entitify();
      if (event instanceof MacroGamme) {
        tooltip += '<br/> Module: ' + event.module.entitify();
        tooltip += '<br/> Sous-module: ' + event['sous-module'].entitify();
      }
    }
    return tooltip;
  },
  getText: function() {
    var event = this.getValue();
    if (!event) return GATONERO.Label.prototype.getText.apply(this);
    if (event instanceof Pause) { return ''; }
    var text = event.description;
    if (!text) { return event.toString(); }
    var d = event.getDuration();
    var n = Math.round(Math.max(0, d - 16) / 8);
    return (text && text.trim() != ''? 
             (text.deentitify().substring(0, n).entitify()) + (n < d && n > 0 ? '&hellip;' : '') :
             '&nbsp;');
  },
  getLockMenuItem: function() {
    if (!this.lock) { 
      var lock = this.factory.createItem(GATONERO.Toggle2, null),
          self = this;
      lock.getCaption = function() { 
        return self.isLocked() ? i18n['unlock'] : i18n['lock'];
      };
      var action = this.factory.createItem(GATONERO.Action, null);
      action.doExecute = function() { self.setLocked(!self.isLocked()); };
      lock.addAction(action, 'select');
      lock.addAction(action, 'deselect');
      this.lock = lock;
    }
    return this.lock;
  },
  isLocked: function() { 
    var event = this.getValue();
    return event ? event.isLocked() : false;
  },
  setLocked: function(locked) {
    var event = this.getValue(),
        old = this.isLocked();
    if (event) event.setLocked(locked);
    this.getSplitMenuItem().setEnabled(!locked);
    this.setDirty(true);
    this.fireItemEvent('locked', old, locked);
  },
  getSplitMenuItem: function() {
    if (!this.split) {
      var split = this.factory.createItem(GATONERO.Button2, null),
          self = this;
      split.setCaption(i18n['split']);
      split.isEnabled = function() {
        return this.enabled && (self.isSplittable() && !self.isLocked());
      };
      var action = this.factory.createItem(GATONERO.Action);
      action.doExecute = function() {
        self.setSplitting(true);
      };
      split.addAction(action, 'select');
      this.split = split;
    }
    return this.split;
  },
  isSplittable: function() {
    var event = this.getValue();
    return event ? event.isSplittable() : false;
  },
  getPopup: function() {
    var event = this.getValue();
    if (!event || !(event instanceof Activity)) { return null; }
    if (typeof this.popup == 'undefined') { 
      var popup = this.factory.createItem(GATONERO.Box, null);
      popup.addComponent(this.getLockMenuItem(), true);
      popup.addComponent(this.getSplitMenuItem(), true);
      this.popup = popup;
    }
    return this.popup;
  },
  getWidth: function() { 
     var event = this.getValue();
     return (event ? 
              parseInt(event.getDuration()): 
              GATONERO.Label.prototype.getWidth.apply(this));
  },
  /*
  getCssClass: function() {
    var event = this.getValue();
    return (event ? 
             (event.phase? event.phase : 'production'):
             GATONERO.Label.prototype.getCssClass.apply(this));
  },*/
  isSplittable: function() {
    return (this.getValue() && this.getValue().splittable);
  },
  splitting: false,
  isSplitting: function() {
    return this.isSplittable() && this['splitting'];
  },
  setSplitting: function(splitting) {
    this.setProperty('splitting', splitting);
  },
  cancelDrag: function() {
    var activity = this.getValue(),
        r_desc = this.factory.findItem(activity.resource),
        resource;
    if (!r_desc) { return; }
    resource = r_desc.getValue();
    if (!resource) { return; }
    resource.addActivity(activity);
    r_desc.setDirty(true);
    r_desc.fireItemEvent(GATONERO.Item.VALUE, null, resource);
  },
  canMoveHorizontally: function() {
    var activity = this.getValue();
    return activity ? activity.phase != SERVICE : false;
  },
  canMoveVertically: function() {
    var activity = this.getValue();
    return activity ? !(activity instanceof Pause) : false;
  },
  doPurge: function() { Timeslot.purge(this); }
});

GATONERO.addProperty(Timeslot, 'selected', { isBoolean: true, default_value: false });

GATONERO.ItemFactory.bind(Timeslot);

var TimeslotRenderer = GATONERO.extend(GATONERO.LabelRenderer, {
  useCaption: false,
  renderCaption: function() {},
  create: function(elt, desc, parent) {
    var sub = document.createElement('DIV');
    this.renderer.setComponent(elt, 'sub', sub);
    var value = desc.getValue();
    if (value && value instanceof Activity) {
      this.attachEvent(elt, 'click', this.select, { bubble: false });
    }
    
    return elt;
  },
  doRender: function(div, desc, parent) {
    var r = this.renderer,
        sub = r.getComponent(desc.element, 'sub'),
        event = desc.getValue();
        
    this.addClass(sub, 'sub');
    if (event) {
      if (event instanceof Pause) { this.addClass(div, css.PAUSE); }
      else {
        if (event.phase) { this.addClass(sub, event.phase); }
        if (event.prestation) { this.addClass(div, event.prestation); }
      }
      if (event.locked) { this.addClass(sub, 'locked'); }
      else { this.removeClass(sub, 'locked'); }
    }
    else { this.addClass(div, css.EMPTY); }

    sub.innerHTML = desc.getText();
    div.appendChild(sub);
    this.renderSplitting(div, desc, parent);
    div.style.visibility = 'visible';
    return div;
  },
  select: function(e, elt, desc) {
    if (e.ctrlKey) {
      this.renderer.dragging = false;
      var r = this.renderer,
          resource = r.getDescriptor(elt, ResourceDesc);
      if (Timeslot.__clipboardResource && 
            resource != Timeslot.__clipboardResource) { return; }
      if (desc.isSelected()) {
        resource.removeFromClipboard(desc, true); 
        Timeslot.__clipboardCount--;
        if (Timeslot.__clipboardCount == 0) { Timeslot.__clipboardResource = null; }
      }
      else { 
        resource.addToClipboard(desc); 
        Timeslot.__clipboardResource = resource;
        Timeslot.__clipboardCount++;
      }
    }
  },
  renderSplitting: function(div, desc, parent) {
    var r = this.renderer,
        delegate = this,
        sub = r.getComponent(desc.element, 'sub');
    if (desc.isSplitting()) {
      sub.cursor = sub.style.cursor;
      sub.style.cursor = 'crosshair';
      sub.innerHTML = new Time(desc.getValue().getDurationMs()).toString();
      
      this.attachEvent(sub, 'mousemove', this.showTimer);
      this.attachEvent(sub, 'click', this.split);
    } 
  },
  showTimer: function(e, elt, desc) {
    var r = this.renderer;
    elt.split = new Time((r.getMousePosition(e).x - r.getPosition(elt).x) * TimedEvent.SCALE);
    elt.innerHTML = elt.split.toString();
  },
  split: function(e, elt, desc) {
    var r = this.renderer,
        split = elt.split;
    this.removeEvent(elt, 'mousemove', this.showTimer);
    elt.style.cursor = elt.cursor;
    elt.cursor = null;
    elt.split = null;
    event = desc.getValue();
    r_desc = r.getDescriptor(elt, ResourceDesc);
    resource = r_desc.getValue();
    p_desc = r.getDescriptor(elt, PlanningDesc);
    planning = p_desc.getValue();
    if (!event || !resource || !planning) { return; }
    resource.split(event, split, planning);
    desc.setSplitting(false);
    r_desc.setDirty(true);
    r_desc.fireItemEvent(GATONERO.Item.prototype.VALUE, null, resource);
    waitarea = p_desc.getWaitArea();
    waitarea.setDirty(true);
    waitarea.fireItemEvent(GATONERO.Item.prototype.VALUE, null, planning);
  },
  setClass: function(elt, desc ) {},
  doStartDrag: function(desc) {
    var r = this.renderer, 
        activity = desc.getValue(),
        resource, elt, options = {};
    elt = desc.element;
    elt.style.visibility = 'hidden';
    resource = r.getDescriptor(elt, ResourceDesc);
    resource.getValue().removeActivity(activity);
    resource.setDirty(true);
    var prestation = r.getDescriptor(elt, PrestationDesc);
    if (prestation) { 
      prestation.setDirty(true);
      prestation.fireItemEvent(GATONERO.Item.VALUE, null, prestation.getValue());
    }
    
/*    resource.fireItemEvent(GATONERO.Item.VALUE, null, resource.getValue());
    return options; */
  },/*
  canMoveHorizontally: function(desc) {
    var activity = desc.getValue();
    return activity ? activity.phase != SERVICE : false;
  },
  canMoveVertically: function(desc) {
    var activity = desc.getValue();
    return activity ? !(activity instanceof Pause) : false;
  },*/
  /*
  drag: function(e,draggable, desc) {
    var r = this.renderer,
        sub = draggable.getComponentsByTagName('DIV'),
        pos = r.getPosition(draggable);
        
    sub.innerHTML = new Time(pos.x * TimedEvent.SCALE)
  },
  * */
  getDroppable: function(draggable, desc, position, offset) {
    var r = this.renderer,
        waitarea = document.getElementById('wait'),
        planning = r.getDescriptor(waitarea, PlanningDesc),
        comps = planning.getComponents(),
        comp,
        prestation,
        droppable,
        n = comps.length,
        i = n - 1,
        bounds;
    for (i; i >= 0; i--) {
      comp = comps[i];
      if (!comp.isCollapsed() && r.inside(position, comps[i].element)) { break; }
    }
    if (i < 0) { return null; }
    if (i == n -1) { return waitarea; }
    prestation = comps[i];
    comps = prestation.getComponents();
    n = comps.length;
    for (i = n - 1; i >= 0; i--) {
      droppable = comps[i];
      if (droppable.mainPanel) {
        if (r.inside(position, droppable.mainPanel.element)) { 
          return droppable.mainPanel.element; 
        }
        else if (r.inside(position, droppable.indicatorsPanel.element)) {
          return droppable.indicatorsPanel.element;
        }
      }
      else if (r.inside(position, droppable.element)) {
        return droppable.element;
      }
    }
    return null;
  },
  dragComplete: function(desc) {
    desc.element.style.visibility = 'visible';
  },
  canMoveHorizontally: function(desc) { return desc.canMoveHorizontally(); },
  canMoveVertically: function(desc) { return desc.canMoveVertically(); }
});

Timeslot.prototype.delegate = new TimeslotRenderer();

Timeslot.ID = '__tsId';
Timeslot.__values = [];
Timeslot.__timeslots = []
Timeslot.getTimeslot = function(value, factory) {
  if (!value || !factory) { return null; }

  var id, timeslot;
  id = value[Timeslot.ID];
  if (!id) { 
    Timeslot.__values[id = value[Timeslot.ID] = Timeslot.__values.length] = value;
  }
  timeslot = Timeslot.__timeslots[id];
  if (!timeslot) {
    Timeslot.__timeslots[id] = timeslot = factory.createItem(Timeslot, null);
    Timeslot.__timeslots[id].setValue(value, { silent: true });
  }
  return timeslot;
};
Timeslot.purge = function(timeslot) {
  var id = timeslot[Timeslot.ID];
  if (!id) { return; }
  Timeslot.__timelots[id] = null;
  Timeslot.__values[id] = null;
};

Timeslot.__clipboardResource;
Timeslot.__clipboardCount = 0;
var Timefield = GATONERO.extend(GATONERO.Spinner, {
  classId: 'timefield',
  loop: true,
  min: new Time("00:00"),
  max: new Time(24 * 60 * 60000),
  step: 60000,
  'step-fast': 900000,
  valueFromString: function(value) { return new Time(value); },
  getNextValue: function(value) { 
    this['scroll-count']++;
    var max = this.getMax();
    return value.time >= max.time ? 
             (this.isLoop() ? this.getMin() : value) : 
             value.after(this.getStep());
  },
  getPreviousValue: function(value) { 
    this['scroll-count']++;
    var min = this.getMin();
    return value.time <= min.time ? 
             (this.isLoop() ? this.getMax() : value) : 
             value.before(this.getStep());
  }
});

GATONERO.ItemFactory.bind(Timefield);
var UpdateResourceAction = GATONERO.extend(GATONERO.Action, {
  classId: "update-resource",
  getResourceDesc: function() {
    return this.getProperty('resource');
  },
  setResourceDesc: function(resource) {
    this.setProperty('resource', resource);
  },
  doExecute: function() {
    
    var desc = this.getResourceDesc();
    if (!desc) { return; }
    var resource = desc.getValue();
    if (!resource) { return; }

    var field, value;
    
    field = this.factory.findItem('name');
    value = field.getValue();
    if (value && value.trim() != '') { 
      resource.name = value;
      desc.setCaption(value);
    }
    
    field = this.factory.findItem('tf-start');
    value = field.getValue();
    try { resource.start = value; }
    catch (ex) {}
    
    field = this.factory.findItem('end');
    value = field.getValue();
    try { resource.end = value; }
    catch (ex) {}
    
    field = this.factory.findItem('pause');
    try { resource.pause = parseInt(field.getValue()); }
    catch (ex) {}
    
    field = this.factory.findItem('break-start');
    value = field.getValue();
    try { resource['break-start'] = value; }
    catch (ex) {}

    field = this.factory.findItem('break-end');
    value = field.getValue();
    try { resource['break-end'] = value; }
    catch (ex) {}

    desc.setDirty(true);
    desc.fireItemEvent(GATONERO.Item.VALUE, null, resource);
  
    return true;
  }
});

var CleanUpAction = GATONERO.extend(GATONERO.Action, {
  classId: 'cleanup',
  doExecute: function() {
    var w = this.factory.findItem('detaisl'),
        fields = ['name', 'tf-start', 'end', 'pause', 'break-start', 'break-end'],
        n = fields.length;
    
    for (var i = n - 1; i >= 0; i--) {
     this.factory.findItem(fields[i]).setValue(0);
    }
    return true;
  }
});

GATONERO.ItemFactory.bind(UpdateResourceAction);
GATONERO.ItemFactory.bind(CleanUpAction);
var Clipboard = GATONERO.extend(Timeline, {
  classId: 'clipboard',
  'css-class': 'clipboard',
  isDraggable: function() { return this.getEvents().length > 0; },
  service: 0,
  getStart: function() { 
    var events = this.getEvents();
    return events && events[0] ? events[0].start : DAY_START;
  },
  getEnd: function() { 
    var events = this.getEvents();
    return events && events[0] ? events[events.length - 1].getEndTime() : DAY_START;
  },
  getValue: function() { return this.getEvents(); },
  getEvents: function() {
    if (!this.events) { this.events = []; }
    return this.events.sort(function(a, b) { return a.compare(b); }) ;
  },
  addEvent: function(event) {
    if (!event) { return; }
    var events = this.getEvents();
    events.push(event);
    if (event.phase == SERVICE) this.service++;
    this.setDirty(true);
    this.fireItemEvent('event', null, events);
  },
  removeEvent: function(event) {
    if (!event) { return; }
    var events = this.getEvents();
    events.remove(event);
    if (event.phase == SERVICE) this.service--;
    this.setDirty(true);
    this.fireItemEvent('event', null, events);
  },
  getY: function() { return 0; },
  getX: function() { 
    return (this.getStart() - DAY_START) / TimedEvent.SCALE;
  },
  getWidth: function() { return this.getDuration() / this.scale; },
  isVisible: function() { return this.events && this.events.length > 0; }
});

GATONERO.ItemFactory.bind(Clipboard);

var ClipboardRenderer = GATONERO.extend(TimelineRenderer, {
  getDragTarget: function(elt, desc) {
    return elt;
  },
  dragComplete: function(desc) {
    desc.setDirty(true);
    desc.fireItemEvent(GATONERO.Item.VALUE, null, desc.getValue());
  },
  canMoveHorizontally: function(desc) { return desc.service <= 0; }
});

Clipboard.prototype.delegate = new ClipboardRenderer();
