/*            
  SFBRandom.cpp - Arduino API compatibility shims around random.c
  Copyright (C) 2009 The Regents of the University of New Mexico.  All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
  USA
*/

#include "SFBTypes.h"
#include "SFBMacros.h"          /* For IN_BODYRAM */ 
#include "SFBRandom.h"

/* TO COMPILE FOR TESTING:
  gcc -O0 -g3 -Wall -pedantic -Werror -Wundef    -I../../../src/include  -DHOST_MODE -c random.c;g++ -O0 -g3 -Wall -pedantic -Werror -Wundef    -I../../../src/include  -DHOST_MODE -DTEST_RANDOM -o"./testsfbrandom" SFBRandom.cpp random.o;./testsfbrandom
*/

static u32 randomBitBuffer IN_BODYRAM_BSS;     
static u32 randomBitsAvailable IN_BODYRAM_BSS; 

u32 randomBits(u32 n) {
  u32 mask;
  u32 ret = 0;

  if (n >= 32) {
    ret = randomImpl()<<1;      // pick upper 31 bits, then go
    n = 1;                      // for one more from the buffer
  }
  mask = (1<<n)-1;              // n<32, so shift is portable, FWIW..
  
  if (randomBitsAvailable < n) {
    randomBitBuffer = randomImpl();
    randomBitsAvailable = 31;   // randomImpl only gives 31 bits
  }
  randomBitsAvailable -= n;
  ret |= randomBitBuffer&mask;
  randomBitBuffer >>= n;
  return ret;
}

void randomSeed(u32 seed) {
  srandomImpl(seed);
  randomBitsAvailable = 0;
}

long random(u32 max) {
  if (max==0) return 0;  /* bogons.. */
  return randomImpl()%max;
}

long random(int min, int max) {
  if (min > max) return random(max,min);
  return random((u32) (max-min))+min;
}

#ifdef TEST_RANDOM

#include <stdio.h>
#include <stdlib.h> // for exit, random
#include <string.h> // for strcmp

#define TEST(condition) do { if (!(condition)) die(__FILE__,__LINE__,#condition); } while (0)

void _apiError_(u32 code,const char * file, int lineno) { 
  fprintf(stderr,"APIERROR %d %s:%d\n",code,file,lineno);
  exit(code); 
}

void die(const char * file, int line, const char * msg) {
  fprintf(stderr,"%s:%d: %s\n",file, line, msg);
  exit(100);
}


#define BUF_SIZE 1000
#define DEF_MIN 0
#define DEF_MAX 1000000
#define DEF_NUMS 100000

static void testGiven(int lo, int hi, int num) {
  TEST(num >= lo);
  TEST(num < hi);
}
static void testRange(int lo, int hi) {
  testGiven(lo,hi,random(lo,hi));
}
void test1() {
  int buf1[BUF_SIZE];
  for (u32 k = 1; k < 10; ++k) {
    randomSeed(k);
    for (u32 i = 0; i < BUF_SIZE; ++i) 
      buf1[i] = random(DEF_MIN,DEF_MAX);
    for (u32 j = 0; j < 10; ++j) {
      randomSeed(k);
      for (u32 i = 0; i < BUF_SIZE; ++i) 
        TEST(buf1[i] == random(DEF_MIN,DEF_MAX));
    }
  }
}
void test2() {
  randomSeed(22222);
  for (u32 i = 0; i < DEF_NUMS; ++i) {
    testRange(0,2);
    testRange(0,100);
    testRange(-100,3);
    testRange(0,1);
    testRange(9,10);
    testRange(-10000,-9000);
  }
}

void test3() {
  randomSeed(33333);
  for (u32 i = 0; i < DEF_NUMS; ++i) {
    u32 bits = random(1,31); // 1..30
    int min = 0;
    int max = 1<<bits;
    testGiven(min,max,randomBits(bits));
  }
}


void test4() {
  randomSeed(440044);
  for (u32 i = 0; i < DEF_NUMS; ++i) {
    testGiven(0,DEF_NUMS,random(DEF_NUMS));
  }
}


void test5() {
  u32 count[32];
  for (u32 k = 1; k <= 32; ++k) {

    for (u32 j = 0; j<32; ++j) 
      count[j] = 0;

    for (u32 i = 0; i < DEF_NUMS; ++i) {
      u32 bits = randomBits(k);
      for (u32 j = 0; j<32; ++j) {
        if (bits&(1<<j))
          ++count[j];
      }
    }
    for (u32 j = 0; j<32; ++j) {
      if (j < k) {
        TEST(count[j] >= 49*DEF_NUMS/100); 
        TEST(count[j] <= 51*DEF_NUMS/100);
      } else
        TEST(count[j] == 0);
    }
  }
}

void test6() {
  randomSeed(-505);
  for (u32 j = 0; j < 10; ++j) {
    u32 seed = random(DEF_MIN,DEF_MAX);
    randomSeed(seed);
    u32 sum = 0;
    for (u32 i = 0; i < DEF_NUMS; ++i) 
      sum += random(2);
    TEST(sum >= 49*DEF_NUMS/100); 
    TEST(sum <= 51*DEF_NUMS/100);
  }
}

void test7() {
  u32 buf1[BUF_SIZE];
  for (u32 s = 2; s < 13; ++s) {
    for (u32 k = 1; k < 10; ++k) {
      randomSeed(k);
      for (u32 i = 0; i < BUF_SIZE; ++i) 
        buf1[i] = randomBits(s);
      for (u32 j = 0; j < 10; ++j) {
        randomSeed(k);
        for (u32 i = 0; i < BUF_SIZE; ++i) 
          TEST(buf1[i] == randomBits(s));
      }
    }
  }
}


int main() {

  test7();
  test6();
  test5();
  test4();
  test3();
  test2();
  test1();

  return 0;
}

#endif /* TEST_RANDOM */
