#include <charconv>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <random>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

#include "fixed.h"

// g++ -std=c++17 -O3 parse.cc -o parse

void genRandomNumStrings(
    uint32_t number, 
    std::vector<std::string>& values,
    uint64_t seed)
{
    values.clear();
    
    std::random_device dev;
    //~ std::mt19937 rng(dev());
    std::mt19937 rng;
    rng.seed(seed);
    std::uniform_int_distribution<std::mt19937::result_type> numDigits(1, 20);
    std::uniform_int_distribution<std::mt19937::result_type> digits(0, 9);
    
    for (uint32_t idx = 0; idx < number; ++idx)
    {
        std::ostringstream output;
        // Generate random whole number digits
        for (uint32_t wholeIdx = 0; wholeIdx < numDigits(rng); ++wholeIdx)
        {
            output << digits(rng);
        }
        
        output << ".";
        
        // Generate random fractional number digits
        for (uint32_t fracIdx = 0; fracIdx < numDigits(rng); ++fracIdx)
        {
            output << digits(rng);
        }
        
        values.emplace_back(output.str());
        //~ std::cout << "VAL: " << values.back() << std::endl;
    }
}

int main(int argc, char** argv)
{
    std::cout << "Long Double Parse Test Util\n" << std::endl;
    
    if (argc < 3)
    {
        std::cerr << "ERROR: not enough arguments" << std::endl;
        return -1;
    }
    
    uint32_t parseType = std::strtoul(argv[1], nullptr, 10);
    uint32_t numRandomStrings = std::strtoul(argv[2], nullptr, 10);
    uint64_t seed = 0xDEADBEEF;
    if (argc > 3)
    {
        seed = std::strtoull(argv[3], nullptr, 0);
    }
        
    std::vector<std::string> values;
    {
        auto start = std::chrono::system_clock::now();
        genRandomNumStrings(numRandomStrings, values, seed);
        auto end = std::chrono::system_clock::now();
        auto diff = end - start;
        std::cout << "Time to generate " << numRandomStrings << " values: "
                  << std::chrono::duration <double, std::milli>(diff).count() 
                  << " ms" << std::endl;
    }
    
    long double value = 0.0;
    fixed_64_64 fvalue;
    
    // 0) strtold() 
    if (parseType == 0)
    {
        std::cout << "Parse type 0: std::strtold()" << std::endl;
        auto start = std::chrono::system_clock::now();
        for (auto& strValue : values)
        {
            value = std::strtold(strValue.c_str(), nullptr);
            //~ std::cout << "In: " << strValue << " -- Out: " << value << std::endl;
        }
        auto end = std::chrono::system_clock::now();
        auto diff = end - start;
        std::cout << "Total parse time: "
                  << std::chrono::duration <double, std::milli>(diff).count() 
                  << " ms" << std::endl;
    }
    // 1) stold()
    else if (parseType == 1)
    {
        std::cout << "Parse type 1: std::stold()" << std::endl;
        auto start = std::chrono::system_clock::now();
        for (auto& strValue : values)
        {
            value = std::stold(strValue.c_str(), nullptr);
            //~ std::cout << "In: " << strValue << " -- Out: " << value << std::endl;
        }
        auto end = std::chrono::system_clock::now();
        auto diff = end - start;
        std::cout << "Total parse time: "
                  << std::chrono::duration <double, std::milli>(diff).count() 
                  << " ms" << std::endl;
    }
    // 2) istringstream
    else if (parseType == 2)
    {
        std::cout << "Parse type 2: std::istringstream" << std::endl;
        auto start = std::chrono::system_clock::now();
        std::istringstream stream;
        for (auto& strValue : values)
        {
            stream.clear();
            stream.str(strValue);
            stream >> value;
            //~ std::cout << "In: " << strValue << " -- Out: " << value << std::endl;
        }
        auto end = std::chrono::system_clock::now();
        auto diff = end - start;
        std::cout << "Total parse time: "
                  << std::chrono::duration <double, std::milli>(diff).count() 
                  << " ms" << std::endl;
    }
    // 3) 
    else if (parseType == 3)
    {
        std::cout << "Parse type 3: fixed_64_64" << std::endl;
        auto start = std::chrono::system_clock::now();
        for (auto& strValue : values)
        {
            fvalue.parse(strValue.c_str());
            //~ std::cout << "In: " << strValue << " -- Out: " << fvalue << std::endl;
        }
        auto end = std::chrono::system_clock::now();
        auto diff = end - start;
        std::cout << "Total parse time: "
                  << std::chrono::duration <double, std::milli>(diff).count() 
                  << " ms" << std::endl;
        
    }
#if 0 // This needs >= gcc 11
    // 4) from_chars()
    else if (parseType == 4)
    {
        std::cout << "Parse type 4: std::from_chars()" << std::endl;
        auto start = std::chrono::system_clock::now();
        for (auto& strValue : values)
        {
            std::from_chars(strValue.begin(), strValue.end(),
                            value, std::chars_format::general);
            //~ std::cout << "In: " << strValue << " -- Out: " << value << std::endl;
        }
        auto end = std::chrono::system_clock::now();
        auto diff = end - start;
        std::cout << "Total parse time: "
                  << std::chrono::duration <double, std::milli>(diff).count() 
                  << " ms" << std::endl;
    }
#endif
    else
    {
        std::cerr << "ERROR: invalid parse type: " << parseType << std::endl;
        return -2;
    }
    
    return 0;
}    
