/*  $Id$

    Part of SWI-Prolog

    Author:        L.Damas, V.S.Costa, Jan Wielemaker
    E-mail:        wielemak@science.uva.nl
    WWW:           http://www.swi-prolog.org
    Copyright (C): Universidade do Porto, University of Amsterdam

    Original code create for YAP under Artistic license.  As this
    license is compatible (but less restrictive than) the SWI-Prolog
    license we keep the Artistic license.
*/


/*************************************************************************
*									 *
*	 YAP Prolog 							 *
*									 *
*	Yap Prolog was developed at NCCUP - Universidade do Porto	 *
*									 *
* Copyright L.Damas, V.S.Costa and Universidade do Porto 1985-1997	 *
*									 *
**************************************************************************
*									 *
* File:		random.c						 *
* Last rev:								 *
* mods:									 *
* comments:	Random number generation                                 *
*									 *
*************************************************************************/

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Ported to SWI-Prolog by Jan Wielemaker

To compile:

	plld -o random -shared -fpic random.c
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#include <SWI-Prolog.h>
#include <math.h>
#include <limits.h>

static functor_t FUNCTOR_rand3;

static short a1 = 27314, b1 = 9213, c1 = 17773;

static int
type_error(term_t actual, const char *expected)
{ term_t ex;

  if ( (ex = PL_new_term_ref()) &&
       PL_unify_term(ex,
		     PL_FUNCTOR_CHARS, "error", 2,
		       PL_FUNCTOR_CHARS, "type_error", 2,
		         PL_CHARS, expected,
		         PL_TERM, actual,
		       PL_VARIABLE) )
    return PL_raise_exception(ex);

  return FALSE;
}


static int
domain_error(term_t actual, const char *expected)
{ term_t ex;

  if ( (ex = PL_new_term_ref()) &&
       PL_unify_term(ex,
		     PL_FUNCTOR_CHARS, "error", 2,
		       PL_FUNCTOR_CHARS, "domain_error", 2,
		         PL_CHARS, expected,
		         PL_TERM, actual,
		       PL_VARIABLE) )
    return PL_raise_exception(ex);

  return FALSE;
}


static int
get_short_ex(term_t t, short *p)
{ long v;

  if ( !PL_get_long(t, &v) )
    return type_error(t, "integer");
  if ( v < SHRT_MIN || v > SHRT_MAX )
    return domain_error(t, "short integer");

  *p = (short)v;

  return TRUE;
}


static int
get_short_arg_ex(int a, term_t state, short *p)
{ term_t arg = PL_new_term_ref();

  _PL_get_arg(a, state, arg);
  return get_short_ex(arg, p);
}


static foreign_t
p_random(term_t rnd)
{
  double fli;
  long int t1, t2, t3;

  t1 = (a1 * 171) % 30269;
  t2 = (b1 * 172) % 30307;
  t3 = (c1 * 170) % 30323;
  fli = (t1/30269.0) + (t2/30307.0) + (t3/30323.0);
  a1 = (short)t1;
  b1 = (short)t2;
  c1 = (short)t3;

  return PL_unify_float(rnd, fli-(int)(fli));
}

static foreign_t
p_setrand(term_t state)
{ if ( !PL_is_functor(state, FUNCTOR_rand3) )
    return type_error(state, "rand_state");

  if ( !get_short_arg_ex(1, state, &a1) ||
       !get_short_arg_ex(2, state, &b1) ||
       !get_short_arg_ex(3, state, &c1) )
    return FALSE;

  return TRUE;
}

static foreign_t
p_getrand(term_t state)
{ return PL_unify_term(state,
		       PL_FUNCTOR, FUNCTOR_rand3,
		         PL_INTEGER, a1,
		         PL_INTEGER, b1,
		         PL_INTEGER, c1);
}


install_t
install_random()
{ FUNCTOR_rand3 = PL_new_functor(PL_new_atom("rand"), 3);

  PL_register_foreign("random",  1, p_random, 0);
  PL_register_foreign("setrand", 1, p_setrand, 0);
  PL_register_foreign("getrand", 1, p_getrand, 0);
}