// Mide la multiplicación de la escuela contra Karatsuba, con -O2 y sin sanitizers.
// Los tiempos dependen de la máquina y de esta implementación. Cada dato es la mediana de varias corridas,
// y cada corrida repite la multiplicación hasta sumar al menos unos milisegundos.
//
//   build/karatsuba_bench            hasta n = 65536
//   build/karatsuba_bench 262144     hasta el n que se indique, potencia de 2; tarda varios minutos
#include <cstdio>
#include <cstdlib>
#include <random>
#include <vector>

#include "bench.h"
#include "karatsuba.h"

static Digits random_number(int n, std::mt19937& rng) {
    Digits a(n);
    for (int& digit : a) {
        digit = static_cast<int>(rng() % 10);
    }
    a[n - 1] = 1 + static_cast<int>(rng() % 9);
    return a;
}

// Microsegundos por multiplicación: 7 corridas, o 3 si cada una tarda más de un segundo.
template <typename Fn>
double micros_per_call(Fn fn) {
    int repeats = 1;
    double first = 0;
    while (true) {
        first = median_seconds(1, [&] {
            for (int i = 0; i < repeats; i++) {
                fn();
            }
        });
        if (first >= 0.005) {
            break;
        }
        repeats *= 2;
    }
    int runs = first > 1.0 ? 3 : 7;
    return 1e6 * median_seconds(runs, [&] {
               for (int i = 0; i < repeats; i++) {
                   fn();
               }
           }) / repeats;
}

int main(int argc, char** argv) {
    int largest = argc > 1 ? std::atoi(argv[1]) : 65536;
    std::mt19937 rng(2026);
    const std::size_t hybrid = 128;
    std::printf("microsegundos por multiplicacion\n");
    std::printf("%8s %12s %12s %12s %9s %9s\n", "n", "escuela", "umbral 3", "umbral 128", "esc/u3", "esc/u128");
    for (int n = 16; n <= largest; n *= 2) {
        Digits x = random_number(n, rng);
        Digits y = random_number(n, rng);
        double school = micros_per_call([&] {
            long long ignored = 0;
            keep(school_multiply(x, y, ignored).size());
        });
        double pure = micros_per_call([&] {
            long long ignored = 0;
            keep(karatsuba(x, y, 3, ignored).size());
        });
        double mixed = micros_per_call([&] {
            long long ignored = 0;
            keep(karatsuba(x, y, hybrid, ignored).size());
        });
        std::printf("%8d %12.1f %12.1f %12.1f %9.2f %9.2f\n", n, school, pure, mixed, school / pure, school / mixed);
    }

    const int n = 16384;
    Digits x = random_number(n, rng);
    Digits y = random_number(n, rng);
    std::printf("\nn = %d con distintos umbrales\n", n);
    std::printf("%8s %12s\n", "umbral", "microseg");
    for (std::size_t threshold : {3, 8, 16, 32, 64, 128, 256, 512, 1024}) {
        double kar = micros_per_call([&] {
            long long ignored = 0;
            keep(karatsuba(x, y, threshold, ignored).size());
        });
        std::printf("%8zu %12.1f\n", threshold, kar);
    }
}
