/*
 * Copyright (C) 2018 Eistec AB
 *
 * This file is subject to the terms and conditions of the GNU Lesser
 * General Public License v2.1. See the file LICENSE in the top level
 * directory for more details.
 */

#include <string.h>
#include "embUnit.h"
#include "tests-frac.h"

#include "kernel_defines.h"
#include "frac.h"
#include "div.h"

#define ENABLE_DEBUG 0
#include "debug.h"

static const uint32_t u32_fraction_operands[] = {
    1ul,
    2ul,
    5ul,
    10ul,
    100ul,
    1000ul,
    1000000ul,
    2000000ul,
    4000000ul,
    8000000ul,
    16000000ul,
    32000000ul,
    641ul,
    274177ul,
    32768ul,
    9600ul,
    38400ul,
    115200ul,
    230400ul,
    460800ul,
    921600ul,
    4096ul,
    15625ul,
    125ul,
    1048576ul,
    0x10000000ul,
    0x1000000ul,
    1000000000ul,
    999999733ul,  /* <- prime */
    512000000ul,
    1024000000ul,
    0x40000000ul,
    0x80000000ul,
    0xc0000000ul,
    0xe0000000ul,
    0xf0000000ul,
    0xfffffff0ul,
    0xfffffff8ul,
    0xfffffffcul,
    0xfffffffeul,
    0xfffffffful,
};

static const uint32_t u32_test_values[] = {
    0ul,
    1ul,
    10ul,
    32ul,
    15625ul,
    15625ul*5,
    (15625ul*5)+1,
    0xfffful,
    0xfffful<<10,
    1234567890ul,
    99999999ul,
    1000000ul,
    115200ul,
    38400ul,
    57600ul,
    921600ul,
    32768ul,
    16000000ul,
    15999999ul,
    32767ul,
    327679999ul,
    100000000ul,
    2100012683ul,            /* <- prime */
    0x7ffffffful,
    0x80000000ul,
    0xc0000000ul,
    0xe0000000ul,
    0xf0000000ul,
    0xfffffff0ul,
    0xfffffff8ul,
    0xfffffffcul,
    0xfffffffeul,
    0xfffffffful,
};

#define N_U32_OPERANDS ARRAY_SIZE(u32_fraction_operands)
#define N_U32_VALS ARRAY_SIZE(u32_test_values)

static void test_frac_scale32(void)
{
    for (unsigned k = 0; k < N_U32_OPERANDS; ++k) {
        for (unsigned j = 0; j < N_U32_OPERANDS; ++j) {
            uint32_t num = u32_fraction_operands[j];
            uint32_t den = u32_fraction_operands[k];
            frac_t frac;
            frac_init(&frac, num, den);
            for (unsigned i = 0; i < N_U32_VALS; i++) {
                DEBUG("Scaling %" PRIu32 " by (%" PRIu32 " / %" PRIu32 "), ",
                    u32_test_values[i], num, den);
                /* intermediate result */
                volatile uint64_t tmp = (uint64_t)u32_test_values[i] * num;
                volatile uint64_t expected = tmp / (uint64_t)den;
                if (expected > 0xfffffffful) {
                    expected &= 0xfffffffful;
                }
                uint32_t actual = frac_scale(&frac, u32_test_values[i]);
                if ((uint32_t)expected != actual) {
                    int32_t diff = actual - expected;
                    DEBUG("%" PRIu32 " * (%" PRIu32 " / %" PRIu32 ")"
                        " tmp %" PRIu64 " expect %" PRIu32 ", actual %" PRIu32 ", diff = %" PRId32 " shift=%u\n",
                        u32_test_values[i], num, den, tmp, (uint32_t)expected,
                        actual, diff, frac.shift);

                    /* The frac algorithm sacrifices accuracy for speed,
                     * some large numbers will be incorrectly rounded,
                     * resulting in small differences here.. */
                    uint32_t max_error = frac_scale(&frac, 2);
                    max_error = max_error ? max_error : 1;
                    TEST_ASSERT_EQUAL_INT(1, diff >= 0);
                    if ((uint32_t)diff <= max_error) {
                        continue;
                    }
                }
                TEST_ASSERT_EQUAL_INT((uint32_t)expected, (uint32_t)actual);
            }
        }
    }
}

Test *tests_frac_tests(void)
{
    EMB_UNIT_TESTFIXTURES(fixtures) {
        new_TestFixture(test_frac_scale32),
    };

    EMB_UNIT_TESTCALLER(frac_tests, NULL, NULL, fixtures);

    return (Test *)&frac_tests;
}

void tests_frac(void)
{
    TESTS_RUN(tests_frac_tests());
}
