#include "nail_led_mapper.h"

#include <stddef.h>
#include <string.h>

static void clear_result(nlm_result *result)
{
    if (result != NULL) {
        memset(result, 0, sizeof(*result));
    }
}

nlm_status nlm_map_frame(const nlm_profile *profile,
                         const nlm_policy *policy,
                         const nlm_frame *frame,
                         nlm_result *result)
{
    clear_result(result);
    if (profile == NULL || policy == NULL || frame == NULL || result == NULL) {
        return NLM_INVALID_ARGUMENT;
    }
    if (profile->channels == 0u || profile->channels > NLM_MAX_CHANNELS ||
        profile->optical_calibration_id == 0u ||
        profile->irradiance_weights == NULL || profile->max_pwm == NULL) {
        return NLM_INVALID_ARGUMENT;
    }
    if (!profile->calibrated) {
        return NLM_UNCALIBRATED;
    }
    if (policy->optical_calibration_id == 0u ||
        policy->optical_calibration_id != profile->optical_calibration_id ||
        policy->minimum_nail_energy_permille == 0u ||
        policy->minimum_nail_energy_permille > 1000u ||
        policy->minimum_model_confidence == 0u) {
        return NLM_INVALID_POLICY;
    }
    if (frame->optical_calibration_id != profile->optical_calibration_id ||
        !frame->exposure_started_after_request || frame->frame_seq == 0u ||
        frame->frame_seq <= frame->capture_baseline_seq ||
        frame->frame_seq < frame->min_source_frame_seq ||
        frame->frame_seq <= frame->last_accepted_frame_seq ||
        frame->nail_probability == NULL) {
        return NLM_STALE_FRAME;
    }
    if (!frame->model_valid ||
        frame->model_confidence < policy->minimum_model_confidence) {
        return NLM_INVALID_MODEL;
    }
    if (!frame->hand_present) {
        return NLM_NO_HAND;
    }

    result->channels = profile->channels;
    for (uint16_t channel = 0u; channel < profile->channels; ++channel) {
        uint32_t total_weight = 0u;
        uint64_t nail_energy = 0u;
        const uint8_t *weights = profile->irradiance_weights[channel];

        for (uint16_t cell = 0u; cell < NLM_GRID_CELLS; ++cell) {
            const uint8_t weight = weights[cell];
            total_weight += weight;
            nail_energy += (uint64_t)weight * frame->nail_probability[cell];
        }
        if (total_weight == 0u) {
            continue;
        }

        const uint64_t maximum_energy = (uint64_t)total_weight * 255u;
        const uint64_t permille =
            (nail_energy * 1000u + maximum_energy / 2u) / maximum_energy;
        const uint16_t nail_permille =
            (uint16_t)(permille > 1000u ? 1000u : permille);
        result->nail_energy_permille[channel] = nail_permille;

        /* Decide on exact energy; rounded diagnostics must not grant output. */
        if (nail_energy * 1000u >=
                maximum_energy * policy->minimum_nail_energy_permille &&
            policy->requested_pwm != 0u && profile->max_pwm[channel] != 0u) {
            result->channel_mask[channel] = 1u;
            result->pwm[channel] =
                policy->requested_pwm < profile->max_pwm[channel]
                    ? policy->requested_pwm : profile->max_pwm[channel];
        }
    }

    return NLM_OK;
}
