<commit_before>/* -*- mode: espresso; espresso-indent-level: 2; indent-tabs-mode: nil -*- */
/* vim: set softtabstop=2 shiftwidth=2 tabstop=2 expandtab: */

(function(CATMAID) {

  /**
   * The basic skeleton source implements the skeleton source interface and
   * maintains an ordered list of skeletons. Additionally, grouping of the
   * managed skeletons is possible.
   */
  var BasicSkeletonSource = function(name) {
    this.name = name;
    this.registerSource();
    this.skeletonModels = {};
    this.orderedSkeletonIDs = [];
    // Elements can be groups or single skeletons. A group is represented with
    // its name in here. If it is present as a field in the groups object, it is
    // a real group otherwise, it is treated as a skeleton ID.
    this.orderedElements = [];
    // A group maps a name to a list of skeleton IDs.
    this.groups = {};
    // Indicate if newly appended skeletons should be removed from existing
    // groups.
    this.moveExistingToNewGroup = true;
    // If set, the next skeletons appended are added to this group.
    this.nextGroupName;
  };

  BasicSkeletonSource.prototype = new CATMAID.SkeletonSource();

  /* Implement SkeletonSource interface */

  /**
   * Get unique name of this dimension source.
   */
  BasicSkeletonSource.prototype.getName = function()
  {
    return this.name;
  };

  /**
   * Handle destruction of this skeleton source.
   */
  BasicSkeletonSource.prototype.destroy = function() {
    this.unregisterSource();
  };

  /**
   * Append a list of skeleton models. If a model is already known, the stored
   * model is updated. If the appended skeletons should be added as a group, all
   * added skeletons that are already grouped will be removed from those groups.
   *
   * @param models {object} An object mapping skeleton IDs to skeleton models.
   */
  BasicSkeletonSource.prototype.append = function(models) {
    // Indicate if a new group should be created
    var createGroup = !!this.nextGroupName;
    // Get a number representation of all skeleton IDs
    var skeleton_ids = Object.keys(models).map(function(skid) {
      return parseInt(skid, 10);
    });
    for (var i=0; i<skeleton_ids.length; ++i) {
      var skid = skeleton_ids[i];

      // If a skeleton is known already, either continue or move it to the end
      // of the ordered list, if a group should be created and known skeletons
      // should be moved to the new group.
      if (skid in this.skeletonModels) {
        if (createGroup) {
          if (this.moveExistingToNewGroup) {
            var skeletonIndex = this.orderedSkeletonIDs.indexOf(skid);
            this.orderedSkeletonIDs.splice(skeletonIndex, 1);
            var groupIndex = this.orderedElements.indexOf(skid);
            this.orderedElements.splice(groupIndex, 1);
          } else {
            skeleton_ids.splice(skeleton_ids.indexOf(skid), 1);
          }
        } else {
          // Update existing model and continue
          this.skeletonModels[skid] = models[skid];
          continue;
        }
      }

      // Store skeleton ID reference (as number) and model
      this.orderedSkeletonIDs.push(skid);
      this.skeletonModels[skid] = models[skid];

      // If no group should be created, add the ID of every added skeleton to
      // the group index, but don't create an entry in the groups object.
      if (!createGroup) this.orderedElements.push(skid);
    }

    if (createGroup) {
      // Add group if a new group was requested
      this.orderedElements.push(this.nextGroupName);
      this.groups[this.nextGroupName] = skeleton_ids;
    }

    this.updateLink(models);
  };

  /**
   * Clear all references to skeletons.
   */
  BasicSkeletonSource.prototype.clear = function(source_chain) {
    this.orderedSkeletonIDs = [];
    this.skeletonModels = {};
    this.orderedElements = [];
    this.groups = {};
    // Clear link target, if any
    this.clearLink(source_chain);
  };

  /**
   * Remove all skeletons with the given IDs.
   *
   * @param skeletonIDs {Array<number>} The list of skeletons to remove
   */
  BasicSkeletonSource.prototype.removeSkeletons = function(skeletonIDs) {
    // Remove skeleton IDs
    this.orderedSkeletonIDs = this.orderedSkeletonIDs.filter(function(skid) {
      return -1 === skeletonIDs.indexOf(skid);
    });

    skeletonIDs.forEach(function(skid) {
      var nSkid = parseInt(skid, 10);
      // Remove models
      if (skid in this.skeletonModels) {
        delete this.skeletonModels[skid];
      }
      // Remove from element index
      var groupIndex = this.orderedElements.indexOf(nSkid);
      if (-1 !== groupIndex) {
        this.orderedElements.splice(groupIndex, 1);
      }
      // Remove from groups
      for (var key in this.groups) {
        var index = this.groups[key].indexOf(nSkid);
        if (-1 !== index) {
          this.groups[key].splice(index, 1);
        }
      }
    }, this);

    // Remove skeletons from link target, if any
    if (this.linkTarget) {
      // Prevent propagation loop by checking if the target has the skeletons anymore
      if (skeletonIDs.some(this.linkTarget.hasSkeleton, this.linkTarget)) {
        this.linkTarget.removeSkeletons(skeletonIDs);
      }
    }
  };

  /**
   * Update skeleton models and propagade this to the comple source chain.
   */
  BasicSkeletonSource.prototype.updateModels = function(models, sourceChain) {
    sourceChain = sourceChain || {};
    // break propagation loop
    if (sourceChain && (this in sourceChain)) {
      return;
    }
    // Add self to source chain
    source_chain[this] = this;

    var newModels = {};
    Object.keys(models).forEach(function(skid) {
      var model = models[skid];
      if (skid in this.skeletonModels) {
        this.skeletonModels[model.id] = model.clone();
      } else {
        newModels[skid] = model;
      }
    }, this);

    if (Object.keys(new_models).length > 0) {
      this.append(new_models);
    }

    this.updateLink(models, sourceChain);
  };

  /**
   * Return true if the given skeleton ID is known to this source. Otherwise,
   * return false.
   *
   * @param skeletonID {number} The skeleton ID to test
   */
  BasicSkeletonSource.prototype.hasSkeleton = function(skeletonID) {
    return skeleton_id in this.skeletonModels;
  };


  /**
   * Return all known skeleton IDs. Override for more specific and actual
   * selection behavior.
   */
  BasicSkeletonSource.prototype.getSelectedSkeletons = function() {
    return this.orderedSkeletonIDs.slice(0);
  };

  /**
   * Return models object for all known skeleton IDs. Override for more specific
   * and actual selection behavior.
   */
  BasicSkeletonSource.prototype.getSelectedSkeletonModels = function() {
    return this.orderedSkeletonIDs.reduce((function(m, skid) {
      m[skid] = this.skeletonModels[skid].clone();
      return m;
    }).bind(this), {});
  };

  /**
   * Highlighting is not implemented in this source since it is use case
   * specific.<commit_msg>Basic skeleton source: fix some typos<commit_after>/* -*- mode: espresso; espresso-indent-level: 2; indent-tabs-mode: nil -*- */
/* vim: set softtabstop=2 shiftwidth=2 tabstop=2 expandtab: */

(function(CATMAID) {

  /**
   * The basic skeleton source implements the skeleton source interface and
   * maintains an ordered list of skeletons. Additionally, grouping of the
   * managed skeletons is possible.
   */
  var BasicSkeletonSource = function(name) {
    this.name = name;
    this.registerSource();
    this.skeletonModels = {};
    this.orderedSkeletonIDs = [];
    // Elements can be groups or single skeletons. A group is represented with
    // its name in here. If it is present as a field in the groups object, it is
    // a real group otherwise, it is treated as a skeleton ID.
    this.orderedElements = [];
    // A group maps a name to a list of skeleton IDs.
    this.groups = {};
    // Indicate if newly appended skeletons should be removed from existing
    // groups.
    this.moveExistingToNewGroup = true;
    // If set, the next skeletons appended are added to this group.
    this.nextGroupName;
  };

  BasicSkeletonSource.prototype = new CATMAID.SkeletonSource();

  /* Implement SkeletonSource interface */

  /**
   * Get unique name of this dimension source.
   */
  BasicSkeletonSource.prototype.getName = function()
  {
    return this.name;
  };

  /**
   * Handle destruction of this skeleton source.
   */
  BasicSkeletonSource.prototype.destroy = function() {
    this.unregisterSource();
  };

  /**
   * Append a list of skeleton models. If a model is already known, the stored
   * model is updated. If the appended skeletons should be added as a group, all
   * added skeletons that are already grouped will be removed from those groups.
   *
   * @param models {object} An object mapping skeleton IDs to skeleton models.
   */
  BasicSkeletonSource.prototype.append = function(models) {
    // Indicate if a new group should be created
    var createGroup = !!this.nextGroupName;
    // Get a number representation of all skeleton IDs
    var skeleton_ids = Object.keys(models).map(function(skid) {
      return parseInt(skid, 10);
    });
    for (var i=0; i<skeleton_ids.length; ++i) {
      var skid = skeleton_ids[i];

      // If a skeleton is known already, either continue or move it to the end
      // of the ordered list, if a group should be created and known skeletons
      // should be moved to the new group.
      if (skid in this.skeletonModels) {
        if (createGroup) {
          if (this.moveExistingToNewGroup) {
            var skeletonIndex = this.orderedSkeletonIDs.indexOf(skid);
            this.orderedSkeletonIDs.splice(skeletonIndex, 1);
            var groupIndex = this.orderedElements.indexOf(skid);
            this.orderedElements.splice(groupIndex, 1);
          } else {
            skeleton_ids.splice(skeleton_ids.indexOf(skid), 1);
          }
        } else {
          // Update existing model and continue
          this.skeletonModels[skid] = models[skid];
          continue;
        }
      }

      // Store skeleton ID reference (as number) and model
      this.orderedSkeletonIDs.push(skid);
      this.skeletonModels[skid] = models[skid];

      // If no group should be created, add the ID of every added skeleton to
      // the group index, but don't create an entry in the groups object.
      if (!createGroup) this.orderedElements.push(skid);
    }

    if (createGroup) {
      // Add group if a new group was requested
      this.orderedElements.push(this.nextGroupName);
      this.groups[this.nextGroupName] = skeleton_ids;
    }

    this.updateLink(models);
  };

  /**
   * Clear all references to skeletons.
   */
  BasicSkeletonSource.prototype.clear = function(source_chain) {
    this.orderedSkeletonIDs = [];
    this.skeletonModels = {};
    this.orderedElements = [];
    this.groups = {};
    // Clear link target, if any
    this.clearLink(source_chain);
  };

  /**
   * Remove all skeletons with the given IDs.
   *
   * @param skeletonIDs {Array<number>} The list of skeletons to remove
   */
  BasicSkeletonSource.prototype.removeSkeletons = function(skeletonIDs) {
    // Remove skeleton IDs
    this.orderedSkeletonIDs = this.orderedSkeletonIDs.filter(function(skid) {
      return -1 === skeletonIDs.indexOf(skid);
    });

    skeletonIDs.forEach(function(skid) {
      var nSkid = parseInt(skid, 10);
      // Remove models
      if (skid in this.skeletonModels) {
        delete this.skeletonModels[skid];
      }
      // Remove from element index
      var groupIndex = this.orderedElements.indexOf(nSkid);
      if (-1 !== groupIndex) {
        this.orderedElements.splice(groupIndex, 1);
      }
      // Remove from groups
      for (var key in this.groups) {
        var index = this.groups[key].indexOf(nSkid);
        if (-1 !== index) {
          this.groups[key].splice(index, 1);
        }
      }
    }, this);

    // Remove skeletons from link target, if any
    if (this.linkTarget) {
      // Prevent propagation loop by checking if the target has the skeletons anymore
      if (skeletonIDs.some(this.linkTarget.hasSkeleton, this.linkTarget)) {
        this.linkTarget.removeSkeletons(skeletonIDs);
      }
    }
  };

  /**
   * Update skeleton models and propagade this to the comple source chain.
   */
  BasicSkeletonSource.prototype.updateModels = function(models, sourceChain) {
    sourceChain = sourceChain || {};
    // break propagation loop
    if (sourceChain && (this in sourceChain)) {
      return;
    }
    // Add self to source chain
    sourceChain[this] = this;

    var newModels = {};
    Object.keys(models).forEach(function(skid) {
      var model = models[skid];
      if (skid in this.skeletonModels) {
        this.skeletonModels[model.id] = model.clone();
      } else {
        newModels[skid] = model;
      }
    }, this);

    if (Object.keys(newModels).length > 0) {
      this.append(newModels);
    }

    this.updateLink(models, sourceChain);
  };

  /**
   * Return true if the given skeleton ID is known to this source. Otherwise,
   * return false.
   *
   * @param skeletonID {number} The skeleton ID to test
   */
  BasicSkeletonSource.prototype.hasSkeleton = function(skeletonID) {
    return skeletonID in this.skeletonModels;
  };


  /**
   * Return all known skeleton IDs. Override for more specific and actual
   * selection behavior.
   */
  BasicSkeletonSource.prototype.getSelectedSkeletons = function() {
    return this.orderedSkeletonIDs.slice(0);
  };

  /**
   * Return models object for all known skeleton IDs. Override for more specific
   * and actual selection behavior.
   */
  BasicSkeletonSource.prototype.getSelectedSkeletonModels = function() {
    return this.orderedSkeletonIDs.reduce((function(m, skid) {
      m[skid] = this.skeletonModels[skid].clone();
      return m;
    }).bind(this), {});
  };

  /**
   * Highlighting is not implemented in this source since it is use case
   * specific.
{'fraction_non_alphanumeric': 0.10410117176336096, 'fraction_numerical': 0.0027150614461274648, 'mean_word_length': 2.6776142932212297, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5147456', 'n_tokens_mistral': 4208, 'n_tokens_neox': 3972, 'n_words': 1210}