module Util

  # This class implements a necessary logic for ALPS strategy. See:
  # http://www.cs.york.ac.uk/rts/docs/GECCO_2006/docs/p815.pdf
  module AlpsIndividual

    # Set the AGE_GAP parameter of the ALPS. Required for AlpsIndividual.age_limits computation.
    # This is the multiplier of the age_limits serie.
    def AlpsIndividual.age_gap gap
      @@age_gap = gap
      @@limits = nil
    end

    # Set the aging scheme parameter. Required for AlpsIndividual.age_limits computation.   
    # Allowed values are: 
    #   :linear .. 1, 2, 3, 4, 5...
    #   :fibonacci .. 1, 2, 3, 5, 8, 13, 21...
    #   :polynomial .. 1, 2, 4, 9, 16, 25, 49...
    #   :exponential .. 1, 2, 4, 8, 16, 32, 64...
    #
    def AlpsIndividual.aging_scheme scheme
      @@aging_scheme = scheme
      @@limits = nil     
    end

    # Set the maximum number of layers (the requred length of the AlpsIndividual.age_limits serie).
    def AlpsIndividual.layers layers
      raise "AlpsIndividual: not enough layers, needed at least 2" if layers < 2
      @@layers = layers
      @@limits = nil     
    end

    # Get the age limit for each layer. The result is the AlpsIndividual.aging_scheme serie
    # of the AlpsIndividual.layers length, multiplied by AlpsIndividual.age_gap.
    def AlpsIndividual.age_limits
      return @@limits unless @@limits.nil?

      case @@aging_scheme 

        when :linear
          s = [ 1 ]
          s << s.last + 1 while s.size < @@layers 

        when :fibonacci
          s = [ 1, 2 ] # as defined in the original paper, there is a missing F1=1 member
          s << s.last + s[ s.size-2 ] while s.size < @@layers 

        when :polynomial
          s = []
          (1..@@layers).each { |i| s << i * i }

        when :exponential
          s = []
          (0...@@layers).each { |i| s << 2 ** i }

        else 
          raise "AlpsIndividual: a scheme '#{@@aging_scheme}' not supported"

      end

      return @@limits = s.map { |i| i * @@age_gap }
    end

    # The age of the individual. The newly created one has the age 0.
    # If the genome of the individual is based of the genome(s) of the parent(s), 
    # AlpsIndividual#parents method is to be used.
    #
    def age
      @age = 0 unless defined? @age
      @age
    end
    
    # Compute the AlpsIndividual#age of the individual from ages of it's parent(s).
    # If p1 and p2 are AlpsIndividual parents, the age will be:
    #   my.age = 1 + max( p1.age, p2.age )
    #
    def parents( p1, p2=nil )
      @age = 1 + ( ( p2.nil? or p1.age > p2.age ) ? p1.age : p2.age )
    end

    # Compute the index of the layer the individual belongs to.  
    # The result depends on AlpsIndividual.age_limits and AlpsIndividual#age.
    #
    def layer
      return nil unless defined? @@limits
      AlpsIndividual.age_limits.each_with_index do |max,i|
        return i if self.age <= max
      end
      return @@limits.size
    end

  end

end