// Random Number Generator
//
// For ECS 110 - UC Davis - Coverted to C++ by Ted Krovetz
//
// Implementation based on block cipher RC5 which is copywritten.
// RC5REF.C -- Reference implementation of RC5-32/12/16 in C.
// Copyright (C) 1995 RSA Data Security, Inc. Patent Pending.
//
// Version 1.0  - Initial implementation
//         1.01 - Made data static
//         1.02 - Added get_rand_bit()

#include <time.h>
#include "Random.h"

// Random's static data

int   Random::one_is_saved;
WORD  Random::saved;
WORD  Random::counter[2];
WORD* Random::S = 0;      // key table

// Constants

const WORD P = 0xb7e15163;
const WORD Q = 0x9e3779b9;  // magic constants

const int w = 32; // word size in bits                 
const int r = 12; // number of rounds                    
const int b = 16; // number of bytes in key            
const int c = 4;  // number  words in key = ceil(8*b/w)
const int t = 26; // size of table S = 2*(r+1) words   

// Rotation operators. x must be unsigned, to get logical right shift
inline WORD ROTL(WORD x, WORD y) 
{
  return (((x)<<(y&(w-1))) | ((x)>>(w-(y&(w-1)))));
}

inline WORD ROTR(WORD x, WORD y)
{
  return (((x)>>(y&(w-1))) | ((x)<<(w-(y&(w-1)))));
}

void Random::aux_init(unsigned char K[])
{
  WORD i, j, k, u=w/8, A, B, L[c];
  counter[0] = counter[1] = 0;
  one_is_saved = 0;
  if (S == 0)
  	S = new WORD[t];
  
  // Initialize L, then S, then mix key into S
  for (i=b-1,L[c-1]=0; i!=0xffffffff; i--) L[i/u] = (L[i/u]<<8)+K[i];
  for (S[0]=P,i=1; i<t; i++) S[i] = S[i-1]+Q;
  for (A=B=i=j=k=0; k<3*t; k++,i=(i+1)%t,j=(j+1)%c)   // 3*t > 3*c
  { A = S[i] = ROTL(S[i]+(A+B),3);  
    B = L[j] = ROTL(L[j]+(A+B),(A+B)); 
  }
}

Random::Random(void)
{
  if (S == 0) {
	  WORD seed = time(NULL);
	  unsigned char K[b];     // secret input key K[0...b-1]
	  for (int i = 0; i < b; i++)
	    K[i] = ((unsigned char *)(&seed))[i%4];
	  aux_init(K);
  }
}

Random::Random(WORD seed)
{
	  unsigned char K[b];     // secret input key K[0...b-1]
	  for (int i = 0; i < b; i++)
	    K[i] = ((unsigned char *)(&seed))[i%4];
	  aux_init(K);
} 

Random::Random(unsigned char seed[])
{
  	aux_init(seed);
}

WORD Random::get_next_WORD(void)
{
  WORD rval;
  if (one_is_saved) {
    one_is_saved = 0;
    rval = saved;
  } else {
    if (++counter[1] == 0)
      ++counter[0];

    WORD i, A=counter[0]+S[0], B=counter[1]+S[1];
    for (i=1; i<=r; i++) {
      A = ROTL(A^B,B)+S[2*i]; 
      B = ROTL(B^A,A)+S[2*i+1]; 
    }
    saved = A; 
    one_is_saved = 1;  
    rval = B;
  }
  return rval;
}

int Random::get_rand_bit(void)
{
	static WORD bit_word;
	static WORD cur_bit = 0;
	
	cur_bit <<= 1;
	if (cur_bit == 0) {
		bit_word = get_next_WORD();
		cur_bit = 1;
	}
	
	return ((cur_bit & bit_word) ? 1 : 0);
}

WORD Random::get_rand(WORD min_val, WORD max_val)
{
  WORD temp;
  WORD range = max_val - min_val + 1;
  WORD upper_limit = (0xffffffffu / range) * range;
  do {
    temp = get_next_WORD();
  } while (temp >= upper_limit);
  return min_val + (temp % range);
}

