/* $Id: t_dibigdRandomTests.cpp $ */

/*
* Copyright (C) 2026 David Ireland, D.I. Management Services Pty Limited
* <https://di-mgt.com.au/contact/> <https://di-mgt.com.au/bigdigits.html>
* SPDX-License-Identifier: MPL-2.0
*
* Last updated:
* $Date: 2026-05-05 02:02 $
* $Revision: 1.0.1 $
* $Author: dai $
*/

/* Test the internal random number generator and the prime generator. */

#ifdef NDEBUG
#undef NDEBUG
#endif
#include <iostream>
//#include <string>
#include <stdexcept>
#include <assert.h>
#include <time.h>
#include "dibigd.hpp"
using std::cout;
using std::endl;
using namespace dibigd;

int my_rand(unsigned char *bytes, size_t nbytes, const unsigned char *seed, size_t seedlen)
/* Our own (insecure) random generator call-back function using good old rand.
This demonstrates a function with the required format for BD_RANDFUNC
(unsigned char*, size_t, const unsigned char*, size_t)
-- replace this in practice with your own cryptographically-secure function.
*/
{
    unsigned int myseed;
    size_t i;
    int offset;
    /* Use time and clock - then blend in seed, if any */
    myseed = (unsigned)time(NULL) << 8 | (unsigned)clock();
    if (seed) {
        for (offset = 0, i = 0; i < seedlen; i++, offset = (offset + 1) % sizeof(unsigned))
            myseed ^= ((unsigned int)seed[i] << (offset * 8));
    }
    srand(myseed);
    while (nbytes--) {
        *bytes++ = rand() & 0xFF;
    }
    return 0;
}


void do_test() {
    cout << endl << "Testing random numbers..." << endl;
    BigDigit r;
    BigDigit n(100);
    int i;
    cout << "Up to 100 random bits:" << endl;
    for (i = 0; i < 5; i++) {
        r.set_rand_bits(100);
        r.printhex();
    }
    cout << "Up to 64 random bits:" << endl;
    for (i = 0; i < 5; i++) {
        r.set_rand_bits(64);
        r.printbits();
    }
    // Check that set_rand_* funcs return the new value
    BigDigit t;
    t = r.set_rand_bits(128);
    assert(t == r);
    t = r.set_rand_number(n);

    // Check the distribution we get from calling set_rand_number N times for a certain range n.
    const double CHI_CRITICAL = 16.92; 
    /* We sort the results into k=10 buckets and use the chi-square goodness of fit test with the
    null hypothesis being the numbers are drwan from a uniform distribution. 
    The expected result E-i = N/k.
    We have k-1=9 degrees of freedom and want a significance level of 0.05.
    The critical value for 9 degrees of freedom at a significance level of 0.05 is 16.92
    If the calculated chi-square statistic is *greater* than CHI_CRITICAL, then the result 
    is statisically significant and we can reject the null hypothesis. Otherwise we can say that
    the result is consistent with a uniform distribution. */
    int N, k;
    double Ei, Oi, chisq;
    k = 10;   // Number of categories
    int distrib[10] = { 0 };
    N = 200;  // Number of tests
    cout << "set_rand_number(n) for n=" << n.to_str() << endl;
    cout << "N tests = " << N << endl;
    for (i = 0; i < N; i++) {
        // Generate random number in range [0,n-1]
        r.set_rand_number(n);
        cout << r.to_str() << " ";
        // Keep a tally, use integers
        int band =  r.to_short() / k;
        distrib[band]++;
    }
    cout << endl;
    // Display distribution and do a Chi-square goodness of fit test
    // Expected count is N / k 
    Ei = (double)N / (double)k;
    chisq = 0;
    for (i = 0; i < k; i++) {
        cout << std::setfill('0') << std::setw(2) << k * i << "-" << std::setw(2) << k * i + 9 << ": " << distrib[i] << endl;
        Oi = (double)distrib[i];  // Observed count
        chisq += (Oi - Ei)*(Oi - Ei) / Ei;
    }
    cout << "Chi-square=" << chisq <<
        (chisq < CHI_CRITICAL ? " OK, consistent, within limit " : " Probably not uniform! Outside limit! ") << CHI_CRITICAL << endl;

    // Again
    N = 100;
    n = 10;
    for (i = 0; i < k; i++) {
        distrib[i] = 0;
    }
    cout << "set_rand_number(n) for n=" << n.to_str() << endl;
    cout << "N tests = " << N << endl;
    Ei = (double)N / (double)k;
    chisq = 0;
    for (i = 0; i < N; i++) {
        r.set_rand_number(n);
        cout << r.to_str() << " ";
        int band = r.to_short();
        distrib[band]++;
    }
    cout << endl;
    // Display distribution
    for (i = 0; i < k; i++) {
        cout << i << ": " << distrib[i] << endl;
        Oi = (double)distrib[i];  // Observed count
        chisq += (Oi - Ei)*(Oi - Ei) / Ei;
    }
    cout << "Chi-square=" << chisq <<
        (chisq < CHI_CRITICAL ? " OK, consistent, within limit " : " Probably not uniform! Outside limit! ") << CHI_CRITICAL << endl;

    // TEST PRIME GENERATION
    BigDigit p;
    cout << "Generating a prime..." << endl;
    p.set_prime(100);
    p.printhex("p=");
    p.printbits("p=");
    cout << "p is " << (p.is_prime() ? "prime" : "not prime") << " (" << p.bitlen() << " bits)" << endl;
    // Set a bit at n=101, two bits in front -- unlikely to be a prime
    p.setbit(101, 1);
    cout << "After setbit(101) " << p.bitlen() << " bits" << endl;
    p.printbits("p'=");
    cout << "p' is " << (p.is_prime() ? "prime" : "not prime") << " (" << p.bitlen() << " bits)" << endl;
    cout << "p.getbit(101) is " << p.getbit(101) << " (expected 1)" << endl; // expected 1
    cout << "p.getbit(100) is " << p.getbit(100) << " (expected 0)" << endl; // expected 0
    // Use an external RNG function. NB my_rand is not secure! 
    p.set_prime(200, my_rand);
    p.printhex("p(200)=");
    cout << "p is " << (p.is_prime() ? "prime" : "not prime") << " (" << p.bitlen() << " bits)" << endl;
    // Again with a user-supplied seed
    p.set_prime(200, my_rand, (unsigned char*)"NaCl", 4);
    p.printhex("p(200)=");
    cout << "p is " << (p.is_prime() ? "prime" : "not prime") << " (" << p.bitlen() << " bits)" << endl;

    // Use default constructor to set a prime
    BigDigit x = BigDigit().set_prime(64);
    x.printhex("BigDigit().set_prime(64)=");
    // Check that set_prime returns the value it set
    t = p.set_prime(64);
    assert(t == p);
    t = p.set_prime(64, my_rand);
    assert(t == p);
    t = p.set_prime(64, my_rand, (unsigned char*)"NaCl", 4);
    assert(t == p);
}

int main() {
    /* MSVC memory leak checking stuff */
#if _MSC_VER >= 1100
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
    _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
    _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDOUT);
    _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
    _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDOUT);
#endif

    /* Catch any exceptions */
    try {
        do_test();
    }
    catch (const std::exception& e) {
        // Handle standard exceptions with a specific message
        std::cerr << "Standard exception: " << e.what() << std::endl;
    }
    catch (...) {
        std::cerr << "Caught unknown exception." << std::endl;
    }

    cout << endl << "ALL DONE." << endl << endl;

    return 0;
}