//  ================================================================
//
//    test.h
//    https://guattari.tech/git/kit
//
//  Header-only unit-testing and microbenchmarks library for C.
//
//
//    - Define a unique KIT_TEST_FILE for each file to avoid name
//      collisions.
//
//    - Define KIT_TEST_IMPLEMENTATION to include the implementation.
//
//
//  Optional settings
//
//  - KIT_TESTS_SIZE_LIMIT
//  - KIT_TEST_ASSERTIONS_LIMIT
//  - KIT_BENCHS_SIZE_LIMIT
//  - KIT_BENCH_MAX_REPEATS
//  - KIT_BENCH_MAX_CYCLES
//  - KIT_BENCH_REPEATS_DEFAULT_1
//  - KIT_BENCH_REPEATS_DEFAULT_2
//
//
//  Usage example
//
//    //  foo.test.c
//    #define KIT_TEST_FILE foo //  This is required if you want to
//                              //  use multiple test files.
//    #include "test.h"
//    TEST("foo") {
//      REQUIRE(1);
//      REQUIRE_EQ(2 + 2, 4);
//    }
//
//    //  main.c
//    #define KIT_TEST_IMPLEMENTATION
//    #include "test.h"
//    int main(int argc, char **argv) {
//      return run_tests(argc, argv);
//    }
//
//  ================================================================
//
//  The MIT License
//
//  Copyright (c) 2022-2024 Mitya Selivanov
//
//  Permission is hereby granted, free of charge, to any person
//  obtaining a copy of this software and associated documentation
//  files (the "Software"), to deal in the Software without
//  restriction, including without limitation the rights to use, copy,
//  modify, merge, publish, distribute, sublicense, and/or sell copies
//  of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be
//  included in all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
//  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//  DEALINGS IN THE SOFTWARE.
//
//  ================================================================

#ifndef KIT_TEST_H
#define KIT_TEST_H

#ifdef __cplusplus
extern "C" {
#endif

#ifndef _GNU_SOURCE
#  define _GNU_SOURCE
#endif

#include <stddef.h>
#include <stdint.h>

#ifndef KIT_TEST_FILE
#  define KIT_TEST_FILE kit_test
#endif

#ifndef KIT_TESTS_SIZE_LIMIT
#  define KIT_TESTS_SIZE_LIMIT 0x1000
#endif

#ifndef KIT_TEST_ASSERTIONS_LIMIT
#  define KIT_TEST_ASSERTIONS_LIMIT 0x50
#endif

#ifndef KIT_BENCHS_SIZE_LIMIT
#  define KIT_BENCHS_SIZE_LIMIT 200
#endif

#ifndef KIT_BENCH_MAX_REPEATS
#  define KIT_BENCH_MAX_REPEATS 4000
#endif

#ifndef KIT_BENCH_MAX_CYCLES
#  define KIT_BENCH_MAX_CYCLES 40
#endif

#ifndef KIT_BENCH_REPEATS_DEFAULT_1
#  define KIT_BENCH_REPEATS_DEFAULT_1 40
#endif

#ifndef KIT_BENCH_REPEATS_DEFAULT_2
#  define KIT_BENCH_REPEATS_DEFAULT_2 400
#endif

typedef void (*kit_test_report_fn)(int test_index, int line,
                                   int64_t value, int64_t expected);
typedef void (*kit_test_run_fn)(
    int kit_test_index_, kit_test_report_fn kit_test_report_fn_);

typedef struct {
  char const     *test_name;
  char const     *test_file;
  kit_test_run_fn test_fn;
  int             assertions;
  int             line[KIT_TEST_ASSERTIONS_LIMIT];
  int             status[KIT_TEST_ASSERTIONS_LIMIT];
  int64_t         value[KIT_TEST_ASSERTIONS_LIMIT];
  int64_t         expected[KIT_TEST_ASSERTIONS_LIMIT];
  int             signal;
} kit_test_case_t;

typedef struct {
  int             size;
  kit_test_case_t v[KIT_TESTS_SIZE_LIMIT];
} kit_tests_list_t;

extern kit_tests_list_t kit_tests_list;

#define KIT_TEST_CONCAT4_(a, b, c, d) a##b##c##d
#define KIT_TEST_CONCAT3_(a, b, c) KIT_TEST_CONCAT4_(a, b, _, c)

#ifdef __cplusplus
#  define KIT_TEST_ON_START_(f)                              \
    static void f(void);                                     \
    static int  KIT_TEST_CONCAT3_(_kit_test_init_, __LINE__, \
                                  f) = (f(), 0);             \
    static void f(void)
#else
#  ifdef _MSC_VER
#    pragma section(".CRT$XCU", read)
#    define KIT_TEST_ON_START_2_(f, p)                               \
      static void f(void);                                           \
      __declspec(allocate(".CRT$XCU")) void (*f##_)(void) = f;       \
      __pragma(comment(linker, "/include:" p #f "_")) static void f( \
          void)
#    ifdef _WIN64
#      define KIT_TEST_ON_START_(f) KIT_TEST_ON_START_2_(f, "")
#    else
#      define KIT_TEST_ON_START_(f) KIT_TEST_ON_START_2_(f, "_")
#    endif
#  else
#    define KIT_TEST_ON_START_(f)                       \
      static void f(void) __attribute__((constructor)); \
      static void f(void)
#  endif
#endif

void kit_test_register(char const *name, char const *file,
                       kit_test_run_fn fn);

#define KIT_TEST(name)                                              \
  static void KIT_TEST_CONCAT3_(kit_test_run_, __LINE__,            \
                                KIT_TEST_FILE)(int,                 \
                                               kit_test_report_fn); \
  KIT_TEST_ON_START_(                                               \
      KIT_TEST_CONCAT3_(kit_test_case_, __LINE__, KIT_TEST_FILE)) { \
    kit_test_register(                                              \
        name, __FILE__,                                             \
        KIT_TEST_CONCAT3_(kit_test_run_, __LINE__, KIT_TEST_FILE)); \
  }                                                                 \
  static void KIT_TEST_CONCAT3_(kit_test_run_, __LINE__,            \
                                KIT_TEST_FILE)(                     \
      int kit_test_index_, kit_test_report_fn kit_test_report_fn_)

#define KIT_REQUIRE(...) \
  kit_test_report_fn_(kit_test_index_, __LINE__, (__VA_ARGS__), 1)

#define KIT_REQUIRE_EQ(...) \
  kit_test_report_fn_(kit_test_index_, __LINE__, __VA_ARGS__)

int kit_run_tests(int argc, char **argv);

typedef void (*kit_bench_set_repeats_limit_fn)(int bench_index,
                                               int repeats_limit);
typedef int (*kit_bench_loop_fn)(int bench_index);
typedef void (*kit_bench_begin_fn)(int bench_index);
typedef void (*kit_bench_end_fn)(int bench_index);

typedef void (*kit_bench_run_fn)(
    int                            kit_bench_index_,
    kit_bench_set_repeats_limit_fn kit_bench_set_repeats_limit_,
    kit_bench_loop_fn              kit_bench_loop_,
    kit_bench_begin_fn             kit_bench_begin_,
    kit_bench_end_fn               kit_bench_end_);

typedef struct {
  char const      *bench_name;
  char const      *bench_file;
  kit_bench_run_fn bench_fn;
  int64_t          sec[KIT_BENCH_MAX_REPEATS];
  int32_t          nsec[KIT_BENCH_MAX_REPEATS];
  int64_t          duration_nsec[KIT_BENCH_MAX_REPEATS];
  int64_t          duration_sorted_nsec[KIT_BENCH_MAX_REPEATS];
  int              repeats;
  int              cycles_size;
  int              cycles[KIT_BENCH_MAX_CYCLES];
  int              cycle;
  int              signal;
  int              ready;
} kit_benchmark_t;

typedef struct {
  int             size;
  kit_benchmark_t v[KIT_BENCHS_SIZE_LIMIT];
} kit_benchs_list_t;

extern kit_benchs_list_t kit_benchs_list;

void kit_bench_register(char const *name, char const *file,
                        kit_bench_run_fn fn);

#define KIT_BENCHMARK(name)                                          \
  static void KIT_TEST_CONCAT3_(kit_bench_run_, __LINE__,            \
                                KIT_TEST_FILE)(                      \
      int, kit_bench_set_repeats_limit_fn, kit_bench_loop_fn,        \
      kit_bench_begin_fn, kit_bench_end_fn);                         \
  KIT_TEST_ON_START_(                                                \
      KIT_TEST_CONCAT3_(kit_benchmark_, __LINE__, KIT_TEST_FILE)) {  \
    kit_bench_register(                                              \
        name, __FILE__,                                              \
        KIT_TEST_CONCAT3_(kit_bench_run_, __LINE__, KIT_TEST_FILE)); \
  }                                                                  \
  static void KIT_TEST_CONCAT3_(kit_bench_run_, __LINE__,            \
                                KIT_TEST_FILE)(                      \
      int                            kit_bench_index_,               \
      kit_bench_set_repeats_limit_fn kit_bench_set_repeats_limit_,   \
      kit_bench_loop_fn              kit_bench_loop_,                \
      kit_bench_begin_fn             kit_bench_begin_,               \
      kit_bench_end_fn               kit_bench_end_)

#define KIT_BENCHMARK_REPEAT(repeats_limit_) \
  kit_bench_set_repeats_limit_(kit_bench_index_, repeats_limit_)

#define KIT_BENCHMARK_BEGIN                   \
  while (kit_bench_loop_(kit_bench_index_)) { \
    kit_bench_begin_(kit_bench_index_);       \
    {

#define KIT_BENCHMARK_END           \
  }                                 \
  kit_bench_end_(kit_bench_index_); \
  }

//  FIXME
//
#define KIT_DO_NOT_OPTIMIZE(x)        \
  do {                                \
    volatile void *bench_ptr_ = &(x); \
    (void) bench_ptr_;                \
  } while (0)

int kit_run_benchmarks(int argc, char **argv);

#define TEST KIT_TEST
#define REQUIRE KIT_REQUIRE
#define REQUIRE_EQ KIT_REQUIRE_EQ
#define BENCHMARK KIT_BENCHMARK
#define BENCHMARK_REPEAT KIT_BENCHMARK_REPEAT
#define BENCHMARK_BEGIN KIT_BENCHMARK_BEGIN
#define BENCHMARK_END KIT_BENCHMARK_END
#define DO_NOT_OPTIMIZE KIT_DO_NOT_OPTIMIZE
#define test_register kit_test_register
#define run_tests kit_run_tests
#define bench_register kit_bench_register
#define run_benchmarks kit_run_benchmarks

#ifdef __cplusplus
}
#endif

#endif

#if defined(KIT_TEST_IMPLEMENTATION) && !defined(KIT_TEST_H_IMPL)
#define KIT_TEST_H_IMPL

#ifdef __cplusplus
extern "C" {
#endif

#ifndef KIT_TIME_H
#  define KIT_TIME_H

#  include <time.h>

#  ifndef TIME_UTC
#    define TIME_UTC 1
#  endif

#  ifdef KIT_REQUIRE_TIMESPEC_GET
#    ifndef WIN32_LEAN_AND_MEAN
#      define WIN32_LEAN_AND_MEAN 1
#    endif
#    include <windows.h>

#    define KIT_TIMESPEC_IMPL_UNIX_EPOCH_IN_TICKS \
      116444736000000000ull
#    define KIT_TIMESPEC_IMPL_TICKS_PER_SECONDS 10000000ull

static int timespec_get(struct timespec *ts, int base) {
  if (ts == NULL || base != TIME_UTC)
    return 0;

  FILETIME       ft;
  ULARGE_INTEGER date;
  LONGLONG       ticks;

  GetSystemTimeAsFileTime(&ft);
  date.HighPart = ft.dwHighDateTime;
  date.LowPart  = ft.dwLowDateTime;
  ticks         = (LONGLONG) (date.QuadPart -
                      KIT_TIMESPEC_IMPL_UNIX_EPOCH_IN_TICKS);
  ts->tv_sec    = ticks / KIT_TIMESPEC_IMPL_TICKS_PER_SECONDS;
  ts->tv_nsec   = (ticks % KIT_TIMESPEC_IMPL_TICKS_PER_SECONDS) * 100;

  return base;
}
#  endif

#endif

#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

enum {
  kit_white_,
  kit_blue_,
  kit_light_,
  kit_yellow_,
  kit_red_,
  kit_green_
};

static char const *kit_color_codes_[] = { "\x1b[38m", "\x1b[34m",
                                          "\x1b[37m", "\x1b[33m",
                                          "\x1b[31m", "\x1b[32m" };

static int kit_print_color_(int c) {
  return printf("%s", kit_color_codes_[c]);
}

static int kit_signums_[] = { SIGINT, SIGILL,  SIGABRT,
                              SIGFPE, SIGSEGV, SIGTERM };

static char const *kit_signames_[64];

static void kit_signames_init_(void) {
  memset(kit_signames_, 0, sizeof kit_signames_);
  kit_signames_[SIGINT]  = "Interactive attention signal";
  kit_signames_[SIGILL]  = "Illegal instruction";
  kit_signames_[SIGABRT] = "Abnormal termination";
  kit_signames_[SIGFPE]  = "Erroneous arithmetic operation";
  kit_signames_[SIGSEGV] = "Invalid access to storage";
  kit_signames_[SIGTERM] = "Termination request";
}

kit_tests_list_t kit_tests_list = { 0 };

static void kit_report_(int i, int line, int64_t value,
                        int64_t expected) {
  int n = kit_tests_list.v[i].assertions++;

  if (n >= KIT_TEST_ASSERTIONS_LIMIT)
    return;

  kit_tests_list.v[i].line[n]     = line;
  kit_tests_list.v[i].status[n]   = value == expected;
  kit_tests_list.v[i].value[n]    = value;
  kit_tests_list.v[i].expected[n] = expected;
}

static int64_t ns_to_ms(int64_t ns) {
  return (ns + 500000) / 1000000;
}

static int64_t sec_to_ms(int64_t sec) {
  return 1000 * sec;
}

void kit_test_register(char const *name, char const *file,
                       kit_test_run_fn fn) {
  int n = kit_tests_list.size++;
  if (n < KIT_TESTS_SIZE_LIMIT) {
    kit_tests_list.v[n].test_fn    = fn;
    kit_tests_list.v[n].test_name  = name;
    kit_tests_list.v[n].test_file  = file;
    kit_tests_list.v[n].assertions = 0;
  }
}

static jmp_buf kit_test_restore_execution;

static void kit_test_handle_signal(int signum) {
  longjmp(kit_test_restore_execution, signum);
}

static void kit_test_setup_signals() {
  for (int i = 0; i < sizeof kit_signums_ / sizeof *kit_signums_; i++)
    signal(kit_signums_[i], kit_test_handle_signal);
}

static int kit_run_test_(volatile int i) {
  int signum = setjmp(kit_test_restore_execution);

  if (signum != 0) {
    kit_tests_list.v[i].signal = signum;
    return 0;
  }

  kit_tests_list.v[i].test_fn(i, kit_report_);
  return 1;
}

int kit_run_tests(int argc, char **argv) {
  kit_signames_init_();

  int success_count         = 0;
  int fail_assertion_count  = 0;
  int total_assertion_count = 0;
  int status                = 0;
  int quiet                 = 0;
  int no_color              = 0;
  int line_width            = 20;
  int carriage_return       = 1;

  int i, j;

  char const *specific_test = NULL;

  kit_test_setup_signals();

  for (i = 0; i < argc; i++)
    if (strcmp("--no-term-color", argv[i]) == 0)
      no_color = 1;
    else if (strcmp("--no-carriage-return", argv[i]) == 0)
      carriage_return = 0;
    else if (strcmp("--quiet", argv[i]) == 0)
      quiet = 1;
    else if (strcmp("--match", argv[i]) == 0)
      specific_test = argv[++i];

  quiet && (no_color = 1);

  if (specific_test != NULL) {
    no_color || kit_print_color_(kit_light_);
    quiet || printf("Run tests matching ");
    no_color || kit_print_color_(kit_white_);
    quiet || printf("*%s*", specific_test);
    no_color || kit_print_color_(kit_light_);
    quiet || printf("\n\n");
  }

  char const *file        = NULL;
  ptrdiff_t   file_root   = -1;
  int         tests_total = 0;

  for (i = 0; i < kit_tests_list.size && i < KIT_TESTS_SIZE_LIMIT;
       i++) {
    if (specific_test != NULL &&
        strstr(kit_tests_list.v[i].test_name, specific_test) == NULL)
      continue;
    tests_total++;
    int l = 2 + (int) strlen(kit_tests_list.v[i].test_name);
    if (line_width < l)
      line_width = l;
  }

  if (tests_total > 0) {
    char const *s = kit_tests_list.v[0].test_file;

    for (j = 1; j < kit_tests_list.size && j < KIT_TESTS_SIZE_LIMIT;
         j++) {
      if (specific_test != NULL &&
          strstr(kit_tests_list.v[j].test_name, specific_test) ==
              NULL)
        continue;
      if (strcmp(s, kit_tests_list.v[j].test_file) == 0)
        continue;
      int k = 0;
      for (;
           s[k] != '\0' && kit_tests_list.v[j].test_file[k] != '\0' &&
           s[k] == kit_tests_list.v[j].test_file[k];
           k++) { }
      if (file_root == -1 || file_root > k)
        file_root = k;
    }

    if (file_root == -1) {
      for (i = 0; s[i] != '\0'; i++)
        if (s[i] == '/' || s[i] == '\\')
          file_root = i + 1;
    }
  }

  for (i = 0; i < kit_tests_list.size && i < KIT_TESTS_SIZE_LIMIT;
       i++) {
    if (specific_test != NULL &&
        strstr(kit_tests_list.v[i].test_name, specific_test) == NULL)
      continue;
    if (file == NULL ||
        strcmp(file, kit_tests_list.v[i].test_file) != 0) {
      if (file != NULL)
        quiet || printf("\n");
      file = kit_tests_list.v[i].test_file;
      no_color || kit_print_color_(kit_blue_);
      quiet || printf("* ");
      no_color || kit_print_color_(kit_white_);
      quiet || printf("%s\n", file + file_root);
    }

    !carriage_return || no_color || kit_print_color_(kit_yellow_);
    carriage_return || no_color || kit_print_color_(kit_light_);
    quiet || printf("` %s ", kit_tests_list.v[i].test_name);
    !carriage_return || quiet || printf("\r");
    quiet || fflush(stdout);

    struct timespec begin, end;
    timespec_get(&begin, TIME_UTC);

    int test_status = kit_run_test_(i);

    timespec_get(&end, TIME_UTC);
    int duration = (int) (ns_to_ms(end.tv_nsec - begin.tv_nsec) +
                          sec_to_ms(end.tv_sec - begin.tv_sec));

    for (j = 0; j < kit_tests_list.v[i].assertions &&
                j < KIT_TEST_ASSERTIONS_LIMIT;
         j++)
      if (kit_tests_list.v[i].status[j] == 0) {
        fail_assertion_count++;
        test_status = 0;
      }

    if (kit_tests_list.v[i].assertions > KIT_TEST_ASSERTIONS_LIMIT)
      test_status = 0;

    total_assertion_count += kit_tests_list.v[i].assertions;

    !carriage_return || no_color || kit_print_color_(kit_light_);
    !carriage_return || quiet ||
        printf("` %s ", kit_tests_list.v[i].test_name);

    int l = (int) strlen(kit_tests_list.v[i].test_name);
    quiet || printf("%*c", line_width - l, ' ');

    if (test_status == 0) {
      no_color || kit_print_color_(kit_red_);
      quiet || printf("FAIL");
      no_color || kit_print_color_(kit_light_);
      duration == 0 || quiet || printf(" %d ms", duration);
      quiet || printf("\n");
      status = 1;
    } else {
      no_color || kit_print_color_(kit_green_);
      quiet || printf("OK");
      no_color || kit_print_color_(kit_light_);
      duration == 0 || quiet || printf("   %d ms", duration);
      quiet || printf("\n");
      success_count++;
    }

    quiet || fflush(stdout);
  }

  no_color || kit_print_color_(kit_white_);
  quiet || printf("\n%d of %d tests passed.\n", success_count,
                  tests_total);
  quiet || printf("%d of %d assertions passed.\n\n",
                  total_assertion_count - fail_assertion_count,
                  total_assertion_count);

  no_color || kit_print_color_(kit_light_);

  if (!quiet && status != 0) {
    int have_kit_report_s = 0;

    for (i = 0; i < kit_tests_list.size && i < KIT_TESTS_SIZE_LIMIT;
         i++) {
      if (specific_test != NULL &&
          strstr(kit_tests_list.v[i].test_name, specific_test) ==
              NULL)
        continue;
      if (kit_tests_list.v[i].signal != 0) {
        int signum = kit_tests_list.v[i].signal;
        if (signum >= 0 &&
            signum < sizeof kit_signames_ / sizeof *kit_signames_ &&
            kit_signames_[signum] != NULL) {
          no_color || kit_print_color_(kit_light_);
          printf("Signal \"%s\" (%d) for \"", kit_signames_[signum],
                 signum);
          no_color || kit_print_color_(kit_white_);
          printf("%s", kit_tests_list.v[i].test_name);
          no_color || kit_print_color_(kit_light_);
          printf("\" in \"");
          no_color || kit_print_color_(kit_white_);

          printf("%s", kit_tests_list.v[i].test_file + file_root);
          no_color || kit_print_color_(kit_light_);
          printf("\"!\n");
        } else {
          no_color || kit_print_color_(kit_light_);
          printf("Unknown signal (%d) for \"", signum);
          no_color || kit_print_color_(kit_white_);
          printf("%s", kit_tests_list.v[i].test_name);
          no_color || kit_print_color_(kit_light_);
          printf("\" in \"");
          no_color || kit_print_color_(kit_white_);

          printf("%s", kit_tests_list.v[i].test_file + file_root);
          no_color || kit_print_color_(kit_light_);
          printf("\"!\n");
        }
        have_kit_report_s = 1;
      }
      if (kit_tests_list.v[i].assertions >
          KIT_TEST_ASSERTIONS_LIMIT) {
        no_color || kit_print_color_(kit_light_);
        printf("Too many assertions for \"");
        no_color || kit_print_color_(kit_white_);
        printf("%s", kit_tests_list.v[i].test_name);
        no_color || kit_print_color_(kit_light_);
        printf("\" in \"");
        no_color || kit_print_color_(kit_white_);

        printf("%s", kit_tests_list.v[i].test_file + file_root);
        no_color || kit_print_color_(kit_light_);
        printf("\"!\n");
        have_kit_report_s = 1;
      }
    }

    have_kit_report_s &&printf("\n");
  }

  if (!quiet && status != 0) {
    for (i = 0; i < kit_tests_list.size && i < KIT_TESTS_SIZE_LIMIT;
         i++) {
      if (specific_test != NULL &&
          strstr(kit_tests_list.v[i].test_name, specific_test) ==
              NULL)
        continue;

      if (kit_tests_list.v[i].assertions <= KIT_TEST_ASSERTIONS_LIMIT)
        for (j = 0; j < kit_tests_list.v[i].assertions; j++)
          if (!kit_tests_list.v[i].status[j]) {
            no_color || kit_print_color_(kit_light_);
            printf("Assertion on line ");
            no_color || kit_print_color_(kit_white_);
            printf("%d", kit_tests_list.v[i].line[j]);
            no_color || kit_print_color_(kit_light_);
            printf(" in \"");
            no_color || kit_print_color_(kit_white_);
            printf("%s", kit_tests_list.v[i].test_file + file_root);
            no_color || kit_print_color_(kit_light_);
            printf("\" failed.\n");
            no_color || kit_print_color_(kit_red_);
            printf(" -> ");
            no_color || kit_print_color_(kit_light_);
            printf("Got wrong value ");
            no_color || kit_print_color_(kit_white_);
            printf("%10lld",
                   (long long) kit_tests_list.v[i].value[j]);
            no_color || kit_print_color_(kit_light_);
            printf("  (");
            no_color || kit_print_color_(kit_white_);
            printf("0x%08llx",
                   (unsigned long long) kit_tests_list.v[i].value[j]);
            no_color || kit_print_color_(kit_light_);
            printf(")\n");
            no_color || kit_print_color_(kit_green_);
            printf(" -> ");
            no_color || kit_print_color_(kit_light_);
            printf("Expected value  ");
            no_color || kit_print_color_(kit_white_);
            printf("%10lld",
                   (long long) kit_tests_list.v[i].expected[j]);
            no_color || kit_print_color_(kit_light_);
            printf("  (");
            no_color || kit_print_color_(kit_white_);
            printf(
                "0x%08llx",
                (unsigned long long) kit_tests_list.v[i].expected[j]);
            no_color || kit_print_color_(kit_light_);
            printf(")\n\n");
          }
    }
  }

  if (kit_tests_list.size > KIT_TESTS_SIZE_LIMIT) {
    no_color || kit_print_color_(kit_light_);
    quiet || printf("Too many tests!\n\n");
    status = 1;
  }

  if (status == 0) {
    no_color || kit_print_color_(kit_green_);
    quiet || printf("OK\n");
  } else {
    no_color || kit_print_color_(kit_red_);
    quiet || printf("FAILED\n");
  }

  no_color || kit_print_color_(kit_light_);
  quiet || printf("\n");
  return status;
}

kit_benchs_list_t kit_benchs_list = { 0 };

static void bench_set_repeats_limit(int i, int repeats_limit) {
  if (kit_benchs_list.v[i].ready)
    return;
  if (kit_benchs_list.v[i].cycles_size >= KIT_BENCH_MAX_CYCLES)
    return;
  kit_benchs_list.v[i].cycles[kit_benchs_list.v[i].cycles_size] =
      repeats_limit;
  kit_benchs_list.v[i].cycles_size++;
}

static int bench_loop(int i) {
  if (!kit_benchs_list.v[i].ready)
    return 0;
  return kit_benchs_list.v[i].repeats <
         kit_benchs_list.v[i].cycles[kit_benchs_list.v[i].cycle];
}

static void bench_begin(int i) {
  int n = kit_benchs_list.v[i].repeats++;

  if (n >= KIT_BENCH_MAX_REPEATS)
    return;

  struct timespec tv;
  timespec_get(&tv, TIME_UTC);

  kit_benchs_list.v[i].sec[n]  = (int64_t) tv.tv_sec;
  kit_benchs_list.v[i].nsec[n] = (int64_t) tv.tv_nsec;
}

static void bench_end(int i) {
  int n = kit_benchs_list.v[i].repeats - 1;

  if (n < 0 || n >= KIT_BENCH_MAX_REPEATS)
    return;

  struct timespec tv;
  timespec_get(&tv, TIME_UTC);

  int64_t sec  = ((int64_t) tv.tv_sec) - kit_benchs_list.v[i].sec[n];
  int64_t nsec = ((int64_t) tv.tv_nsec) -
                 kit_benchs_list.v[i].nsec[n];

  kit_benchs_list.v[i].duration_nsec[n] = sec * 1000000000 + nsec;
}

void kit_bench_register(char const *name, char const *file,
                        kit_bench_run_fn fn) {
  int n = kit_benchs_list.size++;
  if (n < KIT_BENCHS_SIZE_LIMIT) {
    kit_benchmark_t *bench = kit_benchs_list.v + n;

    bench->bench_fn    = fn;
    bench->bench_name  = name;
    bench->bench_file  = file;
    bench->cycles_size = 0;
    bench->ready       = 0;
  }
}

static void kit_bench_setup_signals() {
  for (int i = 0; i < sizeof kit_signums_ / sizeof *kit_signums_; i++)
    signal(kit_signums_[i], kit_test_handle_signal);
}

static int kit_run_bench_(volatile int i) {
  int signum = setjmp(kit_test_restore_execution);

  if (signum != 0) {
    kit_benchs_list.v[i].signal = signum;
    return 0;
  }

  kit_benchs_list.v[i].bench_fn(i, bench_set_repeats_limit,
                                bench_loop, bench_begin, bench_end);
  return 1;
}

static int kit_compare_64_(void const *x_, void const *y_) {
  int64_t const *x = (int64_t const *) x_;
  int64_t const *y = (int64_t const *) y_;
  return *x - *y;
}

static int kit_compare_32_(void const *x_, void const *y_) {
  int const *x = (int const *) x_;
  int const *y = (int const *) y_;
  return *x - *y;
}

int kit_run_benchmarks(int argc, char **argv) {
  int success_count   = 0;
  int status          = 0;
  int no_color        = 0;
  int line_width      = 20;
  int carriage_return = 1;

  char const *specific_bench = NULL;

  kit_bench_setup_signals();

  for (int i = 0; i < argc; i++)
    if (strcmp("--no-term-color", argv[i]) == 0)
      no_color = 1;
    else if (strcmp("--no-carriage-return", argv[i]) == 0)
      carriage_return = 0;
    else if (strcmp("--match", argv[i]) == 0)
      specific_bench = argv[++i];

  if (specific_bench != NULL) {
    no_color || kit_print_color_(kit_light_);
    printf("Run benchmarks matching ");
    no_color || kit_print_color_(kit_white_);
    printf("*%s*", specific_bench);
    no_color || kit_print_color_(kit_light_);
    printf("\n\n");
  }

  char const *file         = NULL;
  ptrdiff_t   file_root    = -1;
  int         benchs_total = 0;

  for (int i = 0;
       i < kit_benchs_list.size && i < KIT_BENCHS_SIZE_LIMIT; i++) {
    if (specific_bench != NULL &&
        strstr(kit_benchs_list.v[i].bench_name, specific_bench) ==
            NULL)
      continue;
    benchs_total++;
    int l = 2 + (int) strlen(kit_benchs_list.v[i].bench_name);
    if (line_width < l)
      line_width = l;
  }

  if (benchs_total > 0) {
    char const *s = kit_benchs_list.v[0].bench_file;

    for (int j = 1;
         j < kit_benchs_list.size && j < KIT_BENCHS_SIZE_LIMIT; j++) {
      kit_benchmark_t *bench = kit_benchs_list.v + j;

      if (specific_bench != NULL &&
          strstr(bench->bench_name, specific_bench) == NULL)
        continue;
      if (strcmp(s, bench->bench_file) == 0)
        continue;
      int k = 0;
      for (; s[k] != '\0' && bench->bench_file[k] != '\0' &&
             s[k] == bench->bench_file[k];
           k++) { }
      if (file_root == -1 || file_root > k)
        file_root = k;
    }

    if (file_root == -1) {
      for (int i = 0; s[i] != '\0'; i++)
        if (s[i] == '/' || s[i] == '\\')
          file_root = i + 1;
    }
  }

  no_color || kit_print_color_(kit_blue_);
  printf("# ");
  no_color || kit_print_color_(kit_light_);
  printf("BENCHMARK");
  printf("%*c", line_width - 9, ' ');
  no_color || kit_print_color_(kit_green_);
  printf(" LOW      ");
  no_color || kit_print_color_(kit_light_);
  printf("|");
  no_color || kit_print_color_(kit_blue_);
  printf(" MEDIAN   ");
  no_color || kit_print_color_(kit_light_);
  printf("|");
  no_color || kit_print_color_(kit_yellow_);
  printf(" HIGH\n");
  no_color || kit_print_color_(kit_light_);
  printf("                       (in microseconds)\n\n");

  /*  Prepare cycles.
   */

  for (int i = 0;
       i < kit_benchs_list.size && i < KIT_BENCHS_SIZE_LIMIT; i++) {
    kit_benchmark_t *bench = kit_benchs_list.v + i;

    if (specific_bench != NULL &&
        strstr(bench->bench_name, specific_bench) == NULL)
      continue;

    kit_run_bench_(i);

    if (bench->cycles_size == 0) {
      bench->cycles_size = 2;
      bench->cycles[0]   = KIT_BENCH_REPEATS_DEFAULT_1;
      bench->cycles[1]   = KIT_BENCH_REPEATS_DEFAULT_2;
    }

    qsort(bench->cycles, bench->cycles_size, sizeof *bench->cycles,
          kit_compare_32_);

    kit_benchs_list.v[i].ready = 1;
  }

  /*  Run cycles.
   */

  for (int cycle = 0; cycle < KIT_BENCH_MAX_CYCLES; cycle++) {
    /*  Prepare cycle.
     */

    int cycles_done = 1;

    for (int i = 0;
         i < kit_benchs_list.size && i < KIT_BENCHS_SIZE_LIMIT; i++) {
      kit_benchmark_t *bench = kit_benchs_list.v + i;

      if (specific_bench != NULL &&
          strstr(bench->bench_name, specific_bench) == NULL)
        continue;
      if (cycle >= bench->cycles_size)
        continue;

      bench->repeats = 0;
      bench->cycle   = cycle;
      cycles_done    = 0;
    }

    if (cycles_done)
      break;

    /*  Run benchmarks.
     */

    for (int i = 0;
         i < kit_benchs_list.size && i < KIT_BENCHS_SIZE_LIMIT; i++) {
      kit_benchmark_t *bench = kit_benchs_list.v + i;

      if (specific_bench != NULL &&
          strstr(bench->bench_name, specific_bench) == NULL)
        continue;
      if (cycle >= bench->cycles_size)
        continue;

      if (file == NULL || strcmp(file, bench->bench_file) != 0) {
        if (file != NULL)
          printf("\n");
        file = bench->bench_file;
        no_color || kit_print_color_(kit_blue_);
        printf("* ");
        no_color || kit_print_color_(kit_white_);
        printf("%s\n", file + file_root);
      }

      !carriage_return || no_color || kit_print_color_(kit_yellow_);
      carriage_return || no_color || kit_print_color_(kit_light_);
      printf("` %s ", bench->bench_name);
      !carriage_return || printf("\r");
      fflush(stdout);

      int bench_status = kit_run_bench_(i);

      if (bench->repeats > KIT_BENCH_MAX_REPEATS)
        bench_status = 0;

      !carriage_return || no_color || kit_print_color_(kit_light_);
      !carriage_return || printf("` %s ", bench->bench_name);

      int l = (int) strlen(bench->bench_name);
      printf("%*c", line_width - l, ' ');

      if (bench->repeats <= 0) {
        no_color || kit_print_color_(kit_yellow_);
        printf("                                0 runs\n");
        success_count++;
      } else if (bench_status == 0) {
        no_color || kit_print_color_(kit_red_);
        printf("                                FAIL\n");
        status = 1;
      } else {
        int repeats = bench->repeats;

        memcpy(bench->duration_sorted_nsec, bench->duration_nsec,
               repeats * sizeof *bench->duration_sorted_nsec);
        qsort(bench->duration_sorted_nsec, repeats,
              sizeof *bench->duration_sorted_nsec, kit_compare_64_);

        int64_t average = bench->duration_sorted_nsec[repeats / 2];
        int64_t floor   = bench->duration_sorted_nsec[repeats / 20];
        int64_t roof =
            bench->duration_sorted_nsec[repeats - repeats / 20 - 1];

        no_color || kit_print_color_(kit_white_);
        printf("%-9g", (double) floor * 0.001);
        no_color || kit_print_color_(kit_light_);
        printf("| ");
        no_color || kit_print_color_(kit_white_);
        printf("%-9g", (double) average * 0.001);
        no_color || kit_print_color_(kit_light_);
        printf("| ");
        no_color || kit_print_color_(kit_white_);
        printf("%-9g", (double) roof * 0.001);
        no_color || kit_print_color_(kit_light_);
        printf(" %d runs\n", repeats);
        success_count++;
      }
    }
  }

  printf("\n");

  if (status != 0) {
    for (int i = 0;
         i < kit_benchs_list.size && i < KIT_BENCHS_SIZE_LIMIT; i++) {
      kit_benchmark_t *bench = kit_benchs_list.v + i;

      if (specific_bench != NULL &&
          strstr(bench->bench_name, specific_bench) == NULL)
        continue;
      if (bench->signal != 0) {
        int signum = bench->signal;
        if (signum >= 0 &&
            signum < sizeof kit_signames_ / sizeof *kit_signames_ &&
            kit_signames_[signum] != NULL) {
          no_color || kit_print_color_(kit_light_);
          printf("Signal \"%s\" (%d) for \"", kit_signames_[signum],
                 signum);
          no_color || kit_print_color_(kit_white_);
          printf("%s", bench->bench_name);
          no_color || kit_print_color_(kit_light_);
          printf("\" in \"");
          no_color || kit_print_color_(kit_white_);
          printf("%s", bench->bench_file + file_root);
          no_color || kit_print_color_(kit_light_);
          printf("\"!.\n");
        } else {
          no_color || kit_print_color_(kit_light_);
          printf("Unknown signal (%d) for \"", signum);
          no_color || kit_print_color_(kit_white_);
          printf("%s", bench->bench_name);
          no_color || kit_print_color_(kit_light_);
          printf("\" in \"");
          no_color || kit_print_color_(kit_white_);
          printf("%s", bench->bench_file + file_root);
          no_color || kit_print_color_(kit_light_);
          printf("\"!.\n");
        }
      }
    }

    printf("\n");
  }

  if (kit_benchs_list.size > KIT_BENCHS_SIZE_LIMIT) {
    no_color || kit_print_color_(kit_light_);
    printf("Too many benchmarks!\n\n");
    status = 1;
  }

  if (status == 0) {
    no_color || kit_print_color_(kit_green_);
    printf("DONE\n");
  } else {
    no_color || kit_print_color_(kit_red_);
    printf("DONE WITH ERRORS\n");
  }

  no_color || kit_print_color_(kit_light_);
  printf("\n");
  return status;
}

#ifdef __cplusplus
}
#endif

#endif