/*  _________________________________________________________________________
 *
 *  Coliny: A Library of COLIN optimizers
 *  Copyright (c) 2003, Sandia National Laboratories.
 *  This software is distributed under the GNU Lesser General Public License.
 *  For more information, see the README.html file in the top Coliny directory.
 *  _________________________________________________________________________
 */

/**
 * \file DomainOpsIntArray.h
 *
 * Defines the coliny::DomainOpsIntArray class.
 */

#ifndef coliny_DomainIntOpsArray_h
#define coliny_DomainIntOpsArray_h

#include <acro_config.h>
#include <utilib/_math.h>
#include <utilib/Uniform.h>
#include <colin/BoundTypeArray.h>
#include <coliny/DomainOpsArray.h>

#define INT_ARRAY_MUTATION_UNIFORM  1
#define INT_ARRAY_MUTATION_INTERVAL 2

namespace coliny {

using utilib::ParameterSet;

template <class InfoT>
class DomainOpsIntArray : public DomainOpsArray<int, InfoT>
{
public:

  ///
  typedef typename DomainOpsArray<int,InfoT>::info_t  info_t;

  ///
  typedef typename DomainOpsArray<int,InfoT>::point_t point_t;

  ///
  DomainOpsIntArray();

  ///
  void reset();

  ///
  template <class ProblemT>
  void initialize(ProblemT& problem, unsigned int popsize_,double xover_rate, double m_rate)
	{
	DomainOpsArray<int,InfoT>::initialize( problem.num_int_params(), 
						popsize_, xover_rate, m_rate);
        if ((problem.num_int_params() > 0) && (problem.enforcing_bounds())) {
	   problem.get_int_bounds(lower,upper);
	   lbound_type.resize(lower.size());
	   ubound_type.resize(lower.size());
	   range.resize(lower.size());
	   for (unsigned int i=0; i<lower.size(); i++) {
	     lbound_type[i] = problem.int_lower_bound_type(i);
	     ubound_type[i] = problem.int_upper_bound_type(i);
	     range[i] = upper[i]-lower[i];
             }
           }
	}

  ///
  void initialize_point(point_t& point, info_t& info)
        { point.resize(lower.size()); }

  ///
  void randomize(point_t& point, InfoT& info)
	{
	for (unsigned int j=0; j<this->nvars; j++)
          point[j] = utilib::Discretize(this->rnd(),lower[j],upper[j]);
	}

  ///
  point_t lower;

  ///
  point_t upper;

  ///
  point_t range;

  ///
  colin::BoundTypeArray lbound_type;

  ///
  colin::BoundTypeArray ubound_type;

protected:

  ///
  int mutation_range;

  ///
  void mutate_value(int i, int& val, info_t& info);

};



template <class InfoT>
DomainOpsIntArray<InfoT>::DomainOpsIntArray()
{
this->crossover_blocksize=1;
ParameterSet::create_categorized_parameter("intarray_xover_blocksize",
	this->crossover_blocksize,
        "<int>","1",
        "Block size used with array-based two-point and uniform crossover",
	"Crossover");

this->crossover_str="twopoint";
ParameterSet::create_categorized_parameter("intarray_xover_type",
	this->crossover_str,
        "<string>","twopoint",
        "Crossover type\n\
\t  onepoint      - standard one-point mutation\n\
\t  twopoint      - standard two-point mutation\n\
\t  uniform       - standard uniform mutation",
	"Crossover");

this->mutation_str = "uniform";
ParameterSet::create_categorized_parameter("intarray_mutation_type",
	this->mutation_str,
        "<string>","uniform",
        "Integer mutation type\n\
\t  uniform  - replace the value with a uniformly random variable\n\
\t  interval - replace the value with a uniform value in a local interval",
	"Mutation");

this->mutation_range=1;
ParameterSet::create_categorized_parameter("intarray_mutation_range",
	this->mutation_range,
        "<int>","1",
        "Range of mutation used for 'interval' mutation",
	"Mutation");

this->mutate_independently=false;
ParameterSet::create_categorized_parameter("intarray_mutate_independently",
	this->mutate_independently,
        "<bool>","false",
        "If true, then only mutate a single dimension.  Note that if this\n\
\tvalue is true, then a single dimension is always mutated, so the\n\
\tmutation allele rate is ignored.",
	"Mutation");

this->mutation_allele_rate=1.0;
ParameterSet::create_categorized_parameter("intarray_mutation_allele_rate",
	this->mutation_allele_rate,
        "<double>","1.0",
        "The probability that any given dimension of the intarray is mutated\n\tgiven that the individual is mutated",
	"Mutation");
}



template <class InfoT>
void DomainOpsIntArray<InfoT>::reset()
{
DomainOpsArray<int,InfoT>::reset();

if ((this->mutation_str == "uniform") || (this->mutation_str == "offset_uniform")) {
   this->mutation_type = INT_ARRAY_MUTATION_UNIFORM;
   this->mutation_allele_rate =
      (this->mutation_allele_rate < 0.0 ? sqrt(M_E/(double)this->nvars)/(double)this->popsize :
        this->mutation_allele_rate);
   }

else if ((this->mutation_str == "interval") || (this->mutation_str == "replace_uniform")) {
   this->mutation_type = INT_ARRAY_MUTATION_INTERVAL;
   this->mutation_allele_rate =
      (this->mutation_allele_rate < 0.0 ? sqrt(M_E/(double)this->nvars)/(double)this->popsize :
        this->mutation_allele_rate);
   }

else 
   EXCEPTION_MNGR(std::runtime_error,"DomainOpsIntArray::reset - bad mutation type: \"" << this->mutation_str << "\".\n\t\tValid types are uniform and interval\n");

if (this->crossover_str == "none")
   this->crossover_type = ARRAY_XOVER_NONE;
else if (this->crossover_str == "twopoint")
   this->crossover_type = ARRAY_XOVER_TWOPOINT;
else if (this->crossover_str == "uniform")
   this->crossover_type = ARRAY_XOVER_UNIFORM;
else
   EXCEPTION_MNGR(std::runtime_error, "DomainOpsIntArray::reset -- bad xover type: \"" << this->crossover_str << "\".\n\t\tValid types are twopoint and uniform\n");
}


template <class InfoT>
void DomainOpsIntArray<InfoT>::mutate_value(int i, int& value, info_t& info)
{
switch (this->mutation_type) {
  case INT_ARRAY_MUTATION_INTERVAL:
                value = utilib::Discretize(this->rnd(),lower[i],upper[i]);
		break;

  case INT_ARRAY_MUTATION_UNIFORM:
                int tmp = utilib::Discretize(this->rnd(),
					std::max(lower[i],value-mutation_range),
					std::min(upper[i]-1,value+mutation_range-1));
		value = (tmp < value ? tmp : tmp+1);
		break;
  };
if (value > upper[i]) {
   if (ubound_type[i] == colin::hard_bound) value = upper[i];
   else if (ubound_type[i] == colin::periodic_bound)
     while (value > upper[i]) value -= range[i];
   }
if (value < lower[i]) {
   if (lbound_type[i] == colin::hard_bound) value = lower[i];
   else if (lbound_type[i] == colin::periodic_bound)
     while (value < lower[i]) value += range[i];
   }
}

}

#endif