#include "nail_led_mapper.h"

#include <stdio.h>
#include <string.h>

#define CHECK(condition) do { \
    ++checks; \
    if (!(condition)) { \
        fprintf(stderr, "FAIL line %d: %s\n", __LINE__, #condition); \
        return 1; \
    } \
} while (0)

static unsigned checks;
static uint8_t weights[3][NLM_GRID_CELLS];
static uint8_t max_pwm[3];
static uint8_t nail_probability[NLM_GRID_CELLS];

static nlm_profile profile(void)
{
    nlm_profile value = {
        .optical_calibration_id = 0x41u,
        .channels = 3u,
        .calibrated = true,
        .irradiance_weights = weights,
        .max_pwm = max_pwm
    };
    return value;
}

static nlm_policy policy(void)
{
    nlm_policy value = {
        .optical_calibration_id = 0x41u,
        .minimum_nail_energy_permille = 900u,
        .minimum_model_confidence = 200u,
        .requested_pwm = 180u
    };
    return value;
}

static nlm_frame frame(void)
{
    nlm_frame value = {
        .optical_calibration_id = 0x41u,
        .frame_seq = 12u,
        .capture_baseline_seq = 10u,
        .min_source_frame_seq = 11u,
        .last_accepted_frame_seq = 10u,
        .exposure_started_after_request = true,
        .model_valid = true,
        .hand_present = true,
        .model_confidence = 240u,
        .nail_probability = nail_probability
    };
    return value;
}

static void fill_fixture(void)
{
    memset(weights, 0, sizeof(weights));
    memset(max_pwm, 0, sizeof(max_pwm));
    memset(nail_probability, 0, sizeof(nail_probability));
    weights[0][0] = 255u;
    weights[1][0] = 9u;
    weights[1][1] = 1u;
    weights[2][2] = 255u;
    max_pwm[0] = 120u;
    max_pwm[1] = 255u;
    max_pwm[2] = 255u;
    nail_probability[0] = 255u;
}

static int check_fail_dark(nlm_profile *p, nlm_policy *q, nlm_frame *f,
                           nlm_status expected)
{
    nlm_result result;
    memset(&result, 0xA5, sizeof(result));
    const nlm_status status = nlm_map_frame(p, q, f, &result);
    CHECK(status == expected);
    CHECK(result.channels == 0u);
    for (uint16_t i = 0u; i < NLM_MAX_CHANNELS; ++i) {
        CHECK(result.channel_mask[i] == 0u);
        CHECK(result.pwm[i] == 0u);
        CHECK(result.nail_energy_permille[i] == 0u);
    }
    return 0;
}

int main(void)
{
    fill_fixture();
    nlm_profile p = profile();
    nlm_policy q = policy();
    nlm_frame f = frame();
    nlm_result result;

    CHECK(nlm_map_frame(NULL, &q, &f, &result) == NLM_INVALID_ARGUMENT);
    CHECK(nlm_map_frame(&p, NULL, &f, &result) == NLM_INVALID_ARGUMENT);
    CHECK(nlm_map_frame(&p, &q, &f, NULL) == NLM_INVALID_ARGUMENT);

    p.calibrated = false;
    CHECK(check_fail_dark(&p, &q, &f, NLM_UNCALIBRATED) == 0);
    p.calibrated = true;

    q.minimum_nail_energy_permille = 0u;
    CHECK(check_fail_dark(&p, &q, &f, NLM_INVALID_POLICY) == 0);
    q = policy();
    q.minimum_nail_energy_permille = 1001u;
    CHECK(check_fail_dark(&p, &q, &f, NLM_INVALID_POLICY) == 0);
    q = policy();
    q.minimum_model_confidence = 0u;
    CHECK(check_fail_dark(&p, &q, &f, NLM_INVALID_POLICY) == 0);
    q = policy();

    f.optical_calibration_id++;
    CHECK(check_fail_dark(&p, &q, &f, NLM_STALE_FRAME) == 0);
    f = frame();
    f.exposure_started_after_request = false;
    CHECK(check_fail_dark(&p, &q, &f, NLM_STALE_FRAME) == 0);
    f = frame();
    f.frame_seq = f.capture_baseline_seq;
    CHECK(check_fail_dark(&p, &q, &f, NLM_STALE_FRAME) == 0);
    f = frame();
    f.frame_seq = f.min_source_frame_seq - 1u;
    CHECK(check_fail_dark(&p, &q, &f, NLM_STALE_FRAME) == 0);
    f = frame();
    f.frame_seq = f.last_accepted_frame_seq;
    CHECK(check_fail_dark(&p, &q, &f, NLM_STALE_FRAME) == 0);
    f = frame();
    f.nail_probability = NULL;
    CHECK(check_fail_dark(&p, &q, &f, NLM_STALE_FRAME) == 0);
    f = frame();
    f.model_valid = false;
    CHECK(check_fail_dark(&p, &q, &f, NLM_INVALID_MODEL) == 0);
    f = frame();
    f.model_confidence = (uint8_t)(q.minimum_model_confidence - 1u);
    CHECK(check_fail_dark(&p, &q, &f, NLM_INVALID_MODEL) == 0);
    f = frame();
    f.hand_present = false;
    CHECK(check_fail_dark(&p, &q, &f, NLM_NO_HAND) == 0);

    f = frame();
    CHECK(nlm_map_frame(&p, &q, &f, &result) == NLM_OK);
    CHECK(result.channels == 3u);
    CHECK(result.channel_mask[0] == 1u);
    CHECK(result.pwm[0] == 120u);
    CHECK(result.nail_energy_permille[0] == 1000u);
    CHECK(result.channel_mask[1] == 1u);
    CHECK(result.nail_energy_permille[1] == 900u);
    CHECK(result.pwm[1] == 180u);
    CHECK(result.channel_mask[2] == 0u);
    CHECK(result.pwm[2] == 0u);
    CHECK(result.nail_energy_permille[2] == 0u);

    nail_probability[0] = 254u;
    nail_probability[1] = 0u;
    CHECK(nlm_map_frame(&p, &q, &f, &result) == NLM_OK);
    CHECK(result.channel_mask[1] == 0u);
    CHECK(result.pwm[1] == 0u);
    CHECK(result.nail_energy_permille[1] == 896u);

    nail_probability[0] = 255u;
    q.requested_pwm = 0u;
    CHECK(nlm_map_frame(&p, &q, &f, &result) == NLM_OK);
    CHECK(result.channel_mask[0] == 0u);
    CHECK(result.pwm[0] == 0u);

    p.channels = (uint16_t)(NLM_MAX_CHANNELS + 1u);
    CHECK(check_fail_dark(&p, &q, &f, NLM_INVALID_ARGUMENT) == 0);

    printf("PASS: %u nail-to-LED mapper assertions\n", checks);
    return 0;
}
