/**
 * @file pullableLever.c
 * @ingroup Objects
 *
 * @brief Pullable Lever object
 */

#define NENT_DEPRECATED
#include "global.h"
#include "object.h"
#include "functions.h"
#include "sound.h"
#include "hitbox.h"

typedef struct {
    /*0x00*/ Entity base;
    /*0x68*/ u8 unk_68[8];
    /*0x70*/ u16 pullLength;
    /*0x72*/ u16 leverPart; // lever part number? [0,1,2]
    /*0x74*/ u8 actionDelay;
    /*0x75*/ u8 unk_75;
    /*0x76*/ u16 necessaryPullLength;
    /*0x78*/ u8 unk_78[6];
    /*0x7e*/ u8 unk_7e;
    /*0x7f*/ u8 unk_7f;
    /*0x80*/ u16 initialX;
    /*0x82*/ u16 initialY;
    /*0x84*/ u8 unk_84[2];
    /*0x86*/ u16 pulledFlag;
} PullableLeverEntity;

enum PullableLeverPart { HANDLE, MIDDLE, SOCKET };

extern u16 gUnk_02021F00[];

extern void (*const PullableLever_HandleActions[])(PullableLeverEntity*);
extern void (*const PullableLever_MiddleActions[])(PullableLeverEntity*);
extern const s8 PullableLever_InitialOffsets[];
extern void (*const PullableLever_HandleSubActions[])(PullableLeverEntity*);
extern const s8 gUnk_081222B8[];
extern const u8 gUnk_081222C0[];

void sub_08091504(PullableLeverEntity*);
void PullableLever_UpdateLength(PullableLeverEntity*);
void sub_0809153C(PullableLeverEntity*);

void PullableLever(PullableLeverEntity* this) {
    switch (this->leverPart) {
        case HANDLE:
            PullableLever_HandleActions[super->action](this);
            sub_0800445C(super);
            gUnk_02021F00[this->actionDelay] = this->pullLength;
            break;
        case MIDDLE:
            PullableLever_MiddleActions[super->action](this);
            break;
        case SOCKET:
            if (super->action == 0) {
                sub_08091504(this);
            }
            break;
    }
}

void PullableLever_HandleInit(PullableLeverEntity* this) {
    if (gEntCount < 0x46) {
        PullableLeverEntity* entity1 = (PullableLeverEntity*)CreateObject(PULLABLE_LEVER, super->type, super->type2);
        PullableLeverEntity* entity2 = (PullableLeverEntity*)CreateObject(PULLABLE_LEVER, super->type, super->type2);
        (entity1->base).parent = super;
        (entity2->base).parent = super;
        entity1->leverPart = 1;
        entity2->leverPart = 2;
        CopyPosition(super, &entity2->base);
        super->action = 1;
        super->spriteSettings.draw = 1;
        super->speed = 0x60;
        if ((super->type2 & 1)) {
            super->hitbox = (Hitbox*)&gUnk_080FD278;
        } else {
            super->hitbox = (Hitbox*)&gUnk_080FD270;
        }
        super->field_0x16 = 1;
        super->x.HALF.HI += PullableLever_InitialOffsets[super->type2 * 2];
        this->initialX = super->x.HALF.HI;
        super->y.HALF.HI += PullableLever_InitialOffsets[super->type2 * 2 + 1];
        this->initialY = super->y.HALF.HI;
        CopyPosition(super, &entity1->base);
        this->necessaryPullLength = this->unk_7e;
        this->actionDelay = super->actionDelay;
        InitializeAnimation(super, super->type2);
    }
}

void PullableLever_HandleAction1(PullableLeverEntity* this) {
    sub_08078930(super);
    if (this->pullLength != 0) {
        super->direction = super->type2 << 3;
        LinearMoveUpdate(super);
        PullableLever_UpdateLength(this);
    }
}

void PullableLever_HandleAction2(PullableLeverEntity* this) {
    sub_0806FBB4(super);
    PullableLever_HandleSubActions[super->subAction - 5](this);
}

void PullableLever_HandleSubAction0(PullableLeverEntity* this) {
    u16 y;
    u16 x;
    Entity* player = &gPlayerEntity;

    if (this->unk_75 == 0) {
        if (-player->animationState + super->type2 * 2 == 0) {
            x = gUnk_081222B8[super->type2 * 2] + super->x.HALF_U.HI;
            y = gUnk_081222B8[super->type2 * 2 + 1] + super->y.HALF_U.HI;
            if (0x10 < sub_080B1B18(x, y, player->collisionLayer) - 0xf) {
                player->x.HALF.HI = x;
                player->y.HALF.HI = y;
            }
        }
        this->unk_75 = 1;
        super->field_0xf = 2;
    }
    if (player->animationState >> 1 == super->type2) {
        if (gPlayerState.framestate == PL_STATE_PULL) {
            if (gPlayerState.heldObject & 2) {
                if (gPlayerEntity.frame & 2) {
                    sub_0809153C(this);
                    if (this->unk_75 == 1) {
                        this->unk_75 = 2;
                        EnqueueSFX(SFX_19E);
                    }
                }
            }
        }
    }
}

void PullableLever_HandleSubAction1(PullableLeverEntity* this) {
    super->action = 1;
    this->unk_75 = 0;
}

void PullableLever_MiddleInit(PullableLeverEntity* this) {
    super->action = 1;
    super->spritePriority.b0 = 5;
    InitializeAnimation(super, super->parent->type2 + 8);
    UpdateSpriteForCollisionLayer(super);
}

void PullableLever_MiddleAction1(PullableLeverEntity* this) {
    u16 tmp;
    PullableLeverEntity* parent = ((PullableLeverEntity*)super->parent);
    if (parent->pullLength < 8) {
        super->spriteSettings.draw = 0;
    } else {
        super->spriteSettings.draw = 1;
        tmp = (((PullableLeverEntity*)super->parent)->pullLength - 8) & 7;
        switch (super->type2) {
            case 0:
                super->y.HALF.HI = (super->parent->y.HALF.HI - tmp) - 0x11;
                break;
            case 1:
                super->x.HALF.HI = tmp + super->parent->x.HALF.HI + 0x11;
                break;
            case 2:
                super->y.HALF.HI = tmp + super->parent->y.HALF.HI + 0x11;
                break;
            case 3:
                super->x.HALF.HI = (super->parent->x.HALF.HI - tmp) - 0x11;
                break;
        }
        tmp = (parent->pullLength - 8) >> 4;
        if (tmp != super->animationState) {
            super->animationState = tmp;
            InitializeAnimation(super, tmp * 4 + super->type2 + 8);
        }
    }
}

void sub_08091504(PullableLeverEntity* this) {
    super->action = 1;
    super->spriteSettings.draw = 1;
    super->spritePriority.b0 = 6;
    InitializeAnimation(super, super->type2 + 4);
    UpdateSpriteForCollisionLayer(super);
}

void sub_0809153C(PullableLeverEntity* this) {
    s16 prevX;
    s16 prevY;
    u32 dir;
    s16 diff;
    s32 radius;

    Entity* player = &gPlayerEntity;
    dir = gUnk_081222C0[super->type2];
    if (this->necessaryPullLength <= this->pullLength) {
        return;
    }
    prevX = player->x.HALF.HI;
    prevY = player->y.HALF.HI;
    sub_080044AE(&gPlayerEntity, super->speed, dir);
    if ((prevX == gPlayerEntity.x.HALF.HI) && (prevY == gPlayerEntity.y.HALF.HI)) {
        return;
    }

    switch (super->type2) {
        case 0:
            diff = player->y.HALF.HI - prevY;
            break;
        case 1:
            diff = prevX - gPlayerEntity.x.HALF.HI;
            break;
        case 2:
            diff = prevY - gPlayerEntity.y.HALF.HI;
            break;
        case 3:
            diff = gPlayerEntity.x.HALF.HI - prevX;
            break;
    }
    radius = (diff << 0x18);
    super->x.WORD += gSineTable[dir * 8] * (radius >> 0x10);
    super->y.WORD -= gSineTable[dir * 8 + 0x40] * (radius >> 0x10);
    PullableLever_UpdateLength(this);
    if (this->necessaryPullLength <= this->pullLength) {
        EnqueueSFX(SFX_BUTTON_PRESS);
        SetFlag(this->pulledFlag);
    } else {
        if (--super->field_0xf == 0) {
            super->field_0xf = 0x10;
            EnqueueSFX(SFX_10F);
        }
    }
}

void PullableLever_UpdateLength(PullableLeverEntity* this) {
    switch (super->type2) {
        case 0:
            this->pullLength = super->y.HALF.HI - this->initialY;
            break;
        case 1:
            this->pullLength = this->initialX - super->x.HALF.HI;
            break;
        case 2:
            this->pullLength = this->initialY - super->y.HALF.HI;
            break;
        case 3:
            this->pullLength = super->x.HALF.HI - this->initialX;
            break;
    }

    if (CheckFlags(this->pulledFlag) && (this->pullLength == 0)) {
        ClearFlag(this->pulledFlag);
    }
}

void (*const PullableLever_HandleActions[])(PullableLeverEntity*) = {
    PullableLever_HandleInit,
    PullableLever_HandleAction1,
    PullableLever_HandleAction2,
};
void (*const PullableLever_MiddleActions[])(PullableLeverEntity*) = {
    PullableLever_MiddleInit,
    PullableLever_MiddleAction1,
};
const s8 PullableLever_InitialOffsets[] = {
    0, 9, -9, 0, 0, -9, 9, 0,
};
void (*const PullableLever_HandleSubActions[])(PullableLeverEntity*) = {
    PullableLever_HandleSubAction0,
    PullableLever_HandleSubAction1,
};
const s8 gUnk_081222B8[] = {
    0, 10, -8, 0, 0, -10, 8, 0,
};
const u8 gUnk_081222C0[] = {
    16,
    24,
    0,
    8,
};
