require 'lib/individual'
require 'lib/pareto'
require 'lib/alps_individual'

module Util

  # This is the universally configurable subclass of the Individual class, for the use with the WorkPipes evaluator.
  # The user can specify multiple objectives and the kind of their optimisation, describe the item names and types 
  # of the evaluator's output, define the stopping condition, etc.
  # All the configuration is done via ConfigYaml class using the yaml file, without the need of writing domain-specific
  # class for each task. 
  #
  class PipedIndividual < Individual
    include AlpsIndividual

    PipedIndSchema = Struct.new( 'PipedIndSchema', :symb, :conversion )
    @@phenotype_mark = ''
    @@batch_mark = nil

    # Create the new phenotype, based on the genotype, using the mapper.
    def initialize( mapper, genotype )
      super
      @phenotype += @@phenotype_mark unless @phenotype.nil?
    end

    # Take the result of the phenotype evaluation and process it. The text row argument consists of one or more values, 
    # separated by the space. Meaning of these values and their type conversions must be specified by the
    # PipedIndividual.pipe_output static method, before calling parse=.
    #
    # For example:
    #   individual.row = '1243.32 42'  # suppose the pipe returns 2 values per one individual's evaluation
    #
    def parse= row
      items = row.gsub(/^\s+/,'').gsub(/\s+$/,'').split( /\s+/ )
      raise "PipedIndividual: parse= expecting #{@@schema.size} items, got #{items.size}" unless @@schema.size == items.size

      items.each_with_index do |item,index|
        value = item.send( @@schema[index].conversion )
        send( "#{@@schema[index].symb}=", value )
      end
    end

    # This method tests the individual for the meeting of stopping condition. If all conditions are met (ie. 
    # in the case of the 'winner' individual), the stopping_condition returns true.
    # Conditions have to be set by the PipedIndividual.thresholds static method.
    def stopping_condition
      @@thresh_values.each_pair do |symb,value|
        max = @@thresh_over.fetch( symb, nil )

        raise "PipedIndividual: optimisation direction not known for the objective '#{symb}'" if max.nil?
        return false if max ? ( send(symb) < value ) : ( send(symb) > value )
      end

      true
    end
   
    # Specify the schema for the parse= method (see). The outputs argument is to be the array of hashes describing 
    # each item of the pipe output, in the correct order. Each hash contains only one key-value pair. The hash key 
    # represents the name (symbol) of the attribute (usually the objective), the value is the conversion method for
    # parsing the text to the correct numeric type.
    # 
    # For example:
    #   schema = []
    #   schema << {:amount => 'to_f'}
    #   schema << {:score => 'to_i'}
    #   PipedIndividual.pipe_output schema
    #
    #   individual.row = '1243.32 42'  # suppose the pipe returns 2 values per one individual's evaluation
    #   individual.amount  # >> 1243.32
    #   individual.score   # >> 42
    #
    def PipedIndividual.pipe_output outputs
      
      @@schema = []     

      outputs.each do |item|
        item.each_pair do |sym,conv|

          attr_accessor sym unless method_defined? sym

          @@schema << PipedIndSchema.new( sym, conv )
        end
      end

    end

    # Report symbols (meaning of the output items), previously entered by PipedIndividual.pipe_output call.
    def PipedIndividual.pipe_schema
      @@schema.map { |item| item.symb }
    end

    # Shorthand for the PipedIndividual.pareto_core( Moea::Pareto, par )
    def PipedIndividual.pareto par
      PipedIndividual.pareto_core( Moea::Pareto, par )
    end

    # Shorthand for the PipedIndividual.pareto_core( Moea::WeakPareto, par )
    def PipedIndividual.weak_pareto par
      PipedIndividual.pareto_core( Moea::WeakPareto, par )
    end

    # Describe the optimisation objectives for the Pareto module (see).
    # The outputs argument is to be the hash describing each objective. 
    # Each key-value pair represents one objective. The hash key is the name (symbol) of the objective, 
    # the value is the 'direction' of the optimisation, ie. either 'minimize' or 'maximize'
    # The klass is either Moea::Pareto or Moea::WeakPareto, specifying the strong or weak pareto definition.
    #
    #   objectives = {} 
    #   objectives[ :amount ] = 'minimize'
    #   objectives[ :score ] = 'maximize'
    #   PipedIndividual.pareto objectives
    # 
    def PipedIndividual.pareto_core( klass, par )
      include klass     
      Moea::Pareto.clear_objectives PipedIndividual
      @@thresh_over = {}

      par.each_pair do |sym,dir|
        attr_accessor sym unless method_defined? sym

        case dir.to_s
          when 'maximize'
            Moea::Pareto.maximize( PipedIndividual, sym )
            @@thresh_over[ sym ] = true
          when 'minimize'
            Moea::Pareto.minimize( PipedIndividual, sym )
            @@thresh_over[ sym ] = false
          else
            raise "PipedIndividual:wrong objective direction '#{dir}' for objective '#{sym}'" 
        end

      end
    end

    # Return true if the symbol would be Pareto-maximized
    def PipedIndividual.symbol_maximized? symbol
      result = @@thresh_over.fetch( symbol, nil )
      raise "PipedIndividual: symbol '#{symbol}' not known" if result.nil?
      result
    end

    # Set the optional text separating each phenotype in the pipe's input, if needed by the pipe process. 
    # This text is attached to the end of :phenotype attribute, defaulting to ''.
    def PipedIndividual.mark_phenotype mark
      @@phenotype_mark = mark
    end

    # Set the optional text separating each WorkPipes#run call in the pipe's input, if needed by the pipe process. 
    # This text is retrieved by the WorkPipes's instance using PipedIndividual#batch_mark method, defaulting to ''.
    def PipedIndividual.mark_batch mark
      @@batch_mark = mark
    end

    # Return the text separating the batch of pipe inputs for one WorkPipes#run call. This text is set by the
    # PipedIndividual.mark_batch static method.
    def PipedIndividual.batch_mark
      @@batch_mark
    end
   
    # Specify the components of the stopping_condition method (see). The thresh argument is the hash describing 
    # each attribute of the stopping expression. The hash key represents the name (symbol) of the objective, 
    # the value is the threshold value used for comparision.
    # Note the 'direction' of the optimisation has to be set by the PipedIndividual.pareto_core static method. 
    # 
    # For example:
    #   objectives = {} 
    #   objectives[ :amount ] = 'minimize'
    #   objectives[ :score ] = 'maximize'
    #   PipedIndividual.pareto objectives
    # 
    #   condition = {}
    #   condition[ :amount ] = 5435.34
    #   condition[ :score ] = 61
    #   PipedIndividual.thresholds condition
    #   
    #   individual.amount = 8430.2
    #   individual.score = 144
    #   individual.stopping_condition # >> false (individual.amount <= 5435.34 and individual.score > 61)
    #
    def PipedIndividual.thresholds thresh
      @@thresh_values = thresh
    end
    
  end # class

end # module