#include <math.h>
#include <stdbool.h>

#include "geninput/hid-mgr.h"
#include "geninput/mapper.h"

#include "util/array.h"
#include "util/log.h"
#include "util/mem.h"

struct action_iter {
    struct mapper *mapper;
    size_t i;
};

struct action_mapping {
    struct mapped_action src;
    uint8_t action;
    uint8_t page;
    uint8_t bit;
};

struct analog_mapping {
    struct mapped_analog src;
    int32_t sensitivity;
    bool bound;
    bool valid;
    bool absolute;
    double affine_scale;
    double affine_bias;
    uint8_t pos;
};

struct light_iter {
    struct mapper *mapper;
    size_t i;
};

struct light_mapping {
    struct mapped_light dest;
    uint8_t game_light;
    bool bound;
    bool valid;
    double affine_scale;
    int32_t affine_bias;
};

struct mapper {
    struct array actions;
    struct analog_mapping *analogs;
    uint8_t nanalogs;
    struct array light_maps;
    uint8_t *lights;
    uint8_t nlights;
};

static uint64_t action_mapping_update(struct action_mapping *am);
static void analog_mapping_bind(struct analog_mapping *am);
static void analog_mapping_update(struct analog_mapping *am);
static void light_mapping_bind(struct light_mapping *lm);
static void
light_mapping_send(struct light_mapping *lm, const struct mapper *m);

struct mapper *mapper_inst;

void action_iter_get_mapping(action_iter_t iter, struct mapped_action *ma)
{
    struct action_mapping *am;

    log_assert(action_iter_is_valid(iter));

    am = array_item(struct action_mapping, &iter->mapper->actions, iter->i);
    *ma = am->src;
}

uint8_t action_iter_get_action(action_iter_t iter)
{
    struct action_mapping *am;

    log_assert(action_iter_is_valid(iter));

    am = array_item(struct action_mapping, &iter->mapper->actions, iter->i);

    return am->action;
}

uint8_t action_iter_get_page(action_iter_t iter)
{
    struct action_mapping *am;

    log_assert(action_iter_is_valid(iter));

    am = array_item(struct action_mapping, &iter->mapper->actions, iter->i);

    return am->page;
}

uint8_t action_iter_get_bit(action_iter_t iter)
{
    struct action_mapping *am;

    log_assert(action_iter_is_valid(iter));

    am = array_item(struct action_mapping, &iter->mapper->actions, iter->i);

    return am->bit;
}

bool action_iter_is_valid(action_iter_t iter)
{
    return iter->i < iter->mapper->actions.nitems;
}

void action_iter_next(action_iter_t iter)
{
    iter->i++;
}

void action_iter_free(action_iter_t iter)
{
    free(iter);
}

static uint64_t action_mapping_update(struct action_mapping *am)
{
    int32_t value;

    if (am->src.hid == NULL) {
        return 0;
    }

    if (!hid_stub_get_value(am->src.hid, am->src.control_no, &value)) {
        return 0;
    }

    if (value < am->src.value_min || value > am->src.value_max) {
        return 0;
    }

    return 1ULL << am->bit;
}

static void analog_mapping_bind(struct analog_mapping *am)
{
    const struct hid_control *ctl;
    struct hid_control *controls;
    size_t ncontrols;

    am->bound = true;
    am->valid = false;

    if (am->src.hid == NULL) {
        goto unbound_fail;
    }

    if (!hid_stub_get_controls(am->src.hid, NULL, &ncontrols)) {
        goto size_fail;
    }

    if (am->src.control_no >= ncontrols) {
        goto bounds_fail;
    }

    controls = xmalloc(sizeof(*controls) * ncontrols);

    if (!hid_stub_get_controls(am->src.hid, controls, &ncontrols)) {
        goto read_fail;
    }

    ctl = &controls[am->src.control_no];

    am->affine_bias = ctl->value_min;
    am->affine_scale = ctl->value_max - ctl->value_min;
    am->absolute = !(ctl->flags & HID_FLAG_RELATIVE);
    am->valid = true;

read_fail:
    free(controls);

bounds_fail:
size_fail:
unbound_fail:
    return;
}

static void analog_mapping_update(struct analog_mapping *am)
{
    double tmp;
    int32_t value;

    if (!am->bound) {
        analog_mapping_bind(am);
    }

    if (!am->valid) {
        return;
    }

    if (!hid_stub_get_value(am->src.hid, am->src.control_no, &value)) {
        return;
    }

    if (am->absolute) {
        tmp = (value - am->affine_bias) / am->affine_scale;
        am->pos = (uint8_t) ((tmp + 0.5) * 256.0);
    } else {
        am->pos += (int8_t) (value * exp(am->sensitivity / 256.0));
    }
}

void light_iter_get_mapping(light_iter_t iter, struct mapped_light *ml)
{
    struct light_mapping *lm;

    log_assert(light_iter_is_valid(iter));

    lm = array_item(struct light_mapping, &iter->mapper->light_maps, iter->i);
    *ml = lm->dest;
}

uint8_t light_iter_get_game_light(light_iter_t iter)
{
    struct light_mapping *lm;

    log_assert(light_iter_is_valid(iter));

    lm = array_item(struct light_mapping, &iter->mapper->light_maps, iter->i);

    return lm->game_light;
}

bool light_iter_is_valid(light_iter_t iter)
{
    return iter->i < iter->mapper->light_maps.nitems;
}

void light_iter_next(light_iter_t iter)
{
    iter->i++;
}

void light_iter_free(light_iter_t iter)
{
    free(iter);
}

static void light_mapping_bind(struct light_mapping *lm)
{
    const struct hid_light *light;
    struct hid_light *light_defs;
    size_t nlights;

    lm->bound = true;
    lm->valid = false;

    if (lm->dest.hid == NULL) {
        goto unbound_fail;
    }

    if (!hid_stub_get_lights(lm->dest.hid, NULL, &nlights)) {
        goto size_fail;
    }

    if (lm->dest.light_no >= nlights) {
        goto bounds_fail;
    }

    light_defs = xmalloc(sizeof(*light_defs) * nlights);

    if (!hid_stub_get_lights(lm->dest.hid, light_defs, &nlights)) {
        goto read_fail;
    }

    light = &light_defs[lm->dest.light_no];

    lm->affine_bias = light->value_min;
    lm->affine_scale = light->value_max - light->value_min;
    lm->valid = true;

read_fail:
    free(light_defs);

bounds_fail:
size_fail:
unbound_fail:
    return;
}

static void light_mapping_send(struct light_mapping *lm, const struct mapper *m)
{
    double tmp;
    uint32_t value;
    uint8_t intensity;

    if (!lm->bound) {
        light_mapping_bind(lm);
    }

    if (!lm->valid) {
        return;
    }

    if (lm->game_light >= m->nlights) {
        return;
    }

    intensity = m->lights[lm->game_light];
    tmp = (intensity / 256.0) * lm->affine_scale;
    value = (int32_t) (tmp + 0.5) + lm->affine_bias;

    hid_stub_set_light(lm->dest.hid, lm->dest.light_no, value);
}

struct mapper *mapper_impl_create(void)
{
    struct mapper *m;

    m = xmalloc(sizeof(*m));

    array_init(&m->actions);

    m->analogs = NULL;
    m->nanalogs = 0;

    array_init(&m->light_maps);

    m->lights = NULL;
    m->nlights = 0;

    return m;
}

void mapper_impl_clear_action_map(
    struct mapper *m, uint8_t action, uint8_t page)
{
    struct action_mapping *am;
    size_t i;

    for (i = 0; i < m->actions.nitems; i++) {
        am = array_item(struct action_mapping, &m->actions, i);

        if (am->action == action && am->page == page) {
            array_remove(struct action_mapping, &m->actions, i);

            return;
        }
    }
}

void mapper_impl_clear_light_map(
    struct mapper *m, const struct mapped_light *ml)
{
    struct light_mapping *lm;
    size_t i;

    for (i = 0; i < m->light_maps.nitems;) {
        lm = array_item(struct light_mapping, &m->light_maps, i);

        if (memcmp(&lm->dest, ml, sizeof(*ml)) == 0) {
            array_remove(struct light_mapping, &m->light_maps, i);
        } else {
            i++;
        }
    }
}

bool mapper_impl_get_action_map(
    struct mapper *m, uint8_t action, uint8_t page, struct mapped_action *ma)
{
    const struct action_mapping *am;
    size_t i;

    for (i = 0; i < m->actions.nitems; i++) {
        am = array_item(struct action_mapping, &m->actions, i);

        if (am->action == action && am->page == page) {
            *ma = am->src;

            return true;
        }
    }

    return false;
}

bool mapper_impl_get_analog_map(
    struct mapper *m, uint8_t analog, struct mapped_analog *ma)
{
    memset(ma, 0, sizeof(*ma));

    if (analog >= m->nanalogs) {
        return false;
    }

    *ma = m->analogs[analog].src;

    return true;
}

int32_t mapper_impl_get_analog_sensitivity(struct mapper *m, uint8_t analog)
{
    if (analog >= m->nanalogs) {
        return 0;
    }

    return m->analogs[analog].sensitivity;
}

uint8_t mapper_impl_get_nanalogs(struct mapper *m)
{
    return m->nanalogs;
}

uint8_t mapper_impl_get_nlights(struct mapper *m)
{
    return m->nlights;
}

uint8_t mapper_impl_get_npages(struct mapper *m)
{
    const struct action_mapping *am;
    size_t i;
    int max_page;

    max_page = -1;

    for (i = 0; i < m->actions.nitems; i++) {
        am = array_item(struct action_mapping, &m->actions, i);

        if (max_page < am->page) {
            max_page = am->page;
        }
    }

    return (uint8_t) (max_page + 1);
}

action_iter_t mapper_impl_iterate_actions(struct mapper *m)
{
    struct action_iter *iter;

    iter = xmalloc(sizeof(*iter));
    iter->mapper = m;
    iter->i = 0;

    return iter;
}

light_iter_t mapper_impl_iterate_lights(struct mapper *m)
{
    struct light_iter *iter;

    iter = xmalloc(sizeof(*iter));
    iter->mapper = m;
    iter->i = 0;

    return iter;
}

bool mapper_impl_is_analog_absolute(struct mapper *m, uint8_t analog)
{
    struct analog_mapping *am;

    if (analog >= m->nanalogs) {
        return false;
    }

    am = &m->analogs[analog];

    if (!am->bound) {
        analog_mapping_bind(am);
    }

    return m->analogs[analog].absolute;
}

void mapper_impl_set_action_map(
    struct mapper *m,
    uint8_t action,
    uint8_t page,
    uint8_t bit,
    const struct mapped_action *ma)
{
    struct action_mapping *am;
    size_t i;

    for (i = 0; i < m->actions.nitems; i++) {
        am = array_item(struct action_mapping, &m->actions, i);

        if (am->action == action && am->page == page) {
            am->src = *ma;

            return;
        }
    }

    am = array_append(struct action_mapping, &m->actions);

    am->src = *ma;
    am->action = action;
    am->page = page;
    am->bit = bit;
}

bool mapper_impl_set_analog_map(
    struct mapper *m, uint8_t analog, const struct mapped_analog *ma)
{
    struct analog_mapping *am;

    if (analog >= m->nanalogs) {
        return false;
    }

    am = &m->analogs[analog];

    am->src = *ma;
    am->bound = false;

    return true;
}

bool mapper_impl_set_analog_sensitivity(
    struct mapper *m, uint8_t analog, int32_t sensitivity)
{
    if (analog >= m->nanalogs) {
        return false;
    }

    m->analogs[analog].sensitivity = sensitivity;

    return true;
}

void mapper_impl_set_light_map(
    struct mapper *m, const struct mapped_light *ml, uint8_t game_light)
{
    struct light_mapping *lm;
    size_t i;

    for (i = 0; i < m->light_maps.nitems; i++) {
        lm = array_item(struct light_mapping, &m->light_maps, i);

        if (memcmp(&lm->dest, ml, sizeof(*ml)) == 0) {
            lm->game_light = game_light;

            return;
        }
    }

    lm = array_append(struct light_mapping, &m->light_maps);

    memset(lm, 0, sizeof(*lm));

    lm->dest = *ml;
    lm->game_light = game_light;
}

void mapper_impl_set_nanalogs(struct mapper *m, uint8_t nanalogs)
{
    free(m->analogs);
    m->analogs = xcalloc(sizeof(*m->analogs) * nanalogs);
    m->nanalogs = nanalogs;
}

void mapper_impl_set_nlights(struct mapper *m, uint8_t nlights)
{
    free(m->lights);
    m->lights = xcalloc(sizeof(*m->lights) * nlights);
    m->nlights = nlights;
}

uint8_t mapper_impl_read_analog(struct mapper *m, uint8_t analog)
{
    if (analog >= m->nanalogs) {
        return 0;
    }

    return m->analogs[analog].pos;
}

uint64_t mapper_impl_update(struct mapper *m)
{
    struct action_mapping *am;
    struct light_mapping *lm;
    size_t i;
    uint64_t result;

    hid_mgr_lock();

    for (i = 0; i < m->light_maps.nitems; i++) {
        lm = array_item(struct light_mapping, &m->light_maps, i);
        light_mapping_send(lm, m);
    }

    for (i = 0; i < m->nanalogs; i++) {
        analog_mapping_update(&m->analogs[i]);
    }

    result = 0;

    for (i = 0; i < m->actions.nitems; i++) {
        am = array_item(struct action_mapping, &m->actions, i);
        result |= action_mapping_update(am);
    }

    hid_mgr_unlock();

    return result;
}

void mapper_impl_write_light(struct mapper *m, uint8_t light, uint8_t intensity)
{
    if (light >= m->nlights) {
        return;
    }

    m->lights[light] = intensity;
}

void mapper_impl_destroy(struct mapper *m)
{
    free(m->lights);
    array_fini(&m->light_maps);
    free(m->analogs);
    array_fini(&m->actions);
    free(m);
}
