#include "global.h"
#include "manager.h"
#include "flags.h"
#include "room.h"
#include "area.h"
#include "common.h"
#include "sound.h"

/*
 * Manager B is used to create fights:
 * It possibly waits for an inhibitor flag to be set, then spawns a bunch of entities (based on room data).
 * Once all enemies created this way are dead, it sets a flag.
 * (There is also a part about changing the music and setting it back when the fight is done, which is song 0x33 (a
 * fight theme) by default but can be overridden through room data)
 */

void (*const ManagerB_ActionFuncs[])(Manager*);

void ManagerB_Main(Manager* this) {
    // make a distincion if this is a controller (volumeMasterTarget = 0) or a helper (volumeMasterTarget = 1)
    ManagerB_ActionFuncs[this->unk_0a](this);
}

enum ManagerB_State { Init, WaitForFlag, WaitForDone };

void (*const ManagerB_StateFuncs[])(ManagerB*);

void ManagerB_Main2(ManagerB* this) {
    // make a distinction based on the state of this controller
    ManagerB_StateFuncs[this->manager.action](this);
}

void ManagerB_LoadFight(Manager*);

void ManagerB_Init(ManagerB* this) {
    // check if the fight was already completed previously (checks the flag that gets set after the fight is done)
    if (!CheckFlags(this->unk_3e)) {
        this->manager.action = WaitForFlag;
        // if there is no flag that needs to be set before the fight is started, start it immediately
        if (!this->unk_3c) {
            ManagerB_LoadFight(&this->manager);
        }
        SetDefaultPriority((Entity*)this, PRIO_NO_BLOCK);
    } else {
        DeleteThisEntity();
    }
}

void ManagerB_WaitForFlag(ManagerB* this) {
    int tmp;
    if (CheckFlags(this->unk_3c)) {
        ManagerB_LoadFight(&this->manager);
        if (!this->unk_35) {
            tmp = gRoomVars.field_0x9 ? gRoomVars.field_0x9 : 0x33;
            this->unk_20 = gArea.bgm;
            gArea.bgm = tmp;
            SoundReq(tmp);
        }
        sub_080186C0(0xB0F);
    }
}

void ManagerB_WaitForDone(ManagerB* this) {
    // check if all helpers are done
    if (this->manager.unk_0e)
        return;
    // set the completion flag for the fight
    SetFlag(this->unk_3e);
    // restore music (if it was set, which apparently is only possible if there's a flag the fight waited for)
    if (this->unk_3c) {
        if (!this->unk_35) {
            gArea.bgm = this->unk_20;
            SoundReq(gArea.bgm);
            sub_0801855C();
        }
    }
    DeleteThisEntity();
}

ManagerBHelper* CreateHelper(Manager*);
void ManagerBHelper_Monitor(ManagerBHelper*, Entity*, u32);

extern Entity* LoadRoomEntity(EntityData*);

void ManagerB_LoadFight(Manager* this) {
    ManagerBHelper* monitor;
    EntityData* prop;
    Entity* ent;
    u32 counter;
    this->action = 2;
    this->unk_0e = 0;
    counter = 0;
    // Create a helper to keep track of the created entities.
    monitor = CreateHelper(this);
    if (!monitor)
        DeleteThisEntity();
    prop = (EntityData*)GetCurrentRoomProperty(this->unk_0b);
    if (prop) {
        while (*((u8*)prop) != 0xFF) {
            ent = LoadRoomEntity(prop++);
            if (ent && (ent->kind == ENEMY)) {
                ent->field_0x6c.HALF.HI |= 0x40;
                ManagerBHelper_Monitor(monitor, ent, counter++);
            }
            if (counter >= 7) {
                counter = 0;
                monitor = CreateHelper(this);
                if (!monitor)
                    return;
            }
        }
    }
}

/*
 * Create a helper and increment the counter for the number of helpers (unk_0e).
 * The helper will decrease said counter when it deletes itself (when none of the enemies it monitors remain).
 */
ManagerBHelper* CreateHelper(Manager* this) {
    ManagerBHelper* extra;
    extra = (ManagerBHelper*)GetEmptyManager();
    if (extra) {
        extra->manager.type = 9;
        extra->manager.subtype = 0xB;
        extra->manager.unk_0a = 1;
        extra->manager.parent = this;
        this->unk_0e++;
        MemClear(&extra->enemies, 0x20);
        AppendEntityToList((Entity*)extra, 8);
    }
    return extra;
}

void ManagerBHelper_Monitor(ManagerBHelper* this, Entity* ent, u32 index) {
    this->enemies[index] = ent;
    this->manager.unk_0e++;
}

// case volumeMasterTarget is 1: The manager is a helper

void ManagerBHelper_Main(Manager* this) {
    u8 i, anyRemaining;
    Entity* current;
    if (this->action == 0) {
        this->action = 1;
        SetDefaultPriority((Entity*)this, PRIO_NO_BLOCK);
    }
    // go through and check all monitored enemies.
    anyRemaining = 0;
    for (i = 0; i < 8; i++) {
        if ((current = ((ManagerBHelper*)this)->enemies[i])) {
            if (!current->next) {
                ((ManagerBHelper*)this)->enemies[i] = 0;
            } else {
                anyRemaining = 1;
            }
        }
    }
    if (!anyRemaining) {
        // inform the parent that we're done
        if (((ManagerB*)this->parent)->manager.unk_0e) {
            ((ManagerB*)this->parent)->manager.unk_0e--;
        }
        DeleteThisEntity();
    }
}

/*
 * Replace an entity that is currently being monitored with a new one
 */
void ReplaceMonitoredEntity(Entity* old, Entity* new) {
    ManagerBHelper* current;
    Manager* end = (Manager*)&gEntityLists[8];
    u32 i;
    for (current = (ManagerBHelper*)end->next; (Manager*)current != end;
         current = (ManagerBHelper*)current->manager.next) {
        if (current->manager.type != 0x9 || current->manager.subtype != 0xB)
            continue;
        for (i = 0; i < 8; i++) {
            if (old == current->enemies[i]) {
                current->enemies[i] = new;
                return;
            }
        }
    }
}

void (*const ManagerB_ActionFuncs[])(Manager*) = { (void (*)(Manager*))ManagerB_Main2,
                                                   (void (*)(Manager*))ManagerBHelper_Main };
void (*const ManagerB_StateFuncs[])(ManagerB*) = { ManagerB_Init, ManagerB_WaitForFlag, ManagerB_WaitForDone };
