;+
; NAME:
;       RANDOMNUMBERGENERATOR
;
; PURPOSE:
;
;       Allows the user to obtain a sequence of pseudo-random numbers. The
;       object maintains the random number generator seed in such a way that
;       subsequent calls to GetRandomNumbers will guarentee that you don't 
;       get the same random numbers each time you ask for random numbers.
;
; AUTHOR:
;
;       FANNING SOFTWARE CONSULTING
;       David Fanning, Ph.D.
;       1645 Sheely Drive
;       Fort Collins, CO 80526 USA
;       Phone: 970-221-0438
;       E-mail: david@idlcoyote.com
;       Coyote's Guide to IDL Programming: http://www.idlcoyote.com
;
; CATEGORY:

;       Utilities
;
; CALLING SEQUENCE:
; 
;       Generate three random numbers.
;
;       IDL> rng = Obj_New('RandomNumberGenerator', initialSeed)
;       IDL> numberOfNumbersNeeded = 3
;       IDL> randomNumbers = rng -> GetRandomNumbers(numberOfNumbersNeeded)
;       IDL> Print, randomNumbers
;             0.80952855      0.35878432      0.52150406
;             
;       Generate a sequence of 8 random digits.
;       IDL> Print, rng -> GetRandomDigits(8)
;             21855786
;
; INPUT PARAMETERS FOR INIT METHOD:
;
;       initialSeed:    The initial seed for the random number generator. If undefined
;                       or absent, the number of seconds after 1 January 1970 is used.
;                       
; METHODS:
;
;       randomNumbers -> GetRandomNumbers(d1, d2, d3, d4 d5, d6, d7, d8)
;       randomDigits = obj -> GetRandomDigits(numDigets)
;       obj -> SetSeed, seed
;
; MODIFICATION HISTORY:
;
;       Written by David W. Fanning, 13 November 2009.
;       Added GetRandomDigits method. 7 February 2010. DWF.
;       Incorrect cleanup of the seed pointer fixed in the CLEANUP procedure. 
;           25 February 2010, DWF.
;-
;******************************************************************************************;
;  Copyright (c) 2009-2010, by Fanning Software Consulting, Inc.                           ;
;  All rights reserved.                                                                    ;
;                                                                                          ;
;  Redistribution and use in source and binary forms, with or without                      ;
;  modification, are permitted provided that the following conditions are met:             ;
;                                                                                          ;
;      * Redistributions of source code must retain the above copyright                    ;
;        notice, this list of conditions and the following disclaimer.                     ;
;      * Redistributions in binary form must reproduce the above copyright                 ;
;        notice, this list of conditions and the following disclaimer in the               ;
;        documentation and/or other materials provided with the distribution.              ;
;      * Neither the name of Fanning Software Consulting, Inc. nor the names of its        ;
;        contributors may be used to endorse or promote products derived from this         ;
;        software without specific prior written permission.                               ;
;                                                                                          ;
;  THIS SOFTWARE IS PROVIDED BY FANNING SOFTWARE CONSULTING, INC. ''AS IS'' AND ANY        ;
;  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES    ;
;  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT     ;
;  SHALL FANNING SOFTWARE CONSULTING, INC. BE LIABLE FOR ANY DIRECT, INDIRECT,             ;
;  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED    ;
;  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;         ;
;  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND             ;
;  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT              ;
;  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS           ;
;  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                            ;
;******************************************************************************************;
;------------------------------------------------------------------------------------------;
; NAME:                                                                                
;    RandomNumberGenerator::GetRandomDigits                                                                   
;                                                                                      
; Purpose:                                                                             
;                                                                                      
;    Allows the user to obtain a string variable of random digits.                                                               
;                                                                                      
; Method Syntax:                                                                       
;                                                                                      
;    randomDigits = obj -> GetRandomDigits(numberOfDigits)                             
;                                                                                      
; Auguments:                                                                           
;                                                                                      
;    numberOfDigits:   The number of digits desired. (Input, default is 8. )           
;                                                                                      
; Keywords:                                                                            
;                                                                                      
;    BYTES:             If this keyword is set, the random digits are returned
;                       as a byte array, rather than as a string.                                                                             
;                                                                                      
; Return Value:                                                                        
;                                                                                      
;    randomDigits:      A string variable containing the random digits.                
;                                                                                      
;------------------------------------------------------------------------------------------;
FUNCTION RandomNumberGenerator::GetRandomDigits, digits, BYTES=bytes

    COMPILE_OPT idl2

    On_Error, 2
    
    ; Default is 8 digits.
    IF N_Elements(digits) EQ 0 THEN digits = 8
    
    ; How many random numbers are needed to construct this number of digits?
    numbersNeeded = Long( digits/16 ) + 1
    
    ; Get the numbers and use only the digits after the "0.".
    randomNumbers = self -> GetRandomNumbers(numbersNeeded, /DOUBLE)
    strNumbers = StrMid(String(randomNumbers, FORMAT='(F18.16)'), 2)
    
    ; Put the digits together to come up with the correct number.
    digitNumbers = strNumbers[0]
    FOR j=1,N_Elements(randomNumbers)-1 DO digitNumbers = digitNumbers + strNumbers[j]
    
    ; Trim the result to the correct number of digits.
    IF StrLen(digitNumbers) GT digits THEN digitNumbers = StrMid(digitNumbers,0,digits)
    
    ; Do you need bytes instead of a string?
    IF Keyword_Set(bytes) THEN BEGIN
       numbers = BytArr(digits)
       FOR j=0,digits-1 DO BEGIN
            numbers[j] = Byte(Fix(StrMid(digitNumbers, j, 1)))
       ENDFOR
       digitNumbers = numbers
    ENDIF
    
    ;Return the digits you need.
    RETURN, digitNumbers
    
END
    
    
;------------------------------------------------------------------------------------------;
; NAME:                                                                                
;    RandomNumberGenerator::GetRandomNumbers                                                                  
;                                                                                      
; Purpose:                                                                             
;                                                                                      
;    Allows the user to obtain a scalar or array of random numbers.                                                              
;                                                                                      
; Method Syntax:                                                                       
;                                                                                      
;    randomNumbers = obj -> GetRandomNumbers(d1, d2, d3, d4, d5, d6, d7, d8)           
;                                                                                      
; Auguments:                                                                           
;                                                                                      
;    d1...d8         Up to 8 dimension values can be specified, as in the IDL on-line  
;                    documentation for RANDOMU.                                        
; Keywords:                                                                            
;                                                                                      
;        All the keywords that are defined for the IDL built-in routine RANDOMU are    
;        available for use here. The only difference is that non-integer numbers are   
;        *always* returned as double-precision values. That is to say DOUBLE is always 
;        set to 1. See the IDL on-line documentation for RANDOMU to understand how to use 
;        the following keywords:                                                       
;                BINOMIAL                                                              
;                DOUBLE                                                                
;                GAMMA                                                                 
;                NORMAL                                                                
;                POISSON                                                               
;                UNIFORM                                                               
;                LONG                                                                  
;                                                                                      
; Return Value:                                                                        
;                                                                                      
;    randomNumbers:      The scalar or array of random numbers.                        
;                                                                                      
;------------------------------------------------------------------------------------------;
FUNCTION RandomNumberGenerator::GetRandomNumbers, d1, d2, d3, d4, d5, d6, d7, d8, $
    BINOMIAL=binomial, $
    DOUBLE=double, $ 
    GAMMA=gamma, $
    NORMAL=normal, $
    POISSON=poisson, $
    UNIFORM=uniform, $
    LONG=long
    
    ; I just assume any floating value is *suppose* to be in double precision.
    IF N_Elements(double) EQ 0 THEN double = 1
    
    ; Yuck! Have to do this according to the number of positional parameters.
    CASE N_Params() OF
        0: results = RandomU(*self.seed, $
                BINOMIAL=binomial, $
                DOUBLE=double, $ 
                GAMMA=gamma, $
                NORMAL=normal, $
                POISSON=poisson, $
                UNIFORM=uniform, $
                LONG=long)
        1: results = RandomU(*self.seed, d1, $
                BINOMIAL=binomial, $
                DOUBLE=double, $ 
                GAMMA=gamma, $
                NORMAL=normal, $
                POISSON=poisson, $
                UNIFORM=uniform, $
                LONG=long)
        2: results = RandomU(*self.seed, d1, d2, $
                BINOMIAL=binomial, $
                DOUBLE=double, $ 
                GAMMA=gamma, $
                NORMAL=normal, $
                POISSON=poisson, $
                UNIFORM=uniform, $
                LONG=long)
        3: results = RandomU(*self.seed, d1, d2, d3, $
                BINOMIAL=binomial, $
                DOUBLE=double, $ 
                GAMMA=gamma, $
                NORMAL=normal, $
                POISSON=poisson, $
                UNIFORM=uniform, $
                LONG=long)
        4: results = RandomU(*self.seed, d1, d2, d3, d4, $
                BINOMIAL=binomial, $
                DOUBLE=double, $ 
                GAMMA=gamma, $
                NORMAL=normal, $
                POISSON=poisson, $
                UNIFORM=uniform, $
                LONG=long)
        5: results = RandomU(*self.seed, d1, d2, d3, d4, d5, $
                BINOMIAL=binomial, $
                DOUBLE=double, $ 
                GAMMA=gamma, $
                NORMAL=normal, $
                POISSON=poisson, $
                UNIFORM=uniform, $
                LONG=long)
        6: results = RandomU(*self.seed, d1, d2, d3, d4, d5, d6, $
                BINOMIAL=binomial, $
                DOUBLE=double, $ 
                GAMMA=gamma, $
                NORMAL=normal, $
                POISSON=poisson, $
                UNIFORM=uniform, $
                LONG=long)
        7: results = RandomU(*self.seed, d1, d2, d3, d4, d5, d6, d7, $
                BINOMIAL=binomial, $
                DOUBLE=double, $ 
                GAMMA=gamma, $
                NORMAL=normal, $
                POISSON=poisson, $
                UNIFORM=uniform, $
                LONG=long)
        8: results = RandomU(*self.seed, d1, d2, d3, d4, d5, d6, d7, d8, $
                BINOMIAL=binomial, $
                DOUBLE=double, $ 
                GAMMA=gamma, $
                NORMAL=normal, $
                POISSON=poisson, $
                UNIFORM=uniform, $
                LONG=long)
    ENDCASE
       
    ; Return the results.
    RETURN, results
    
END ;----------------------------------------------------------------



PRO RandomNumberGenerator::SetSeed, theSeed

    ; Sets the seed for the random number generator.

    IF N_Elements(theSeed) NE 0 THEN *self.seed = theSeed
    
 END ;----------------------------------------------------------------

   

PRO RandomNumberGenerator::CLEANUP

    Ptr_Free, self.seed

END ;----------------------------------------------------------------


FUNCTION RandomNumberGenerator::INIT, initialSeed

    ; Initializes the random number generator. The initialSeed is the initial
    ; seed for the generator. If absent, the number of seconds after 1 January 1970 
    ; is used as the seed.

    Catch, theError
    IF theError NE 0 THEN BEGIN
        Catch, /CANCEL
        Help, /Last_Message
        Return, 0
    ENDIF
    
    ; Initialize the pointer.
    self.seed = Ptr_New(/ALLOCATE_HEAP)

    ; Create the initial seed, if needed. Otherwise, just store it.
    IF N_Elements(initialSeed) EQ 0 $
        THEN self -> SetSeed, Systime(1) $
        ELSE self -> SetSeed, initialSeed
        
    RETURN, 1
    
END ;----------------------------------------------------------------


PRO RandomNumberGenerator__Define

    class = { RANDOMNUMBERGENERATOR, $
              seed: Ptr_New() }
END ;----------------------------------------------------------------
