text: /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for 
   * full list of contributors). Published under the 2-clause BSD license.
   * See license.txt in the OpenLayers distribution or repository for the
   * full text of the license. */
  
  /**
   * @requires OpenLayers/Control.js
   * @requires OpenLayers/Handler/Path.js
   * @requires OpenLayers/Layer/Vector.js
   */
  
  /**
   * Class: OpenLayers.Control.Split
   * Acts as a split feature agent while editing vector features.
   *
   * Inherits from:
   *  - <OpenLayers.Control>
   */
  OpenLayers.Control.Split = OpenLayers.Class(OpenLayers.Control, {
  
      /** 
       * APIProperty: events
       * {<OpenLayers.Events>} Events instance for listeners and triggering
       *     control specific events.
       *
       * Register a listener for a particular event with the following syntax:
       * (code)
       * control.events.register(type, obj, listener);
       * (end)
       *
       * Supported event types (in addition to those from <OpenLayers.Control.events>):
       * beforesplit - Triggered before a split occurs.  Listeners receive an
       *     event object with *source* and *target* properties.
       * split - Triggered when a split occurs.  Listeners receive an event with
       *     an *original* property and a *features* property.  The original
       *     is a reference to the target feature that the sketch or modified
       *     feature intersects.  The features property is a list of all features
       *     that result from this single split.  This event is triggered before
       *     the resulting features are added to the layer (while the layer still
       *     has a reference to the original).
       * aftersplit - Triggered after all splits resulting from a single sketch
       *     or feature modification have occurred.  The original features
       *     have been destroyed and features that result from the split
       *     have already been added to the layer.  Listeners receive an event
       *     with a *source* and *features* property.  The source references the
       *     sketch or modified feature used as a splitter.  The features
       *     property is a list of all resulting features.
       */
      
      /**
       * APIProperty: layer
       * {<OpenLayers.Layer.Vector>} The target layer with features to be split.
       *     Set at construction or after construction with <setLayer>.
       */
      layer: null,
      
      /**
       * Property: source
       * {<OpenLayers.Layer.Vector>} Optional source layer.  Any newly created
       *     or modified features from this layer will be used to split features
       *     on the target layer.  If not provided, a temporary sketch layer will
       *     be created.
       */
      source: null,
      
      /**
       * Property: sourceOptions
       * {Options} If a temporary sketch layer is created, these layer options
       *     will be applied.
       */
      sourceOptions: null,
  
      /**
       * APIProperty: tolerance
       * {Number} Distance between the calculated intersection and a vertex on
       *     the source geometry below which the existing vertex will be used
       *     for the split.  Default is null.
       */
      tolerance: null,
      
      /**
       * APIProperty: edge
       * {Boolean} Allow splits given intersection of edges only.  Default is
       *     true.  If false, a vertex on the source must be within the
       *     <tolerance> distance of the calculated intersection for a split
       *     to occur.
       */
      edge: true,
      
      /**
       * APIProperty: deferDelete
       * {Boolean} Instead of removing features from the layer, set feature
       *     states of split features to DELETE.  This assumes a save strategy
       *     or other component is in charge of removing features from the
       *     layer.  Default is false.  If false, split features will be
       *     immediately deleted from the layer.
       */
      deferDelete: false,
      
      /**
       * APIProperty: mutual
       * {Boolean} If source and target layers are the same, split source
       *     features and target features where they intersect.  Default is
       *     true.  If false, only target features will be split.
       */
      mutual: true,
      
      /**
       * APIProperty: targetFilter
       * {<OpenLayers.Filter>} Optional filter that will be evaluated
       *     to determine if a feature from the target layer is eligible for
       *     splitting.
       */
      targetFilter: null,
      
      /**
       * APIProperty: sourceFilter
       * {<OpenLayers.Filter>} Optional filter that will be evaluated
       *     to determine if a feature from the source layer is eligible for
       *     splitting.
       */
      sourceFilter: null,
      
      /**
       * Property: handler
       * {<OpenLayers.Handler.Path>} The temporary sketch handler created if
       *     no source layer is provided.
       */
      handler: null,
  
      /**
       * Constructor: OpenLayers.Control.Split
       * Creates a new split control. A control is constructed with a target
       *     layer and an optional source layer. While the control is active,
       *     creating new features or modifying existing features on the source
       *     layer will result in splitting any eligible features on the target
       *     layer.  If no source layer is provided, a temporary sketch layer will
       *     be created to create lines for splitting features on the target.
       *
       * Parameters:
       * options - {Object} An object containing all configuration properties for
       *     the control.
       *
       * Valid options:
       * layer - {<OpenLayers.Layer.Vector>} The target layer.  Features from this
       *     layer will be split by new or modified features on the source layer
       *     or temporary sketch layer.
       * source - {<OpenLayers.Layer.Vector>} Optional source layer.  If provided
       *     newly created features or modified features will be used to split
       *     features on the target layer.  If not provided, a temporary sketch
       *     layer will be created for drawing lines.
       * tolerance - {Number} Optional value for the distance between a source
       *     vertex and the calculated intersection below which the split will
       *     occur at the vertex.
       * edge - {Boolean} Allow splits given intersection of edges only.  Default
       *     is true.  If false, a vertex on the source must be within the
       *     <tolerance> distance of the calculated intersection for a split
       *     to occur.
       * mutual - {Boolean} If source and target are the same, split source
       *     features and target features where they intersect.  Default is
       *     true.  If false, only target features will be split.
       * targetFilter - {<OpenLayers.Filter>} Optional filter that will be evaluated
       *     to determine if a feature from the target layer is eligible for
       *     splitting.
       * sourceFilter - {<OpenLayers.Filter>} Optional filter that will be evaluated
       *     to determine if a feature from the target layer is eligible for
       *     splitting.
       */
      initialize: function(options) {
          OpenLayers.Control.prototype.initialize.apply(this, [options]);
          this.options = options || {}; // TODO: this could be done by the super
          
          // set the source layer if provided
          if(this.options.source) {
              this.setSource(this.options.source);
          }
      },
      
      /**
       * APIMethod: setSource
       * Set the source layer for edits layer.
       *
       * Parameters:
       * layer - {<OpenLayers.Layer.Vector>}  The new source layer layer.  If
       *     null, a temporary sketch layer will be created.
       */
      setSource: function(layer) {
          if(this.active) {
              this.deactivate();
              if(this.handler) {
                  this.handler.destroy();
                  delete this.handler;
              }
              this.source = layer;
              this.activate();
          } else {
              this.source = layer;
          }
      },
      
      /**
       * APIMethod: activate
       * Activate the control.  Activating the control registers listeners for
       *     editing related events so that during feature creation and
       *     modification, features in the target will be considered for
       *     splitting.
       */
      activate: function() {
          var activated = OpenLayers.Control.prototype.activate.call(this);
          if(activated) {
              if(!this.source) {
                  if(!this.handler) {
                      this.handler = new OpenLayers.Handler.Path(this,
                          {done: function(geometry) {
                              this.onSketchComplete({
                                  feature: new OpenLayers.Feature.Vector(geometry)
                              });
                          }},
                          {layerOptions: this.sourceOptions}
                      );
                  }
                  this.handler.activate();
              } else if(this.source.events) {
                  this.source.events.on({
                      sketchcomplete: this.onSketchComplete,
                      afterfeaturemodified: this.afterFeatureModified,
                      scope: this
                  });
              }
          }
          return activated;
      },
      
      /**
       * APIMethod: deactivate
       * Deactivate the control.  Deactivating the control unregisters listeners
       *     so feature editing may proceed without engaging the split agent.
       */
      deactivate: function() {
          var deactivated = OpenLayers.Control.prototype.deactivate.call(this);
          if(deactivated) {
              if(this.source && this.source.events) {
                  this.source.events.un({
                      sketchcomplete: this.onSketchComplete,
                      afterfeaturemodified: this.afterFeatureModified,
                      scope: this
                  });
              }
          }
          return deactivated;
      },
      
      /**
       * Method: onSketchComplete
       * Registered as a listener for the sketchcomplete event on the editable
       *     layer.
       *
       * Parameters:
       * event - {Object} The sketch complete event.
       *
       * Returns:
       * {Boolean} Stop the sketch from being added to the layer (it has been
       *     split).
       */
      onSketchComplete: function(event) {
          this.feature = null;
          return !this.considerSplit(event.feature);
      },
      
      /**
       * Method: afterFeatureModified
       * Registered as a listener for the afterfeaturemodified event on the
       *     editable layer.
       *
       * Parameters:
       * event - {Object} The after feature modified event.
       */
      afterFeatureModified: function(event) {
          if(event.modified) {
              var feature = event.feature;
              if (typeof feature.geometry.split === "function") {
                  this.feature = event.feature;
                  this.considerSplit(event.feature);
              }
          }
      },
      
      /**
       * Method: removeByGeometry
       * Remove a feature from a list based on the given geometry.
       *
       * Parameters:
       * features - {Array(<OpenLayers.Feature.Vector>)} A list of features.
       * geometry - {<OpenLayers.Geometry>} A geometry.
       */
      removeByGeometry: function(features, geometry) {
          for(var i=0, len=features.length; i<len; ++i) {
              if(features[i].geometry === geometry) {
                  features.splice(i, 1);
                  break;
              }
          }
      },
      
      /**
       * Method: isEligible
       * Test if a target feature is eligible for splitting.
       *
       * Parameters:
       * target - {<OpenLayers.Feature.Vector>} The target feature.
       *
       * Returns:
       * {Boolean} The target is eligible for splitting.
       */
      isEligible: function(target) {
          if (!target.geometry) {
              return false;
          } else {
              return (
                  target.state !== OpenLayers.State.DELETE
              ) && (
                  typeof target.geometry.split === "function"
              ) && (
                  this.feature !== target
              ) && (
                  !this.targetFilter ||
                  this.targetFilter.evaluate(target.attributes)
              );
          }
      },
  
      /**
       * Method: considerSplit
       * Decide whether or not to split target features with the supplied
       *     feature.  If <mutual> is true, both the source and target features
       *     will be split if eligible.
       *
       * Parameters:
       * feature - {<OpenLayers.Feature.Vector>} The newly created or modified
       *     feature.
       *
       * Returns:
       * {Boolean} The supplied feature was split (and destroyed).
       */
      considerSplit: function(feature) {
          var sourceSplit = false;
          var targetSplit = false;
          if(!this.sourceFilter ||
             this.sourceFilter.evaluate(feature.attributes)) {
              var features = this.layer && this.layer.features || [];
              var target, results, proceed;
              var additions = [], removals = [];
              var mutual = (this.layer === this.source) && this.mutual;
              var options = {
                  edge: this.edge,
                  tolerance: this.tolerance,
                  mutual: mutual
              };
              var sourceParts = [feature.geometry];
              var targetFeature, targetParts;
              var source, parts;
              for(var i=0, len=features.length; i<len; ++i) {
                  targetFeature = features[i];
                  if(this.isEligible(targetFeature)) {
                      targetParts = [targetFeature.geometry];
                      // work through source geoms - this array may change
                      for(var j=0; j<sourceParts.length; ++j) { 
                          source = sourceParts[j];
                          // work through target parts - this array may change
                          for(var k=0; k<targetParts.length; ++k) {
                              target = targetParts[k];
                              if(source.getBounds().intersectsBounds(target.getBounds())) {
                                  results = source.split(target, options);
                                  if(results) {
                                      proceed = this.events.triggerEvent(
                                          "beforesplit", {source: feature, target: targetFeature}
                                      );
                                      if(proceed !== false) {
                                          if(mutual) {
                                              parts = results[0];
                                              // handle parts that result from source splitting
                                              if(parts.length > 1) {
                                                  // splice in new source parts
                                                  parts.unshift(j, 1); // add args for splice below
                                                  Array.prototype.splice.apply(sourceParts, parts);
                                                  j += parts.length - 3;
                                              }
                                              results = results[1];
                                          }
                                          // handle parts that result from target splitting
                                          if(results.length > 1) {
                                              // splice in new target parts
                                              results.unshift(k, 1); // add args for splice below
                                              Array.prototype.splice.apply(targetParts, results);
                                              k += results.length - 3;
                                          }
                                      }
                                  }
                              }
                          }
                      }
                      if(targetParts && targetParts.length > 1) {
                          this.geomsToFeatures(targetFeature, targetParts);
                          this.events.triggerEvent("split", {
                              original: targetFeature,
                              features: targetParts
                          });
                          Array.prototype.push.apply(additions, targetParts);
                          removals.push(targetFeature);
                          targetSplit = true;
                      }
                  }
              }
              if(sourceParts && sourceParts.length > 1) {
                  this.geomsToFeatures(feature, sourceParts);
                  this.events.triggerEvent("split", {
                      original: feature,
                      features: sourceParts
                  });
                  Array.prototype.push.apply(additions, sourceParts);
                  removals.push(feature);
                  sourceSplit = true;
              }
              if(sourceSplit || targetSplit) {
                  // remove and add feature events are suppressed
                  // listen for split event on this control instead
                  if(this.deferDelete) {
                      // Set state instead of removing.  Take care to avoid
                      // setting delete for features that have not yet been
                      // inserted - those should be destroyed immediately.
                      var feat, destroys = [];
                      for(var i=0, len=removals.length; i<len; ++i) {
                          feat = removals[i];
                          if(feat.state === OpenLayers.State.INSERT) {
                              destroys.push(feat);
                          } else {
                              feat.state = OpenLayers.State.DELETE;
                              this.layer.drawFeature(feat);
                          }
                      }
                      this.layer.destroyFeatures(destroys, {silent: true});
                      for(var i=0, len=additions.length; i<len; ++i) {
                          additions[i].state = OpenLayers.State.INSERT;
                      }
                  } else {
                      this.layer.destroyFeatures(removals, {silent: true});
                  }
                  this.layer.addFeatures(additions, {silent: true});
                  this.events.triggerEvent("aftersplit", {
                      source: feature,
                      features: additions
                  });
              }
          }
          return sourceSplit;
      },
      
      /**
       * Method: geomsToFeatures
       * Create new features given a template feature and a list of geometries.
       *     The list of geometries is modified in place.  The result will be
       *     a list of new features.
       *
       * Parameters:
       * feature - {<OpenLayers.Feature.Vector>} The feature to be cloned.
       * geoms - {Array(<OpenLayers.Geometry>)} List of goemetries.  This will
       *     become a list of new features.
       */
      geomsToFeatures: function(feature, geoms) {
          var clone = feature.clone();
          delete clone.geometry;
          var newFeature;
          for(var i=0, len=geoms.length; i<len; ++i) {
              // turn results list from geoms to features
              newFeature = clone.clone();
              newFeature.geometry = geoms[i];
              newFeature.state = OpenLayers.State.INSERT;
              geoms[i] = newFeature;
          }
      },
      
      /**
       * Method: destroy
       * Clean up the control.
       */
      destroy: function() {
          if(this.active) {
              this.deactivate(); // TODO: this should be handled by the super
          }
          OpenLayers.Control.prototype.destroy.call(this);
      },
  
      CLASS_NAME: "OpenLayers.Control.Split"
  });
  