
#include "mob_hostile.hh"

#include <algorithm>

#include "itty/paths.hh"

#include "strings.hh"
#include "scene.hh"
#include "mob_player.hh"
#include "mob_effect.hh"

//////////////////////////////////////////////////////////////////////////////
/// Thorns
//////////////////////////////////////////////////////////////////////////////

Mob_thorns::Mob_thorns(Mobs& mobs, const itty::Box& box, Place*) : Mob{box, HARM}
{
}

bool Mob_thorns::update(Scene& sc)
{
        return false;
}

void Mob_thorns::render(Mobs& mobs)
{
        // nothing
}

//////////////////////////////////////////////////////////////////////////////
/// Rotten
//////////////////////////////////////////////////////////////////////////////

Mob_rotten::Mob_rotten(Mobs& mobs, const itty::Box& box, Place*) : Mob{box, SOLID | ROOM | HARM | PHYS}
{
        brush = mobs.load_frames("rotten.png", 7, 1);
        sound_kill = mobs.load_audio("kill.wav");
}

bool Mob_rotten::update(Scene& sc)
{
        if (sc.mobs.pc->hit.overlaps(box)) {
                MIX_PlayAudio(itty::mixer, sound_kill);
                sc.mobs.construct("splat", p + itty::Vec{0, -2}, itty::Vec{8, 8});
                return true;
        }

        if (is(HIT_RT))
                set(FLIP);
        if (is(HIT_LF))
                unset(FLIP);

        sc.phys.add_engine(this, itty::Vec{1, 0}, is(FLIP) ? -1 : 1, 1);
        sc.phys.add_engine(this, itty::Vec{0, 1}, INFINITY, 1.6f);
        return false;
}

void Mob_rotten::render(Mobs& mobs)
{
        int frame = brush + size_t((is(FLIP) ? -p.x : p.x) / 6) % 4;
        auto mat = itty::Mat::translate(p.floor() + itty::Vec(0, -4));
        if (is(FLIP))
                mat *= itty::Mat::scale(-1, 1);
        mobs.quads.push(frame, mat);
}

//////////////////////////////////////////////////////////////////////////////
/// Bones
//////////////////////////////////////////////////////////////////////////////

Mob_bones::Mob_bones(Mobs& mobs, const itty::Box& box, Place*) : Mob{box, SOLID | ROOM | HARM | PHYS}
{
        brush = mobs.load_frames("bones.png", 7, 1);
        sound_kill = mobs.load_audio("kill.wav");
}

bool Mob_bones::update(Scene& sc)
{
        if (sc.mobs.pc->hit.overlaps(box)) {
                MIX_PlayAudio(itty::mixer, sound_kill);
                sc.mobs.construct("poof", p + itty::Vec{0, -2}, itty::Vec{8, 8});
                return true;
        }

        if (sc.mobs.tick) switch (state) {
         case IDLE_END:
                {
                        auto d = sc.mobs.pc->p.x - p.x;
                        state = std::abs(d) < 32 ? FIRE : WALK;
                        d < 0 ? set(FLIP) : unset(FLIP);
                        break;
                }
         case WALK_END:
         case FIRE_END:
                state = IDLE;
                break;
         case FIRE + 2:
                {
                        auto m = sc.mobs.construct("bone", p, itty::Vec{4, 4});
                        m->v.x = is(FLIP) ? -2 : 2;
                        m->v.y = -8;
                }
                ++state;
                break;
         default:
                ++state;
                break;
        }

        if (state >= WALK && state <= WALK_END) {
                if (!is(HIT_DN))
                        state = IDLE;
                else if (is(FLIP) ? is(HIT_LF) : is(HIT_RT))
                        state = IDLE;
                else if (!sc.phys.at((is(FLIP) ? box.dl() : box.dr()) + itty::Vec(0, 1)))
                        state = IDLE;
        }

        if (state >= WALK && state <= WALK_END)
                sc.phys.add_engine(this, itty::Vec{1, 0}, is(FLIP) ? -1 : 1, 1);
        else
                sc.phys.add_engine(this, itty::Vec{1, 0}, 0, INFINITY);
        sc.phys.add_engine(this, itty::Vec{0, 1}, INFINITY, 1.6f);
        return false;
}

void Mob_bones::render(Mobs& mobs)
{
        static const int16_t frames[] = {
                0, 0, 0, 0, 0, 0, 0, 0, // idle
                1, 1, 2, 2, 3, 3, 4, 4, 1, 1, 2, 2, 3, 3, 4, 4, 1, 1, 2, 2, 3, 3, 4, 4, // walk
                5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, // throw
        };

        auto mat = itty::Mat::translate(p.floor() + itty::Vec(0, -4));
        if (is(FLIP))
                mat *= itty::Mat::scale(-1, 1);
        mobs.quads.push(brush + frames[state], mat);
}

//////////////////////////////////////////////////////////////////////////////
/// Bone
//////////////////////////////////////////////////////////////////////////////

Mob_bone::Mob_bone(Mobs& mobs, const itty::Box& box, Place*) : Mob{box, ROOM | HARM | PHYS}
{
        brush = mobs.load_frames("bone.png", 3, 1);
        sound_kill = mobs.load_audio("kill.wav");
}

bool Mob_bone::update(Scene& sc)
{
        if (sc.mobs.tick) state = (state + 1) % 3;
        sc.phys.add_engine(this, itty::Vec{0, 1}, INFINITY, 1.6f);
        return false;
}

void Mob_bone::render(Mobs& mobs)
{
        auto mat = itty::Mat::translate(p.floor());
        if (is(FLIP))
                mat *= itty::Mat::scale(-1, 1);
        mobs.quads.push(brush + state, mat);
}

//////////////////////////////////////////////////////////////////////////////
/// Peeper
//////////////////////////////////////////////////////////////////////////////

Mob_peeper::Mob_peeper(Mobs& mobs, const itty::Box& box, Place*) : Mob{box, HARM | PHYS}
{
        brush = mobs.load_frames("peeper.png", 4, 1);
        sound_kill = mobs.load_audio("kill.wav");
}

bool Mob_peeper::update(Scene& sc)
{
        if (sc.mobs.pc->hit.overlaps(box)) {
                MIX_PlayAudio(itty::mixer, sound_kill);
                sc.mobs.construct("poof", p, itty::Vec{8, 8});
                return true;
        }

        auto d = p - sc.mobs.pc->p;

        if (sc.mobs.tick) switch (state) {
         case HANG:
                if (d.x < -64.f) {
                        // nothing to do
                } else if (d.x < 0) {
                        v.x = 1.5f;
                        state = GLIDE;
                        unset(FLIP);
                } else if (d.x < 64.f) {
                        v.x = -1.5f;
                        state = GLIDE;
                        set(FLIP);
                } else {
                        // nothing again
                }
                break;

         case GLIDE:
                if (is(FLIP) && d.x < -32.f) {
                        state = TURN;
                        unset(FLIP);
                } else if (!is(FLIP) && d.x > 32.f) {
                        state = TURN;
                        set(FLIP);
                } else if (d.y > 0.f) {
                        state = FLY;
                }
                break;

         case FLY_END:
                state = GLIDE;
                break;

         case TURN ... TURN_END:
                if (is(FLIP)) {
                        if (v.x == -1.5f) state = GLIDE;
                        else if (++state == TURN_END) state = TURN;
                } else {
                        if (v.x == 1.5f) state = GLIDE;
                        else if (++state == TURN_END) state = TURN;
                }
                break;

         default:
                state++;
                break;
        }

        switch (state) {
         case HANG:
                // no vertical speed
                break;
         case FLY + 2 ... FLY + 3:
                sc.phys.add_engine(this, itty::Vec{0, 1}, INFINITY, -1.0f);
                break;
         default:
                sc.phys.add_engine(this, itty::Vec{0, 1}, INFINITY, 0.1f);
                break;
        }

        switch (state) {
         case HANG:
         case GLIDE:
                // no horizontal speed
                break;
         default:
                sc.phys.add_engine(this, itty::Vec{1, 0}, is(FLIP) ? -1.5f : 1.5f, 0.25f);
                break;
        }

        v.y = std::clamp(v.y, -2.f, 2.f);

        return false;
}

void Mob_peeper::render(Mobs& mobs)
{
        static const int16_t frames[] = {
                0, // hang
                3, // glide
                2, 2, 1, 1, 2, 2, 3, 3, // fly
                2, 2, 1, 1, 2, 2, 3, 3, // turn
        };

        auto mat = itty::Mat::translate(p.floor());
        if (is(FLIP))
                mat *= itty::Mat::scale(-1, 1);
        mobs.quads.push(brush + frames[state], mat);
}

//////////////////////////////////////////////////////////////////////////////
/// Crawler
//////////////////////////////////////////////////////////////////////////////

Mob_crawler::Mob_crawler(Mobs& mobs, const itty::Box& box, Place*) : Mob{box, SOLID | ROOM | HARM | PHYS}
{
        brush = mobs.load_frames("crawler.png", 2, 1);
        sound_kill = mobs.load_audio("kill.wav");
}

bool Mob_crawler::update(Scene& sc)
{
        static const int ccw[] = {HIT_RT, HIT_DN, HIT_LF, HIT_UP};
        static const int cw[] = {HIT_DN, HIT_LF, HIT_UP, HIT_RT};

        if (sc.mobs.pc->hit.overlaps(box)) {
                MIX_PlayAudio(itty::mixer, sound_kill);
                sc.mobs.construct("poof", p, itty::Vec{8, 8});
                return true;
        }

        if (state == 255) {
                state = 0;
        } else if (sc.mobs.tick) {
                if (is(ccw[state]))
                        state = (uint8_t)(state - 1) % 4;
                else if (!is(cw[state]))
                        state = (uint8_t)(state + 1) % 4;
        }

        v = normals[state] * -10 + tangents[state];
        return false;
}

void Mob_crawler::render(Mobs& mobs)
{
        int frame = brush + (size_t)(p * tangents[state]) % 2;
        auto mat = itty::Mat::translate(p.floor());
        mat *= itty::Mat::rotate(tangents[state]);
        mobs.quads.push(frame, mat);
}

