%-*-Mode:erlang;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
% ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et nomod:

%% Modified version of random module
%% to use Wichmann-Hill algorithm published on 2006
%% which succeeds the old AS183 algorithm in 1982.

%% Copyright (c) 2010 Kenji Rikitake All rights reserved.
%% Copyright (c) 2012-2020 Michael Truog All rights reserved.

%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1996-2011. All Rights Reserved.
%% 
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%% 
%% %CopyrightEnd%
%%
-module(cloudi_x_random_wh06_int).

%% Reasonable random number generator (period is 2.66e36):
%%  The method is attributed to B. A. Wichmann and I. D. Hill
%%  See "Generating good pseudo-random numbers",
%%  Computational Statistics & Data Analysis 51 (2006) 1614-1622.

%% Explanation:
%%  Example C code, using 64-bit integer arithmetic
%%  (from Richard O'Keefe)
%%
%%   a = (a * 11600LL) % 2147483579;
%%   b = (b * 47003LL) % 2147483543;
%%   c = (c * 23000LL) % 2147483423;
%%   d = (d * 33000LL) % 2147483123;
%%   w = a/2147483579.0 + b/2147483543.0
%%     + c/2147483423.0 + d/2147483123.0;
%%   if (w >= 2.0) w -= 2.0;
%%   if (w >= 1.0) w -= 1.0;
%%   return w;
%%
%%  To avoid floating-point precision problems,
%%  it is best to use Erlang's native bigint support:
%%
%%   B1 = (11600 * A1) rem 2147483579,
%%   B2 = (47003 * A2) rem 2147483543,
%%   B3 = (23000 * A3) rem 2147483423,
%%   B4 = (33000 * A4) rem 2147483123,
%%   put(random_wh06_seed, {B1, B2, B3, B4}),
%%   I = ((B1 * 9903516371291919229607132747) +
%%        (B2 * 9903516537312557910938853791) +
%%        (B3 * 9903517090714727049595319831) +
%%        (B4 * 9903518474220420479167438931))
%%       rem 21267638781707063560975648195455661513,
%%
%%  (21267638781707063560975648195455661513 ==
%%   2147483579 * 2147483543 * 2147483423 * 2147483123,
%%   so w * 21267638781707063560975648195455661513.0 == I,
%%   based on modular arithmetic)
%%
%%  The algorithm provides 123 bits of randomness:
%%  1> math:log(21267638781707063560975648195455661513) / math:log(2).
%%

-export([seed0/0, seed/0, seed/1, seed/4,
         uniform/0, uniform/1,
         uniform_s/1, uniform_s/2,
         next_sequence/1]).

-define(PRIME1, 2147483579).
-define(PRIME2, 2147483543).
-define(PRIME3, 2147483423).
-define(PRIME4, 2147483123).

-define(SEED_DICT, random_wh06_seed).

%%-----------------------------------------------------------------------
%% The type of the state

-type seed() :: {pos_integer(), pos_integer(), pos_integer(), pos_integer()}.

%%-----------------------------------------------------------------------

-spec seed0() -> {123456789, 345678901, 567890123, 789012345}.

seed0() ->
    {123456789, 345678901, 567890123, 789012345}.

%% seed()
%%  Seed random number generation with default values

-spec seed() -> seed().

seed() ->
    reseed(seed0()).

%% seed({A1, A2, A3, A4}) 
%%  Seed random number generation 

-spec seed(seed()) ->
    'undefined' | seed().

seed({A1, A2, A3, A4}) ->
    seed(A1, A2, A3, A4).

%% seed(A1, A2, A3, A4) 
%%  Seed random number generation 

-spec seed(pos_integer(), pos_integer(), pos_integer(), pos_integer()) ->
    'undefined' | seed().

seed(A1, A2, A3, A4)
    when is_integer(A1), A1 > 0,
         is_integer(A2), A2 > 0,
         is_integer(A3), A3 > 0,
         is_integer(A4), A4 > 0 ->
    put(?SEED_DICT,
        {(A1 rem (?PRIME1 - 1)) + 1,
         (A2 rem (?PRIME2 - 1)) + 1,
         (A3 rem (?PRIME3 - 1)) + 1,
         (A4 rem (?PRIME4 - 1)) + 1}).

reseed({A1, A2, A3, A4}) ->
    case seed(A1, A2, A3, A4) of
        undefined -> seed0();
        {_,_,_,_} = Tuple -> Tuple
    end.        

%% uniform()
%%  Returns a random integer between
%%  0 and 21267638781707063560975648195455661512.

-spec uniform() -> non_neg_integer().

uniform() ->
    {A1, A2, A3, A4} = case get(?SEED_DICT) of
                           undefined -> seed0();
                           Tuple -> Tuple
                       end,

    B1 = (11600 * A1) rem ?PRIME1,
    B2 = (47003 * A2) rem ?PRIME2,
    B3 = (23000 * A3) rem ?PRIME3,
    B4 = (33000 * A4) rem ?PRIME4,

    put(?SEED_DICT, {B1, B2, B3, B4}),

    I = ((B1 * 9903516371291919229607132747) +
         (B2 * 9903516537312557910938853791) +
         (B3 * 9903517090714727049595319831) +
         (B4 * 9903518474220420479167438931))
        rem 21267638781707063560975648195455661513,
    I.

%% uniform(N) -> I
%%  Given an integer N > 1, N =< 21267638781707063560975648195455661513,
%%  uniform(N) returns a random integer
%%  between 1 and N.

-spec uniform(pos_integer()) -> pos_integer().

uniform(N)
    when is_integer(N), N > 1, N =< 21267638781707063560975648195455661513 ->
    (uniform() rem N) + 1.

%%% Functional versions

%% uniform_s(State) -> {I, NewState}
%%  Returns a random integer I, between
%%  0 and 21267638781707063560975648195455661512 (inclusive).

-spec uniform_s(seed()) -> {non_neg_integer(), seed()}.

uniform_s({A1, A2, A3, A4})
    when is_integer(A1), A1 > 0,
         is_integer(A2), A2 > 0,
         is_integer(A3), A3 > 0,
         is_integer(A4), A4 > 0 ->
    B1 = (11600 * A1) rem ?PRIME1,
    B2 = (47003 * A2) rem ?PRIME2,
    B3 = (23000 * A3) rem ?PRIME3,
    B4 = (33000 * A4) rem ?PRIME4,

    I = ((B1 * 9903516371291919229607132747) +
         (B2 * 9903516537312557910938853791) +
         (B3 * 9903517090714727049595319831) +
         (B4 * 9903518474220420479167438931))
        rem 21267638781707063560975648195455661513,

    {I, {B1, B2, B3, B4}}.

%% uniform_s(N, State) -> {I, NewState}
%%  Given an integer N > 1, N =< 21267638781707063560975648195455661513,
%%  uniform(N) returns a random integer
%%  between 1 and N.

-spec uniform_s(pos_integer(), seed()) -> {pos_integer(), seed()}.

uniform_s(N, State0)
    when is_integer(N), N > 1, N =< 21267638781707063560975648195455661513 ->
    {I, State1} = uniform_s(State0),
    {(I rem N) + 1, State1}.

%% generating another seed for multiple sequences

-spec next_sequence(seed()) -> seed().

next_sequence({A1, A2, A3, A4})
    when is_integer(A1), A1 > 0,
         is_integer(A2), A2 > 0,
         is_integer(A3), A3 > 0,
         is_integer(A4), A4 > 0 ->
    B1 = (11600 * A1) rem ?PRIME1,
    B2 = (47003 * A2) rem ?PRIME2,
    B3 = (23000 * A3) rem ?PRIME3,
    B4 = (33000 * A4) rem ?PRIME4,
    {B1, B2, B3, B4}.

