#include "Actor/ActorManager.hpp"
#include "DTCM/UnkStruct_027e077c.hpp"
#include "DTCM/UnkStruct_027e103c.hpp"
#include "Map/MapManager.hpp"

static char *sShipTypes[8] = {"anc", "bow", "hul", "can", "dco", "pdl", "fnl", "brg"};

extern "C" Cylinder *func_ov000_020c3ef0(void *, u32 index);
extern "C" bool func_ov000_020c3f08(void *, u32 index);
extern "C" void func_ov000_020c3f3c(void *, u32 index, Cylinder *cylinder);
extern "C" void func_ov000_020c3f54(void *, u32 param2);

ARM void ActorManager::DeleteActor(u32 index, bool param2) {
    if (!param2) {
        func_ov000_020c3f54(this->mUnk_14, index & 0xffff);
        this->mActorTable[index]->StopLinkFollow();
    }

    if (this->mActorTable[index] != NULL) {
        delete this->mActorTable[index];
    }

    this->mActorTable[index] = NULL;

    if (param2) {
        return;
    }

    this->mNumActors--;

    if (index + 1 != this->mMaxActorIndex) {
        return;
    }

    int i = index - 1;

    while (i >= 0) {
        if (this->mActorTable[i] != NULL) {
            break;
        }
        i--;
    }

    this->mMaxActorIndex = i + 1 & 0xffff;
    return;
}

ARM ActorRef ActorManager::func_ov00_020c3484(unk32 param3) {
    ActorRef ref;
    if (data_027e103c->mUnk_24 == 0) {
        return ref;
    }

    q20 minDistance       = 0x7fffffff;
    Actor **actorIter     = this->mActorTable;
    u8 unkByte            = data_02056be4[data_027e077c.GetUnk0()];
    bool unk1             = (unkByte & 1) != 0;
    Actor **actorTableEnd = actorIter + this->mMaxActorIndex;

    for (; actorIter < actorTableEnd; actorIter++) {
        Actor *actor = *actorIter;
        if (actor != NULL && actor->mAlive) {
            if (actor->func_ov00_020c27a8(param3)) {
                if ((*actorIter)->mUnk_0a4.mUnk_00 || unk1) {
                    if ((*actorIter)->IsHitboxTouched(false)) {
                        q20 distance = (*actorIter)->DistanceToLink();
                        if (distance < minDistance) {
                            ref         = (*actorIter)->mRef;
                            minDistance = distance;
                        }
                    }
                }
            }
        }
    }
    return ref;
}

ARM void ActorManager::Actor_vfunc_10(u32 param1) {
    Actor *actor;
    int i;

    u32 uVar1            = param1;
    Actor **currentActor = this->mActorTable;

    for (i = 0; i < this->mMaxActorIndex; ++i) {
        actor = *currentActor;
        if (actor != NULL) {
            uVar1 = actor->mAlive;
            if (uVar1 != 0) {
                actor->vfunc_10(param1);
            }
        }
        currentActor += 1;
    }
    return;
}

ARM Actor *ActorManager::FindActorById(s32 id) {
    u32 actorId;
    s32 cacheIndex;
    Actor *actor;
    Actor **pActor;
    Actor **lastActor;

    actor = NULL;
    if (id >= 0) {
        cacheIndex = this->mCacheEmptyActorIndex;
        if ((cacheIndex >= 0) && (cacheIndex < this->mMaxActorIndex)) {
            pActor = this->mActorTable;
            if (pActor[cacheIndex] != NULL) {
                if ((Actor **) (u32) pActor[cacheIndex]->mAlive != NULL && id == pActor[cacheIndex]->mRef.id) {
                    actor = pActor[cacheIndex];
                    goto ret;
                }
            }
        }
        pActor    = this->mActorTable;
        actorId   = this->mMaxActorIndex;
        lastActor = pActor + actorId;

        for (; pActor < lastActor; pActor = pActor + 1) {
            if (*pActor != NULL) {
                actorId = (u32) (*pActor)->mAlive;
                if (actorId != 0 && id == (*pActor)->mRef.id) {
                    actor = *pActor;
                    break;
                }
            }
        }
    }

ret:
    return actor;
}

ARM Actor *ActorManager::GetActor(ActorRef *ref) {
    Actor *pActor;
    u32 alive;

    alive = ref->index;

    if ((s32) alive < 0) {
        return NULL;
    }

    pActor = this->mActorTable[alive];
    if (pActor != NULL) {
        alive = pActor->mAlive;
        if (alive != 0 && ref->id == pActor->mRef.id) {
            return pActor;
        }
    }

    return NULL;
}

ARM bool FilterActor::Filter(Actor *actor) {
    if (mType != actor->mType) {
        return false;
    }
    if (mUnk_08 != -1 && mUnk_08 != actor->mUnk_034.id) {
        return false;
    }
    if (mExcludeRefs != NULL) {
        for (s32 i = 0; mExcludeRefs[i].id != -1; ++i) {
            ActorRef *ref          = &mExcludeRefs[i];
            volatile Actor *actor2 = actor;
            if (ref->id == actor2->mRef.id) {
                return false;
            }
        }
    }
    if (mExcludeNotInAABB) {
        Vec3p pos;
        pos.x       = actor->mPos.x;
        pos.y       = actor->mPos.y;
        pos.z       = actor->mPos.z;
        bool result = !mAABB.ContainsInXZ(&pos);
        if (result) {
            return false;
        }
    }
    if (mExcludeNotInSphere) {
        Vec3p pos;
        pos.x       = actor->mPos.x;
        pos.y       = actor->mPos.y;
        pos.z       = actor->mPos.z;
        bool result = !mSphere.ContainsInXZ(&pos);
        if (result) {
            return false;
        }
    }
    return true;
}

ARM s32 ActorManager::FilterActors(FilterActorBase *filter, ActorList *filteredActors) {
    s32 numApplied = 0;
    if (filteredActors != NULL) {
        filteredActors->length = 0;
    }

    Actor **actorIter     = mActorTable;
    Actor **actorTableEnd = actorIter + mMaxActorIndex;
    for (; actorIter < actorTableEnd; actorIter++) {
        Actor *actor = *actorIter;
        if (actor != NULL && actor->mAlive && filter->Filter(actor)) {
            if (filteredActors != NULL && filteredActors->length < filteredActors->capacity) {
                ActorRef *ref = &filteredActors->actors[filteredActors->length];

                *ref = (*actorIter)->mRef;
                filteredActors->length += 1;
            }
            numApplied += 1;
        }
    }

    return numApplied;
}

ARM ActorRef ActorManager::FindActorByType(ActorTypeId type) {
    ActorRef ref;

    Actor **actorIter     = this->mActorTable;
    Actor **actorTableEnd = actorIter + this->mMaxActorIndex;

    for (; actorIter < actorTableEnd; actorIter++) {
        Actor *actor = *actorIter;
        if (actor != NULL && actor->mAlive && type == actor->mType) {
            ref = actor->mRef;
            return ref;
        }
    }
    return ref;
}

ARM ActorRef ActorManager::FindNearestActorOfType(ActorTypeId type, Vec3p *pos) {
    ActorRef ref;

    q20 minDistance = 0x7fffffff;

    Actor **actorIter     = this->mActorTable;
    Actor **actorTableEnd = actorIter + this->mMaxActorIndex;

    for (; actorIter < actorTableEnd; actorIter++) {
        Actor *actor = *actorIter;
        if (actor != NULL && actor->mAlive && type == actor->mType) {
            q20 distance = Vec3p_Distance(&actor->mPos, pos);
            if (distance < minDistance) {
                ref         = (*actorIter)->mRef;
                minDistance = distance;
            }
        }
    }

    return ref;
}

ARM bool ActorManager::func_ov00_020c398c(u32 index) {
    return func_ov000_020c3f08(mUnk_14, index);
}

ARM void ActorManager::func_ov00_020c399c(u32 index, Cylinder *cylinder) {
    func_ov000_020c3f3c(mUnk_14, index, cylinder);
}

ARM Actor *ActorManager::func_ov00_020c39ac(s32 index, const ActorTypeId *actorTypes, bool param3) {
    if (actorTypes == NULL && param3) {
        return NULL;
    }

    Actor *actor  = mActorTable[index];
    Actor *result = NULL;
    if (actor != NULL && actor->mAlive) {
        u16 i;
        bool unk1 = actorTypes == NULL && !param3 ? true : false;
        for (u16 i = 0; i < mMaxActorIndex; ++i) {
            if (i != index) {
                Actor *otherActor = mActorTable[i];
                if (mActorTable[i] != NULL && func_ov000_020c3f08(mUnk_14, i)) {
                    if (unk1 || (param3 != this->ActorTypeIsOneOf(mActorTable[i]->mType, actorTypes))) {
                        Cylinder oldHitbox  = mActorTable[i]->mHitbox;
                        Cylinder *newHitbox = func_ov000_020c3ef0(mUnk_14, i);

                        Actor *otherActor2         = mActorTable[i];
                        otherActor2->mHitbox.pos.x = newHitbox->pos.x;
                        otherActor2->mHitbox.pos.y = newHitbox->pos.y;
                        otherActor2->mHitbox.pos.z = newHitbox->pos.z;
                        otherActor2->mHitbox.size  = newHitbox->size;
                        if (mActorTable[i]->CollidesWith(actor)) {
                            result = mActorTable[i];
                        }

                        Actor *otherActor3         = mActorTable[i];
                        otherActor3->mHitbox.pos.x = oldHitbox.pos.x;
                        otherActor3->mHitbox.pos.y = oldHitbox.pos.y;
                        otherActor3->mHitbox.pos.z = oldHitbox.pos.z;
                        otherActor3->mHitbox.size  = oldHitbox.size;

                        if (result != NULL) {
                            break;
                        }
                    }
                }
            }
        }
    }

    return result;
}

ARM s32 ActorManager::func_ov00_020c3b2c(s32 *param1) {
    if (param1 != NULL) {
        *param1 = 0;
    }

    s32 i;
    s32 numAlive = 0;

    Actor **actorIter = mActorTable;

    for (i = 0; i < mMaxActorIndex; ++i, ++actorIter) {
        Actor *actor = *actorIter;
        if (actor != NULL && actor->mAlive && actor->mUnk_128) {
            if (param1 != NULL && actor->mUnk_120 > 0) {
                *param1 += 1;
            }
            numAlive += 1;
        }
    }

    return numAlive;
}

ARM s32 ActorManager::func_ov00_020c3bb0(unk32 param1, s32 *param2) {
    if (param2 != NULL) {
        *param2 = 0;
    }

    AABB boxes[8];
    s32 numBoxes = gMapManager->GetTriggerBoundingBoxes(param1, boxes, ARRAY_LEN_U(boxes));
    if (numBoxes <= 0) {
        return 0;
    }

    s32 i;
    s32 numInBox = 0;
    bool isInBox;

    Actor **actorIter = mActorTable;
    for (i = 0; i < mMaxActorIndex; ++i, ++actorIter) {
        Actor *actor = *actorIter;
        if (actor != NULL && actor->mAlive && actor->mUnk_128) {
            isInBox = false;
            for (s32 j = 0; j < numBoxes; ++j) {
                Actor *actor2 = *actorIter;
                Vec3p pos;
                pos.x = actor2->mPos.x;
                pos.y = actor2->mPos.y;
                pos.z = actor2->mPos.z;
                if (boxes[j].ContainsInXZ(&pos)) {
                    isInBox = true;
                    break;
                }
            }
            if (isInBox) {
                if (param2 != NULL && (*actorIter)->mUnk_120 > 0) {
                    *param2 += 1;
                }
                numInBox += 1;
            }
        }
    }

    return numInBox;
}

ARM void ActorManager::func_ov00_020c3ce8(unk32 param1, bool param2) {
    AABB boxes[8];
    s32 numBoxes = gMapManager->GetTriggerBoundingBoxes(param1, boxes, ARRAY_LEN_U(boxes));
    if (numBoxes <= 0) {
        return;
    }

    s32 i;
    Actor **actorIter = mActorTable;
    for (i = 0; i < mMaxActorIndex; ++i, ++actorIter) {
        Actor *actor = *actorIter;
        if (actor != NULL && actor->mAlive && actor->mUnk_128) {
            for (s32 j = 0; j < numBoxes; ++j) {
                Actor *actor2 = *actorIter;
                Vec3p pos;
                pos.x = actor2->mPos.x;
                pos.y = actor2->mPos.y;
                pos.z = actor2->mPos.z;
                if (boxes[j].Contains(&pos)) {
                    (*actorIter)->SetUnk_129(param2);
                }
            }
        }
    }
}

ARM void ActorManager::Actor_vfunc_28() {
    int i;
    Actor **pActor;

    pActor = this->mActorTable;

    for (i = 0; i < this->mMaxActors; i++) {
        if (*pActor != NULL) {
            (*pActor)->vfunc_28();
        }
        pActor = pActor + 1;
    }
}

ARM bool ActorManager::ActorTypeIsOneOf(ActorTypeId type, const ActorTypeId *types) {
    int i;
    bool found = false;

    for (i = 0; *types != ActorTypeId_Null; i++) {
        if (type == *types) {
            found = true;
            break;
        }
        types += 1;
    }

    return found;
}
