
#include <cstdint>
#include <limits>
#include <random>
#include <sstream>
#include <string>
#include <vector>

#define BOOST_TEST_MAIN  1
#include <boost/test/unit_test.hpp>

#include "fixed.h"

BOOST_AUTO_TEST_CASE(t0)
{
    /* TEST CASE 0: Check storage sizes (in bytes) of all predefined fixed 
     * point types.
     */
    fixed_8_8  f_8_8;
    fixed_8_32 f_8_32;
    fixed_8_16 f_8_16;
    fixed_8_64 f_8_64;
    BOOST_CHECK(sizeof(f_8_8)  == 2);
    BOOST_CHECK(sizeof(f_8_16) == 4);
    BOOST_CHECK(sizeof(f_8_32) == 8);
    BOOST_CHECK(sizeof(f_8_64) == 16);
    
    fixed_16_8  f_16_8;
    fixed_16_16 f_16_16;
    fixed_16_32 f_16_32;
    fixed_16_64 f_16_64;
    BOOST_CHECK(sizeof(f_16_8)  == 4);
    BOOST_CHECK(sizeof(f_16_16) == 4);
    BOOST_CHECK(sizeof(f_16_32) == 8);
    BOOST_CHECK(sizeof(f_16_64) == 16);
    
    fixed_32_8  f_32_8;
    fixed_32_16 f_32_16;
    fixed_32_32 f_32_32;
    fixed_32_64 f_32_64;
    BOOST_CHECK(sizeof(f_32_8)  == 8);
    BOOST_CHECK(sizeof(f_32_16) == 8);
    BOOST_CHECK(sizeof(f_32_32) == 8);
    BOOST_CHECK(sizeof(f_32_64) == 16);
    
    fixed_64_8  f_64_8;
    fixed_64_16 f_64_16;
    fixed_64_32 f_64_32;
    fixed_64_64 f_64_64;
    BOOST_CHECK(sizeof(f_64_8)  == 16);
    BOOST_CHECK(sizeof(f_64_16) == 16);
    BOOST_CHECK(sizeof(f_64_32) == 16);
    BOOST_CHECK(sizeof(f_64_64) == 16);
}

BOOST_AUTO_TEST_CASE(t1)
{
    /* TEST CASE 1: Test basic comparisons
     */ 
    fixed_64_64 val1(100, 1, 0);
    fixed_64_64 val2(100, 1);
    
    BOOST_CHECK(  (val1 == val2));
    BOOST_CHECK(! (val1 != val2));
    BOOST_CHECK(! (val1 > val2));
    BOOST_CHECK(! (val1 < val2));
    BOOST_CHECK(  (val1 >= val2));
    BOOST_CHECK(  (val1 <= val2));
    
    val2.assignUnscaledFrac(100, 1, 18);
    
    BOOST_CHECK(! (val1 == val2));
    BOOST_CHECK(  (val1 != val2));
    BOOST_CHECK(  (val1 > val2));
    BOOST_CHECK(! (val1 < val2));
    BOOST_CHECK(  (val1 >= val2));
    BOOST_CHECK(! (val1 <= val2));
}

BOOST_AUTO_TEST_CASE(t2)
{
    /* TEST CASE 2: Test parsing with leading zeros.
     */ 
    fixed_64_64 val1;
    fixed_64_64 val2;

    // 1
    val1.assign(1, 2000000000000000000);
    val2.parse("1.2");
    BOOST_CHECK(val1 == val2);

    // 2
    val1.assign(1, 200000000000000000);
    val2.parse("1.02");
    BOOST_CHECK(val1 == val2);

    // 3
    val1.assign(1, 20000000000000000);
    val2.parse("1.002");
    BOOST_CHECK(val1 == val2);

    // 4
    val1.assign(1, 2000000000000000);
    val2.parse("1.0002");
    BOOST_CHECK(val1 == val2);

    // 5
    val1.assign(1, 200000000000000);
    val2.parse("1.00002");
    BOOST_CHECK(val1 == val2);

    // 6
    val1.assign(1, 20000000000000);
    val2.parse("1.000002");
    BOOST_CHECK(val1 == val2);

    // 7
    val1.assign(1, 2000000000000);
    val2.parse("1.0000002");
    BOOST_CHECK(val1 == val2);

    // 8
    val1.assign(1, 200000000000);
    val2.parse("1.00000002");
    BOOST_CHECK(val1 == val2);

    // 9
    val1.assign(1, 20000000000);
    val2.parse("1.000000002");
    BOOST_CHECK(val1 == val2);

    // 10
    val1.assign(1, 2000000000);
    val2.parse("1.0000000002");
    BOOST_CHECK(val1 == val2);

    // 11
    val1.assign(1, 200000000);
    val2.parse("1.00000000002");
    BOOST_CHECK(val1 == val2);

    // 12
    val1.assign(1, 20000000);
    val2.parse("1.000000000002");
    BOOST_CHECK(val1 == val2);

    // 13
    val1.assign(1, 2000000);
    val2.parse("1.0000000000002");
    BOOST_CHECK(val1 == val2);

    // 14
    val1.assign(1, 200000);
    val2.parse("1.00000000000002");
    BOOST_CHECK(val1 == val2);

    // 15
    val1.assign(1, 20000);
    val2.parse("1.000000000000002");
    BOOST_CHECK(val1 == val2);

    // 16
    val1.assign(1, 2000);
    val2.parse("1.0000000000000002");
    BOOST_CHECK(val1 == val2);

    // 17
    val1.assign(1, 200);
    val2.parse("1.00000000000000002");
    BOOST_CHECK(val1 == val2);

    // 18
    val1.assign(1, 20);
    val2.parse("1.000000000000000002");
    BOOST_CHECK(val1 == val2);

    // 19
    val1.assign(1, 2);
    val2.parse("1.0000000000000000002");
    BOOST_CHECK(val1 == val2);
}

BOOST_AUTO_TEST_CASE(t3)
{
    /* TEST CASE 3: Test parsing and printing randomly generated values
     */ 
    using theType = fixed_64_64;

    uint32_t NUMBER = 1000000;
    uint64_t SEED = 0xDEADBEEF123;
    
    std::random_device dev;
    std::mt19937 rng;
    rng.seed(SEED);
    
    std::uniform_int_distribution<std::mt19937::result_type> intDigits(1, theType::integer_decimal_digits);
    std::uniform_int_distribution<std::mt19937::result_type> fracDigits(1, theType::fractional_decimal_digits);
    std::uniform_int_distribution<std::mt19937::result_type> digits(0, 9);
    
    // Generate NUMBER worth of parsable decimal number strings
    std::vector<std::string> values;
    for (uint32_t idx = 0; idx < NUMBER; ++idx)
    {
        std::ostringstream output;
        // Generate random integer number digits
        for (uint32_t integerIdx = 0; integerIdx < intDigits(rng); ++integerIdx)
        {
            int digit = digits(rng);
            
            // Integer part cannot start with a zero
            if (integerIdx == 0)
            {
                while (digit == 0)
                {
                    digit = digits(rng);
                }
            }
            
            output << digit;
        }
        
        output << ".";
        
        // Generate random fractional number digits
        int numFracDigits = fracDigits(rng);
        for (uint32_t fracIdx = 0; fracIdx < numFracDigits; ++fracIdx)
        {
            int digit = digits(rng);
            
            // Fractional part cannot end with a zero
            if (fracIdx == (numFracDigits - 1))
            {
                while (digit == 0)
                {
                    digit = digits(rng);
                }
            }
            
            output << digit;
        }
        
        values.emplace_back(output.str());
        //~ std::cout << "VAL: " << values.back() << std::endl;
    }
    
    // Now validate that the output of the parsed 
    theType fixedValue;
    std::ostringstream output;
    for (auto& value : values)
    {
        fixedValue.parse(value.c_str());
        output << fixedValue;
        
        //~ std::cout << "TEST: " << output.str() << " -- " << value << std::endl;
        BOOST_CHECK(output.str() == value);
        output.str("");
    }
}

BOOST_AUTO_TEST_CASE(t4)
{
    /* TEST CASE 4: Basic test division by unsigned integer
     */ 
    fixed_64_64 val1(18, 55, 0);
    fixed_64_64 val2(0, 1855, 0);
    
    fixed_64_64 answer = val1 / 100;
    BOOST_CHECK(answer == val2);
}


