module Operator

# Single point GE crossover. It assumes two parents has the form of Arrays. 
# First, the cutting points in both parents are randomly selected, 
# then the parts after these points are swapped, forming two new offsprings.
#
# The positions of possible cutting points can be flexibly configured
# (which may be important for complex Mappers using more than one genome value per codon).
# Production of empty offsprings can be allowed or prohibited as well by the crossover's configuration.
# The cutting point positions in both parents can be either fixed or independent.
#
class CrossoverRipple

  # Create a new crossover facility with the default attribute values.
  def initialize
    @random = Kernel
    @margin = 0
    @step = 1
    @fixed = false
    @tolerance = true
  end

  # The source of randomness, used for calling "random.rand( limit )", defaulting to 'Kernel' class.
  attr_accessor :random

  # The minimal number of genome vector values kept from the beginning and ending of the parent vector, defaulting to 0.
  attr_accessor :margin

  # The number of genome vector values between tho possible cutting points, defaulting to 1.
  attr_accessor :step

  # Whether the CrossoverRipple uses the same position (Array index) for both parents' cutting points, defaulting to false.
  attr_accessor :fixed

  # Whether the empty array is allowed as the valid offspring (if set to false, the exception is raised), defaulting to true.
  attr_accessor :tolerance

  # Take parent1, parent2 arguments and produce [offspring1, offspring2].
  # For instance:
  # 
  #   parent1 = [ 1,  2,  3,  4,  5,  6,  7,  8,  9,  10 ]
  #   parent2 = [ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 ]
  #   xover = CrossoverRipple.new
  # 
  # possible cutting points with default setting (denoted by '|' symbol):
  # 
  #   parent1 = [ | 1  |  2 |  3 |  4 |  5 |  6 |  7 |  8 |  9 | 10 | ]
  #   parent2 = [ | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ]
  # 
  # after xover.margin = 3, the allowed cutting points are:
  #   parent1 = [ 1,   2,   3 |  4 |  5 |  6 |  7 |  8,  9, 10 ]
  #   parent2 = [ 11, 12,  13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21, 22, 23 ]
  #
  # after xover.step = 2, the allowed cutting points are:
  #   parent1 = [ 1,   2,   3 |  4,  5 |  6,  7 |  8,  9, 10 ]
  #   parent2 = [ 11, 12,  13 | 14, 15 | 16, 17 | 18,  19 | 20, 21, 22, 23 ]
  #
  # now producing the offsprings:
  #   offspring1, offspring2 = xover.crossover( parent1, parent2 )
  # offspring1 is [ 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23 ] 
  # offspring2 is [ 11, 12, 13, 14, 15, 8, 9, 10 ]
  #
  # This is stochastic process, thus repeating: 
  #   offspring1, offspring2 = xover.crossover( parent1, parent2 )
  # produces:  
  # offspring1 : [ 1, 2, 3, 18, 19, 20, 21, 22, 23 ]
  # offspring2 : [ 11, 12, 13, 14, 15, 16, 17, 4, 5, 6, 7, 8, 9, 10 ]
  # 
  def crossover( parent1, parent2, dummy1=nil, dummy2=nil )
    pts1 = []
    @margin.step( parent1.size - @margin, @step ) { |i| pts1.push i }
    pts2 = []
    @margin.step( parent2.size - @margin, @step ) { |i| pts2.push i }

    if pts1.empty? or pts2.empty? 
      return parent1.clone, parent2.clone if @tolerance #fallback 
      raise "CrossoverRipple: operand(s) too short"
    end
  
    if @fixed 
      if parent1.size < parent2.size
        pt1 = pt2 = pts1.at( @random.rand(pts1.size) )
      else
        pt1 = pt2 = pts2.at( @random.rand(pts2.size) )
      end
    else
      pt1 = pts1.at @random.rand(pts1.size)
      pt2 = pts2.at @random.rand(pts2.size)
    end

    offs1 = parent1[0...pt1].clone
    offs1 = offs1.concat parent2[pt2...parent2.size]
    offs2 = parent2[0...pt2].clone   
    offs2 = offs2.concat parent1[pt1...parent1.size]
    return offs1, offs2
  end

end

end # Operator