<commit_before>var async = require('async');
var naan = require('naan');
var _ = require('underscore');
var util = require('util');

var node = module.exports = {
  /**
   * Save or update an object. If a new object is passed, the callback will
   * return a copy of that object with the <options.id> key set to the id of the
   * created object.
   */
  save: function(obj, key, val, callback) {
    if (typeof key == 'function') {
      callback = key;
      key = val = undefined;
    } else if (typeof val == 'function') {
      callback = val;
      val = undefined;
    }

    var label = undefined;
    if (key != undefined && val == undefined) label = key;

    if (label) {
      if (this.isBatch) {
        return callback(new Error("node.save with labels is not compatible with "
                + "batch mode. Use db.save & db.label instead."));
      }
      var txn = this._safeBatch();
      var node = txn.save(obj);
      txn.label(node, label);
      return this._safeBatchCommit(txn, function(err, result) {
        if (err) callback(err);
        else callback(null, result[node]);
      });
    }

    if (Array.isArray(obj)) {
      var txn = this._safeBatch();
      var args = [key, val];
      async.map(obj, naan.ecurry(txn.save, args, [1, 2]), callback);
      return this._safeBatchCommit(txn);
    }

    var id = this._getId(obj, !key);

    if (!this._isValidId(id)) {
      if (typeof obj !== 'object') {
        return callback(new Error("No data to save"));
      }

      this.node._create(obj, callback);
    } else {
      if (val) this.node._updateProp(obj, key, val, callback);
      else this.node._update(obj, callback);
    }
  },

  /**
   * Create a new object. Maps to /node.
   */
  _create: function(obj, callback) {
    var op = this.operation('node', obj);
    this.call(op, function(err, body, location) {
      if (err) {
        return callback(err);
      }

      var result = _.clone(obj);
      result[this.options.id] = this._extractId(location);

      callback(null, result);
    });
  },

  /**
   * Save the properties of an object. Maps to PUT /node/{id}/properties.
   */
  _update: function(obj, callback) {
    var id = this._getId(obj, true);
    if (!this._isValidId(id)) {
      return callback(new Error("Invalid ID"));
    }

    var untouchedObj = _.clone(obj);
    if (untouchedObj[this.options.id] != null) {
      delete untouchedObj[this.options.id];
    }

    var endpoint = util.format('%s/properties', this._nodeRoot(id));
    var op = this.operation(endpoint, 'PUT', untouchedObj);
    this.call(op, function(err, body, response) {
      if (err) {
        return callback(err);
      } else {
        return callback(null, obj);
      }
    });
  },

  _updateProp: function(obj, key, val, callback) {
    var id = this._getId(obj);
    if (!this._isValidId(id)) {
      return callback(new Error("Invalid ID"));
    }

    var endpoint = util.format('%s/properties/%s', this._nodeRoot(id), key);
    var op = this.operation(endpoint, 'PUT', val);
    this.call(op, function(err, body) {
      if (err) return callback(err);
      else {
        obj = _.clone(obj);
        obj[key] = val;
        callback(null, obj);
      }
    });
  },

  /**
   * Read an object's properties. Maps to GET node/{id}/properties.
   */
  read: function(id, property, callback) {
    if (typeof property == 'function') {
      callback = property;
      property = null;
    }

    if (Array.isArray(id)) {
      var txn = this._safeBatch();
      async.map(id, naan.ncurry(txn.read, property, 1), callback);
      return this._safeBatchCommit(txn);
    }

    id = this._getId(id);
    if (!this._isValidId(id)) {
      return callback(new Error("Invalid ID"));
    }

    var endpoint;
    if (!property)
      endpoint = util.format('%s/properties', this._nodeRoot(id));
    else
      endpoint = util.format('%s/properties/%s', this._nodeRoot(id), property);
    var op = this.operation(endpoint);
    this.call(op, function(err, body) {
      if (err) {
        return callback(err);
      } else {
        if (!property) body[this.options.id] = id;
        return callback(null, body);
      }
    });
  },

  /**
   * Delete an object. Maps to DELETE node/{id}
   * If force is truthy, delete node and all its relations.
   */
  delete: function(id, force, callback) {
    var property = null;
    if (typeof force == 'function') {
      callback = force;
      force = false;
    } else if (typeof force == 'string') {
      property = force;
      force = false;
    }

    if (Array.isArray(id)) {
      var txn = this._safeBatch();
      async.map(id, naan.ncurry(txn.node.delete, force || property, 1), callback);
      return this._safeBatchCommit(txn);
    }

    var object = id;
    id = this._getId(id);
    if (!this._isValidId(id)) {
      return callback(new Error("Invalid ID"));
    }

    if (!force) {
      var endpoint;
      if (!property) endpoint = this._nodeRoot(id);
      else
        endpoint = util.format('%s/properties/%s', this._nodeRoot(id), property);
      var op = this.operation(endpoint, 'DELETE');
      this.call(op, function(err) {
        if (!property || err || typeof object != 'object') callback(err);
        else {
          delete object[property];
          callback(null, object);
        }
      });
    } else {
      this.query([
        'START node=node({root})',
        'OPTIONAL MATCH node-[rel]-()',
        'DELETE node, rel'
      ].join(' '), {root: id}, function(err) { callback(err) });
    }
  },

  /**
   * Relate objects together. maps to POST node/{first}/relationships
   */
  relate: function(startNode, type, endNode, properties, callback) {
    this.rel.create(startNode, type, endNode, properties, callback);
  },

  /**
   * Retrieve a set of relationships for the given node. Optionally specify the
   * direction and relationship name.
   *
   * `direction` must be one of "all", "in" or "out".
   *
   * db.relationships(obj|id, [direction, [relationshipName]], callback)
   */
  relationships: function(obj, direction, relName, callback) {
    if (typeof direction === 'function') {
      callback = direction;
      direction = 'all';
      relName = '';
    } else {
      if (typeof relName === 'function') {
        callback = relName;
        relName = '';
      }

      if (typeof direction !== 'string') {
        return callback(new Error('Invalid direction - ' + direction));
      } else if (typeof relName !== 'string') {
        return callback(new Error('Invalid relationship name - ' + relName));
      }

      direction = direction.toLowerCase();
      if (['in', 'all', 'out'].indexOf(direction) === -1) {
        return callback(new Error('Invalid direction - ' + direction));
      }
    }

    if (Array.isArray(obj)) {
      var txn = this._safeBatch();
      var args = [ direction, relName ];
      var rels = naan.ecurry(txn.node.relationships, args, [1, 2]);
      async.map(obj, rels, callback);
      return this._safeBatchCommit(txn);
    }

    var objId = this._getId(obj);

    if (!this._isValidId(objId)) {
      callback(new Error("Invalid ID"));
    }

    var nodeRoot = this._nodeRoot(objId);
    var endpoint = util.format('%s/relationships/%s', nodeRoot, direction);
    if (relName) {
      endpoint += "/" + relName;
    }
    var op = this.operation(endpoint);
    var self = this;
    this.call(op, function(err, rels) {
      if (err) {
        callback(err);
      } else {
        rels = rels.map(function(rel) {
          return self._createRelationshipObject(rel);
        });
        callback(null, rels);
      }
    });
  },

  /**
   * Perform a query based on a predicate. The predicate is translated to a
   * cypher query.
   */
  find: function(predicate, any, start, label, callback) {
    if (typeof any === 'function') {
      callback = any;
      any = false;
      start = 'node(*)';
      label = null;
    } else if (typeof start === 'function') {
      callback = start;
      start = 'node(*)';
      label = null;
    } else if (typeof label === 'function') {
      callback = label;
      label = null;
    }

    if (Array.isArray(predicate)) {
      var txn = this._safeBatch();
      var finder = naan.ecurry(txn.node.find, [any, start, label], [1, 2, 3]);
      async.map(predicate, finder, callback);
      return this._safeBatchCommit(txn);
    }

    if (typeof predicate !== 'object') {
      callback(new Error('Invalid Predicate'));
    }

    var matchers = Object.keys(predicate).reduce(function(matchers, key) {
      return matchers.push(util.format(" (n.%s = {%s}) ", key, key)), matchers;
    }, []);

    if (label) {
      matchers.push(util.format(" ('%s' IN LABELS(n)) ", label));
    }

    var cypher = [
      "START n = " + start + " WHERE",
      matchers.join(any ? "or" : "and"),
      "RETURN n"
    ].join(" ");

    var self = this;
    this.query(cypher, predicate, callback);
  },

  label: function(node, label, replace, callback) {
    if (typeof replace == 'function') {
      callback = replace;
      replace = false;
    }

    if (Array.isArray(node)) {
      var txn = this._safeBatch();
      async.map(node, naan.ecurry(txn.label, [label, replace], [1,2]), callback);
      return this._safeBatchCommit(txn);
    }

    var id = this._getId(node);
    if (!this._isValidId(id)) return callback(new Error("Invalid ID"));

    var endpoint = util.format('%s/labels', this._nodeRoot(id));
    var op = this.operation(endpoint, replace ? 'PUT' : 'POST', label);
    this.call(op, function(err) {
      if (err) callback(err);
      else callback();
    });
  },

  removeLabel: function(node, label, callback) {
    if (Array.isArray(node)) {
      var txn = this._safeBatch();
      async.map(node, naan.ncurry(txn.removeLabel, label, 1), callback);
      return this._safeBatchCommit(txn);
    }

    var id = this._getId(node);
    if (!this._isValidId(id)) return callback(new Error("Invalid ID"));

    var endpoint = util.format('%s/labels/%s', this._nodeRoot(id), label);
    var op = this.operation(endpoint, 'DELETE');
    this.call(op, function(err) {
      if (err) callback(err);
      else callback();
    });
  },

  nodesWithLabel: function(label, callback) {
    var endpoint = util.format('label/%s/nodes', label);
    var op = this.operation(endpoint, 'GET');
    this.call(op, function(err, nodes) {
      if (err || !nodes) return callback(err, nodes);
      nodes = nodes.map(this._createNodeObject);
      callback(null, nodes);
    });
  },

  readLabels: function(node, callback) {
    if (typeof node == 'function') {
      callback = node;
      node = undefined;
    }

    if (Array.isArray(node)) {
      var txn = this._safeBatch();
      async.map(node, txn.readLabels, function(err, allLabels) {
        if (err) return callback(err);
        callback(null, _.uniq(_.flatten(allLabels)));
      });
      return this._safeBatchCommit(txn);
    }

    var endpoint;
    if (node) {
      var id = this._getId(node);
      if (!this._isValidId(id)) return callback(new Error("Invalid ID"));
      endpoint = util.format('%s/labels', this._nodeRoot(id));
    } else {
      endpoint = 'labels';
    }
    var op = this.operation(endpoint, 'GET');
    this.call(op, function(err, labels) {
      if (err) callback(err);
      else callback(null, labels);
    });
  }
}
<commit_msg>remove start from node.find, optimize query<commit_after>var async = require('async');
var naan = require('naan');
var _ = require('underscore');
var util = require('util');

var node = module.exports = {
  /**
   * Save or update an object. If a new object is passed, the callback will
   * return a copy of that object with the <options.id> key set to the id of the
   * created object.
   */
  save: function(obj, key, val, callback) {
    if (typeof key == 'function') {
      callback = key;
      key = val = undefined;
    } else if (typeof val == 'function') {
      callback = val;
      val = undefined;
    }

    var label = undefined;
    if (key != undefined && val == undefined) label = key;

    if (label) {
      if (this.isBatch) {
        return callback(new Error("node.save with labels is not compatible with "
                + "batch mode. Use db.save & db.label instead."));
      }
      var txn = this._safeBatch();
      var node = txn.save(obj);
      txn.label(node, label);
      return this._safeBatchCommit(txn, function(err, result) {
        if (err) callback(err);
        else callback(null, result[node]);
      });
    }

    if (Array.isArray(obj)) {
      var txn = this._safeBatch();
      var args = [key, val];
      async.map(obj, naan.ecurry(txn.save, args, [1, 2]), callback);
      return this._safeBatchCommit(txn);
    }

    var id = this._getId(obj, !key);

    if (!this._isValidId(id)) {
      if (typeof obj !== 'object') {
        return callback(new Error("No data to save"));
      }

      this.node._create(obj, callback);
    } else {
      if (val) this.node._updateProp(obj, key, val, callback);
      else this.node._update(obj, callback);
    }
  },

  /**
   * Create a new object. Maps to /node.
   */
  _create: function(obj, callback) {
    var op = this.operation('node', obj);
    this.call(op, function(err, body, location) {
      if (err) {
        return callback(err);
      }

      var result = _.clone(obj);
      result[this.options.id] = this._extractId(location);

      callback(null, result);
    });
  },

  /**
   * Save the properties of an object. Maps to PUT /node/{id}/properties.
   */
  _update: function(obj, callback) {
    var id = this._getId(obj, true);
    if (!this._isValidId(id)) {
      return callback(new Error("Invalid ID"));
    }

    var untouchedObj = _.clone(obj);
    if (untouchedObj[this.options.id] != null) {
      delete untouchedObj[this.options.id];
    }

    var endpoint = util.format('%s/properties', this._nodeRoot(id));
    var op = this.operation(endpoint, 'PUT', untouchedObj);
    this.call(op, function(err, body, response) {
      if (err) {
        return callback(err);
      } else {
        return callback(null, obj);
      }
    });
  },

  _updateProp: function(obj, key, val, callback) {
    var id = this._getId(obj);
    if (!this._isValidId(id)) {
      return callback(new Error("Invalid ID"));
    }

    var endpoint = util.format('%s/properties/%s', this._nodeRoot(id), key);
    var op = this.operation(endpoint, 'PUT', val);
    this.call(op, function(err, body) {
      if (err) return callback(err);
      else {
        obj = _.clone(obj);
        obj[key] = val;
        callback(null, obj);
      }
    });
  },

  /**
   * Read an object's properties. Maps to GET node/{id}/properties.
   */
  read: function(id, property, callback) {
    if (typeof property == 'function') {
      callback = property;
      property = null;
    }

    if (Array.isArray(id)) {
      var txn = this._safeBatch();
      async.map(id, naan.ncurry(txn.read, property, 1), callback);
      return this._safeBatchCommit(txn);
    }

    id = this._getId(id);
    if (!this._isValidId(id)) {
      return callback(new Error("Invalid ID"));
    }

    var endpoint;
    if (!property)
      endpoint = util.format('%s/properties', this._nodeRoot(id));
    else
      endpoint = util.format('%s/properties/%s', this._nodeRoot(id), property);
    var op = this.operation(endpoint);
    this.call(op, function(err, body) {
      if (err) {
        return callback(err);
      } else {
        if (!property) body[this.options.id] = id;
        return callback(null, body);
      }
    });
  },

  /**
   * Delete an object. Maps to DELETE node/{id}
   * If force is truthy, delete node and all its relations.
   */
  delete: function(id, force, callback) {
    var property = null;
    if (typeof force == 'function') {
      callback = force;
      force = false;
    } else if (typeof force == 'string') {
      property = force;
      force = false;
    }

    if (Array.isArray(id)) {
      var txn = this._safeBatch();
      async.map(id, naan.ncurry(txn.node.delete, force || property, 1), callback);
      return this._safeBatchCommit(txn);
    }

    var object = id;
    id = this._getId(id);
    if (!this._isValidId(id)) {
      return callback(new Error("Invalid ID"));
    }

    if (!force) {
      var endpoint;
      if (!property) endpoint = this._nodeRoot(id);
      else
        endpoint = util.format('%s/properties/%s', this._nodeRoot(id), property);
      var op = this.operation(endpoint, 'DELETE');
      this.call(op, function(err) {
        if (!property || err || typeof object != 'object') callback(err);
        else {
          delete object[property];
          callback(null, object);
        }
      });
    } else {
      this.query([
        'START node=node({root})',
        'OPTIONAL MATCH node-[rel]-()',
        'DELETE node, rel'
      ].join(' '), {root: id}, function(err) { callback(err) });
    }
  },

  /**
   * Relate objects together. maps to POST node/{first}/relationships
   */
  relate: function(startNode, type, endNode, properties, callback) {
    this.rel.create(startNode, type, endNode, properties, callback);
  },

  /**
   * Retrieve a set of relationships for the given node. Optionally specify the
   * direction and relationship name.
   *
   * `direction` must be one of "all", "in" or "out".
   *
   * db.relationships(obj|id, [direction, [relationshipName]], callback)
   */
  relationships: function(obj, direction, relName, callback) {
    if (typeof direction === 'function') {
      callback = direction;
      direction = 'all';
      relName = '';
    } else {
      if (typeof relName === 'function') {
        callback = relName;
        relName = '';
      }

      if (typeof direction !== 'string') {
        return callback(new Error('Invalid direction - ' + direction));
      } else if (typeof relName !== 'string') {
        return callback(new Error('Invalid relationship name - ' + relName));
      }

      direction = direction.toLowerCase();
      if (['in', 'all', 'out'].indexOf(direction) === -1) {
        return callback(new Error('Invalid direction - ' + direction));
      }
    }

    if (Array.isArray(obj)) {
      var txn = this._safeBatch();
      var args = [ direction, relName ];
      var rels = naan.ecurry(txn.node.relationships, args, [1, 2]);
      async.map(obj, rels, callback);
      return this._safeBatchCommit(txn);
    }

    var objId = this._getId(obj);

    if (!this._isValidId(objId)) {
      callback(new Error("Invalid ID"));
    }

    var nodeRoot = this._nodeRoot(objId);
    var endpoint = util.format('%s/relationships/%s', nodeRoot, direction);
    if (relName) {
      endpoint += "/" + relName;
    }
    var op = this.operation(endpoint);
    var self = this;
    this.call(op, function(err, rels) {
      if (err) {
        callback(err);
      } else {
        rels = rels.map(function(rel) {
          return self._createRelationshipObject(rel);
        });
        callback(null, rels);
      }
    });
  },

  /**
   * Perform a query based on a predicate. The predicate is translated to a
   * cypher query.
   */
  find: function(predicate, any, label, callback) {
    if (typeof any == 'function') {
      callback = any;
      any = false;
      label = null;
    } else if (typeof label == 'function') {
      callback = label;
      label = null;
    }

    if (Array.isArray(predicate)) {
      var txn = this._safeBatch();
      var finder = naan.ecurry(txn.node.find, [any, label], [1, 2]);
      async.map(predicate, finder, callback);
      return this._safeBatchCommit(txn);
    }

    if (typeof predicate !== 'object') callback(new Error('Invalid Predicate'));

    var matchers = Object.keys(predicate).reduce(function(matchers, key) {
      return matchers.push(util.format("n.%s = {%s}", key, key)), matchers;
    }, []);

    var cypher = [
      label == null ? "MATCH n WHERE" : util.format("MATCH (n:%s) WHERE", label),
      matchers.join(any ? "or" : "and"),
      "RETURN n"
    ].join(" ");

    var self = this;
    this.query(cypher, predicate, callback);
  },

  label: function(node, label, replace, callback) {
    if (typeof replace == 'function') {
      callback = replace;
      replace = false;
    }

    if (Array.isArray(node)) {
      var txn = this._safeBatch();
      async.map(node, naan.ecurry(txn.label, [label, replace], [1,2]), callback);
      return this._safeBatchCommit(txn);
    }

    var id = this._getId(node);
    if (!this._isValidId(id)) return callback(new Error("Invalid ID"));

    var endpoint = util.format('%s/labels', this._nodeRoot(id));
    var op = this.operation(endpoint, replace ? 'PUT' : 'POST', label);
    this.call(op, function(err) {
      if (err) callback(err);
      else callback();
    });
  },

  removeLabel: function(node, label, callback) {
    if (Array.isArray(node)) {
      var txn = this._safeBatch();
      async.map(node, naan.ncurry(txn.removeLabel, label, 1), callback);
      return this._safeBatchCommit(txn);
    }

    var id = this._getId(node);
    if (!this._isValidId(id)) return callback(new Error("Invalid ID"));

    var endpoint = util.format('%s/labels/%s', this._nodeRoot(id), label);
    var op = this.operation(endpoint, 'DELETE');
    this.call(op, function(err) {
      if (err) callback(err);
      else callback();
    });
  },

  nodesWithLabel: function(label, callback) {
    var endpoint = util.format('label/%s/nodes', label);
    var op = this.operation(endpoint, 'GET');
    this.call(op, function(err, nodes) {
      if (err || !nodes) return callback(err, nodes);
      nodes = nodes.map(this._createNodeObject);
      callback(null, nodes);
    });
  },

  readLabels: function(node, callback) {
    if (typeof node == 'function') {
      callback = node;
      node = undefined;
    }

    if (Array.isArray(node)) {
      var txn = this._safeBatch();
      async.map(node, txn.readLabels, function(err, allLabels) {
        if (err) return callback(err);
        callback(null, _.uniq(_.flatten(allLabels)));
      });
      return this._safeBatchCommit(txn);
    }

    var endpoint;
    if (node) {
      var id = this._getId(node);
      if (!this._isValidId(id)) return callback(new Error("Invalid ID"));
      endpoint = util.format('%s/labels', this._nodeRoot(id));
    } else {
      endpoint = 'labels';
    }
    var op = this.operation(endpoint, 'GET');
    this.call(op, function(err, labels) {
      if (err) callback(err);
      else callback(null, labels);
    });
  }
}
<|endoftext|>


<commit_before>var _ = require('_'),

    _ID_PREFIX = 'D-uniqueId-',

    _GET_ELEMENT_BY_ID          = 'getElementById',
    _GET_ELEMENTS_BY_TAG_NAME   = 'getElementsByTagName',
    _GET_ELEMENTS_BY_CLASS_NAME = 'getElementsByClassName',
    _QUERY_SELECTOR_ALL         = 'querySelectorAll',

    _SUPPORTS  = require('../../../supports'),
    _NODE_TYPE = require('../../../nodeType'),

    _cache = require('../../../cache'),
    _regex = require('../../../regex'),

    _querySelectorCache = _cache(),

    _isMatch = require('../selector/selector-match');

var _determineMethod = function(selector) {
        var method = _querySelectorCache.get(selector);
        if (method) { return method; }

        if (_regex.selector.isStrictId(selector)) {
            method = _GET_ELEMENT_BY_ID;
        } else if (_regex.selector.isClass(selector)) {
            method = _GET_ELEMENTS_BY_CLASS_NAME;
        } else if (_regex.selector.isTag(selector)) {
            method = _GET_ELEMENTS_BY_TAG_NAME;
        } else {
            method = _QUERY_SELECTOR_ALL;
        }

        _querySelectorCache.set(selector, method);
        return method;
    },
    _uniqueId = function() {
        return _ID_PREFIX + _.uniqueId();
    };

var Selector = function(str) {
    var selector                = _.string.trim(str),
        isChildOrSiblingSelect  = (selector[0] === '>' || selector[0] === '+'),
        method                  = isChildOrSiblingSelect ? _QUERY_SELECTOR_ALL : _determineMethod(selector);

    this.str                    = str;
    this.selector               = selector;
    this.isChildOrSiblingSelect = isChildOrSiblingSelect;
    this.isIdSearch             = method === _GET_ELEMENT_BY_ID;
    this.isClassSearch          = !this.isIdSearch && method === _GET_ELEMENTS_BY_CLASS_NAME;
    this.method                 = method;
};

Selector.prototype = {

    _tailorChildSelector: function(id, selector) {
        return '#' + id + ' ' + selector;
    },

    match: function(context) {
        // No neeed to check, a match will fail if it's
        // child or sibling
        if (this.isChildOrSiblingSelect) { return false; }

        return _isMatch(context, this.selector);
    },

    exec: function(context) {
        context = context || document;

        var selection,
            method = this.method,
            nodeType;

        // Early return if context is not an element or document
        if ((nodeType = context.nodeType) !== _NODE_TYPE.ELEMENT && nodeType !== _NODE_TYPE.DOCUMENT) { return; }

        // Child select - needs special help so that "> div" doesn't break
        var id,
            newId,
            idApplied = false,
            selector  = this.selector;

        if (this.isChildOrSiblingSelect) {
            id = context.id;
            if (id === '' || !_.exists(id)) {
                newId = _uniqueId();
                context.id = newId;
                idApplied = true;
            }

            selector = this._tailorChildSelector(idApplied ? newId : id, selector);
            context = document;
        } else if (this.isClassSearch) {
            if (_SUPPORTS.getElementsByClassName) {
                // Class search, don't start with '.'
                selector = selector.substr(1);
            } else {
                method = _QUERY_SELECTOR_ALL;
            }
        } else if (this.isIdSearch) {
            if (context === document) {
                // Id search, don't start with '#'
                selector = selector.substr(1);
            } else {
                // context is not the document,
                // change to querySelectorAll and
                // leave the '#'
                method = _QUERY_SELECTOR_ALL;
            }
        }

        selection = context[method](selector);

        if (idApplied) { context.id = id; }

        // No selection
        if (!selection) {
            return [];
        }
        // Nodelist without a length
        if (_.isNodeList(selection) && !selection.length) {
            return [];
        }
        // IE8 DispStaticNodeList
        if (selection.item && selection.length === 0) {
            return [];
        }

        // If it's an id, return it as an array, otherwise, toArray
        // to keep from returning out NodeLists and HTMLCollections
        return _.isElement(selection) || !selection.length ? [selection] : _.toArray(selection);
    }
};

module.exports = Selector;
<commit_msg>Change from _.toArray to a more specific use case<commit_after>var _ = require('_'),

    _ID_PREFIX = 'D-uniqueId-',

    _GET_ELEMENT_BY_ID          = 'getElementById',
    _GET_ELEMENTS_BY_TAG_NAME   = 'getElementsByTagName',
    _GET_ELEMENTS_BY_CLASS_NAME = 'getElementsByClassName',
    _QUERY_SELECTOR_ALL         = 'querySelectorAll',

    _SUPPORTS  = require('../../../supports'),
    _NODE_TYPE = require('../../../nodeType'),

    _cache = require('../../../cache'),
    _regex = require('../../../regex'),

    _querySelectorCache = _cache(),

    _isMatch = require('../selector/selector-match');

var _determineMethod = function(selector) {
        var method = _querySelectorCache.get(selector);
        if (method) { return method; }

        if (_regex.selector.isStrictId(selector)) {
            method = _GET_ELEMENT_BY_ID;
        } else if (_regex.selector.isClass(selector)) {
            method = _GET_ELEMENTS_BY_CLASS_NAME;
        } else if (_regex.selector.isTag(selector)) {
            method = _GET_ELEMENTS_BY_TAG_NAME;
        } else {
            method = _QUERY_SELECTOR_ALL;
        }

        _querySelectorCache.set(selector, method);
        return method;
    },
    _uniqueId = function() {
        return _ID_PREFIX + _.uniqueId();
    },
    _fromDomArrayToArray = function(arrayLike) {
        var idx = arrayLike.length,
            arr = new Array(idx);
        while (idx--) {
            arr[idx] = arrayLike[idx];
        }
        return arr;
    };

var Selector = function(str) {
    var selector                = _.string.trim(str),
        isChildOrSiblingSelect  = (selector[0] === '>' || selector[0] === '+'),
        method                  = isChildOrSiblingSelect ? _QUERY_SELECTOR_ALL : _determineMethod(selector);

    this.str                    = str;
    this.selector               = selector;
    this.isChildOrSiblingSelect = isChildOrSiblingSelect;
    this.isIdSearch             = method === _GET_ELEMENT_BY_ID;
    this.isClassSearch          = !this.isIdSearch && method === _GET_ELEMENTS_BY_CLASS_NAME;
    this.method                 = method;
};

Selector.prototype = {

    _tailorChildSelector: function(id, selector) {
        return '#' + id + ' ' + selector;
    },

    match: function(context) {
        // No neeed to check, a match will fail if it's
        // child or sibling
        if (this.isChildOrSiblingSelect) { return false; }

        return _isMatch(context, this.selector);
    },

    exec: function(context) {
        context = context || document;

        var selection,
            method = this.method,
            nodeType;

        // Early return if context is not an element or document
        if ((nodeType = context.nodeType) !== _NODE_TYPE.ELEMENT && nodeType !== _NODE_TYPE.DOCUMENT) { return; }

        // Child select - needs special help so that "> div" doesn't break
        var id,
            newId,
            idApplied = false,
            selector  = this.selector;

        if (this.isChildOrSiblingSelect) {
            id = context.id;
            if (id === '' || !_.exists(id)) {
                newId = _uniqueId();
                context.id = newId;
                idApplied = true;
            }

            selector = this._tailorChildSelector(idApplied ? newId : id, selector);
            context = document;
        } else if (this.isClassSearch) {
            if (_SUPPORTS.getElementsByClassName) {
                // Class search, don't start with '.'
                selector = selector.substr(1);
            } else {
                method = _QUERY_SELECTOR_ALL;
            }
        } else if (this.isIdSearch) {
            if (context === document) {
                // Id search, don't start with '#'
                selector = selector.substr(1);
            } else {
                // context is not the document,
                // change to querySelectorAll and
                // leave the '#'
                method = _QUERY_SELECTOR_ALL;
            }
        }

        selection = context[method](selector);

        if (idApplied) { context.id = id; }

        // No selection
        if (!selection) {
            return [];
        }
        // Nodelist without a length
        if (_.isNodeList(selection) && !selection.length) {
            return [];
        }
        // IE8 DispStaticNodeList
        if (selection.item && selection.length === 0) {
            return [];
        }

        // If it's an id, return it as an array
        return _.isElement(selection) || !selection.length ? [selection] : _fromDomArrayToArray(selection);
    }
};

module.exports = Selector;
<|endoftext|>


<commit_before>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

// An Algorithm for Automatically Fitting Digitized Curves
// by Philip J. Schneider
// from "Graphics Gems", Academic Press, 1990
var PathFitter = Base.extend({
	initialize: function(path, error) {
		this.points = [];
		var segments = path._segments,
			prev;
		// Copy over points from path and filter out adjacent duplicates.
		for (var i = 0, l = segments.length; i < l; i++) {
			var point = segments[i].point.clone();
			if (!prev || !prev.equals(point)) {
				this.points.push(point);
				prev = point;
			}
		}
		this.error = error;
	},

	fit: function() {
		this.segments = [new Segment(this.points[0])];
		this.fitCubic(0, this.points.length - 1,
				// Left Tangent
				this.points[1].subtract(this.points[0]).normalize(),
				// Right Tangent
				this.points[this.points.length - 2].subtract(
					this.points[this.points.length - 1]).normalize());
		return this.segments;
	},

	// Fit a Bezier curve to a (sub)set of digitized points
	fitCubic: function(first, last, tan1, tan2) {
		//	Use heuristic if region only has two points in it
		if (last - first == 1) {
			var pt1 = this.points[first],
				pt2 = this.points[last],
				dist = pt1.getDistance(pt2) / 3;
			this.addCurve([pt1, pt1.add(tan1.normalize(dist)),
					pt2.add(tan2.normalize(dist)), pt2]);
			return;
		}
		// Parameterize points, and attempt to fit curve
		var uPrime = this.chordLengthParameterize(first, last),
			maxError = Math.max(this.error, this.error * this.error),
			error,
			split;
		// Try 4 iterations
		for (var i = 0; i <= 4; i++) {
			var curve = this.generateBezier(first, last, uPrime, tan1, tan2);
			//	Find max deviation of points to fitted curve
			var max = this.findMaxError(first, last, curve, uPrime);
			if (max.error < this.error) {
				this.addCurve(curve);
				return;
			}
			split = max.index;
			// If error not too large, try reparameterization and iteration
			if (max.error >= maxError)
				break;
			this.reparameterize(first, last, uPrime, curve);
			maxError = max.error;
		}
		// Fitting failed -- split at max error point and fit recursively
		var V1 = this.points[split - 1].subtract(this.points[split]),
			V2 = this.points[split].subtract(this.points[split + 1]),
			tanCenter = V1.add(V2).divide(2).normalize();
		this.fitCubic(first, split, tan1, tanCenter);
		this.fitCubic(split, last, tanCenter.negate(), tan2);
	},

	addCurve: function(curve) {
		var prev = this.segments[this.segments.length - 1];
		prev.setHandleOut(curve[1].subtract(curve[0]));
		this.segments.push(
				new Segment(curve[3], curve[2].subtract(curve[3])));
	},

	// Use least-squares method to find Bezier control points for region.
	generateBezier: function(first, last, uPrime, tan1, tan2) {
		var epsilon = Numerical.EPSILON,
			pt1 = this.points[first],
			pt2 = this.points[last],
			// Create the C and X matrices
		 	C = [[0, 0], [0, 0]],
			X = [0, 0];

		for (var i = 0, l = last - first + 1; i < l; i++) {
			var u = uPrime[i],
				t = 1 - u,
				b = 3 * u * t,
				b0 = t * t * t,
				b1 = b * t,
				b2 = b * u,
				b3 = u * u * u,
				a1 = tan1.normalize(b1),
				a2 = tan2.normalize(b2),
				tmp = this.points[first + i]
					.subtract(pt1.multiply(b0 + b1))
					.subtract(pt2.multiply(b2 + b3));
			C[0][0] += a1.dot(a1);
			C[0][1] += a1.dot(a2);
			// C[1][0] += a1.dot(a2);
			C[1][0] = C[0][1];
			C[1][1] += a2.dot(a2);
			X[0] += a1.dot(tmp);
			X[1] += a2.dot(tmp);
		}

		// Compute the determinants of C and X
		var detC0C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1],
			alpha1, alpha2;
		if (Math.abs(detC0C1) > epsilon) {
			// Kramer's rule
			var detC0X  = C[0][0] * X[1]    - C[1][0] * X[0],
				detXC1  = X[0]    * C[1][1] - X[1]    * C[0][1];
			// Derive alpha values
			alpha1 = detXC1 / detC0C1;
			alpha2 = detC0X / detC0C1;
		} else {
			// Matrix is under-determined, try assuming alpha1 == alpha2
			var c0 = C[0][0] + C[0][1],
				c1 = C[1][0] + C[1][1];
			if (Math.abs(c0) > epsilon) {
				alpha1 = alpha2 = X[0] / c0;
			} else if (Math.abs(c0) > epsilon) {
				alpha1 = alpha2 = X[1] / c1;
			} else {
				// Handle below
				alpha1 = alpha2 = 0.;
			}
		}

		// If alpha negative, use the Wu/Barsky heuristic (see text)
		// (if alpha is 0, you get coincident control points that lead to
		// divide by zero in any subsequent NewtonRaphsonRootFind() call.
		var segLength = pt2.getDistance(pt1);
		epsilon *= segLength;
		if (alpha1 < epsilon || alpha2 < epsilon) {
			// fall back on standard (probably inaccurate) formula,
			// and subdivide further if needed.
			alpha1 = alpha2 = segLength / 3;
		}

		// First and last control points of the Bezier curve are
		// positioned exactly at the first and last data points
		// Control points 1 and 2 are positioned an alpha distance out
		// on the tangent vectors, left and right, respectively
		return [pt1, pt1.add(tan1.normalize(alpha1)),
				pt2.add(tan2.normalize(alpha2)), pt2];
	},

	// Given set of points and their parameterization, try to find
	// a better parameterization.
	reparameterize: function(first, last, u, curve) {
		for (var i = first; i <= last; i++) {
			u[i - first] = this.findRoot(curve, this.points[i], u[i - first]);
		}
	},

	// Use Newton-Raphson iteration to find better root.
	findRoot: function(curve, point, u) {
		var curve1 = [],
			curve2 = [];
		// Generate control vertices for Q'
		for (var i = 0; i <= 2; i++) {
			curve1[i] = curve[i + 1].subtract(curve[i]).multiply(3);
		}
		// Generate control vertices for Q''
		for (var i = 0; i <= 1; i++) {
			curve2[i] = curve1[i + 1].subtract(curve1[i]).multiply(2);
		}
		// Compute Q(u), Q'(u) and Q''(u)
		var pt = this.evaluate(3, curve, u),
		 	pt1 = this.evaluate(2, curve1, u),
		 	pt2 = this.evaluate(1, curve2, u),
		 	diff = pt.subtract(point),
			df = pt1.dot(pt1) + diff.dot(pt2);
		// Compute f(u) / f'(u)
		if (Math.abs(df) < Numerical.TOLERANCE)
			return u;
		// u = u - f(u) / f'(u)
		return u - diff.dot(pt1) / df;
	},

	// Evaluate a Bezier curve at a particular parameter value
	evaluate: function(degree, curve, t) {
		// Copy array
		var tmp = curve.slice();
		// Triangle computation
		for (var i = 1; i <= degree; i++) {
			for (var j = 0; j <= degree - i; j++) {
				tmp[j] = tmp[j].multiply(1 - t).add(tmp[j + 1].multiply(t));
			}
		}
		return tmp[0];
	},

	// Assign parameter values to digitized points
	// using relative distances between points.
	chordLengthParameterize: function(first, last) {
		var u = [0];
		for (var i = first + 1; i <= last; i++) {
			u[i - first] = u[i - first - 1]
					+ this.points[i].getDistance(this.points[i - 1]);
		}
		for (var i = 1, m = last - first; i <= m; i++) {
			u[i] /= u[m];
		}
		return u;
	},

	// Find the maximum squared distance of digitized points to fitted curve.
	findMaxError: function(first, last, curve, u) {
		var index = Math.floor((last - first + 1) / 2),
			maxDist = 0;
		for (var i = first + 1; i < last; i++) {
			var P = this.evaluate(3, curve, u[i - first]);
			var v = P.subtract(this.points[i]);
			var dist = v.x * v.x + v.y * v.y; // squared
			if (dist >= maxDist) {
				maxDist = dist;
				index = i;
			}
		}
		return {
			error: maxDist,
			index: index
		};
	}
});
<commit_msg>Include comment about algorithm modifications.<commit_after>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

// An Algorithm for Automatically Fitting Digitized Curves
// by Philip J. Schneider
// from "Graphics Gems", Academic Press, 1990
// Modifications and optimisations of original algorithm by Juerg Lehni.

var PathFitter = Base.extend({
	initialize: function(path, error) {
		this.points = [];
		var segments = path._segments,
			prev;
		// Copy over points from path and filter out adjacent duplicates.
		for (var i = 0, l = segments.length; i < l; i++) {
			var point = segments[i].point.clone();
			if (!prev || !prev.equals(point)) {
				this.points.push(point);
				prev = point;
			}
		}
		this.error = error;
	},

	fit: function() {
		this.segments = [new Segment(this.points[0])];
		this.fitCubic(0, this.points.length - 1,
				// Left Tangent
				this.points[1].subtract(this.points[0]).normalize(),
				// Right Tangent
				this.points[this.points.length - 2].subtract(
					this.points[this.points.length - 1]).normalize());
		return this.segments;
	},

	// Fit a Bezier curve to a (sub)set of digitized points
	fitCubic: function(first, last, tan1, tan2) {
		//	Use heuristic if region only has two points in it
		if (last - first == 1) {
			var pt1 = this.points[first],
				pt2 = this.points[last],
				dist = pt1.getDistance(pt2) / 3;
			this.addCurve([pt1, pt1.add(tan1.normalize(dist)),
					pt2.add(tan2.normalize(dist)), pt2]);
			return;
		}
		// Parameterize points, and attempt to fit curve
		var uPrime = this.chordLengthParameterize(first, last),
			maxError = Math.max(this.error, this.error * this.error),
			error,
			split;
		// Try 4 iterations
		for (var i = 0; i <= 4; i++) {
			var curve = this.generateBezier(first, last, uPrime, tan1, tan2);
			//	Find max deviation of points to fitted curve
			var max = this.findMaxError(first, last, curve, uPrime);
			if (max.error < this.error) {
				this.addCurve(curve);
				return;
			}
			split = max.index;
			// If error not too large, try reparameterization and iteration
			if (max.error >= maxError)
				break;
			this.reparameterize(first, last, uPrime, curve);
			maxError = max.error;
		}
		// Fitting failed -- split at max error point and fit recursively
		var V1 = this.points[split - 1].subtract(this.points[split]),
			V2 = this.points[split].subtract(this.points[split + 1]),
			tanCenter = V1.add(V2).divide(2).normalize();
		this.fitCubic(first, split, tan1, tanCenter);
		this.fitCubic(split, last, tanCenter.negate(), tan2);
	},

	addCurve: function(curve) {
		var prev = this.segments[this.segments.length - 1];
		prev.setHandleOut(curve[1].subtract(curve[0]));
		this.segments.push(
				new Segment(curve[3], curve[2].subtract(curve[3])));
	},

	// Use least-squares method to find Bezier control points for region.
	generateBezier: function(first, last, uPrime, tan1, tan2) {
		var epsilon = Numerical.EPSILON,
			pt1 = this.points[first],
			pt2 = this.points[last],
			// Create the C and X matrices
		 	C = [[0, 0], [0, 0]],
			X = [0, 0];

		for (var i = 0, l = last - first + 1; i < l; i++) {
			var u = uPrime[i],
				t = 1 - u,
				b = 3 * u * t,
				b0 = t * t * t,
				b1 = b * t,
				b2 = b * u,
				b3 = u * u * u,
				a1 = tan1.normalize(b1),
				a2 = tan2.normalize(b2),
				tmp = this.points[first + i]
					.subtract(pt1.multiply(b0 + b1))
					.subtract(pt2.multiply(b2 + b3));
			C[0][0] += a1.dot(a1);
			C[0][1] += a1.dot(a2);
			// C[1][0] += a1.dot(a2);
			C[1][0] = C[0][1];
			C[1][1] += a2.dot(a2);
			X[0] += a1.dot(tmp);
			X[1] += a2.dot(tmp);
		}

		// Compute the determinants of C and X
		var detC0C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1],
			alpha1, alpha2;
		if (Math.abs(detC0C1) > epsilon) {
			// Kramer's rule
			var detC0X  = C[0][0] * X[1]    - C[1][0] * X[0],
				detXC1  = X[0]    * C[1][1] - X[1]    * C[0][1];
			// Derive alpha values
			alpha1 = detXC1 / detC0C1;
			alpha2 = detC0X / detC0C1;
		} else {
			// Matrix is under-determined, try assuming alpha1 == alpha2
			var c0 = C[0][0] + C[0][1],
				c1 = C[1][0] + C[1][1];
			if (Math.abs(c0) > epsilon) {
				alpha1 = alpha2 = X[0] / c0;
			} else if (Math.abs(c0) > epsilon) {
				alpha1 = alpha2 = X[1] / c1;
			} else {
				// Handle below
				alpha1 = alpha2 = 0.;
			}
		}

		// If alpha negative, use the Wu/Barsky heuristic (see text)
		// (if alpha is 0, you get coincident control points that lead to
		// divide by zero in any subsequent NewtonRaphsonRootFind() call.
		var segLength = pt2.getDistance(pt1);
		epsilon *= segLength;
		if (alpha1 < epsilon || alpha2 < epsilon) {
			// fall back on standard (probably inaccurate) formula,
			// and subdivide further if needed.
			alpha1 = alpha2 = segLength / 3;
		}

		// First and last control points of the Bezier curve are
		// positioned exactly at the first and last data points
		// Control points 1 and 2 are positioned an alpha distance out
		// on the tangent vectors, left and right, respectively
		return [pt1, pt1.add(tan1.normalize(alpha1)),
				pt2.add(tan2.normalize(alpha2)), pt2];
	},

	// Given set of points and their parameterization, try to find
	// a better parameterization.
	reparameterize: function(first, last, u, curve) {
		for (var i = first; i <= last; i++) {
			u[i - first] = this.findRoot(curve, this.points[i], u[i - first]);
		}
	},

	// Use Newton-Raphson iteration to find better root.
	findRoot: function(curve, point, u) {
		var curve1 = [],
			curve2 = [];
		// Generate control vertices for Q'
		for (var i = 0; i <= 2; i++) {
			curve1[i] = curve[i + 1].subtract(curve[i]).multiply(3);
		}
		// Generate control vertices for Q''
		for (var i = 0; i <= 1; i++) {
			curve2[i] = curve1[i + 1].subtract(curve1[i]).multiply(2);
		}
		// Compute Q(u), Q'(u) and Q''(u)
		var pt = this.evaluate(3, curve, u),
		 	pt1 = this.evaluate(2, curve1, u),
		 	pt2 = this.evaluate(1, curve2, u),
		 	diff = pt.subtract(point),
			df = pt1.dot(pt1) + diff.dot(pt2);
		// Compute f(u) / f'(u)
		if (Math.abs(df) < Numerical.TOLERANCE)
			return u;
		// u = u - f(u) / f'(u)
		return u - diff.dot(pt1) / df;
	},

	// Evaluate a Bezier curve at a particular parameter value
	evaluate: function(degree, curve, t) {
		// Copy array
		var tmp = curve.slice();
		// Triangle computation
		for (var i = 1; i <= degree; i++) {
			for (var j = 0; j <= degree - i; j++) {
				tmp[j] = tmp[j].multiply(1 - t).add(tmp[j + 1].multiply(t));
			}
		}
		return tmp[0];
	},

	// Assign parameter values to digitized points
	// using relative distances between points.
	chordLengthParameterize: function(first, last) {
		var u = [0];
		for (var i = first + 1; i <= last; i++) {
			u[i - first] = u[i - first - 1]
					+ this.points[i].getDistance(this.points[i - 1]);
		}
		for (var i = 1, m = last - first; i <= m; i++) {
			u[i] /= u[m];
		}
		return u;
	},

	// Find the maximum squared distance of digitized points to fitted curve.
	findMaxError: function(first, last, curve, u) {
		var index = Math.floor((last - first + 1) / 2),
			maxDist = 0;
		for (var i = first + 1; i < last; i++) {
			var P = this.evaluate(3, curve, u[i - first]);
			var v = P.subtract(this.points[i]);
			var dist = v.x * v.x + v.y * v.y; // squared
			if (dist >= maxDist) {
				maxDist = dist;
				index = i;
			}
		}
		return {
			error: maxDist,
			index: index
		};
	}
});
<|endoftext|>


<commit_before>angular.module('anol.map')

/**
 * @ngdoc object
 * @name anol.map.ClusterSelectServiceProvider
 */
.provider('ClusterSelectService', [function() {

    var _clusterSelectOptions;

    this.setClusterSelectOptions = function(options) {
        _clusterSelectOptions = options;
    };

    this.$get = ['MapService', function(MapService) {

        var defaultClusterOptions = {
            selectCluster: true,
            pointRadius: 7,
            spiral: true,
            circleMaxObjects: 10,
            maxObjects: 60,
            animate: true,
            animationDuration: 500,
        };

        var ClusterSelect = function(clusterSelectOptions) {
            this.clusterLayers = [];
            this.selectRevealedFeatureCallbacks = [];
            this.clusterSelectOptions = clusterSelectOptions;
            this.selectedClusterLayer = undefined;
        };

        ClusterSelect.prototype.registerSelectRevealedFeatureCallback = function(f) {
            this.selectRevealedFeatureCallbacks.push(f);
        };

        ClusterSelect.prototype.addLayer = function(layer) {
            var self = this;
            layer.olLayer.on('change:visible', function() {
                if(!layer.getVisible() && layer === self.selectedClusterLayer) {
                    self.selectClusterInteraction.clear();
                }
            });
            this.clusterLayers.push(layer);
        };

        ClusterSelect.prototype.layerByFeature = function(feature) {
            var self = this;
            var resultLayer;
            // TODO collect all anol.layer.Feature into a list
            angular.forEach(self.clusterLayers, function(layer) {
                if(angular.isDefined(resultLayer)) {
                    return;
                }
                if(layer.unclusteredSource.getFeatures().indexOf(feature) > -1) {
                    if(layer instanceof anol.layer.DynamicGeoJSON) {
                        if(feature.get('__layer__') === layer.name) {
                            resultLayer = layer;
                        }
                    } else {
                        resultLayer = layer;
                    }
                }
            });
            return resultLayer;
        };

        ClusterSelect.prototype.getControl = function(recreate) {
            var self = this;
            if(self.clusterLayers.length === 0) {
                return;
            }

            if(angular.isDefined(self.selectClusterControl) && recreate !== true) {
                return self.selectClusterControl;
            }

            var olClusterLayers = [];
            angular.forEach(self.clusterLayers, function(layer) {
                olClusterLayers.push(layer.olLayer);
            });

            var interactionOptions = $.extend({}, defaultClusterOptions, this.clusterSelectOptions, {
                layers: olClusterLayers,
                // for each revealed feature of selected cluster, this function is called
                featureStyle: function(revealedFeature, resolution) {
                    var style = new ol.style.Style();
                    // style link lines
                    if(revealedFeature.get('selectclusterlink') === true) {
                        style = new ol.style.Style({
                            stroke: new ol.style.Stroke({
                                color: '#f00',
                                width: 1
                            })
                        });
                    }
                    if(revealedFeature.get('selectclusterfeature') === true) {
                        var originalFeature = revealedFeature.get('features')[0];
                        var layer = self.layerByFeature(originalFeature);
                        var layerStyle = layer.olLayer.getStyle();

                        if(angular.isFunction(layerStyle)) {
                            layerStyle = layerStyle(originalFeature, resolution)[0];
                        }

                        style = layerStyle;
                    }

                    return [style];
                },
                style: function(clusterFeature, resolution) {
                    return [new ol.style.Style()];
                }
            });

            var selectedCluster;
            self.selectClusterInteraction = new ol.interaction.SelectCluster(interactionOptions);
            self.selectClusterInteraction.on('select', function(a) {
                if(a.selected.length === 1) {
                    if(a.selected[0].get('features').length > interactionOptions.maxObjects) {
                        var view = MapService.getMap().getView();
                        view.setZoom(view.getZoom() + 1);
                        return;
                    }
                    if(a.selected[0].get('features').length > 1) {
                        selectedCluster = a.selected[0];
                        selectedCluster.setStyle(new ol.style.Style());
                        return;
                    }
                    if(a.selected[0].get('selectclusterfeature') === true) {
                        angular.forEach(self.selectRevealedFeatureCallbacks, function(f) {
                            f(a.selected[0]);
                        });
                    }
                } else if(a.selected.length === 0 && angular.isDefined(selectedCluster)) {
                    selectedCluster.setStyle(null);
                    selectedCluster = undefined;
                }
            });

            self.selectClusterInteraction.getFeatures().on('add', function(e) {
                var features = e.element.get('features');
                var layer = self.layerByFeature(features[0]);
                self.selectedClusterLayer = layer;
                if(angular.isFunction(layer.clusterOptions.onSelect)) {
                    layer.clusterOptions.onSelect(features);
                }
            });

            MapService.getMap().addInteraction(self.selectClusterInteraction);

            self.selectClusterControl = new anol.control.Control({
                subordinate: true,
                olControl: null,
                interactions: [self.selectClusterInteraction]
            });

            var changeCursorCondition = function(pixel) {
                return MapService.getMap().hasFeatureAtPixel(pixel, function(layer) {
                    var anolLayer = layer.get('anolLayer');
                    if(anolLayer === undefined) {
                        return false;
                    }
                    return anolLayer.isClustered();
                });
            };

            self.selectClusterControl.onDeactivate(function() {
                self.selectClusterInteraction.setActive(false);
                MapService.removeCursorPointerCondition(changeCursorCondition);
            });
            self.selectClusterControl.onActivate(function() {
                self.selectClusterInteraction.setActive(true);
                MapService.addCursorPointerCondition(changeCursorCondition);
            });

            // control active by default
            MapService.addCursorPointerCondition(changeCursorCondition);

            return this.selectClusterControl;
        };

        return new ClusterSelect(_clusterSelectOptions);
    }];
}]);<commit_msg>Show pointer cursor over revealed features<commit_after>angular.module('anol.map')

/**
 * @ngdoc object
 * @name anol.map.ClusterSelectServiceProvider
 */
.provider('ClusterSelectService', [function() {

    var _clusterSelectOptions;

    this.setClusterSelectOptions = function(options) {
        _clusterSelectOptions = options;
    };

    this.$get = ['MapService', function(MapService) {

        var defaultClusterOptions = {
            selectCluster: true,
            pointRadius: 7,
            spiral: true,
            circleMaxObjects: 10,
            maxObjects: 60,
            animate: true,
            animationDuration: 500,
        };

        var ClusterSelect = function(clusterSelectOptions) {
            this.clusterLayers = [];
            this.selectRevealedFeatureCallbacks = [];
            this.clusterSelectOptions = clusterSelectOptions;
            this.selectedClusterLayer = undefined;
        };

        ClusterSelect.prototype.registerSelectRevealedFeatureCallback = function(f) {
            this.selectRevealedFeatureCallbacks.push(f);
        };

        ClusterSelect.prototype.addLayer = function(layer) {
            var self = this;
            layer.olLayer.on('change:visible', function() {
                if(!layer.getVisible() && layer === self.selectedClusterLayer) {
                    self.selectClusterInteraction.clear();
                }
            });
            this.clusterLayers.push(layer);
        };

        ClusterSelect.prototype.layerByFeature = function(feature) {
            var self = this;
            var resultLayer;
            // TODO collect all anol.layer.Feature into a list
            angular.forEach(self.clusterLayers, function(layer) {
                if(angular.isDefined(resultLayer)) {
                    return;
                }
                if(layer.unclusteredSource.getFeatures().indexOf(feature) > -1) {
                    if(layer instanceof anol.layer.DynamicGeoJSON) {
                        if(feature.get('__layer__') === layer.name) {
                            resultLayer = layer;
                        }
                    } else {
                        resultLayer = layer;
                    }
                }
            });
            return resultLayer;
        };

        ClusterSelect.prototype.getControl = function(recreate) {
            var self = this;
            if(self.clusterLayers.length === 0) {
                return;
            }

            if(angular.isDefined(self.selectClusterControl) && recreate !== true) {
                return self.selectClusterControl;
            }

            var olClusterLayers = [];
            angular.forEach(self.clusterLayers, function(layer) {
                olClusterLayers.push(layer.olLayer);
            });

            var interactionOptions = $.extend({}, defaultClusterOptions, this.clusterSelectOptions, {
                layers: olClusterLayers,
                // for each revealed feature of selected cluster, this function is called
                featureStyle: function(revealedFeature, resolution) {
                    var style = new ol.style.Style();
                    // style link lines
                    if(revealedFeature.get('selectclusterlink') === true) {
                        style = new ol.style.Style({
                            stroke: new ol.style.Stroke({
                                color: '#f00',
                                width: 1
                            })
                        });
                    }
                    if(revealedFeature.get('selectclusterfeature') === true) {
                        var originalFeature = revealedFeature.get('features')[0];
                        var layer = self.layerByFeature(originalFeature);
                        var layerStyle = layer.olLayer.getStyle();

                        if(angular.isFunction(layerStyle)) {
                            layerStyle = layerStyle(originalFeature, resolution)[0];
                        }

                        style = layerStyle;
                    }

                    return [style];
                },
                style: function(clusterFeature, resolution) {
                    return [new ol.style.Style()];
                }
            });

            var selectedCluster;
            self.selectClusterInteraction = new ol.interaction.SelectCluster(interactionOptions);

            var changeCursorCondition = function(pixel) {
                return MapService.getMap().hasFeatureAtPixel(pixel, function(layer) {
                    var found = false;
                    if(self.selectClusterInteraction.overlayLayer_ === layer) {
                        MapService.getMap().forEachFeatureAtPixel(pixel, function(feature) {
                            if(found) {
                                return;
                            }
                            if(feature.get('selectclusterfeature')) {
                                found = true;
                            }
                        })
                    }
                    return found;
                });
            };

            MapService.addCursorPointerCondition(changeCursorCondition);
            self.selectClusterInteraction.on('select', function(a) {
                if(a.selected.length === 1) {
                    if(a.selected[0].get('features').length > interactionOptions.maxObjects) {
                        var view = MapService.getMap().getView();
                        view.setZoom(view.getZoom() + 1);
                        return;
                    }
                    if(a.selected[0].get('features').length > 1) {
                        selectedCluster = a.selected[0];
                        selectedCluster.setStyle(new ol.style.Style());
                        return;
                    }
                    if(a.selected[0].get('selectclusterfeature') === true) {
                        angular.forEach(self.selectRevealedFeatureCallbacks, function(f) {
                            f(a.selected[0]);
                        });
                    }
                } else if(a.selected.length === 0 && angular.isDefined(selectedCluster)) {
                    selectedCluster.setStyle(null);
                    selectedCluster = undefined;
                }
            });

            self.selectClusterInteraction.getFeatures().on('add', function(e) {
                var features = e.element.get('features');
                var layer = self.layerByFeature(features[0]);
                self.selectedClusterLayer = layer;
                if(angular.isFunction(layer.clusterOptions.onSelect)) {
                    layer.clusterOptions.onSelect(features);
                }
            });

            MapService.getMap().addInteraction(self.selectClusterInteraction);

            self.selectClusterControl = new anol.control.Control({
                subordinate: true,
                olControl: null,
                interactions: [self.selectClusterInteraction]
            });

            var changeCursorCondition = function(pixel) {
                return MapService.getMap().hasFeatureAtPixel(pixel, function(layer) {
                    var anolLayer = layer.get('anolLayer');
                    if(anolLayer === undefined) {
                        return false;
                    }
                    return anolLayer.isClustered();
                });
            };

            self.selectClusterControl.onDeactivate(function() {
                self.selectClusterInteraction.setActive(false);
                MapService.removeCursorPointerCondition(changeCursorCondition);
            });
            self.selectClusterControl.onActivate(function() {
                self.selectClusterInteraction.setActive(true);
                MapService.addCursorPointerCondition(changeCursorCondition);
            });

            // control active by default
            MapService.addCursorPointerCondition(changeCursorCondition);

            return this.selectClusterControl;
        };

        return new ClusterSelect(_clusterSelectOptions);
    }];
}]);<|endoftext|>


<commit_before>var cx = require('classnames');
var React = require('react');

var Node = React.createClass({
  displayName: 'Node',

  renderCollapse() {
    var index = this.props.index;

    if(index.children && index.children.length) {
      return (

      );
    }

    return null;
  },

  render() {
    var tree = this.props.tree;
    var index = this.props.index;
    var dragging = this.props.dragging;
    var node = index.node;
    var styles = {};
    var children = null;
    var childrenStyles = {};

    if(index.children && index.children.length) {
      children = index.children.map((child) => {
        var childIndex = tree.getIndex(child);
        return (
          <Node
            tree={tree}
            index={childIndex}
            key={childIndex.id}
            dragging={dragging}
            paddingLeft={this.props.paddingLeft}
            onCollapse={this.props.onCollapse}
            onDragStart={this.props.onDragStart}/>
        );
      });
    }

    if(index.collapsed) childrenStyles.display = 'none';
    childrenStyles['paddingLeft'] = this.props.paddingLeft + 'px';

    return (

          {this.renderCollapse()}
          {tree.renderNode(node)}

{children}

    );
  },

  _onCollapse(e) {
    e.stopPropagation();
    var nodeId = this.props.index.id;
    if(this.props.onCollapse) this.props.onCollapse(nodeId);
  },

  _onMouseDown(e) {
    var nodeId = this.props.index.id;
    var dom = this.refs.inner.getDOMNode();

    if(this.props.onDragStart) {
      this.props.onDragStart(nodeId, dom, e);
    }
  }
});

module.exports = Node;
<commit_msg>node.renderChildren<commit_after>var cx = require('classnames');
var React = require('react');

var Node = React.createClass({
  displayName: 'Node',

  renderCollapse() {
    var index = this.props.index;

    if(index.children && index.children.length) {
      return (

      );
    }

    return null;
  },

  renderChildren() {
    var index = this.props.index;
    var tree = this.props.tree;
    var dragging = this.props.dragging;

    if(index.children && index.children.length) {
      var childrenStyles = {};
      if(index.collapsed) childrenStyles.display = 'none';
      childrenStyles['paddingLeft'] = this.props.paddingLeft + 'px';

      return (

          {index.children.map((child) => {
            var childIndex = tree.getIndex(child);
            return (
              <Node
                tree={tree}
                index={childIndex}
                key={childIndex.id}
                dragging={dragging}
                paddingLeft={this.props.paddingLeft}
                onCollapse={this.props.onCollapse}
                onDragStart={this.props.onDragStart}/>
            );
          })}

      );
    }

    return null;
  },

  render() {
    var tree = this.props.tree;
    var index = this.props.index;
    var dragging = this.props.dragging;
    var node = index.node;
    var styles = {};

    return (

          {this.renderCollapse()}
          {tree.renderNode(node)}

        {this.renderChildren()}

    );
  },

  handleCollapse(e) {
    e.stopPropagation();
    var nodeId = this.props.index.id;
    if(this.props.onCollapse) this.props.onCollapse(nodeId);
  },

  handleMouseDown(e) {
    var nodeId = this.props.index.id;
    var dom = this.refs.inner.getDOMNode();

    if(this.props.onDragStart) {
      this.props.onDragStart(nodeId, dom, e);
    }
  }
});

module.exports = Node;
<|endoftext|>


<commit_before>var _ = require("./_");
var { getCards, getSets, getMws } = require("./data");
const { getRandomSet } = require("./sets-service");

function selectRarity(set) {
  // average pack contains:
  // 14 cards
  // 10 commons
  // 3 uncommons
  // 7/8 rare
  // 1/8 mythic
  // * 8 -> 112/80/24/7/1

  let n = _.rand(112);
  if (n < 1)
    return set.mythic;
  if (n < 8)
    return set.rare;
  if (n < 32)
    return set.uncommon;
  return set.common;
}

function pickFoil(set) {
  var rngFoil = _.rand(6);
  if (rngFoil < 1)
    if (set.mythic)
      if (_.rand(set.mythic.length + (set.rare.length * 2)) < set.mythic.length)
        return set.mythic;
      else
        return set.rare;
    else
      return set.rare;
  if (rngFoil < 3)
    return set.uncommon;
  return set.common;
}

function toPack(code) {
  var set = getSets()[code];
  var { basic, common, uncommon, rare, mythic, special, size } = set;

  if (mythic && !_.rand(8))
    rare = mythic;
  //make small sets draftable.
  if (size < 10 && code != "SOI" && code != "EMN")
    size = 10;

  var pack = [].concat(
    _.choose(3, uncommon),
    _.choose(1, rare)
  );

  if (code == "SOI")
    //http://markrosewater.tumblr.com/post/141794840953/if-the-as-fan-of-double-face-cards-is-1125-that
    if (_.rand(8) == 0) {
      size = size - 1;
      if (_.rand(15) < 3)
        pack.push(_.choose(1, special.mythic));
      else
        pack.push(_.choose(1, special.rare));
    }
  if (code == "EMN")
    if (_.rand(8) == 0) {
      size = size - 1;
      if (_.rand(5) < 1)
        pack.push(_.choose(1, special.mythic));
      else
        pack.push(_.choose(1, special.rare));
    }
  let foilCard = false;
  let specialrnd;
  switch (code) {
    case "EMN":
      if (_.rand(2) < 1)
        special = special.uncommon;
      else
        special = special.common;
      break;

    case "SOI":
      if (_.rand(106) < 38)
        special = special.uncommon;
      else
        special = special.common;
      break;
    case "DGM":
      special = _.rand(20)
        ? special.gate
        : special.shock;
      break;
    case "EMA":
    case "MMA":
    case "MM2":
    case "MM3":
    case "IMA":
    case "UMA":
      special = selectRarity(set);
      foilCard = true;
      break;
    case "VMA":
      //http://www.wizards.com/magic/magazine/article.aspx?x=mtg/daily/arcana/1491
      if (_.rand(53))
        special = selectRarity(set);
      break;
    case "FRF":
      special = _.rand(20)
        ? special.common
        : special.fetch;
      break;
    case "ISD":
      //http://www.mtgsalvation.com/forums/magic-fundamentals/magic-general/327956-innistrad-block-transforming-card-pack-odds?comment=4
      //121 card sheet, 1 mythic, 12 rare (13), 42 uncommon (55), 66 common
      specialrnd = _.rand(121);
      if (specialrnd == 0)
        special = special.mythic;
      else if (specialrnd < 13)
        special = special.rare;
      else if (specialrnd < 55)
        special = special.uncommon;
      else
        special = special.common;
      break;
    case "DKA":
      //http://www.mtgsalvation.com/forums/magic-fundamentals/magic-general/327956-innistrad-block-transforming-card-pack-odds?comment=4
      //80 card sheet, 2 mythic, 6 rare (8), 24 uncommon (32), 48 common
      specialrnd = _.rand(80);
      if (specialrnd <= 1)
        special = special.mythic;
      else if (specialrnd < 8)
        special = special.rare;
      else if (specialrnd < 32)
        special = special.uncommon;
      else
        special = special.common;
      break;
    case "DOM":
      // http://markrosewater.tumblr.com/post/172581930278/do-the-legendaries-that-appear-in-the-legendary
      // Every booster must contain a legendary creature either as uncommon or rare
      let hasLegendaryCreature = false;
      const isLegendaryCreature = cardName => {
        const card = getCards()[cardName];
        return card.supertypes.includes("Legendary") && card.type === "Creature";
      };

      pack.some(cardName => {
        return hasLegendaryCreature = isLegendaryCreature(cardName);
      });

      if (!hasLegendaryCreature) {
        // Choose to replace an uncommon or rare slot
        if (_.rand(4) === 0) {
          var packIndex = 3;
          var isMythic = mythic.includes(pack[packIndex]);
          var pool = isMythic ? mythic : rare;
        } else {
          // Uncommon slot
          var packIndex = 0;
          var pool = uncommon;
        }
        const legendaryCreatures = pool.filter(isLegendaryCreature);
        pack[packIndex] = _.choose(1, legendaryCreatures);
      }
      break;
    case "M19":
      //http://wizardsmagic.tumblr.com/post/175584204911/core-set-2019-packs-basic-lands-and-upcoming
      // 5/12 of times -> dual-land
      // 7/12 of times -> basic land
      const dualLands = common.filter(cardName => getCards()[cardName].type === "Land");
      common = common.filter(cardName => !dualLands.includes(cardName)); //delete dualLands from possible choice as common slot
      const isDualLand = _.rand(12) < 6;
      const land = _.choose(1, isDualLand ? dualLands : basic);
      pack.push(...land);
      break;
    case "GRN":
    case "RNA":
      // No basics. Always 1 common slots are occupied by guildgates
      const guildGates = common.filter(cardName => getCards()[cardName].type === "Land" && getCards()[cardName].sets[code].rarity == "common");
      common = common.filter(cardName => !guildGates.includes(cardName)); //delete guildGates from possible choice as common slot
      pack.push(_.choose(1, guildGates));
      break;
  }
  var masterpiece = "";
  if (special) {
    if (special.masterpieces) {
      if (_.rand(144) == 0) {
        size = size - 1;
        specialpick = _.choose(1, special.masterpieces);
        specialpick = specialpick[0];
        pack.push(specialpick);
        masterpiece = specialpick;
      }
      special = 0;
    }
    else {
      var specialpick = _.choose(1, special);
      specialpick = specialpick[0];
      pack.push(specialpick);
      if (foilCard) {
        foilCard = specialpick;
      }
    }
  }
  if (_.rand(7) < 1 && !(foilCard) && !(masterpiece)) {
    // http://www.mtgsalvation.com/forums/magic-fundamentals/magic-general/768235-what-are-current-pack-odds-including-foils
    size = size - 1;
    foilCard = _.choose(1, pickFoil(set));
    pack.push(foilCard[0]);
  }
  pack = _.choose(size, common).concat(pack);

  return toCards(pack, code, foilCard, masterpiece);
}

function toCards(pool, code, foilCard, masterpiece) {
  var isCube = !code;
  var packSize = pool.length;
  return pool.map(cardName => {
    var card = Object.assign({}, getCards()[cardName]);
    card.packSize = packSize;
    var { sets } = card;

    if (isCube)
      [code] = Object.keys(sets)
        .filter(set => !["EXP", "MPS", "MPS_AKH"].includes(set)); // #121: Filter Invocations art

    card.code = getMws()[code] || code;
    var set = sets[code];

    if (masterpiece == card.name.toString().toLowerCase()) {
      card.rarity = "special";
      card.foil = true;
      if (code == "BFZ" || code == "OGW") {
        card.code = "EXP";
      } else if (code == "KLD" || code == "AER") {
        card.code = "MPS";
      } else if (code == "AKH" || code == "HOU") {
        card.code = "MPS_AKH";
      }
      set = sets[card.code];
      masterpiece = "";
    }
    if (foilCard == card.name.toString().toLowerCase()) {
      card.foil = true;
      foilCard = "";
    }
    delete card.sets;
    return Object.assign(card, set);
  });
}

module.exports = function (src, playerCount, isSealed, isChaos, modernOnly, totalChaos) {
  if (!(src instanceof Array)) {
    if (!(isChaos)) {
      var isCube = true;
      _.shuffle(src.list);
    }
  }
  else {
    for (i = 0; i < src.length; i++) {
      if (src[i] == "RNG") {
        src[i] = getRandomSet().code;
      }
    }
  }
  if (isSealed) {
    var count = playerCount;
    var size = 90;
  } else {
    count = playerCount * src.packs;
    size = src.cards;
  }
  var pools = [];

  if (isCube || isSealed) {
    if (!(isChaos)) {
      while (count--)
        pools.push(isCube
          ? toCards(src.list.splice(-size))
          : [].concat(...src.map(toPack)));
    } else {
      const allSets = getSets();
      var setlist = [];
      var modernSets = ["AER", "KLD", "EMN", "SOI", "OGW", "BFZ", "ORI", "DTK", "FRF", "KTK", "M15", "JOU", "BNG", "THS", "M14", "DGM", "GTC", "RTR", "M13", "AVR", "DKA", "ISD", "M12", "NPH", "MBS", "SOM", "M11", "ROE", "WWK", "ZEN", "M10", "ARB", "CON", "ALA", "EVE", "SHM", "MOR", "LRW", "10E", "FUT", "PLC", "TSP", "CSP", "DIS", "GPT", "RAV", "9ED", "SOK", "8ED", "BOK", "CHK", "5DN", "DST", "MRD"];
      if (modernOnly) {
        setlist = modernSets;
      }
      else {
        for (let code in allSets) {
          const { type } = allSets[code];
          if (type !== "custom" && code != "UNH" && code != "UGL") {
            setlist.push(code);
          }
        }
      }
      if (!(totalChaos)) {
        for (let i = 0; i < 3; i++) {
          for (let j = 0; j < playerCount; j++) {
            let setindex = _.rand(setlist.length);
            let code = setlist[setindex];
            setlist.splice(setindex, 1);
            pools.push(toPack(code));
          }
        }
      }
      else {
        var setindex = 0;
        for (var i = 0; i < 3; i++) {
          for (var j = 0; j < playerCount; j++) {
            var chaosPool = [];
            setindex = _.rand(setlist.length);
            if (setlist[setindex].mythic && !_.rand(8)) {
              chaosPool.push(_.choose(1, allSets[setlist[setindex]].mythic));
            }
            else {
              chaosPool.push(_.choose(1, allSets[setlist[setindex]].rare));
            }
            for (let k = 0; k < 3; k++) {
              setindex = _.rand(setlist.length);
              chaosPool.push(_.choose(1, allSets[setlist[setindex]].uncommon));
            }
            for (let k = 0; k < 11; k++) {
              setindex = _.rand(setlist.length);
              chaosPool.push(_.choose(1, allSets[setlist[setindex]].common));
            }
            pools.push(toCards(chaosPool));
          }
        }
      }
    }
  } else {
    for (let code of src.reverse())
      for (let i = 0; i < playerCount; i++)
        pools.push(toPack(code));
  }
  return pools;
};
<commit_msg>we don't want unstable in chaos draft<commit_after>var _ = require("./_");
var { getCards, getSets, getMws } = require("./data");
const { getRandomSet } = require("./sets-service");

function selectRarity(set) {
  // average pack contains:
  // 14 cards
  // 10 commons
  // 3 uncommons
  // 7/8 rare
  // 1/8 mythic
  // * 8 -> 112/80/24/7/1

  let n = _.rand(112);
  if (n < 1)
    return set.mythic;
  if (n < 8)
    return set.rare;
  if (n < 32)
    return set.uncommon;
  return set.common;
}

function pickFoil(set) {
  var rngFoil = _.rand(6);
  if (rngFoil < 1)
    if (set.mythic)
      if (_.rand(set.mythic.length + (set.rare.length * 2)) < set.mythic.length)
        return set.mythic;
      else
        return set.rare;
    else
      return set.rare;
  if (rngFoil < 3)
    return set.uncommon;
  return set.common;
}

function toPack(code) {
  var set = getSets()[code];
  var { basic, common, uncommon, rare, mythic, special, size } = set;

  if (mythic && !_.rand(8))
    rare = mythic;
  //make small sets draftable.
  if (size < 10 && code != "SOI" && code != "EMN")
    size = 10;

  var pack = [].concat(
    _.choose(3, uncommon),
    _.choose(1, rare)
  );

  if (code == "SOI")
    //http://markrosewater.tumblr.com/post/141794840953/if-the-as-fan-of-double-face-cards-is-1125-that
    if (_.rand(8) == 0) {
      size = size - 1;
      if (_.rand(15) < 3)
        pack.push(_.choose(1, special.mythic));
      else
        pack.push(_.choose(1, special.rare));
    }
  if (code == "EMN")
    if (_.rand(8) == 0) {
      size = size - 1;
      if (_.rand(5) < 1)
        pack.push(_.choose(1, special.mythic));
      else
        pack.push(_.choose(1, special.rare));
    }
  let foilCard = false;
  let specialrnd;
  switch (code) {
    case "EMN":
      if (_.rand(2) < 1)
        special = special.uncommon;
      else
        special = special.common;
      break;

    case "SOI":
      if (_.rand(106) < 38)
        special = special.uncommon;
      else
        special = special.common;
      break;
    case "DGM":
      special = _.rand(20)
        ? special.gate
        : special.shock;
      break;
    case "EMA":
    case "MMA":
    case "MM2":
    case "MM3":
    case "IMA":
    case "UMA":
      special = selectRarity(set);
      foilCard = true;
      break;
    case "VMA":
      //http://www.wizards.com/magic/magazine/article.aspx?x=mtg/daily/arcana/1491
      if (_.rand(53))
        special = selectRarity(set);
      break;
    case "FRF":
      special = _.rand(20)
        ? special.common
        : special.fetch;
      break;
    case "ISD":
      //http://www.mtgsalvation.com/forums/magic-fundamentals/magic-general/327956-innistrad-block-transforming-card-pack-odds?comment=4
      //121 card sheet, 1 mythic, 12 rare (13), 42 uncommon (55), 66 common
      specialrnd = _.rand(121);
      if (specialrnd == 0)
        special = special.mythic;
      else if (specialrnd < 13)
        special = special.rare;
      else if (specialrnd < 55)
        special = special.uncommon;
      else
        special = special.common;
      break;
    case "DKA":
      //http://www.mtgsalvation.com/forums/magic-fundamentals/magic-general/327956-innistrad-block-transforming-card-pack-odds?comment=4
      //80 card sheet, 2 mythic, 6 rare (8), 24 uncommon (32), 48 common
      specialrnd = _.rand(80);
      if (specialrnd <= 1)
        special = special.mythic;
      else if (specialrnd < 8)
        special = special.rare;
      else if (specialrnd < 32)
        special = special.uncommon;
      else
        special = special.common;
      break;
    case "DOM":
      // http://markrosewater.tumblr.com/post/172581930278/do-the-legendaries-that-appear-in-the-legendary
      // Every booster must contain a legendary creature either as uncommon or rare
      let hasLegendaryCreature = false;
      const isLegendaryCreature = cardName => {
        const card = getCards()[cardName];
        return card.supertypes.includes("Legendary") && card.type === "Creature";
      };

      pack.some(cardName => {
        return hasLegendaryCreature = isLegendaryCreature(cardName);
      });

      if (!hasLegendaryCreature) {
        // Choose to replace an uncommon or rare slot
        if (_.rand(4) === 0) {
          var packIndex = 3;
          var isMythic = mythic.includes(pack[packIndex]);
          var pool = isMythic ? mythic : rare;
        } else {
          // Uncommon slot
          var packIndex = 0;
          var pool = uncommon;
        }
        const legendaryCreatures = pool.filter(isLegendaryCreature);
        pack[packIndex] = _.choose(1, legendaryCreatures);
      }
      break;
    case "M19":
      //http://wizardsmagic.tumblr.com/post/175584204911/core-set-2019-packs-basic-lands-and-upcoming
      // 5/12 of times -> dual-land
      // 7/12 of times -> basic land
      const dualLands = common.filter(cardName => getCards()[cardName].type === "Land");
      common = common.filter(cardName => !dualLands.includes(cardName)); //delete dualLands from possible choice as common slot
      const isDualLand = _.rand(12) < 6;
      const land = _.choose(1, isDualLand ? dualLands : basic);
      pack.push(...land);
      break;
    case "GRN":
    case "RNA":
      // No basics. Always 1 common slots are occupied by guildgates
      const guildGates = common.filter(cardName => getCards()[cardName].type === "Land" && getCards()[cardName].sets[code].rarity == "common");
      common = common.filter(cardName => !guildGates.includes(cardName)); //delete guildGates from possible choice as common slot
      pack.push(_.choose(1, guildGates));
      break;
  }
  var masterpiece = "";
  if (special) {
    if (special.masterpieces) {
      if (_.rand(144) == 0) {
        size = size - 1;
        specialpick = _.choose(1, special.masterpieces);
        specialpick = specialpick[0];
        pack.push(specialpick);
        masterpiece = specialpick;
      }
      special = 0;
    }
    else {
      var specialpick = _.choose(1, special);
      specialpick = specialpick[0];
      pack.push(specialpick);
      if (foilCard) {
        foilCard = specialpick;
      }
    }
  }
  if (_.rand(7) < 1 && !(foilCard) && !(masterpiece)) {
    // http://www.mtgsalvation.com/forums/magic-fundamentals/magic-general/768235-what-are-current-pack-odds-including-foils
    size = size - 1;
    foilCard = _.choose(1, pickFoil(set));
    pack.push(foilCard[0]);
  }
  pack = _.choose(size, common).concat(pack);

  return toCards(pack, code, foilCard, masterpiece);
}

function toCards(pool, code, foilCard, masterpiece) {
  var isCube = !code;
  var packSize = pool.length;
  return pool.map(cardName => {
    var card = Object.assign({}, getCards()[cardName]);
    card.packSize = packSize;
    var { sets } = card;

    if (isCube)
      [code] = Object.keys(sets)
        .filter(set => !["EXP", "MPS", "MPS_AKH"].includes(set)); // #121: Filter Invocations art

    card.code = getMws()[code] || code;
    var set = sets[code];

    if (masterpiece == card.name.toString().toLowerCase()) {
      card.rarity = "special";
      card.foil = true;
      if (code == "BFZ" || code == "OGW") {
        card.code = "EXP";
      } else if (code == "KLD" || code == "AER") {
        card.code = "MPS";
      } else if (code == "AKH" || code == "HOU") {
        card.code = "MPS_AKH";
      }
      set = sets[card.code];
      masterpiece = "";
    }
    if (foilCard == card.name.toString().toLowerCase()) {
      card.foil = true;
      foilCard = "";
    }
    delete card.sets;
    return Object.assign(card, set);
  });
}

module.exports = function (src, playerCount, isSealed, isChaos, modernOnly, totalChaos) {
  if (!(src instanceof Array)) {
    if (!(isChaos)) {
      var isCube = true;
      _.shuffle(src.list);
    }
  }
  else {
    for (i = 0; i < src.length; i++) {
      if (src[i] == "RNG") {
        src[i] = getRandomSet().code;
      }
    }
  }
  if (isSealed) {
    var count = playerCount;
    var size = 90;
  } else {
    count = playerCount * src.packs;
    size = src.cards;
  }
  var pools = [];

  if (isCube || isSealed) {
    if (!(isChaos)) {
      while (count--)
        pools.push(isCube
          ? toCards(src.list.splice(-size))
          : [].concat(...src.map(toPack)));
    } else {
      const allSets = getSets();
      var setlist = [];
      var modernSets = ["AER", "KLD", "EMN", "SOI", "OGW", "BFZ", "ORI", "DTK", "FRF", "KTK", "M15", "JOU", "BNG", "THS", "M14", "DGM", "GTC", "RTR", "M13", "AVR", "DKA", "ISD", "M12", "NPH", "MBS", "SOM", "M11", "ROE", "WWK", "ZEN", "M10", "ARB", "CON", "ALA", "EVE", "SHM", "MOR", "LRW", "10E", "FUT", "PLC", "TSP", "CSP", "DIS", "GPT", "RAV", "9ED", "SOK", "8ED", "BOK", "CHK", "5DN", "DST", "MRD"];
      if (modernOnly) {
        setlist = modernSets;
      }
      else {
        for (let code in allSets) {
          const { type } = allSets[code];

          // We don't want Unstable or Unhinged sets, neither custom sets
          if (!["custom", "funny"].includes(type)) {
            setlist.push(code);
          }
        }
      }
      if (!(totalChaos)) {
        for (let i = 0; i < 3; i++) {
          for (let j = 0; j < playerCount; j++) {
            let setindex = _.rand(setlist.length);
            let code = setlist[setindex];
            setlist.splice(setindex, 1);
            pools.push(toPack(code));
          }
        }
      }
      else {
        var setindex = 0;
        for (var i = 0; i < 3; i++) {
          for (var j = 0; j < playerCount; j++) {
            var chaosPool = [];
            setindex = _.rand(setlist.length);
            if (setlist[setindex].mythic && !_.rand(8)) {
              chaosPool.push(_.choose(1, allSets[setlist[setindex]].mythic));
            }
            else {
              chaosPool.push(_.choose(1, allSets[setlist[setindex]].rare));
            }
            for (let k = 0; k < 3; k++) {
              setindex = _.rand(setlist.length);
              chaosPool.push(_.choose(1, allSets[setlist[setindex]].uncommon));
            }
            for (let k = 0; k < 11; k++) {
              setindex = _.rand(setlist.length);
              chaosPool.push(_.choose(1, allSets[setlist[setindex]].common));
            }
            pools.push(toCards(chaosPool));
          }
        }
      }
    }
  } else {
    for (let code of src.reverse())
      for (let i = 0; i < playerCount; i++)
        pools.push(toPack(code));
  }
  return pools;
};
<|endoftext|>