text: "use strict";
  /**
   * Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
   *
   * @author David Sehnal <david.sehnal@gmail.com>
   * @author Alexander Rose <alexander.rose@weirdbyte.de>
   */
  Object.defineProperty(exports, "__esModule", { value: true });
  exports.Loci = void 0;
  var generic_1 = require("../../../../mol-data/generic");
  var int_1 = require("../../../../mol-data/int");
  var linear_algebra_1 = require("../../../../mol-math/linear-algebra");
  var builder_1 = require("../../../../mol-script/language/builder");
  var structure_1 = require("../structure");
  var unit_1 = require("../unit");
  var util_1 = require("../../../../mol-data/util");
  var location_1 = require("./location");
  var principal_axes_1 = require("../../../../mol-math/linear-algebra/matrix/principal-axes");
  var properties_1 = require("../properties");
  var boundary_helper_1 = require("../../../../mol-math/geometry/boundary-helper");
  var tuple_1 = require("../../../../mol-data/int/tuple");
  // avoiding namespace lookup improved performance in Chrome (Aug 2020)
  var itDiff = tuple_1.IntTuple.diff;
  function Loci(structure, elements) {
      return { kind: 'element-loci', structure: structure, elements: elements };
  }
  exports.Loci = Loci;
  (function (Loci) {
      function is(x) {
          return !!x && x.kind === 'element-loci';
      }
      Loci.is = is;
      function areEqual(a, b) {
          if (a.structure !== b.structure)
              return false;
          if (a.elements.length !== b.elements.length)
              return false;
          for (var i = 0, il = a.elements.length; i < il; ++i) {
              var elementA = a.elements[i];
              var elementB = b.elements[i];
              if (elementA.unit.id !== elementB.unit.id)
                  return false;
              if (!int_1.OrderedSet.areEqual(elementA.indices, elementB.indices))
                  return false;
          }
          return true;
      }
      Loci.areEqual = areEqual;
      function isEmpty(loci) {
          for (var _a = 0, _b = loci.elements; _a < _b.length; _a++) {
              var u = _b[_a];
              if (int_1.OrderedSet.size(u.indices) > 0)
                  return false;
          }
          return true;
      }
      Loci.isEmpty = isEmpty;
      function isWholeStructure(loci) {
          return size(loci) === loci.structure.elementCount;
      }
      Loci.isWholeStructure = isWholeStructure;
      function size(loci) {
          var s = 0;
          // inlined for max performance, crucial for marking large cellpack models
          // `for (const u of loci.elements) s += OrderedSet.size(u.indices);`
          for (var _a = 0, _b = loci.elements; _a < _b.length; _a++) {
              var indices = _b[_a].indices;
              if (typeof indices === 'number') {
                  s += itDiff(indices);
              }
              else {
                  s += indices.length;
              }
          }
          return s;
      }
      Loci.size = size;
      function all(structure) {
          return Loci(structure, structure.units.map(function (unit) { return ({
              unit: unit,
              indices: int_1.OrderedSet.ofBounds(0, unit.elements.length)
          }); }));
      }
      Loci.all = all;
      function none(structure) {
          return Loci(structure, []);
      }
      Loci.none = none;
      function getFirstLocation(loci, e) {
          if (isEmpty(loci))
              return void 0;
          var unit = loci.elements[0].unit;
          var element = unit.elements[int_1.OrderedSet.getAt(loci.elements[0].indices, 0)];
          if (e) {
              e.structure = loci.structure;
              e.unit = loci.elements[0].unit;
              e.element = element;
              return e;
          }
          return location_1.Location.create(loci.structure, unit, element);
      }
      Loci.getFirstLocation = getFirstLocation;
      function firstElement(loci) {
          if (isEmpty(loci))
              return loci;
          return Loci(loci.structure, [{
                  unit: loci.elements[0].unit,
                  indices: int_1.OrderedSet.ofSingleton(int_1.OrderedSet.start(loci.elements[0].indices))
              }]);
      }
      Loci.firstElement = firstElement;
      function firstResidue(loci) {
          if (isEmpty(loci))
              return loci;
          return extendToWholeResidues(firstElement(loci));
      }
      Loci.firstResidue = firstResidue;
      function firstChain(loci) {
          if (isEmpty(loci))
              return loci;
          return extendToWholeChains(firstElement(loci));
      }
      Loci.firstChain = firstChain;
      function toStructure(loci) {
          var units = [];
          var _loop_1 = function (e) {
              var unit = e.unit, indices = e.indices;
              var elements = new Int32Array(int_1.OrderedSet.size(indices));
              int_1.OrderedSet.forEach(indices, function (v, i) { return elements[i] = unit.elements[v]; });
              units.push(unit.getChild(int_1.SortedArray.ofSortedArray(elements)));
          };
          for (var _a = 0, _b = loci.elements; _a < _b.length; _a++) {
              var e = _b[_a];
              _loop_1(e);
          }
          return structure_1.Structure.create(units, { parent: loci.structure.parent });
      }
      Loci.toStructure = toStructure;
      // TODO: there should be a version that properly supports partitioned units
      function remap(loci, structure) {
          if (structure === loci.structure)
              return loci;
          var elements = [];
          loci.elements.forEach(function (e) {
              if (!structure.unitMap.has(e.unit.id))
                  return;
              var unit = structure.unitMap.get(e.unit.id);
              var indices = int_1.OrderedSet.indexedIntersect(e.indices, e.unit.elements, unit.elements);
              if (int_1.OrderedSet.size(indices) > 0)
                  elements.push({ unit: unit, indices: indices });
          });
          return Loci(structure, elements);
      }
      Loci.remap = remap;
      /** Create union of `xs` and `ys` */
      function union(xs, ys) {
          if (xs.elements.length > ys.elements.length)
              return union(ys, xs);
          if (Loci.isEmpty(xs))
              return ys;
          var map = new Map();
          for (var _a = 0, _b = xs.elements; _a < _b.length; _a++) {
              var e = _b[_a];
              map.set(e.unit.id, e.indices);
          }
          var elements = [];
          for (var _c = 0, _d = ys.elements; _c < _d.length; _c++) {
              var e = _d[_c];
              if (map.has(e.unit.id)) {
                  elements[elements.length] = { unit: e.unit, indices: int_1.OrderedSet.union(map.get(e.unit.id), e.indices) };
                  map.delete(e.unit.id);
              }
              else {
                  elements[elements.length] = e;
              }
          }
          map.forEach(function (indices, id) {
              elements[elements.length] = { unit: xs.structure.unitMap.get(id), indices: indices };
          });
          return Loci(xs.structure, elements);
      }
      Loci.union = union;
      /** Subtract `ys` from `xs` */
      function subtract(xs, ys) {
          var map = new Map();
          for (var _a = 0, _b = ys.elements; _a < _b.length; _a++) {
              var e = _b[_a];
              map.set(e.unit.id, e.indices);
          }
          var elements = [];
          for (var _c = 0, _d = xs.elements; _c < _d.length; _c++) {
              var e = _d[_c];
              if (map.has(e.unit.id)) {
                  var indices = int_1.OrderedSet.subtract(e.indices, map.get(e.unit.id));
                  if (int_1.OrderedSet.size(indices) === 0)
                      continue;
                  elements[elements.length] = { unit: e.unit, indices: indices };
              }
              else {
                  elements[elements.length] = e;
              }
          }
          return Loci(xs.structure, elements);
      }
      Loci.subtract = subtract;
      /** Intersect `xs` and `ys` */
      function intersect(xs, ys) {
          var map = new Map();
          for (var _a = 0, _b = xs.elements; _a < _b.length; _a++) {
              var e = _b[_a];
              map.set(e.unit.id, e.indices);
          }
          var elements = [];
          for (var _c = 0, _d = ys.elements; _c < _d.length; _c++) {
              var e = _d[_c];
              if (!map.has(e.unit.id))
                  continue;
              var indices = int_1.OrderedSet.intersect(map.get(e.unit.id), e.indices);
              if (int_1.OrderedSet.size(indices) === 0)
                  continue;
              elements[elements.length] = { unit: e.unit, indices: indices };
          }
          return Loci(xs.structure, elements);
      }
      Loci.intersect = intersect;
      function areIntersecting(xs, ys) {
          if (xs.elements.length > ys.elements.length)
              return areIntersecting(ys, xs);
          if (Loci.isEmpty(xs))
              return Loci.isEmpty(ys);
          var map = new Map();
          for (var _a = 0, _b = xs.elements; _a < _b.length; _a++) {
              var e = _b[_a];
              map.set(e.unit.id, e.indices);
          }
          for (var _c = 0, _d = ys.elements; _c < _d.length; _c++) {
              var e = _d[_c];
              if (!map.has(e.unit.id))
                  continue;
              if (int_1.OrderedSet.areIntersecting(map.get(e.unit.id), e.indices))
                  return true;
          }
          return false;
      }
      Loci.areIntersecting = areIntersecting;
      /** Check if second loci is a subset of the first */
      function isSubset(xs, ys) {
          if (Loci.isEmpty(xs))
              return Loci.isEmpty(ys);
          var map = new Map();
          for (var _a = 0, _b = xs.elements; _a < _b.length; _a++) {
              var e = _b[_a];
              map.set(e.unit.id, e.indices);
          }
          var isSubset = false;
          for (var _c = 0, _d = ys.elements; _c < _d.length; _c++) {
              var e = _d[_c];
              if (!map.has(e.unit.id))
                  continue;
              if (!int_1.OrderedSet.isSubset(map.get(e.unit.id), e.indices))
                  return false;
              else
                  isSubset = true;
          }
          return isSubset;
      }
      Loci.isSubset = isSubset;
      function makeIndexSet(newIndices) {
          if (newIndices.length > 3 && int_1.SortedArray.isRange(newIndices)) {
              return int_1.Interval.ofRange(newIndices[0], newIndices[newIndices.length - 1]);
          }
          else {
              return int_1.SortedArray.ofSortedArray(newIndices);
          }
      }
      function extendToWholeResidues(loci, restrictToConformation) {
          var elements = [];
          var residueAltIds = new Set();
          for (var _a = 0, _b = loci.elements; _a < _b.length; _a++) {
              var lociElement = _b[_a];
              if (isWholeUnit(lociElement)) {
                  elements[elements.length] = lociElement;
                  continue;
              }
              if (lociElement.unit.kind === 0 /* Atomic */) {
                  var unitElements = lociElement.unit.elements;
                  var h = lociElement.unit.model.atomicHierarchy;
                  var label_alt_id = lociElement.unit.model.atomicHierarchy.atoms.label_alt_id;
                  var _c = h.residueAtomSegments, residueIndex = _c.index, residueOffsets = _c.offsets;
                  var newIndices = [];
                  var indices = lociElement.indices, len = int_1.OrderedSet.size(indices);
                  var i = 0;
                  while (i < len) {
                      residueAltIds.clear();
                      var eI = unitElements[int_1.OrderedSet.getAt(indices, i)];
                      var rI = residueIndex[eI];
                      residueAltIds.add(label_alt_id.value(eI));
                      i++;
                      while (i < len) {
                          var eI_1 = unitElements[int_1.OrderedSet.getAt(indices, i)];
                          if (residueIndex[eI_1] !== rI)
                              break;
                          residueAltIds.add(label_alt_id.value(eI_1));
                          i++;
                      }
                      var hasSharedAltId = residueAltIds.has('');
                      for (var j = residueOffsets[rI], _j = residueOffsets[rI + 1]; j < _j; j++) {
                          var idx = int_1.OrderedSet.indexOf(unitElements, j);
                          if (idx >= 0) {
                              var altId = label_alt_id.value(j);
                              if (!restrictToConformation || hasSharedAltId || !altId || residueAltIds.has(altId)) {
                                  newIndices[newIndices.length] = idx;
                              }
                          }
                      }
                  }
                  elements[elements.length] = { unit: lociElement.unit, indices: makeIndexSet(newIndices) };
              }
              else {
                  // coarse elements are already by-residue
                  elements[elements.length] = lociElement;
              }
          }
          return Loci(loci.structure, elements);
      }
      Loci.extendToWholeResidues = extendToWholeResidues;
      function getChainSegments(unit) {
          switch (unit.kind) {
              case 0 /* Atomic */: return unit.model.atomicHierarchy.chainAtomSegments;
              case 1 /* Spheres */: return unit.model.coarseHierarchy.spheres.chainElementSegments;
              case 2 /* Gaussians */: return unit.model.coarseHierarchy.gaussians.chainElementSegments;
          }
      }
      function isWholeUnit(element) {
          return element.unit.elements.length === int_1.OrderedSet.size(element.indices);
      }
      function collectChains(unit, chainIndices, elements) {
          var index = getChainSegments(unit).index;
          var xs = unit.elements;
          var size = 0;
          for (var i = 0, _i = xs.length; i < _i; i++) {
              var eI = xs[i];
              var cI = index[eI];
              if (!chainIndices.has(cI))
                  continue;
              size++;
          }
          if (size === unit.elements.length) {
              elements[elements.length] = { unit: unit, indices: int_1.Interval.ofBounds(0, size) };
              return;
          }
          var newIndices = new Int32Array(size);
          size = 0;
          for (var i = 0, _i = xs.length; i < _i; i++) {
              var eI = xs[i];
              var cI = index[eI];
              if (!chainIndices.has(cI))
                  continue;
              newIndices[size++] = i;
          }
          if (newIndices.length > 0) {
              elements[elements.length] = { unit: unit, indices: makeIndexSet(newIndices) };
          }
      }
      function extendGroupToWholeChains(loci, start, end, isPartitioned, elements) {
          var chainIndex = getChainSegments(loci.elements[0].unit).index;
          var chainIndices = new Set();
          for (var lI = start; lI < end; lI++) {
              var lociElement = loci.elements[lI];
              var indices = lociElement.indices;
              var unitElements = lociElement.unit.elements;
              for (var i = 0, _i = int_1.OrderedSet.size(indices); i < _i; i++) {
                  chainIndices.add(chainIndex[unitElements[int_1.OrderedSet.getAt(indices, i)]]);
              }
          }
          if (isPartitioned) {
              var baseUnit = loci.elements[0].unit;
              // TODO: check for accidental quadratic for really large structures (but should be ok).
              for (var _a = 0, _b = loci.structure.units; _a < _b.length; _a++) {
                  var unit = _b[_a];
                  if (!unit_1.Unit.areSameChainOperatorGroup(unit, baseUnit))
                      continue;
                  collectChains(unit, chainIndices, elements);
              }
          }
          else {
              for (var lI = start; lI < end; lI++) {
                  collectChains(loci.elements[lI].unit, chainIndices, elements);
              }
          }
      }
      function extendToWholeChains(loci) {
          var elements = [];
          for (var i = 0, len = loci.elements.length; i < len; i++) {
              var e = loci.elements[i];
              if (unit_1.Unit.Traits.is(e.unit.traits, 2 /* Partitioned */)) {
                  var start = i;
                  while (i < len && unit_1.Unit.areSameChainOperatorGroup(loci.elements[i].unit, e.unit)) {
                      i++;
                  }
                  var end = i;
                  i--;
                  extendGroupToWholeChains(loci, start, end, true, elements);
              }
              else {
                  if (isWholeUnit(e)) {
                      elements[elements.length] = e;
                  }
                  else {
                      extendGroupToWholeChains(loci, i, i + 1, false, elements);
                  }
              }
          }
          return Loci(loci.structure, elements);
      }
      Loci.extendToWholeChains = extendToWholeChains;
      function entityModelKey(location) {
          return "".concat(location.unit.model.id, "|").concat(properties_1.StructureProperties.entity.id(location));
      }
      function extendToWholeEntities(loci) {
          var elements = [];
          var l = location_1.Location.create(loci.structure);
          var entities = new Set();
          var units = loci.structure.units;
          for (var i = 0, len = loci.elements.length; i < len; i++) {
              var e = loci.elements[i];
              l.unit = e.unit;
              l.element = e.unit.elements[0];
              entities.add(entityModelKey(l));
          }
          for (var i = 0, il = units.length; i < il; ++i) {
              var unit = units[i];
              l.unit = unit;
              l.element = unit.elements[0];
              if (entities.has(entityModelKey(l))) {
                  var indices = int_1.OrderedSet.ofBounds(0, unit.elements.length);
                  elements[elements.length] = { unit: unit, indices: indices };
              }
          }
          return Loci(loci.structure, elements);
      }
      Loci.extendToWholeEntities = extendToWholeEntities;
      function extendToWholeModels(loci) {
          var elements = [];
          var models = new Set();
          var units = loci.structure.units;
          for (var i = 0, len = loci.elements.length; i < len; i++) {
              var e = loci.elements[i];
              models.add(e.unit.model.id);
          }
          for (var i = 0, il = units.length; i < il; ++i) {
              var unit = units[i];
              if (models.has(unit.model.id)) {
                  var indices = int_1.OrderedSet.ofBounds(0, unit.elements.length);
                  elements[elements.length] = { unit: unit, indices: indices };
              }
          }
          return Loci(loci.structure, elements);
      }
      Loci.extendToWholeModels = extendToWholeModels;
      function getElementIndices(elements, indices) {
          var elementIndices = [];
          for (var i = 0, il = int_1.OrderedSet.size(indices); i < il; ++i) {
              elementIndices.push(elements[int_1.OrderedSet.getAt(indices, i)]);
          }
          return int_1.SortedArray.ofSortedArray(elementIndices);
      }
      function getUnitIndices(elements, indices) {
          if (int_1.SortedArray.isRange(elements) && int_1.SortedArray.areEqual(elements, indices)) {
              return int_1.Interval.ofLength(elements.length);
          }
          return makeIndexSet(int_1.SortedArray.indicesOf(elements, indices));
      }
      function extendToAllInstances(loci) {
          var elements = [];
          var byModel = new Map();
          for (var i = 0, len = loci.elements.length; i < len; i++) {
              var e = loci.elements[i];
              var model = e.unit.model;
              var elementIndices = getElementIndices(e.unit.elements, e.indices);
              if (byModel.has(model)) {
                  byModel.set(model, int_1.SortedArray.union(elementIndices, byModel.get(model)));
              }
              else {
                  byModel.set(model, elementIndices);
              }
          }
          for (var i = 0, il = loci.structure.units.length; i < il; ++i) {
              var unit = loci.structure.units[i];
              var elementIndices = byModel.get(unit.model);
              if (!elementIndices)
                  continue;
              var indices = getUnitIndices(unit.elements, elementIndices);
              elements[elements.length] = { unit: unit, indices: indices };
          }
          return Loci(loci.structure, elements);
      }
      Loci.extendToAllInstances = extendToAllInstances;
      function extendToWholeOperators(loci) {
          var elements = [];
          var operators = new Set();
          var units = loci.structure.units;
          for (var i = 0, len = loci.elements.length; i < len; i++) {
              var e = loci.elements[i];
              operators.add(e.unit.conformation.operator.name);
          }
          for (var i = 0, il = units.length; i < il; ++i) {
              var unit = units[i];
              if (operators.has(unit.conformation.operator.name)) {
                  var indices = int_1.OrderedSet.ofBounds(0, unit.elements.length);
                  elements[elements.length] = { unit: unit, indices: indices };
              }
          }
          return Loci(loci.structure, elements);
      }
      Loci.extendToWholeOperators = extendToWholeOperators;
      //
      var boundaryHelper = new boundary_helper_1.BoundaryHelper('98');
      var tempPosBoundary = (0, linear_algebra_1.Vec3)();
      function getBoundary(loci, transform) {
          boundaryHelper.reset();
          for (var _a = 0, _b = loci.elements; _a < _b.length; _a++) {
              var e = _b[_a];
              var indices = e.indices;
              var pos = e.unit.conformation.position, r = e.unit.conformation.r;
              var elements = e.unit.elements;
              for (var i = 0, _i = int_1.OrderedSet.size(indices); i < _i; i++) {
                  var eI = elements[int_1.OrderedSet.getAt(indices, i)];
                  pos(eI, tempPosBoundary);
                  if (transform)
                      linear_algebra_1.Vec3.transformMat4(tempPosBoundary, tempPosBoundary, transform);
                  boundaryHelper.includePositionRadius(tempPosBoundary, r(eI));
              }
          }
          boundaryHelper.finishedIncludeStep();
          for (var _c = 0, _d = loci.elements; _c < _d.length; _c++) {
              var e = _d[_c];
              var indices = e.indices;
              var pos = e.unit.conformation.position, r = e.unit.conformation.r;
              var elements = e.unit.elements;
              for (var i = 0, _i = int_1.OrderedSet.size(indices); i < _i; i++) {
                  var eI = elements[int_1.OrderedSet.getAt(indices, i)];
                  pos(eI, tempPosBoundary);
                  if (transform)
                      linear_algebra_1.Vec3.transformMat4(tempPosBoundary, tempPosBoundary, transform);
                  boundaryHelper.radiusPositionRadius(tempPosBoundary, r(eI));
              }
          }
          return { box: boundaryHelper.getBox(), sphere: boundaryHelper.getSphere() };
      }
      Loci.getBoundary = getBoundary;
      var tempPos = (0, linear_algebra_1.Vec3)();
      function toPositionsArray(loci, positions, offset) {
          if (offset === void 0) { offset = 0; }
          var m = offset;
          for (var _a = 0, _b = loci.elements; _a < _b.length; _a++) {
              var e = _b[_a];
              var indices = e.indices;
              var pos = e.unit.conformation.position;
              var elements = e.unit.elements;
              var indexCount = int_1.OrderedSet.size(indices);
              for (var i = 0; i < indexCount; i++) {
                  var eI = elements[int_1.OrderedSet.getAt(indices, i)];
                  pos(eI, tempPos);
                  linear_algebra_1.Vec3.toArray(tempPos, positions, m + i * 3);
              }
              m += indexCount * 3;
          }
          return positions;
      }
      Loci.toPositionsArray = toPositionsArray;
      function getPrincipalAxes(loci) {
          var elementCount = size(loci);
          var positions = toPositionsArray(loci, new Float32Array(3 * elementCount));
          return principal_axes_1.PrincipalAxes.ofPositions(positions);
      }
      Loci.getPrincipalAxes = getPrincipalAxes;
      function getPrincipalAxesMany(locis) {
          var elementCount = 0;
          locis.forEach(function (l) {
              elementCount += size(l);
          });
          var positions = new Float32Array(3 * elementCount);
          var offset = 0;
          locis.forEach(function (l) {
              toPositionsArray(l, positions, offset);
              offset += size(l) * 3;
          });
          return principal_axes_1.PrincipalAxes.ofPositions(positions);
      }
      Loci.getPrincipalAxesMany = getPrincipalAxesMany;
      function sourceIndex(unit, element) {
          return unit_1.Unit.isAtomic(unit)
              ? unit.model.atomicHierarchy.atomSourceIndex.value(element)
              // TODO: when implemented, this should map to the source index.
              : element;
      }
      function toExpression(loci) {
          var _a;
          if (Loci.isEmpty(loci))
              return builder_1.MolScriptBuilder.struct.generator.empty();
          var models = loci.structure.models;
          var sourceIndexMap = new Map();
          for (var _b = 0, _c = loci.elements; _b < _c.length; _b++) {
              var e = _c[_b];
              var indices = e.indices;
              var elements = e.unit.elements;
              var key = e.unit.conformation.operator.name;
              var sourceIndices = void 0;
              if (sourceIndexMap.has(key))
                  sourceIndices = sourceIndexMap.get(key).xs;
              else {
                  sourceIndices = generic_1.UniqueArray.create();
                  sourceIndexMap.set(key, { modelLabel: e.unit.model.label, modelIndex: e.unit.model.modelNum, xs: sourceIndices });
              }
              for (var i = 0, _i = int_1.OrderedSet.size(indices); i < _i; i++) {
                  var idx = sourceIndex(e.unit, elements[int_1.OrderedSet.getAt(indices, i)]);
                  generic_1.UniqueArray.add(sourceIndices, idx, idx);
              }
          }
          var opData = [];
          var keys = sourceIndexMap.keys();
          while (true) {
              var k = keys.next();
              if (k.done)
                  break;
              var e = sourceIndexMap.get(k.value);
              opData.push(getOpData(k.value, e.xs.array, models.length > 1, e.modelLabel, e.modelIndex));
          }
          var opGroups = new Map();
          for (var i = 0, il = opData.length; i < il; ++i) {
              var d = opData[i];
              var hash = (0, util_1.hash2)((0, util_1.hashFnv32a)(d.atom.ranges), (0, util_1.hashFnv32a)(d.atom.set));
              var key = "".concat(hash, "|").concat(d.entity ? (d.entity.modelLabel + d.entity.modelIndex) : '');
              if (opGroups.has(key)) {
                  (_a = opGroups.get(key).chain.opName).push.apply(_a, d.chain.opName);
              }
              else {
                  opGroups.set(key, d);
              }
          }
          var opQueries = [];
          opGroups.forEach(function (d) {
              var _a = d.atom, ranges = _a.ranges, set = _a.set;
              var opName = d.chain.opName;
              var opProp = builder_1.MolScriptBuilder.struct.atomProperty.core.operatorName();
              var siProp = builder_1.MolScriptBuilder.struct.atomProperty.core.sourceIndex();
              var tests = [];
              // TODO: add set.ofRanges constructor to MolQL???
              if (set.length > 0) {
                  tests[tests.length] = builder_1.MolScriptBuilder.core.set.has([builder_1.MolScriptBuilder.core.type.set(set), siProp]);
              }
              for (var rI = 0, _rI = ranges.length / 2; rI < _rI; rI++) {
                  tests[tests.length] = builder_1.MolScriptBuilder.core.rel.inRange([siProp, ranges[2 * rI], ranges[2 * rI + 1]]);
              }
              if (d.entity) {
                  var _b = d.entity, modelLabel = _b.modelLabel, modelIndex = _b.modelIndex;
                  opQueries.push(builder_1.MolScriptBuilder.struct.generator.atomGroups({
                      'atom-test': tests.length > 1 ? builder_1.MolScriptBuilder.core.logic.or(tests) : tests[0],
                      'chain-test': opName.length > 1
                          ? builder_1.MolScriptBuilder.core.set.has([builder_1.MolScriptBuilder.core.type.set(opName), opProp])
                          : builder_1.MolScriptBuilder.core.rel.eq([opProp, opName[0]]),
                      'entity-test': builder_1.MolScriptBuilder.core.logic.and([
                          builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.struct.atomProperty.core.modelLabel(), modelLabel]),
                          builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.struct.atomProperty.core.modelIndex(), modelIndex]),
                      ])
                  }));
              }
              else {
                  opQueries.push(builder_1.MolScriptBuilder.struct.generator.atomGroups({
                      'atom-test': tests.length > 1 ? builder_1.MolScriptBuilder.core.logic.or(tests) : tests[0],
                      'chain-test': opName.length > 1
                          ? builder_1.MolScriptBuilder.core.set.has([builder_1.MolScriptBuilder.core.type.set(opName), opProp])
                          : builder_1.MolScriptBuilder.core.rel.eq([opProp, opName[0]])
                  }));
              }
          });
          return builder_1.MolScriptBuilder.struct.modifier.union([
              opQueries.length === 1
                  ? opQueries[0]
                  // Need to union before merge for fast performance
                  : builder_1.MolScriptBuilder.struct.combinator.merge(opQueries.map(function (q) { return builder_1.MolScriptBuilder.struct.modifier.union([q]); }))
          ]);
      }
      Loci.toExpression = toExpression;
      function getOpData(opName, xs, multimodel, modelLabel, modelIndex) {
          (0, util_1.sortArray)(xs);
          var ranges = [];
          var set = [];
          var i = 0;
          var len = xs.length;
          while (i < len) {
              var start = i;
              i++;
              while (i < len && xs[i - 1] + 1 === xs[i])
                  i++;
              var end = i;
              // TODO: is this a good value?
              if (end - start > 12) {
                  ranges[ranges.length] = xs[start];
                  ranges[ranges.length] = xs[end - 1];
              }
              else {
                  for (var j = start; j < end; j++) {
                      set[set.length] = xs[j];
                  }
              }
          }
          return multimodel
              ? {
                  atom: { set: set, ranges: ranges },
                  chain: { opName: [opName] },
                  entity: { modelLabel: modelLabel, modelIndex: modelIndex }
              }
              : {
                  atom: { set: set, ranges: ranges },
                  chain: { opName: [opName] },
              };
      }
  })(Loci = exports.Loci || (exports.Loci = {}));
  //# sourceMappingURL=loci.js.map