/*

Copyright (C) 2003 John W. Eaton

This file is part of Octave.

Octave 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, or (at your option) any
later version.

Octave 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 Octave; see the file COPYING.  If not, write to the Free
Software Foundation, 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "f77-fcn.h"
#include "lo-error.h"
#include "oct-rand.h"
#include "oct-time.h"

// Possible distributions of random numbers.  This was handled with an
// enum, but unwind_protecting that doesn't work so well.
#define uniform_dist 1
#define normal_dist 2

// Current distribution of random numbers.
static int current_distribution = uniform_dist;

// Has the seed been set yet?
static bool initialized = false;

extern "C"
{
  F77_RET_T
  F77_FUNC (dgennor, DGENNOR) (const double&, const double&, double&);

  F77_RET_T
  F77_FUNC (dgenunf, DGENUNF) (const double&, const double&, double&);

  F77_RET_T
  F77_FUNC (setall, SETALL) (const int&, const int&);

  F77_RET_T
  F77_FUNC (getsd, GETSD) (int&, int&);

  F77_RET_T
  F77_FUNC (setsd, SETSD) (const int&, const int&);

  F77_RET_T
  F77_FUNC (setcgn, SETCGN) (const int&);
}

static int
force_to_fit_range (int i, int lo, int hi)
{
  assert (hi > lo && lo >= 0 && hi > lo);

  i = i > 0 ? i : -i;

  if (i < lo)
    i = lo;
  else if (i > hi)
    i = i % hi;

  return i;
}

// Make the random number generator give us a different sequence every
// time we start octave unless we specifically set the seed.  The
// technique used below will cycle monthly, but it it does seem to
// work ok to give fairly different seeds each time Octave starts.

static void
do_initialization (void)
{
  octave_localtime tm;
 
  int hour = tm.hour() + 1;
  int minute = tm.min() + 1;
  int second = tm.sec() + 1;

  int s0 = tm.mday() * hour * minute * second;
  int s1 = hour * minute * second;

  s0 = force_to_fit_range (s0, 1, 2147483563);
  s1 = force_to_fit_range (s1, 1, 2147483399);

  F77_FUNC (setall, SETALL) (s0, s1);

  initialized = true;
}

static inline void
maybe_initialize (void)
{
  if (! initialized)
    do_initialization ();
}

double
octave_rand::seed (void)
{
  maybe_initialize ();

  union d2i { double d; int i[2]; };
  union d2i u;
  F77_FUNC (getsd, GETSD) (u.i[0], u.i[1]);
  return u.d;
}

void
octave_rand::seed (double s)
{
  maybe_initialize ();

  union d2i { double d; int i[2]; };
  union d2i u;
  u.d = s;
  int i0 = force_to_fit_range (u.i[0], 1, 2147483563);
  int i1 = force_to_fit_range (u.i[1], 1, 2147483399);
  F77_FUNC (setsd, SETSD) (i0, i1);
}

std::string
octave_rand::distribution (void)
{
  maybe_initialize ();

  if (current_distribution == uniform_dist)
    return "uniform";
  else if (current_distribution == normal_dist)
    return "normal";
  else
    {
      abort ();
      return "";
    }
}

void
octave_rand::distribution (const std::string& d)
{
  if (d == "uniform")
    octave_rand::uniform_distribution ();
  else if (d == "normal")
    octave_rand::normal_distribution ();
  else
    (*current_liboctave_error_handler) ("rand: invalid distribution");
}

void
octave_rand::uniform_distribution (void)
{
  maybe_initialize ();

  current_distribution = uniform_dist;

  F77_FUNC (setcgn, SETCGN) (uniform_dist);
}

void
octave_rand::normal_distribution (void)
{
  maybe_initialize ();

  current_distribution = normal_dist;

  F77_FUNC (setcgn, SETCGN) (normal_dist);
}

double
octave_rand::scalar (void)
{
  maybe_initialize ();

  double retval = 0.0;

  switch (current_distribution)
    {
    case uniform_dist:
      F77_FUNC (dgenunf, DGENUNF) (0.0, 1.0, retval);
      break;

    case normal_dist:
      F77_FUNC (dgennor, DGENNOR) (0.0, 1.0, retval);
      break;

    default:
      abort ();
      break;
    }

  return retval;
}

#define MAKE_RAND_MAT(mat, nr, nc, f, F) \
  do \
    { \
      double val; \
      for (volatile int j = 0; j < nc; j++) \
	for (volatile int i = 0; i < nr; i++) \
	  { \
	    OCTAVE_QUIT; \
	    F77_FUNC (f, F) (0.0, 1.0, val); \
	    mat(i,j) = val; \
	  } \
    } \
  while (0)

Matrix
octave_rand::matrix (int n, int m)
{
  maybe_initialize ();

  Matrix retval;

  if (n >= 0 && m >= 0)
    {
      retval.resize (n, m);

      if (n > 0 && m > 0)
	{
	  switch (current_distribution)
	    {
	    case uniform_dist:
	      MAKE_RAND_MAT (retval, n, m, dgenunf, DGENUNF);
	      break;

	    case normal_dist:
	      MAKE_RAND_MAT (retval, n, m, dgennor, DGENNOR);
	      break;

	    default:
	      abort ();
	      break;
	    }
	}
    }
  else
    (*current_liboctave_error_handler) ("rand: invalid negative argument");

  return retval;
}

#define MAKE_RAND_ND_ARRAY(mat, len, f, F) \
  do \
    { \
      double val; \
      for (volatile int i = 0; i < len; i++) \
	{ \
	  OCTAVE_QUIT; \
	  F77_FUNC (f, F) (0.0, 1.0, val); \
	  mat(i) = val; \
	} \
    } \
  while (0)

NDArray
octave_rand::nd_array (const dim_vector& dims)
{
  maybe_initialize ();

  NDArray retval;

  if (! dims.all_zero ())
    {
      retval.resize (dims);

      int len = retval.length ();

      switch (current_distribution)
	{
	case uniform_dist:
	  MAKE_RAND_ND_ARRAY (retval, len, dgenunf, DGENUNF);
	  break;

	case normal_dist:
	  MAKE_RAND_ND_ARRAY (retval, len, dgennor, DGENNOR);
	  break;

	default:
	  abort ();
	  break;
	}
    }

  return retval;
}

#define MAKE_RAND_ARRAY(array, n, f, F) \
  do \
    { \
      double val; \
      for (volatile int i = 0; i < n; i++) \
	{ \
	  OCTAVE_QUIT; \
	  F77_FUNC (f, F) (0.0, 1.0, val); \
	  array(i) = val; \
	} \
    } \
  while (0)

Array<double>
octave_rand::vector (int n)
{
  maybe_initialize ();

  Array<double> retval;

  if (n > 0)
    {
      retval.resize (n);

      switch (current_distribution)
	{
	case uniform_dist:
	  MAKE_RAND_ARRAY (retval, n, dgenunf, DGENUNF);
	  break;

	case normal_dist:
	  MAKE_RAND_ARRAY (retval, n, dgennor, DGENNOR);
	  break;

	default:
	  abort ();
	  break;
	}
    }
  else if (n < 0)
    (*current_liboctave_error_handler) ("rand: invalid negative argument");

  return retval;
}

/*
;;; Local Variables: ***
;;; mode: C++ ***
;;; End: ***
*/
