/**
 * Declaration of Rule-sets used by GARP
 * 
 * @file   ruleset.cpp
 * @author Ricardo Scachetti Pereira (rpereira@ku.edu)
 * @date   2004-04-02
 * $Id: ruleset.cpp 5573 2012-07-13 14:25:27Z dbolgheroni $
 * 
 * LICENSE INFORMATION 
 * 
 * Copyright(c), The Center for Research, University of Kansas, 
 *                 2385 Irving Hill Road, Lawrence, KS 66044-4755, USA.
 * Copyright(c), David R.B. Stockwell of Symbiotik Pty. Ltd.
 * Copyright(c), CRIA - Centro de Referencia em Informacao Ambiental
 *
 * http://www.nhm.ku.edu
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details:
 * 
 * http://www.gnu.org/copyleft/gpl.html
 * 
 * This is an implementation of the GARP algorithm first developed 
 * by David Stockwell
 * 
 */


#include <openmodeller/Random.hh>
#include <math.h>
#include <string.h>

#include <openmodeller/Log.hh>

#include "ruleset.hh"
#include "rules_base.hh"


/****************************************************************/
/****************** GarpRuleSet class ***************************/
/****************************************************************/


/****************************************************************/
/****************** Constructor *********************************/

GarpRuleSet::GarpRuleSet(int size)
{
  _size = size;
  _rules = new GarpRule*[size];
  _numRules = 0;
  memset(_rules, 0, size * sizeof(GarpRule*));
}


/****************************************************************/
/****************** Destructor **********************************/

GarpRuleSet::~GarpRuleSet()
{
  clear();
  delete [] _rules;
}

/****************************************************************/
/****************** size ****************************************/

int GarpRuleSet::size()
{ 
  return _size; 
}

/****************************************************************/
/****************** numRules ************************************/

int GarpRuleSet::numRules()
{ 
  return _numRules; 
}

/****************************************************************/
/****************** clear ***************************************/

void GarpRuleSet::clear()
{ 
  trim(0); 
}

/****************************************************************/
/****************** trim ****************************************/

void GarpRuleSet::trim(int rules)
{
  int i;

  if (rules >= _numRules)
    // there are less rules than specified: nothing to do
    return;

  for (i = rules; i < _size; i++)
  {
    if (_rules[i]) 
      {
        delete _rules[i];
        _rules[i] = NULL;
      }
  }

  _numRules = rules;
}

/****************************************************************/
/****************** filter **************************************/

void GarpRuleSet::filter(PerfIndex index, double threshold)
{
  int i, j;
  
  i = 0;
  while (i < _numRules)
    {
      //printf("%3d] Performance: %+8.4f (threshold=%+8.4f) - ", 
      //	    i, _rules[i]->getPerformance(index), threshold);

      if (_rules[i]->getPerformance(index) < threshold)
	{
	  //printf("deleting\n");

	  // this rule has not enough performance
	  // delete rule 
	  delete _rules[i];
	  
	  // and shift rules up
	  for (j = i; j < _numRules - 1; j++)
	    _rules[j] = _rules[j + 1];
	  
	  // remove the duplicated reference to the last rule
	  _rules[_numRules - 1] = NULL;
	  
	  // update the number of rules in this set
	  // no need to increment <i> because the rules were shifted up one position
	  _numRules--;
	}
      else
	{
	  //printf("keeping\n");

	  // this rule passed the test
	  // go to the next one
	  i++;
	}
    }
}

/****************************************************************/
/****************** insert **************************************/

int GarpRuleSet::insert(PerfIndex perfIndex, GarpRule * rule)
{
  double newRulePerformance;
  int i, j;

  // insert rule and keep set sorted by performance index specified (_performance[perfIndex])
  // find place where rule should be inserted
  newRulePerformance = rule->getPerformance(perfIndex);
  for (i = 0; i < _numRules; i++)
  { 
    /*
    printf("Perfs[%3d/%3d]: (%+8.4f > %+8.4f)? %2d\n", i, _numRules, 
	   newRulePerformance, _rules[i]->getPerformance(perfIndex),
	   (newRulePerformance > _rules[i]->getPerformance(perfIndex)));
    */

    if (newRulePerformance > _rules[i]->getPerformance(perfIndex))
      break;
  }

  // <i> has the index where new rule should be inserted
  // move remaining rules one position down
  // and insert new rule at index <i>
  for (j = _numRules - 1; j >= i; j--)
  { _rules[j + 1] = _rules[j]; }

  _rules[i] = rule;

  _numRules++;

  return i;
}

/****************************************************************/
/****************** get *****************************************/

GarpRule * GarpRuleSet::get(int index)
{ 
  if (index >= 0 || index < _numRules)
    return _rules[index];
  else
    return NULL;
}

/****************************************************************/
/****************** replace *************************************/

int GarpRuleSet::replace(int index, GarpRule * rule)
{
  if (!rule || index < 0 || index >= _numRules) 
    return 0;

  delete _rules[index];
  _rules[index] = rule;
  return 1;
}

/****************************************************************/
/****************** remove **************************************/

int GarpRuleSet::remove(int index)
{
  if (index < 0 || index >= _numRules) 
    return 0;

  delete _rules[index];

  int i;
  for (i = index; i < _numRules - 1; i++)
    _rules[i] = _rules[i + 1];

  _rules[--_numRules] = NULL;

  return 1;
}

/****************************************************************/
/****************** add *****************************************/

int GarpRuleSet::add(GarpRule * rule)
{
  if ( rule ) {

    if ( _numRules < _size ) {

      _rules[_numRules++] = rule;
      return _numRules;
    }
    else {
      // "Cannot add rule. Ruleset is full"
      return 0;
    }
  }
  else {
    // Cannot add null rule
    return 0;
  }
}

/****************************************************************/
/****************** findSimilar *********************************/

int GarpRuleSet::findSimilar(GarpRule * rule)
{
  int i;
  for ( i = 0; i < _numRules; i++ ) {

    if ( _rules[i]->similar(rule) ) { 

      return i; 
    }
  }

  return -1;
}

/****************************************************************/
/****************** getValue ************************************/


Scalar GarpRuleSet::getValue(const Sample& x) const
{
  int i;

  for ( i = 0; i < _numRules; i++ ) {

    if (_rules[i]->applies(x)) {

      //return i / (double) _numRules;
      return _rules[i]->getPrediction();
    }
  }

  return 0.0;
}

/****************************************************************/
/*************** performanceSummary *****************************/

void GarpRuleSet::performanceSummary(PerfIndex perfIndex, 
				     double * best, 
				     double * worst, 
				     double * average)
{
  int i;
  double performance;

  *worst = *best = *average = 0.0;

  if (!_numRules)
    return;

  for (i = 0; i < _numRules; i++)
    {
      performance = _rules[i]->getPerformance(perfIndex);
      
      if (performance < *worst || !i) *worst = performance;
      if (performance > *best  || !i) *best  = performance;
      *average += performance;
    }

  *average /= _numRules;
}

/****************************************************************/
/****************** log *****************************************/

void GarpRuleSet::log() const
{
  for ( int i = 0; i < _numRules; i++ )
    {
      Log::instance()->info( "%2d] ", i );
      _rules[i]->log();
    }
}

// ==============================================================

// gather rule set statistics
void GarpRuleSet::gatherRuleSetStats(int gen)
{
  char type='0'; //initialise to some invalid value

  printf("%4d]", gen);

  for (int i = 0; i < 3; i++)
  {
    switch (i)
    {
      case 0: type ='d'; break;
      case 1: type ='!'; break;
      case 2: type ='r'; break;
	//case 3: type ='a'; break;
    }

    double max = -10000;
    double sum = 0;
    int ct = 0;
    int pres = 0;
    for (int j = 0; j < _numRules; j++)
    {
      GarpRule * rule = _rules[j];
      if (rule->type() == type)
      {
        ct++;
        sum += rule->getPerformance(PerfUtil);
	pres += (int) rule->getPrediction();
	if (max < rule->getPerformance(PerfUtil))
	  max = rule->getPerformance(PerfUtil);
      }
    }

    if (max == -10000)
      max = 0;

    printf("%c %2d %+7.2f %+7.2f %2d|", type, ct, max, sum / ct, pres);
  }

  printf("\n");
}