/*
 *  random_numbers.cpp
 *
 *  This file is part of NEST.
 *
 *  Copyright (C) 2004 The NEST Initiative
 *
 *  NEST 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.
 *
 *  NEST 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.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with NEST.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "config.h"
#include "dict.h"
#include "dictdatum.h"
#include "random_numbers.h"
#include "integerdatum.h"
#include "doubledatum.h"
#include "arraydatum.h"
#include "lockptrdatum_impl.h"
#include "tokenutils.h"
#include "sliexceptions.h"

#include "random_datums.h"
#include "knuthlfg.h"
#include "mt19937.h"
#include "gslrandomgen.h"

#include "binomial_randomdev.h"
#include "poisson_randomdev.h"
#include "normal_randomdev.h"
#include "exp_randomdev.h"
#include "gamma_randomdev.h"
#include "uniformint_randomdev.h"

#ifdef HAVE_GSL
#include "gsl_binomial_randomdev.h"
#endif

SLIType RandomNumbers::RngType;
SLIType RandomNumbers::RngFactoryType;
SLIType RandomNumbers::RdvType;
SLIType RandomNumbers::RdvFactoryType;


template class lockPTRDatum<librandom::RandomGen, &RandomNumbers::RngType>;
template class lockPTRDatum<librandom::RandomDev, &RandomNumbers::RdvType>;
template class lockPTRDatum<librandom::GenericRandomDevFactory, &RandomNumbers::RdvFactoryType>;


RandomNumbers::~RandomNumbers()
{
  RngType.deletetypename();
  RngFactoryType.deletetypename();

  RdvType.deletetypename();
  RdvFactoryType.deletetypename();
}

template <typename NumberGenerator>
void RandomNumbers::register_rng_(const std::string& name, DictionaryDatum& dict)
{
  Token rngfactory = new librandom::RngFactoryDatum(
                         new librandom::BuiltinRNGFactory<NumberGenerator>);
  dict->insert_move(Name(name), rngfactory);
}

template <typename DeviateGenerator>
void RandomNumbers::register_rdv_(const std::string& name, DictionaryDatum& dict)
{
  Token rdevfactory = new librandom::RdvFactoryDatum(
                          new librandom::RandomDevFactory<DeviateGenerator>);
  dict->insert_move(Name(name), rdevfactory);
}

void RandomNumbers::init(SLIInterpreter *i)
{
  RngType.settypename("rngtype");
  RngType.setdefaultaction(SLIInterpreter::datatypefunction);

  RngFactoryType.settypename("rngfactorytype");
  RngFactoryType.setdefaultaction(SLIInterpreter::datatypefunction);

  RdvType.settypename("rdvtype");
  RdvType.setdefaultaction(SLIInterpreter::datatypefunction);
  RdvFactoryType.settypename("rdvfactorytype");
  RdvFactoryType.setdefaultaction(SLIInterpreter::datatypefunction);

  // create random number generator type dictionary
  DictionaryDatum rngdict(new Dictionary());
  i->def("rngdict", rngdict);
  
  // add built-in rngs
  register_rng_<librandom::KnuthLFG>("knuthlfg", rngdict);
  register_rng_<librandom::MT19937>("MT19937", rngdict);

  // let GslRandomGen add all of the GSL rngs
  librandom::GslRandomGen::add_gsl_rngs(rngdict);

  // create random deviate generator dictionary
  DictionaryDatum rdvdict(new Dictionary());
  i->def("rdevdict", rdvdict);

  register_rdv_<librandom::BinomialRandomDev>("binomial", rdvdict);
  register_rdv_<librandom::PoissonRandomDev>("poisson", rdvdict);
  register_rdv_<librandom::NormalRandomDev>("normal", rdvdict);
  register_rdv_<librandom::ExpRandomDev>("exponential", rdvdict);
  register_rdv_<librandom::GammaRandomDev>("gamma", rdvdict);
  register_rdv_<librandom::UniformIntRandomDev>("uniformint", rdvdict);

#ifdef HAVE_GSL
  register_rdv_<librandom::GSL_BinomialRandomDev>("gsl_binomial", rdvdict);
#endif

  // create function
  i->createcommand("CreateRNG_gt_i", &createrngfunction);
  i->createcommand("CreateRDV_g_vf", &createrdvfunction);

  i->createcommand("SetStatus_v", &setstatus_vdfunction);
  i->createcommand("GetStatus_v",  &getstatus_vfunction);
  
  // access functions
  i->createcommand("seed_g_i",&seedfunction);
  i->createcommand("irand_g_i",&irandfunction);
  i->createcommand("drand_g",&drandfunction);

  i->createcommand("RandomArray_v_i", &randomarrayfunction);
  i->createcommand("Random_i", &randomfunction);
}

// see librandom.sli for SLI documentation
void RandomNumbers::CreateRNGFunction::execute(SLIInterpreter *i) const
{
  assert(i->OStack.load()>1);

  const long seed = getValue<long>(i->OStack.top());
  librandom::RngFactoryDatum factory 
    = getValue<librandom::RngFactoryDatum>(i->OStack.pick(1));  

  librandom::RngDatum rng( factory->create(seed) );

  i->OStack.pop(2);
  i->OStack.push(rng);
  i->EStack.pop();
}

// see librandom.sli for SLI documentation
void RandomNumbers::CreateRDVFunction::execute(SLIInterpreter *i) const
{
  assert(i->OStack.load()>1);

  librandom::RdvFactoryDatum factory 
    = getValue<librandom::RdvFactoryDatum>(i->OStack.top());
  librandom::RngDatum rng = getValue<librandom::RngDatum>(i->OStack.pick(1));

  librandom::RdvDatum rdv( factory->create(rng) );
			
  i->OStack.pop(2);
  i->OStack.push(rdv);
  i->EStack.pop();
}

// see librandom.sli for SLI documentation
void RandomNumbers::SetStatus_vdFunction::execute(SLIInterpreter *i) const
{
  assert(i->OStack.load() > 1);

  DictionaryDatum dict;
  librandom::RdvDatum rdv;

  try 
    {
      dict = getValue<DictionaryDatum>(i->OStack.top());
      rdv = getValue<librandom::RdvDatum>(i->OStack.pick(1));
    }
  catch ( TypeMismatch &e )
    {
      i->raiseerror("TypeMismatch");
      return;
    }

  try
    {
      rdv->set_status(dict);
      i->OStack.pop(2);
      i->EStack.pop();
    }
  catch(...)
    {
      /* This is not nice.  Before we improve on this,
	 we should unify the exception handling in SLI an the kernel,
	 so that the above set_status can throw a nest::KernelException,
	 which can be queried here with e.what().
	 HEP, 2004-08-05
      */
      i->raiseerror(i->BadErrorHandler);
      return;
    }
}

// see librandom.sli for SLI documentation
void RandomNumbers::GetStatus_vFunction::execute(SLIInterpreter *i) const
{
  
  assert(i->OStack.load() > 0);

  librandom::RdvDatum rdv;
  try
    {
      rdv = getValue<librandom::RdvDatum>(i->OStack.top());
    }
  catch ( TypeMismatch &e )
    {
      i->raiseerror("TypeMismatch");
      return;
    }
  
  try
    {
      DictionaryDatum dict(new Dictionary);
      assert(dict.valid());

      rdv->get_status(dict);

      i->OStack.pop();
      i->OStack.push(dict);
      i->EStack.pop();
    }
  catch(...)
    {
      /* This is not nice.  Before we improve on this,
	 we should unify the exception handling in SLI an the kernel,
	 so that the above set_status can throw a nest::KernelException,
	 which can be queried here with e.what().
	 HEP, 2004-08-05
      */
      i->raiseerror(i->BadErrorHandler);
      return;
    }
}

// see librandom.sli for SLI documentation
void RandomNumbers::SeedFunction::execute(SLIInterpreter *i) const 
{
  assert(i->OStack.load()>1);

  const long seed = getValue<long>(i->OStack.top());
  librandom::RngDatum   rng  = getValue<librandom::RngDatum>(i->OStack.pick(1));

  rng->seed(seed);

  i->OStack.pop(2);
  i->EStack.pop();
}

// see librandom.sli for SLI documentation
void RandomNumbers::IrandFunction::execute(SLIInterpreter *i) const 
{
  assert(i->OStack.load()>1);

  const long N = getValue<long>(i->OStack.top());
  librandom::RngDatum rng = getValue<librandom::RngDatum>(i->OStack.pick(1));

  const unsigned long r = rng->ulrand(N);

  i->OStack.pop(2);
  i->OStack.push(r);
  i->EStack.pop();
}

// see librandom.sli for SLI documentation
void RandomNumbers::DrandFunction::execute(SLIInterpreter *i) const 
{
  assert(i->OStack.load()>0);

  librandom::RngDatum rng = getValue<librandom::RngDatum>(i->OStack.top());

  const double r = rng->drand();

  i->OStack.pop();
  i->OStack.push(r);
  i->EStack.pop();
}


/* see librandom.sli for SLI documentation */
void RandomNumbers::RandomArrayFunction::execute(SLIInterpreter *i) const
{
  if( i->OStack.load() < 2 )
  {
    i->message(SLIInterpreter::M_ERROR, "RandomArray","Too few parameters supplied.");
    i->message(SLIInterpreter::M_ERROR, "RandomArray","Usage: rdv n RandomArray.");
    i->raiseerror(i->StackUnderflowError);
    return;
  }

  librandom::RdvDatum rdv = getValue<librandom::RdvDatum>(i->OStack.pick(1));
  const long n = getValue<long>(i->OStack.pick(0));

  TokenArray result;
  result.reserve(n);

  if ( rdv->has_uldev() )
    for( long j = 0; j < n ; ++j)
      result.push_back(rdv->uldev());
  else
    for( long j=0; j<n ; ++j)
      result.push_back( (*rdv)() );
  
  i->OStack.pop(2);
  i->OStack.push(ArrayDatum(result));
  i->EStack.pop();
}


void RandomNumbers::RandomFunction::execute(SLIInterpreter *i) const
{
  if( i->OStack.load()<1 )
  {
    i->raiseerror(i->StackUnderflowError);
    return;
  }

  librandom::RdvDatum rdv = getValue<librandom::RdvDatum>(i->OStack.top());
 
  i->OStack.pop(); 

  if ( rdv->has_uldev() )
    i->OStack.push( rdv->uldev() );
  else
    i->OStack.push( (*rdv)() );
  
  i->EStack.pop();
}