#include <unit.h>
#include <camera.h>
#include <gfx.h>
#include <sfx.h>
#include <fixmath.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

struct sprite unit_sprites[MAX_UNIT_SPRITES];
struct sound unit_sounds[MAX_UNIT_SOUNDS];

static const unsigned char anim[] = {0, 1, 0, 2};

static void move_unit(struct unit *const u, const fix16_t sx, const fix16_t sy)
{
    switch (u->dir)
    {
        case UNIT_DIR_N:
            u->ry -= sy;
            break;

        case UNIT_DIR_E:
            u->rx += sx;
            break;

        case UNIT_DIR_S:
            u->ry += sy;
            break;

        case UNIT_DIR_W:
            u->rx -= sx;
            break;

        default:
            break;
    }
}

static void get_speed(const struct unit *const u, fix16_t *const x,
    fix16_t *const y)
{
    struct speed
    {
        fix16_t x, y;
    } s;

    switch (u->type)
    {
        case UNIT_TYPE_1:
        case UNIT_TYPE_2:
        case UNIT_TYPE_3:
        case UNIT_TYPE_4:
        case UNIT_TYPE_5:
        case UNIT_TYPE_6:
        {
            static const struct speed ref =
            {
                .x = FIX16_C_FROM_FLOAT(0.4),
                .y = FIX16_C_FROM_FLOAT(0.4)
            };

            s = ref;
        }
            break;

        case UNIT_TYPE_CAR_1:
        case UNIT_TYPE_CAR_2:
        {
            static const struct speed ref =
            {
                .x = FIX16_C_FROM_FLOAT(1.5),
                .y = FIX16_C_FROM_FLOAT(1.5)
            };

            s = ref;
        }

        case MAX_UNIT_TYPES:
            break;
    }

    const int dx = abs(u->rx - u->tx);
    const int dy = abs(u->ry - u->ty);

    *x = dx < s.x ? dx : s.x;
    *y = dy < s.y ? dy : s.y;
}

static enum unit_dir get_direction(const struct unit *const u)
{
    const fix16_t x = u->rx, y = u->ry, tx = u->tx, ty = u->ty;
    enum unit_dir dir = 0;

    if (x != tx)
    {
        if (x > tx)
            dir = UNIT_DIR_W;
        else
            dir = UNIT_DIR_E;
    }
    else if (y != ty)
    {
        if (y > ty)
            dir = UNIT_DIR_N;
        else
            dir = UNIT_DIR_S;
    }

    return dir;
}

static void unit_stop(struct unit *const u)
{
    u->tx = u->rx;
    u->ty = u->ry;
}

static void target_reset(struct unit *const u)
{
    u->target = (const struct unit_target){0};
    u->state = UNIT_STATE_IDLE_MOVING;
    unit_stop(u);
}

static void unit_chase(const struct instance *const i, struct unit *const u)
{
    u->tx = fix16_from_int(i->r.x + (i->r.w >> 1));
    u->ty = fix16_from_int(i->r.y + (i->r.h >> 1));
    u->state = UNIT_STATE_IDLE_MOVING;
}

static void target_interact(struct unit *const u)
{
    struct unit_target *const t = &u->target;
    const struct instance *const ins = t->ins;

    /* TODO: lose u->ti if not visible. */
    if (!ins->alive)
    {
        target_reset(u);
        return;
    }
    else if (t->state == u->state)
        return;

    struct instance *const ui = &u->instance;

    if (!util_collision(&ins->r, &ui->r))
        unit_chase(ins, u);
}

static bool must_move(const struct unit *const u)
{
    return u->rx != u->tx || u->ry != u->ty;
}

void unit_update(struct unit *const u)
{
    struct instance *const i = &u->instance;

    if (!i->alive)
        return;

    if (u->target.ins)
        target_interact(u);
    else if (must_move(u))
    {
        fix16_t x_step, y_step;

        u->dir = get_direction(u);
        get_speed(u, &x_step, &y_step);
        move_unit(u, x_step, y_step);

        enum {FRAME_RATE = 10};

        if (++u->frame.t >= FRAME_RATE)
        {
            u->frame.t = 0;

            if (++u->frame.i >= sizeof anim / sizeof *anim)
                u->frame.i = 0;
        }

        u->state = UNIT_STATE_IDLE_MOVING;
    }
    else
        u->frame.i = 0;

    i->r.x = fix16_to_int(u->rx);
    i->r.y = fix16_to_int(u->ry);
}

void unit_set_target(struct unit *const u, const struct unit_target *const t)
{
    const struct instance *const ti = t->ins;

    u->target = *t;
    unit_chase(ti, u);
}

void unit_move_to(struct unit *const u, const unsigned long x, const unsigned long y)
{
    const struct instance *const i = &u->instance;
    const unsigned long x_off = i->r.w >> 1;
    const unsigned long y_off = i->r.h >> 1;

    u->target.ins = NULL;
    u->state = UNIT_STATE_IDLE_MOVING;
    u->tx = x > x_off ? fix16_from_int(x - x_off) : 0;
    u->ty = y > y_off ? fix16_from_int(y - y_off) : 0;
}

static void adjust_walker(const struct unit *const u, struct sprite *const s,
    struct instance_render_off *const off)
{
    s->w = u->instance.r.w;
    s->h = u->instance.r.h;
    s->u += u->dir * s->w;
    s->v += anim[u->frame.i] * s->h;
    off->x = 0;
    off->y = -2;
}

static void adjust_car(const struct unit *const u, struct sprite *const s,
    struct instance_render_off *const off)
{
    switch (u->dir)
    {
        case UNIT_DIR_W:
            s->w = s->h = 32;
            s->u += 32;
            off->x = -5;
            break;

        case UNIT_DIR_S:
            s->w = 16;
            s->h = 32;
            off->x = 2;
            break;

        case UNIT_DIR_N:
            s->u += 16;
            s->w = 16;
            s->h = 32;
            off->x = 2;
            break;

        case UNIT_DIR_E:
            s->w = s->h = 32;
            s->u += 64;
            off->x = -5;
            break;
    }

    off->y = -10;
}

static void adjust_sprite(const struct unit *const u, struct sprite *const s,
    struct instance_render_off *const off)
{
    switch (u->type)
    {
        case UNIT_TYPE_1:
        case UNIT_TYPE_2:
        case UNIT_TYPE_3:
        case UNIT_TYPE_4:
        case UNIT_TYPE_5:
        case UNIT_TYPE_6:
            adjust_walker(u, s, off);
            break;

        case UNIT_TYPE_CAR_1:
        case UNIT_TYPE_CAR_2:
            adjust_car(u, s, off);
            break;

        case MAX_UNIT_TYPES:
            break;
    }
}

int unit_render(const struct unit *const u, const struct camera *const cam,
    const bool sel)
{
    if (!u->instance.alive)
        return 0;

    sprite_get_or_ret(s, -1);

    struct instance_render_off off;

    if (sprite_clone(&unit_sprites[u->type], s))
    {
        fprintf(stderr, "%s: sprite_clone failed\n", __func__);
        return -1;
    }

    adjust_sprite(u, s, &off);

    const struct instance_render_cfg cfg =
    {
        .i = &u->instance,
        .prim_type = INSTANCE_RENDER_CFG_SPRITE,
        .prim.s = s,
        .off = &off,
        .cam = cam,
        .sel = sel
    };

    return instance_render(&cfg);
}

static void get_dimensions(const enum unit_type type, short *const w,
    short *const h)
{
    switch (type)
    {
        case UNIT_TYPE_1:
        case UNIT_TYPE_2:
        case UNIT_TYPE_3:
        case UNIT_TYPE_4:
        case UNIT_TYPE_5:
        case UNIT_TYPE_6:
            *w = *h = 16;
            break;

        case UNIT_TYPE_CAR_1:
        case UNIT_TYPE_CAR_2:
            *w = *h = 22;
            break;

        case MAX_UNIT_TYPES:
            break;
    }
}

static const char *get_walker_name(const enum unit_type type)
{
    enum {FEMALE, MALE} gender = 0;

    switch (type)
    {
        case UNIT_TYPE_2:
        case UNIT_TYPE_5:
            gender = FEMALE;
            break;

        case UNIT_TYPE_1:
        case UNIT_TYPE_3:
        case UNIT_TYPE_4:
        case UNIT_TYPE_6:
            gender = MALE;
            break;

        default:
            return NULL;
    }

    static const char *const female[] =
    {
        "Amelia",
        "Asher",
        "Aurora",
        "Ava",
        "Charlotte",
        "Dana",
        "Ellie",
        "Emma",
        "Ezra",
        "Haley",
        "Harper",
        "Isabella",
        "Luna",
        "Mire",
        "Olivia",
        "Sophia",
    };

    static const char *const male[] =
    {
        "Oliver",
        "Xavi",
        "Benjamin",
        "Elijah",
        "Ethan",
        "Jack",
        "James",
        "Jan",
        "Kyte",
        "Leo",
        "Levi",
        "Liam",
        "Luca",
        "Lucas",
        "Michael",
        "Noah",
    };

    switch (gender)
    {
        case FEMALE:
        {
            const size_t i = rand() % (sizeof female / sizeof *female);

            return female[i];
        }

        case MALE:
        {
            const size_t i = rand() % (sizeof male / sizeof *male);

            return male[i];
        }
    }

    return NULL;
}

static const char *get_name(const enum unit_type type)
{
    switch (type)
    {
        case UNIT_TYPE_1:
        case UNIT_TYPE_2:
        case UNIT_TYPE_3:
        case UNIT_TYPE_4:
        case UNIT_TYPE_5:
        case UNIT_TYPE_6:
            return get_walker_name(type);

        case UNIT_TYPE_CAR_1:
        case UNIT_TYPE_CAR_2:
            return "Car";

        case MAX_UNIT_TYPES:
            break;
    }

    return NULL;
}

void unit_create(const struct unit_cfg *const cfg, struct unit *const u)
{
    struct instance *const i = &u->instance;
    enum unit_type type = 0;

    switch (cfg->type)
    {
        case UNIT_CFG_TYPE_WALKER:
            type = UNIT_TYPE_1 + (rand() % (UNIT_TYPE_6 + 1));
            break;

        case UNIT_CFG_TYPE_CAR:
            type = UNIT_TYPE_CAR_1 + (rand()
                % (UNIT_TYPE_CAR_2 - UNIT_TYPE_CAR_1 + 1));
            break;
    }

    *u = (const struct unit)
    {
        .instance.alive = true,
        .dir = UNIT_DIR_S,
        .rx = fix16_from_int(cfg->x),
        .ry = fix16_from_int(cfg->y),
        .type = type,
        .name = get_name(type)
    };

    get_dimensions(u->type, &i->r.w, &i->r.h);
    unit_stop(u);
}
