#include <human_player.h>
#include <human_player_private.h>
#include <player.h>
#include <building.h>
#include <camera.h>
#include <gfx.h>
#include <gui.h>
#include <instance.h>
#include <input.h>
#include <keyboard.h>
#include <pad.h>
#include <unit.h>
#include <util.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

static bool instance_selected(const struct human_player *const h,
    const union sel_data *const sel)
{
    if (h->n_sel)
        for (size_t i = 0; i < sizeof h->sel / sizeof *h->sel; i++)
        {
            const struct sel_instance *const si = &h->sel[i];

            if (si && sel->u == si->d.u)
                return true;
        }

    return false;
}

static bool select_units(struct human_player *const h, const short x,
    const short y, const bool excl)
{
    struct player *const pl = &h->pl;

    for (size_t i = 0; i < sizeof pl->units / sizeof *pl->units; i++)
    {
        const struct unit *const u = &pl->units[i];
        const struct instance *const in = &u->instance;

        if (!in->alive)
            continue;

        const union sel_data d = {.u = u};

        if (!instance_selected(h, &d)
            && cursor_collision(&h->cam, &in->r)
            && h->n_sel < sizeof h->sel / sizeof *h->sel)
        {
            struct sel_instance *sel = NULL;

            if (excl)
            {
                sel = h->sel;
                h->n_sel = 1;

                for (size_t i = 1; i < sizeof h->sel / sizeof *h->sel; i++)
                    h->sel[i].d.u = NULL;
            }
            else
            {
                for (size_t i = 0; i < sizeof h->sel / sizeof *h->sel; i++)
                {
                    struct sel_instance *const s = &h->sel[i];

                    if (!s->d.u)
                    {
                        sel = s;
                        h->n_sel++;
                        break;
                    }
                }
            }

            if (sel)
            {
                sel->type = INSTANCE_TYPE_UNIT;
                sel->d.u = u;
                sfx_play(&unit_sounds[UNIT_SOUND_SELECTED]);
                return true;
            }

            return false;
        }
    }

    return false;
}

static bool select_buildings(struct human_player *const h, const short x,
    const short y, const bool excl)
{
    struct player *const pl = &h->pl;

    for (size_t i = 0; i < sizeof pl->buildings / sizeof *pl->buildings; i++)
    {
        const struct building *const b = &pl->buildings[i];
        const struct instance *const in = &b->instance;

        if (in->alive)
        {
            const union sel_data d = {.b = b};

            if (!instance_selected(h, &d)
                && cursor_collision(&h->cam, &in->r)
                && h->n_sel < sizeof h->sel / sizeof *h->sel)
            {
                struct sel_instance *sel = NULL;

                if (excl)
                {
                    sel = h->sel;
                    h->n_sel = 1;

                    for (size_t i = 1; i < sizeof h->sel / sizeof *h->sel; i++)
                        h->sel[i].d.b = NULL;
                }
                else
                {
                    for (size_t i = 0; i < sizeof h->sel / sizeof *h->sel; i++)
                    {
                        struct sel_instance *const s = &h->sel[i];

                        if (!s->d.b)
                        {
                            sel = s;
                            h->n_sel++;
                            break;
                        }
                    }
                }

                if (sel)
                {
                    sel->type = INSTANCE_TYPE_BUILDING;
                    sel->d.b = b;
                    return true;
                }

                return false;
            }
        }
    }

    return false;
}

static bool select_instances(struct human_player *const h,
    const struct player_others *const o, const bool excl,
    const bool same_type)
{
    unsigned long x, y;

    cursor_pos(&h->cam, &x, &y);

    if (!same_type || !h->n_sel)
        return select_buildings(h, x, y, excl)
            || select_units(h, x, y, excl);
    else
    {
        switch (h->sel->type)
        {
            case INSTANCE_TYPE_UNIT:
                return select_units(h, x, y, excl);

            case INSTANCE_TYPE_BUILDING:
                return select_buildings(h, x, y, excl);
        }
    }

    return false;
}

static bool instance_collision(const struct camera *const cam,
    const struct instance *const in)
{
    return in->alive && cursor_collision(cam, &in->r);
}

static bool target_from_own(struct player *const p,
    const struct camera *const cam, struct unit_target *const t)
{
    for (size_t i = 0; i < sizeof p->buildings / sizeof *p->buildings; i++)
    {
        struct building *const b = &p->buildings[i];
        struct instance *const in = &b->instance;

        if (instance_collision(cam, in))
        {
            *t = (const struct unit_target)
            {
                .ins = in,
                /* TODO .state = UNIT_STATE_SHELTERED */
            };

            return true;
        }
    }

    return false;
}

static void set_target(struct human_player *const h, struct unit *const u,
    const struct unit_target *const t)
{
    unit_set_target(u, t);

    h->target = (const struct human_player_target)
    {
        .ins = t->ins,
        .render = true
    };
}

static void target_from_pos(struct player *const p,
    const struct player_others *const o,
    const struct camera *const cam, struct unit_target *const t)
{
    if (!target_from_own(p, cam, t))
        t->ins = NULL;
}

static void move_units(struct human_player *const h,
    struct player_others *const o)
{
    if (!h->n_sel)
        return;

    unsigned long x, y;
    struct player *const p = &h->pl;
    struct unit_target t;

    target_from_pos(p, o, &h->cam, &t);
    cursor_pos(&h->cam, &x, &y);

    bool unit_move = false;

    for (size_t i = 0; i < sizeof h->sel / sizeof *h->sel; i++)
    {
        struct sel_instance *const si = &h->sel[i];

        if (si->d.u && si->type == INSTANCE_TYPE_UNIT)
        {
            struct unit *const u = si->d.rw_u;

            if (!t.ins)
                unit_move_to(u, x, y);
            else
                set_target(h, u, &t);

            unit_move = true;
        }
    }

    if (unit_move)
    {
        const enum unit_sound s = rand() & 1 ?
            UNIT_SOUND_MOVE : UNIT_SOUND_MOVE_2;
        sfx_play(&unit_sounds[s]);
    }
}

static void deselect_instances(struct human_player *const h)
{
    for (size_t i = 0; i < sizeof h->sel / sizeof *h->sel; i++)
        h->sel[i] = (const struct sel_instance){0};

    h->n_sel = 0;
}

static void update_selected(struct human_player *const h)
{
    if (h->n_sel)
    {
        for (size_t i = 0; i < sizeof h->sel / sizeof *h->sel; i++)
        {
            struct sel_instance *const si = &h->sel[i];

            if (si->d.i && !si->d.i->alive)
            {
                si->d.i = NULL;
                h->n_sel--;
            }
        }
    }
}

static void update_target(struct human_player *const h)
{
    const struct instance *const i = h->target.ins;

    if (i)
    {
        enum {TOGGLE = 10};

        if (!i->alive)
            h->target = (const struct human_player_target){0};
        else if (++h->target.t >= TOGGLE)
        {
            enum {TIMES = 5};

            h->target.render ^= true;
            h->target.t = 0;

            if (++h->target.n >= TIMES)
                h->target = (const struct human_player_target){0};
        }
    }
}

static void update_from_pad(struct human_player *const h,
    struct player_others *const o)
{
    const struct pad *const p = &h->periph->pad.pad;
    const struct input *const in = &h->in;

    if (input_pad_justpressed(in, p, PAD_KEY_A))
        select_instances(h, o, false, false);
    else if (input_pad_justpressed(in, p, PAD_KEY_B))
        move_units(h, o);
    else if (input_pad_justpressed(in, p, PAD_KEY_C))
        deselect_instances(h);
}

static void update_from_touch(struct human_player *const h,
    struct player_others *const o)
{
    const struct mouse *const m = &h->periph->kbm.mouse;
    const struct input *const in = &h->in;
    struct peripheral_kbm *const kbm = &h->periph->kbm;
    bool *const pan = &h->cam.pan;

    if (input_mouse_pressed(in, m, MOUSE_BUTTON_LEFT) && !*pan)
    {
        enum {LONG_PRESS_THRESHOLD = 30};

        if (kbm->lp_t < LONG_PRESS_THRESHOLD)
            kbm->lp_t++;
        else if (!kbm->long_press)
        {
            kbm->long_press = true;
            deselect_instances(h);
        }
    }
    else if (input_mouse_justreleased(in, m, MOUSE_BUTTON_LEFT))
    {
        if (!*pan && !select_instances(h, o, false, true))
            move_units(h, o);

        *pan = false;
        kbm->long_press = false;
        kbm->lp_t = 0;
    }
}

static bool hovering_units(const struct human_player *const h)
{
    const struct player *const pl = &h->pl;

    for (size_t i = 0; i < sizeof pl->units / sizeof *pl->units; i++)
    {
        const struct unit *const u = &pl->units[i];
        const struct instance *const in = &u->instance;

        if (in->alive && cursor_collision(&h->cam, &in->r))
            return true;
    }

    return false;
}

static void update_from_keyboard_mouse(struct human_player *const h,
    struct player_others *const o)
{
    struct mouse *const m = &h->periph->kbm.mouse;
    const struct keyboard *const k = &h->periph->kbm.keyboard;
    const struct input *const in = &h->in;

    if (input_mouse_justreleased(in, m, MOUSE_BUTTON_LEFT))
    {
        const bool shift_pressed =
            input_keyboard_pressed(in, k,
                &KEYBOARD_COMBO(KEYBOARD_KEY_LSHIFT))
            || input_keyboard_pressed(in, k,
                &KEYBOARD_COMBO(KEYBOARD_KEY_RSHIFT));

        if (!select_instances(h, o, !shift_pressed, false))
            deselect_instances(h);
    }
    else if (input_mouse_justreleased(in, m, MOUSE_BUTTON_RIGHT))
        move_units(h, o);
    else
        m->hovering = hovering_units(h);
}

void human_player_update(struct human_player *const h,
    struct player_others *const o)
{
    struct player *const p = &h->pl;

    if (p->alive)
    {
        human_player_gui_update(h);
        update_selected(h);
        update_target(h);
        peripheral_update(h->periph);
        input_update(&h->in, h->periph);

        switch (h->periph->common.type)
        {
            case PERIPHERAL_TYPE_PAD:
                update_from_pad(h, o);
                break;

            case PERIPHERAL_TYPE_TOUCH:
                update_from_touch(h, o);
                break;

            case PERIPHERAL_TYPE_KEYBOARD_MOUSE:
                update_from_keyboard_mouse(h, o);
                break;
        }

        camera_update(&h->cam, h->periph, &h->in);
        player_update(p);
    }
}

static int render_target(const struct human_player *const h)
{
    const struct instance *const i = h->target.ins;

    if (!i || !i->alive || !h->target.render)
        return 0;

    return instance_render_target(i, &h->cam);
}

static int render_own_units(const struct human_player *const h)
{
    const struct player *const p = &h->pl;

    for (size_t i = 0; i < sizeof p->units / sizeof *p->units; i++)
    {
        const struct unit *const u = &p->units[i];
        const union sel_data d = {.u = u};
        const bool sel = instance_selected(h, &d);

        if (unit_render(u, &h->cam, sel))
            return -1;
    }

    return 0;
}

static int render_own_buildings(const struct human_player *const h)
{
    const struct player *const p = &h->pl;

    for (size_t i = 0; i < sizeof p->buildings / sizeof *p->buildings; i++)
    {
        const struct building *const b = &p->buildings[i];
        const union sel_data d = {.b = b};
        const bool sel = instance_selected(h, &d);

        if (building_render(b, &h->cam, sel))
            return -1;
    }

    return 0;
}

int human_player_render(const struct human_player *const h,
    const struct player_others *const o)
{
    if (render_target(h)
        || render_own_units(h)
        || render_own_buildings(h)
        || human_player_gui_render(h)
        || input_render(&h->in, h->periph))
        return -1;

    switch (h->periph->common.type)
    {
        case PERIPHERAL_TYPE_PAD:
            /* Fall through. */
        case PERIPHERAL_TYPE_KEYBOARD_MOUSE:
            cursor_render(&h->cam.cursor);
            break;

        case PERIPHERAL_TYPE_TOUCH:
            break;

        default:
            return -1;
    }

    return 0;
}

int human_player_init(const struct human_player_cfg *const cfg,
    struct human_player *const h)
{
    *h = (const struct human_player){0};

    if (player_init(&cfg->pl, &h->pl))
        return -1;

    cursor_init(&h->cam.cursor);
    h->periph = cfg->p;
    h->cam.dim = cfg->dim;
    return 0;
}
