package Math::Random::MTwist;

use 5.010_000;  # see README
use strict;
use warnings;

use Exporter;
use Time::HiRes;  # for timeseed()
use XSLoader;

use constant {
  MT_TIMESEED => \0,
  MT_FASTSEED => \0,
  MT_GOODSEED => \0,
  MT_BESTSEED => \0,
};

our $VERSION = '0.08';

our @ISA = 'Exporter';
our @EXPORT = qw(MT_TIMESEED MT_FASTSEED MT_GOODSEED MT_BESTSEED);
our @EXPORT_OK = @EXPORT;
our %EXPORT_TAGS = (
  'rand'  => [qw(srand rand rand32 rand64 irand irand32 irand64)],
  'seed'  => [qw(seed32 seedfull timeseed fastseed goodseed bestseed)],
  'state' => [qw(savestate loadstate)],
  'dist'  => [
    qw(
        rd_double
        rd_erlang rd_lerlang
        rd_exponential rd_lexponential
        rd_lognormal rd_llognormal
        rd_normal rd_lnormal
        rd_triangular rd_ltriangular
        rd_weibull rd_lweibull
    )],
);

XSLoader::load('Math::Random::MTwist', $VERSION);

# We want the function-oriented interface to provide the same function names as
# the OO interface. But since it doesn't have the state argument (aka $self),
# MTwist.xs provides counterparts with a leading underscore that we map to here.
sub import {
  my $this = shift;

  my @unhandled_args;

  if (@_) {
    my $caller = caller;
    my $call_srand = 0;
    my %exportable = map +($_ => 1), map @$_, values %EXPORT_TAGS;

    while (defined(my $arg = shift)) {
      if ($arg =~ /^:(.+)/ && exists $EXPORT_TAGS{$1}) {
        push @_, @{$EXPORT_TAGS{$1}};
      }
      elsif ($exportable{$arg}) {
        no strict 'refs';
        $call_srand++;
        *{"$caller\::$arg"} = \&{"$this\::_$arg"};
      }
      else {
        push @unhandled_args, $arg;
      }
    }

    _srand() if $call_srand;
  }

  __PACKAGE__->export_to_level(1, $this, @unhandled_args);
}

sub new {
  my $class = shift;
  my $seed = shift;

  my $self = _mts_newstate($class);

  if (! defined $seed) {
    $self->fastseed();
  }
  elsif (! ref $seed) {
    $self->seed32($seed);
  }
  elsif (ref $seed eq 'ARRAY') {
    $self->seedfull($seed);
  }
  elsif ($seed == MT_TIMESEED) {
    $self->timeseed();
  }
  elsif ($seed == MT_FASTSEED) {
    $self->fastseed();
  }
  elsif ($seed == MT_GOODSEED) {
    $self->goodseed();
  }
  elsif ($seed == MT_BESTSEED) {
    $self->bestseed();
  }
  else { # WTF?
    $self->fastseed();
  }

  $self;
}

1;

__END__

=pod

=encoding utf8

=head1 NAME

Math::Random::MTwist - A fast stateful Mersenne Twister pseudo-random number generator.

=head1 SYNOPSIS

  # object-oriented inteface
  use Math::Random::MTwist;

  my $mt = Math::Random::MTwist->new();  # seed from /dev/urandom
  my $int = $mt->irand();                # [0 .. 2^64-1 or 2^32-1]
  my $double = $mt->rand(73);            # [0 .. 73)
  $mt->goodseed();                       # seed from /dev/random
  $mt->savestate("/tmp/foobar");         # save current state to file
  $mt->loadstate("/tmp/foobar");         # load past state from file
  my @dist = map $mt->rd_triangular(1, 3, 2), 1 .. 1e3;  # triangular dist.

  # function-oriented interface (OO interface may be used in parallel)
  use Math::Random::MTwist qw(seed32 seedfull
                              timeseed fastseed goodseed bestseed);
  use Math::Random::MTwist qw(:seed) # gives you all of the above

  use Math::Random::MTwist qw(srand rand rand32 irand irand32 irand64);
  use Math::Random::MTwist qw(:rand); # gives you all of the above

  use Math::Random::MTwist qw(rd_exponential rd_triangular rd_normal ...);
  use Math::Random::MTwist qw(:dist); # gives you all of the above

  use Math::Random::MTwist qw(savestate loadstate);
  use Math::Random::MTwist qw(:state); # gives you all of the above

=head1 DESCRIPTION

Math::Random::MTwist is a Perl interface to Geoff Kuenning's mtwist C
library. It provides several seeding methods, an independent state per OO
instance and various random number distributions.

All functions are available through a function-oriented interface and an
object-oriented interface. If you use the function-oriented interface the
generator maintains a single global state while with the OO interface each
instance has its individual state.

The function-oriented interface provides drop-in replacements for Perl's
built-in C<rand()> and C<srand()> functions. If you C<use> the module with an
import list C<srand()> is called once automatically. If you need the C<MT_>
constants too you must import them through the tag C<:DEFAULT>.

=head1 CONSTRUCTOR

=over 2

=item B<new()>

Takes an optional argument specifying the seed. The seed can be a number (will
be coerced to an unsigned 32-bit integer), an array reference holding up to 624
such numbers (missing values are padded with zeros, excess values are ignored)
or one of the special values C<MT_TIMESEED>, C<MT_FASTSEED>, C<MT_GOODSEED> or
C<MT_BESTSEED> that choose one of the corresponding seeding methods (see
below). If no seed is given, C<MT_FASTSEED> is assumed.

Each instance maintains an individual PRNG state allowing multiple independent
random number streams.

=back

=head1 SEEDING

=over 2

=item B<seed32($number)>

Seeds the generator with C<$number>. The value will be coerced to an unsigned
32-bit integer. Calls mtwist's C<mts_seed32new()>. Returns the seed.

=item B<srand($number)>

Calls C<seed32> if C<$number> is given, C<fastseed()> otherwise. Returns the
seed.

=item B<seedfull($seeds)>

Seeds the generator with up to 624 numbers from the I<array reference>
C<$seeds>. The values are coerced to unsigned 32-bit integers. Missing values
are padded with zeros, excess values are ignored. Calls mtwist's
C<mts_seedfull()>.

=item B<timeseed()>

Seeds the generator from the current system time obtained with
C<gettimeofday()> by calculating C<seconds * 1e6 + microseconds> and coercing
the result to an unsigned 32-bit integer. Returns the seed.

This method is called by C<new(MT_TIMESEED)>.

It doesn't correspond to any of mtwist's functions. The rationale behind it is
that mtwist falls back to the system time if neither C</dev/urandom> nor
C</dev/random> is available. On Windows the time source chosen by mtwist has
only millisecond resolution in contrast to microseconds from
C<Time::HiRes::gettimeofday()>.

=item B<fastseed()>

Seeds the generator with 4 bytes read from C</dev/urandom> if available,
otherwise from the system time (see details under C<timeseed()>). Calls
mtwist's C<mts_seed()>. Returns the seed.

This method is called by C<new(MT_FASTSEED)>.

=item B<goodseed()>

Seeds the generator with 4 bytes read from C</dev/random> if available,
otherwise from the system time (see details under C<timeseed()>). Calls
mtwist's C<mts_goodseed()>. Returns the seed.

This method is called by C<new(MT_GOODSEED)>.

=item B<bestseed()>

Seeds the generator with 642 integers read from C</dev/random> if
available. This might take a very long time and is probably not worth the
waiting. If C</dev/random> is unavailable or there was a reading error it falls
back to C<goodseed()>. Calls mtwist's C<mts_bestseed()>.

This method is called by C<new(MT_BESTSEED)>.

=back

=head1 STATE HANDLING

=over 2

=item B<savestate($filename or $filehandle)>

Saves the current state of the generator to a file given either by a filename
(file will be truncated) or an open Perl file handle.

Returns 1 on success, 0 on error (you might want to check C<$!>).

=item B<loadstate($filename or $filehandle)>

Loads the state of the generator from a file given either by a filename or an
open Perl file handle.

Returns 1 on success, 0 on error (you might want to check C<$!>).

=back

=head1 UNIFORMLY DISTRIBUTED RANDOM NUMBERS

=over 2

=item B<irand()>

Returns a random unsigned integer, 64-bit if your system supports it (see
C<irand64()>), 32-bit otherwise.

=item B<irand32()>

Returns a random unsigned 32-bit integer. Calls mtwist's C<mts_lrand()>.

=item B<irand64()>

If your Perl is 64-bit, returns a 64-bit unsigned integer. If your Perl is
32-bit but your OS knows the C<uint64_t> type, returns a 64-bit unsigned
integer coerced to a double (so it's the full 64-bit range but with only 52-bit
precision). Otherwise it returns undef. Calls mtwist's C<mts_llrand()>.

=item B<rand($bound)>

Returns a random double with 52-bit precision in the range C<[0, $bound)>.
Calls mtwist's C<mts_ldrand()>.

C<$bound> may be negative. If C<$bound> is omitted or zero it defaults to 1.

=item B<rand32($bound)>

Returns a random double with 32-bit precision in the range C<[0, $bound)>.
Slightly faster than rand(). Calls mtwist's C<mts_drand()>.

C<$bound> may be negative. If C<$bound> is omitted or zero it defaults to 1.

=back

=head1 NON-UNIFORMLY DISTRIBUTED RANDOM NUMBERS

With the exception of C<rd_double()> the following methods come in two
variants: C<B<rd_>xxx> and C<B<rd_l>xxx>. They all return a double but the
C<B<rd_>xxx> versions provide 32-bit precision while the C<B<rd_l>xxx> versions
provide 52-bit precision at the expense of speed.

=over 2

=item B<rd_double()>

This is kind of a FUNction (that's the "Fun with flags" sort of fun).

It generates a random double in the complete (signed) double range. It does
that by drawing a random 64-bit integer (if available, otherwise two 32-bit
integers) and interpreting the bit pattern as a double. That's the same as
saying C<unpack 'd', pack 'Q', irand64()>. The results follow a Benford
distribution (each range [2^n, 2^(n+1)[ can hold 2^52 values). Be prepared to
meet some NaNs and Infs.

In scalar context it returns a double. In list context it returns the double,
the corresponding integer (undef if your Perl doesn't have 64-bit integers) and
the packed string representation.

=item B<rd_(l)exponential(double mean)>

Generates an exponential distribution with the given mean.

=item B<rd_(l)erlang(int k, double mean)>

Generates an Erlang-k distribution with the given mean.

=item B<rd_(l)weibull(double shape, double scale)>

Generates a Weibull distribution with the given shape and scale.

=item B<rd_(l)normal(double mean, double sigma)>

Generates a normal (Gaussian) distribution with the given mean and standard
deviation sigma.

=item B<rd_(l)lognormal(double shape, double scale)>

Generates a log-normal distribution with the given shape and scale.

=item B<rd_(l)triangular(double lower, double upper, double mode)>

Generates a triangular distribution in the range C<[lower, upper)> with the
given mode.

=back

=head1 EXPORTS

The module exports the constants MT_TIMESEED, MT_FASTSEED, MT_GOODSEED and
MT_BESTSEED that can be used as an argument to the constructor.

=head1 NOTES

This module is not C<fork()/clone()> aware, i.e. you have to take care of
re-seeding/re-instantiating in new processes/threads yourself.

Imported functions and OO methods have the same names. This works by symbol
table "redirection", e.g. non-OO irand() is actually _irand() internally and so
on (note the underscore). The minor drawback is that you can't use fully
qualified names like C<Math::Random::MTwist::irand()> etc. Instead you have to
say C<Math::Random::MTwist::_irand()>. I considered avoiding this by checking
if the first argument is a blessed reference but I discarded that in favor of
speed.

=head1 SEE ALSO

L<http://www.cs.hmc.edu/~geoff/mtwist.html>

L<Math::Random::MT|https://metacpan.org/pod/Math::Random::MT> and
L<Math::Random::MT::Auto|https://metacpan.org/pod/Math::Random::MT::Auto> are
significantly slower than Math::Random::MTwist. While MRMA has some additional
sophisticated features, it depends on non-core modules.

=head1 AUTHOR

Carsten Gaebler (cgpan ʇɐ gmx ʇop de). I only accept encrypted e-mails, either
via L<SMIME|https://cpan.metacpan.org/authors/id/C/CG/CGPAN/cgpan-smime.crt> or
L<GPG|https://cpan.metacpan.org/authors/id/C/CG/CGPAN/cgpan-gpg.asc>.

=head1 COPYRIGHT

Perl and XS portion: Copyright © 2014 by Carsten Gaebler.

mtwist C library: Copyright © 2001, 2002, 2010, 2012, 2013 by Geoff Kuenning.

=head1 LICENSE

Perl and XS portion: L<WTFPL|http://wtfpl.net/>.

mtwist C library: L<LGPL|https://gnu.org/licenses/lgpl.html>

=cut
