/**
 * @file moldworm.c
 * @ingroup Enemies
 *
 * @brief Moldworm enemy
 */

#include "enemy.h"
#include "functions.h"

extern void SoundReqClipped(Entity*, u32);
extern bool32 sub_08023A38(u32);
extern void sub_08023990(Entity*, u32, u32);
extern void sub_08023A88(Entity*, u32);

void sub_080235BC(Entity*);
void sub_08023644(Entity*);
void sub_08023730(Entity*);
void sub_080237D8(Entity*);
void sub_0802390C(Entity*);
void sub_080239F0(Entity*);
bool32 sub_08023B38(Entity*);

extern const u8 gUnk_080B37A0[];

extern void (*const Moldworm_Functions[])(Entity*);
extern void (*const gUnk_080CBC50[])(Entity*);
extern const s8 gUnk_080CBC70[];
extern const s8 gUnk_080CBC90[];
extern void (*const gUnk_080CBC98[])(Entity*);
extern void (*const gUnk_080CBCA8[])(Entity*);
extern const s8 gUnk_080CBCB8[];

NONMATCH("asm/non_matching/moldworm/Moldworm.inc", void Moldworm(Entity* this)) {
    u16 prevX = this->x.HALF.HI;
    u16 prevY = this->y.HALF.HI;

    if (this->type == 0) {
        if (this->action != 0) {
            sub_0802390C(this);
        }
        this->field_0x7c.BYTES.byte1 = this->field_0x7c.BYTES.byte0;
        EnemyFunctionHandler(this, Moldworm_Functions);
    } else {
        if (this->parent->next) {
            if (this->type != 8) {
                sub_080235BC(this);
            } else {
                sub_08023730(this);
            }
        } else {
            DeleteEntity(this);
            return;
        }
    }

    if (this->parent->field_0x7c.BYTES.byte0 != this->parent->field_0x7c.BYTES.byte1 && this->child) {
        ((u8*)&this->child->field_0x78)[(this->parent->field_0x7c.BYTES.byte0 - 1) & 0xf] =
            (((this->x.HALF.HI - prevX + 8) & 0xf) << 4) | ((this->y.HALF.HI - prevY + 8U) & 0xf);
    }
}
END_NONMATCH

void Moldworm_OnTick(Entity* this) {
    gUnk_080CBC50[this->action](this);
}

void Moldworm_OnCollision(Entity* this) {
    if (this->subAction == 0xff) {
        this->action = 7;
        this->actionDelay = 1;
        this->subAction = 0;
        this->hitType = 0x85;
        this->iframes = -8;
        this->field_0x7c.BYTES.byte3 = 0;
        this->field_0x7a.HALF.HI = 0;
        if (this->bitfield == 0x80 || this->bitfield == 0x9e) {
            this->type2 = 0;
            this->field_0x80.HALF.LO = 0x14;
        } else {
            this->type2 = 1;
            this->field_0x80.HALF.LO = 8;
        }
    }

    if (this->health == 0 && this->field_0x7c.BYTES.byte3 == 0 && this->action == 7) {
        CopyPosition(this, &gPlayerEntity);
        gPlayerEntity.flags |= ENT_COLLIDE;
        gPlayerEntity.spriteSettings.draw = 1;
        gPlayerEntity.zVelocity = Q_16_16(1.5);
        gPlayerEntity.direction = 0xff;
        gPlayerEntity.iframes = -0x14;
        gPlayerState.jump_status = 0x41;
        gPlayerState.flags &= ~PL_MOLDWORM_CAPTURED;
    }

    EnemyFunctionHandlerAfterCollision(this, Moldworm_Functions);
}

void Moldworm_OnKnockback(Entity* this) {
    this->field_0x7c.BYTES.byte0++;
    GenericKnockback(this);
}

void Moldworm_OnGrabbed(Entity* this) {
}

void sub_080231BC(Entity* this) {
    if (gEntCount < 0x40) {
        Entity* ent;

        ent = this->child = CreateEnemy(MOLDWORM, 1);
        ent->parent = this;

        ent = ent->child = CreateEnemy(MOLDWORM, 2);
        ent->parent = this;

        ent = ent->child = CreateEnemy(MOLDWORM, 3);
        ent->parent = this;

        ent = ent->child = CreateEnemy(MOLDWORM, 4);
        ent->parent = this;

        ent = ent->child = CreateEnemy(MOLDWORM, 5);
        ent->parent = this;

        ent = ent->child = CreateEnemy(MOLDWORM, 6);
        ent->parent = this;

        ent = ent->child = CreateEnemy(MOLDWORM, 7);
        ent->parent = this;

        ent->child = CreateEnemy(MOLDWORM, 8);
        *(Entity**)&ent->child->field_0x74 = ent;
        ent = ent->child;
        ent->parent = this;
        ent->child = NULL;
        sub_0804A720(this);
        this->action = 6;
        this->actionDelay = 0x1e;
        this->parent = this;
        this->field_0x78.HWORD = 0x1e;
        this->palette.b.b0 = 5;
        this->direction = Random() & 0x1f;
        this->animationState = Direction8ToAnimationState(Direction8RoundUp(this->direction));
        InitializeAnimation(this, this->animationState);
    }
}

void nullsub_136(Entity* this) {
}

void sub_08023288(Entity* this) {
    if (sub_08049FDC(this, 1) && (this->actionDelay++ & 0xf) == 0) {
        u32 idx = Random() & 0x1e;
        u32 i;

        for (i = 0; i < 0x10; i++) {
            u32 x = gPlayerEntity.x.HALF.HI + gUnk_080CBC70[idx + 0];
            u32 y = gPlayerEntity.y.HALF.HI + gUnk_080CBC70[idx + 1];
            if (sub_08023A38(GetTileTypeByPos(x, y, gPlayerEntity.collisionLayer))) {
                sub_08023990(this, x, y);
                return;
            }
            idx += 2;
            idx &= 0x1e;
        }
    }
}

void sub_08023330(Entity* this) {
    GetNextFrame(this);
    if (this->frame & 0x80) {
        this->action = 4;
        this->actionDelay = 0x19;
        COLLISION_ON(this);
        this->field_0x78.HWORD = 600;
        this->direction = Random() & 0x1c;
        this->animationState = this->direction >> 2;
        this->field_0x7a.HALF.HI = 0;
        this->field_0x7c.BYTES.byte3 = 0;
        sub_08023A88(this, this->animationState);
        CopyPosition(this, this->child);
        CreateFx(this, FX_ROCK, 0);
    }
}

void sub_08023A68(Entity*);
void sub_08023AB0(Entity*);

void sub_08023398(Entity* this) {
    this->field_0x7c.BYTES.byte0++;

    if (this->field_0x7c.BYTES.byte3 && sub_08049FDC(this, 1) == 0) {
        this->field_0x78.HWORD = 1;
    }

    if (--this->field_0x78.HWORD == 0) {
        if (sub_08023A38(GetTileTypeByEntity(this))) {
            this->action = 5;
            this->field_0x7c.BYTES.byte3 = 0;
            COLLISION_OFF(this);
            this->hitType = 0x85;
            this->child->actionDelay = 1;
            sub_08023A68(this);
            CreateFx(this, FX_ROCK, 0);
            return;
        }
        this->field_0x78.HWORD = 0x28;
    }

    if (this->field_0x7c.BYTES.byte3) {
        sub_08023AB0(this);
    }

    if (--this->actionDelay < 3) {
        if (this->actionDelay == 0)
            this->actionDelay = 25;
    } else {
        int prevX = this->x.WORD;
        int prevY = this->y.WORD;
        ProcessMovement0(this);
        if (this->x.WORD == prevX && this->y.WORD == prevY) {
            this->field_0x7c.BYTES.byte0--;
        }

        if ((gRoomTransition.frameCount & 7) == 0) {
            u32 uVar4;

            sub_08004596(this, sub_08049F84(this, 1));
            uVar4 = Direction8ToAnimationState(Direction8RoundUp(this->direction));
            if (uVar4 != this->animationState) {
                this->animationState = uVar4;
                InitializeAnimation(this, uVar4 + this->field_0x7a.HALF.HI);
            }
        }
    }
}

void sub_080234A4(Entity* this) {
    this->field_0x7c.BYTES.byte0++;
    GetNextFrame(this);
    if (this->field_0x7c.BYTES.byte3) {
        this->action = 6;
        this->spriteSettings.draw = 0;
        this->field_0x78.HWORD = 300;
    }
}

void sub_080234D8(Entity* this) {
    if (--this->field_0x78.HWORD == 0) {
        this->action = 2;
        this->palette.b.b0 = 5;
        this->direction = Random() & 0x1f;
        this->animationState = Direction8ToAnimationState(Direction8RoundUp(this->direction));
        sub_08023A88(this, this->animationState);
    }
}

void sub_0802351C(Entity* this) {
    if (this->actionDelay != 0 && (this->type2 == 1 || gPlayerEntity.frameIndex == 0xff)) {
        this->actionDelay = 0;
        this->child->action = 3;
        this->child->field_0xf = this->field_0x80.HALF.LO;
        InitializeAnimation(this->child, this->child->animationState + 1);
        InitializeAnimation(this, this->animationState);
    }

    if (this->field_0x7c.BYTES.byte3 == 0) {
        if (this->type2 == 0) {
            gPlayerEntity.animationState = this->animationState & 7;
            gPlayerState.flags |= PL_MOLDWORM_CAPTURED;
            PositionRelative(this, &gPlayerEntity, 0, Q_16_16(gUnk_080CBC90[this->animationState & 7]));
            gPlayerEntity.spriteOffsetY = -gUnk_080CBC90[this->animationState & 7];
        }
    } else {
        this->action = 4;
    }
}

void sub_080235BC(Entity* this) {
    gUnk_080CBC98[this->action](this);
}

void sub_080235D4(Entity* this) {
    Entity* parent;

    this->action = 1;
    parent = this->parent;
    this->x.HALF.HI = parent->x.HALF.HI;
    this->y.HALF.HI = parent->y.HALF.HI;
    sub_080239F0(this);
    if (this->type == 1) {
        this->animationState = 0x10;
    } else {
        this->animationState = 0x12;
    }
    InitializeAnimation(this, this->animationState);
}

void sub_08023604(Entity* this) {
    if (((u8*)&this->field_0x78)[this->parent->field_0x7c.BYTES.byte0 & 0xf] != 0x88) {
        this->action = 2;
        this->actionDelay = 0;
        COLLISION_ON(this);
        this->spriteSettings.draw = 1;
        sub_08023644(this);
    }
}

void sub_08023644(Entity* this) {
    Entity* parent = this->parent;

    if (parent->animIndex == 0x17 && this->actionDelay != 0 && this->x.HALF.HI == parent->x.HALF.HI &&
        this->y.HALF.HI == parent->y.HALF.HI) {
        this->action = 1;
        COLLISION_OFF(this);
        this->spriteSettings.draw = 0;
        this->child->actionDelay = 1;
        sub_080239F0(this);
    }

    if (parent->field_0x7c.BYTES.byte0 != parent->field_0x7c.BYTES.byte1) {
        u8* tmp = &((u8*)&this->field_0x78)[parent->field_0x7c.BYTES.byte0 & 0xf];
        this->x.HALF.HI += (*tmp >> 4) - 8;
        this->y.HALF.HI += (*tmp & 0xf) - 8;
        this->spriteOrientation.flipY = parent->spriteOrientation.flipY;
        this->spriteRendering.b3 = parent->spriteRendering.b3;
        this->collisionLayer = parent->collisionLayer;
    }
}

void sub_080236F8(Entity* parent) {
    if (--parent->field_0xf == 0) {
        parent->action = 2;
        parent->child->action = 3;
        parent->child->field_0xf = parent->parent->field_0x80.HALF.LO;
        InitializeAnimation(parent->child, parent->child->animationState + 1);
        InitializeAnimation(parent, parent->animationState);
    }
}

void sub_08023730(Entity* this) {
    gUnk_080CBCA8[this->action](this);
}

void sub_08023748(Entity* this) {
    Entity* parent;

    this->action = 1;
    this->animationState = 20;
    parent = this->parent;
    this->x.HALF.HI = parent->x.HALF.HI;
    this->y.HALF.HI = parent->y.HALF.HI;
    sub_080239F0(this);
    sub_08023A68(this);
}

void sub_0802376C(Entity* this) {
    Entity* parent = this->parent;

    if (parent->spriteSettings.draw == 1 && parent->animIndex < 0x10) {
        this->spriteSettings.draw = 1;
        GetNextFrame(this);
    }

    if (((u8*)&this->field_0x78)[parent->field_0x7c.BYTES.byte0 & 0xf] != 0x88) {
        this->action = 2;
        this->actionDelay = 0;
        COLLISION_ON(this);
        this->parent->field_0x7c.BYTES.byte3 = 1;
        sub_08023A88(this, 20);
        sub_080237D8(this);
    }
}

void sub_080237D8(Entity* this) {
    Entity* parent = this->parent;

    if (parent->animIndex == 0x17 && this->actionDelay != 0 && this->x.HALF.HI == parent->x.HALF.HI &&
        this->y.HALF.HI == parent->y.HALF.HI) {
        this->action = 1;
        COLLISION_OFF(this);
        this->spriteSettings.draw = 0;
        parent->field_0x7c.BYTES.byte3 = 1;
        sub_080239F0(this);
        sub_08023A68(this);
    }

    if (parent->field_0x7c.BYTES.byte0 != parent->field_0x7c.BYTES.byte1) {
        u8* tmp = &((u8*)&this->field_0x78)[parent->field_0x7c.BYTES.byte0 & 0xf];
        this->x.HALF.HI += (*tmp >> 4) - 8;
        this->y.HALF.HI += (*tmp & 0xf) - 8;

        this->spriteOrientation.flipY = parent->spriteOrientation.flipY;
        this->spriteRendering.b3 = parent->spriteRendering.b3;
        this->collisionLayer = parent->collisionLayer;
    }
}

void sub_08023894(Entity* this) {
    if (--this->field_0xf == 0) {
        this->action = 2;
        this->parent->field_0x7c.BYTES.byte3 = 1;
        InitializeAnimation(this, this->animationState);
        if (this->parent->type2 == 0) {
            gPlayerState.flags |= PL_MOLDWORM_RELEASED;
            gPlayerEntity.x.HALF.HI = this->x.HALF.HI;
            gPlayerEntity.y.HALF.HI = this->y.HALF.HI;
            gPlayerEntity.direction = DirectionRoundUp(GetFacingDirection(*(Entity**)&this->field_0x74, this));
            gPlayerEntity.animationState = gPlayerEntity.direction >> 2;
            gPlayerEntity.iframes = 12;
            ModHealth(-0x10);
            SoundReqClipped(&gPlayerEntity, SFX_PLY_VO6);
        }
    }
}

void sub_0802390C(Entity* this) {
    if (this->bitfield & 0x80) {
        Entity* ent = this->child;
        do {
            ent->iframes = this->iframes;
        } while (ent = ent->child, ent != NULL);
    } else {
        Entity* ent = this->child;
        do {
            if (ent->bitfield & 0x80) {
                u8 bVar2 = 0xff - ent->health;
                if (bVar2 != 0) {
                    u32 tmp;
                    ent->health = 0xff;
                    tmp = (u8)ent->iframes;
                    if (this->health >= bVar2) {
                        this->health -= bVar2;
                    } else {
                        this->health = 0;
                    }

                    ent = this;
                    do {
                        ent->iframes = tmp;
                    } while (ent = ent->child, ent != NULL);
                    break;
                }
            }
        } while (ent = ent->child, ent != NULL);
    }
}

void sub_08023990(Entity* this, u32 param_2, u32 param_3) {
    Entity* ent;

    this->action = 3;
    this->spriteSettings.draw = 1;
    this->palette.b.b0 = 0x5;
    this->palette.b.b4 = 0x5;
    this->spritePriority.b0 = 7;
    this->x.HALF.HI = param_2;
    this->y.HALF.HI = param_3;
    this->collisionLayer = gPlayerEntity.collisionLayer;
    UpdateSpriteForCollisionLayer(this);
    InitializeAnimation(this, 0x16);

    ent = this->child;
    do {
        sub_080239F0(ent);
        CopyPosition(this, ent);
    } while (ent = ent->child, ent != NULL);
}

/* TODO: fix struct */
ASM_FUNC("asm/non_matching/moldworm/sub_080239F0.inc", void sub_080239F0(Entity* this))

bool32 sub_08023A38(u32 unk) {
    if (unk == 0x1a || unk == 0x29) {
        return TRUE;
    } else {
        unk = gUnk_080B37A0[unk];
        if (unk == 9 || unk == 11 || unk == 10 || unk == 12) {
            return TRUE;
        } else {
            return FALSE;
        }
    }
}

void sub_08023A68(Entity* this) {
    ChangeObjPalette(this, 5);
    this->spritePriority.b0 = 7;
    InitializeAnimation(this, 0x17);
}

void sub_08023A88(Entity* this, u32 unk) {
    ChangeObjPalette(this, 0x22);
    this->spritePriority.b0 = 4;
    InitializeAnimation(this, unk);
}

void sub_08023AB0(Entity* this) {
    if (this->field_0x7a.HALF.HI == 8) {
        if (this->field_0x7c.BYTES.byte2) {
            this->field_0x7c.BYTES.byte2--;
        } else if (!sub_08023B38(this) || 0x1d >= this->field_0x78.HWORD) {
            this->hitType = 0x85;
            this->field_0x7a.HALF.HI = 0;
            this->field_0x7c.BYTES.byte2 = 30;
            InitializeAnimation(this, this->animationState);
        }
    } else if (this->field_0x7c.BYTES.byte2) {
        this->field_0x7c.BYTES.byte2--;
    } else if (this->field_0x78.HWORD >= 90 && sub_08023B38(this)) {
        this->hitType = 0x87;
        this->field_0x7a.HALF.HI = 8;
        this->field_0x7c.BYTES.byte2 = 10;
        InitializeAnimation(this, this->animationState + 8);
    }
}

ASM_FUNC("asm/non_matching/moldworm/sub_08023B38.inc", bool32 sub_08023B38(Entity* this))

// clang-format off
void (*const Moldworm_Functions[])(Entity*) = {
    Moldworm_OnTick,
    Moldworm_OnCollision,
    Moldworm_OnKnockback,
    GenericDeath,
    GenericConfused,
    Moldworm_OnGrabbed,
};

void (*const gUnk_080CBC50[])(Entity*) = {
    sub_080231BC,
    nullsub_136,
    sub_08023288,
    sub_08023330,
    sub_08023398,
    sub_080234A4,
    sub_080234D8,
    sub_0802351C,
};

const s8 gUnk_080CBC70[] = {
    0x00, 0xd0, 0x00, 0x30,
    0xd0, 0x00, 0x30, 0x00,
    0xe0, 0xe0, 0xe0, 0x20,
    0x20, 0xe0, 0x20, 0x20,
    0xf0, 0xe0, 0x10, 0xe0,
    0xf0, 0x20, 0x10, 0x20,
    0xe0, 0xf0, 0x20, 0xf0,
    0xe0, 0x10, 0x20, 0x10,
};

const s8 gUnk_080CBC90[] = {
    0xff, 0xff, 0x01, 0x01, 0x01, 0x01, 0x01, 0xff, 
};

void (*const gUnk_080CBC98[])(Entity*) = {
    sub_080235D4,
    sub_08023604,
    sub_08023644,
    sub_080236F8,
};

void (*const gUnk_080CBCA8[])(Entity*) = {
    sub_08023748,
    sub_0802376C,
    sub_080237D8,
    sub_08023894,
};

const s8 gUnk_080CBCB8[] = {
    0x00, 0xe4, 0x14, 0xec,
    0x1c, 0x00, 0x14, 0x14,
    0x00, 0x1c, 0xec, 0x14,
    0xe4, 0x00, 0xec, 0xec,
};
// clang-format on
