#include "global.h"
#include "trap.h"

#include "code_803E668.h"
#include "code_803E724.h"
#include "code_80450F8.h"
#include "code_8041AD0.h"
#include "code_8045A00.h"
#include "dungeon_message.h"
#include "code_8077274_1.h"
#include "code_807CD9C.h"
#include "code_808417C.h"
#include "constants/trap.h"
#include "constants/type.h"
#include "dungeon_engine.h"
#include "dungeon_items.h"
#include "dungeon_map_access.h"
#include "dungeon_pokemon_attributes.h"
#include "dungeon_random.h"
#include "dungeon_util.h"
#include "items.h"
#include "move_effects_target.h"
#include "moves.h"
#include "status.h"
#include "structs/map.h"
#include "structs/str_dungeon.h"

extern u8 *gTrapNames[];
extern u8 *gUnknown_80FC5FC[];
extern u8 *gUnknown_80FC5F8[];
extern u8 *gUnknown_80FDB5C[];
extern u8 *gUnknown_80FDB7C[];
extern u8 *gUnknown_80FD7F4[];
extern u8 *gUnknown_80FD7F8[];
extern s16 gUnknown_80F4E74[];
extern u8 *gUnknown_80FDC18[];
extern u8 *gUnknown_80FDC40[];
extern u8 *gUnknown_80FDC7C[];
extern u8 *gUnknown_80FD7AC[];
extern u8 *gUnknown_80FD788[];
extern u8 *gUnknown_80FD7D4[];
extern u8 *gUnknown_80F970C[];
extern u8 *gUnknown_80F9728[];
extern u8 *gUnknown_80FED00[];
extern u8 *gUnknown_80FED04[];
extern u8 *gUnknown_80FED0C[];
extern u8 *gUnknown_80FDAA0[];
extern u8 *gUnknown_80FDA80[];
extern u8 *gUnknown_80FDB04[];
extern u8 *gUnknown_80FDB2C[];


extern s16 gUnknown_80F4E0E;
extern s16 gUnknown_80F4F84;
extern s16 gUnknown_80F4F86;
extern u32 gUnknown_8106A4C;
extern u32 gUnknown_8106A50;
extern s16 gUnknown_80F4F8A;

struct unkStruct_806B7F8
{
    s16 species;
    u8 unk2;
    u32 unk4;
    u16 level;
    u8 fillA[2];
    DungeonPos pos;
    u8 unk10;
};
extern Entity* sub_806B7F8(struct unkStruct_806B7F8 *, bool8);

void sub_806A9B4(Entity *, u32);

s16 sub_803D970(u32);
bool8 sub_806AA0C(s32, s32);
void sub_80421EC(DungeonPos *, u32);

bool8 sub_8045888(Entity *);
u8 sub_8043D10(void);
void sub_8068FE0(Entity *, u32, Entity *);
void sub_8045C28(Item *, u8 , u8);
void sub_8045BF8(u8 *, Item *);
void DealDamageToEntity(Entity *,s16,u32,u32);
void sub_806F480(Entity *, u32);
void sub_804225C(Entity *, DungeonPos *, u8);
void sub_8071DA4(Entity *);
void sub_806A1E8(Entity *pokemon);
u8 sub_803D6FC(void);
extern void sub_807DF38(Entity *pokemon, Entity *target, DungeonPos *pos, u32,
			u8 moveType, s16);

void sub_807FC3C(DungeonPos *pos, u32 trapID, u32 param_3)
{
  gDungeon->trapPos.x = pos->x;
  gDungeon->trapPos.y = pos->y;
  gDungeon->trapID = trapID;
  gDungeon->unk13579 = param_3;
  gDungeon->unk13570 = 1;
}

bool8 CanLayTrap(DungeonPos *pos)
{
    Tile *tile = GetTileMut(pos->x, pos->y);
    if (tile->terrainType & TERRAIN_TYPE_STAIRS ||
        tile->room == CORRIDOR_ROOM ||
        tile->terrainType & TERRAIN_TYPE_NATURAL_JUNCTION)
    {
        return FALSE;
    }
    if (tile->terrainType & TERRAIN_TYPE_SHOP)
    {
        return FALSE;
    }
    if ((tile->terrainType & (TERRAIN_TYPE_NORMAL | TERRAIN_TYPE_SECONDARY)) != TERRAIN_TYPE_NORMAL ||
        (tile->object != NULL && GetEntityType(tile->object) != ENTITY_TRAP))
    {
        return FALSE;
    }
    return TRUE;
}

bool8 LayTrap(DungeonPos *pos, u8 trapID, u8 param_3)
{
    Tile *tile;
    Entity *entity;
    int counter;
    u16 terrainType;

    tile = GetTileMut(pos->x, pos->y);
    if (TRAP_SPIKE_TRAP < trapID) {
        counter = 0;
        while ((counter < 0x1e && (trapID = sub_803D6FC(), trapID == TRAP_WONDER_TILE))) {
            counter++;
        }
        if (counter == 0x1e) {
            trapID = TRAP_CHESTNUT_TRAP;
        }
    }
    terrainType = tile->terrainType;
    if ((terrainType & TERRAIN_TYPE_STAIRS) != 0) goto _0807FD6E;
    if (tile->room == CORRIDOR_ROOM) goto _0807FD6E;
    if ((terrainType & TERRAIN_TYPE_NATURAL_JUNCTION) != 0) goto _0807FD6E;
    if ((terrainType & TERRAIN_TYPE_SHOP) != 0) goto _0807FD6E;
    if (((terrainType & (TERRAIN_TYPE_NORMAL | TERRAIN_TYPE_SECONDARY)) != TERRAIN_TYPE_NORMAL)) goto _0807FD6E;
    if (tile->object != NULL) {
        if (GetEntityType(tile->object) != ENTITY_TRAP) goto _0807FD6E;
        GetTrapData(tile->object)->id = trapID;
        GetTrapData(tile->object)->unk1 = param_3;
        tile->object->isVisible = TRUE;
    }
    else {
        entity = sub_8045684(trapID,pos,param_3);
        if (entity == NULL)
_0807FD6E:
            return FALSE;
        else
        {
                tile->object = entity;
                tile->object->isVisible = TRUE;
        }
    }
    return TRUE;
}

bool8 sub_807FD84(Entity *entity)
{
    bool8 flag;
    flag = FALSE;

    if (gDungeon->unk13570 == 0) {
        return FALSE;
    }
    else {
        gDungeon->unk13570 = 0;
        flag = LayTrap(&gDungeon->trapPos,gDungeon->trapID,gDungeon->unk13579);
        if (flag) {
            TryDisplayDungeonLoggableMessage5(entity,&gDungeon->trapPos,*gUnknown_80FC5F8); // A trap was laid!
        }
        else {
            TryDisplayDungeonLoggableMessage5(entity,&gDungeon->trapPos,*gUnknown_80FC5FC); // A trap can't be laid here.
        }
        sub_8049ED4();
    }
    return flag;
}

bool8 sub_807FE04(DungeonPos *pos, char param_2)
{
    Tile *tile;

    tile = GetTileMut(pos->x,pos->y);
    if ((tile->object != NULL) && (GetEntityType(tile->object) == ENTITY_TRAP)) {
        tile->object->type = 0;
        tile->object = NULL;
        if (param_2 != 0) {
            sub_8049ED4();
        }
        return TRUE;
    }
    else {
        return FALSE;
    }
}

bool8 sub_807FE44(DungeonPos *pos, char param_2)
{
    const Tile *tile;

    tile = GetTile(pos->x,pos->y);
    if ((tile->object != NULL) && (GetEntityType(tile->object) == ENTITY_TRAP)) {
        tile->object->isVisible = TRUE;
        if (param_2 != 0) {
            sub_8049ED4();
        }
        return TRUE;
    }
    else {
        return FALSE;
    }
}

void GetTrapName(u8 *buffer, u8 trapIndex)
{
    strcpy(buffer, gTrapNames[trapIndex]);
}

void sub_807FE9C(Entity *pokemon, DungeonPos *pos, int param_3, char param_4)
{
    Tile *tile;
    bool8 flag1;
    bool8 flag;

    Entity *entity;
    s32 rand;
    Trap *trapData;
    Entity *target;
    u8 *text;

    tile = GetTileMut(pos->x,pos->y);
    entity = tile->object;
    flag1 = FALSE;
    if (entity == NULL) {
        return;
    }
    if (GetEntityType(entity) != ENTITY_TRAP) {
        return;
    }
    trapData = GetTrapData(entity);
    GetTrapName(gFormatBuffer_Monsters[0],trapData->id);
    target = tile->monster;
    if ((target != NULL) && (GetEntityType(target) != ENTITY_MONSTER)) {
        target = NULL;
    }
    flag = entity->isVisible;
    entity->isVisible = TRUE;
    if (((target != NULL) && (sub_806A1E8(target), param_4 != '\0')) && (trapData->id != TRAP_WONDER_TILE)) {
        rand = DungeonRandInt(100);
        text = NULL;
        if (HasHeldItem(target, ITEM_TRAP_SCARF)) {
            text = *gUnknown_80FDB5C;
        }
        if ((flag == TRUE) || (gDungeon->unk181e8.unk1820F != 0)) {
            if (rand < 0) text = *gUnknown_80FDB7C;
        }
        else if (rand < 0xf) {
            text = *gUnknown_80FDB7C;
        }
        if (text != NULL) {
            if (sub_803F428(pos)) {
                sub_8049ED4();
            }
            TryDisplayDungeonLoggableMessage3(pokemon,target,text);
            if (param_3 == 0) {
                return;
            }
        }
    }
    if (sub_803F428(pos)) {
        sub_80421C0(0,0x15c);
        sub_8049ED4();
        sub_804225C(pokemon,pos,trapData->id);
        if (gDungeon->unk181e8.blinded) {
            LogMessageByIdWithPopupCheckUser(pokemon,*gUnknown_80FD7F4);
        }
        else {
            LogMessageByIdWithPopupCheckUser(pokemon,(gUnknown_80FD7F8)[trapData->id]);
        }
    }
    if (target != NULL) {
        if (trapData->unk1 == 2) {
            sub_806F480(target,1);
        }
        else if (trapData->unk1 == 1) {
            sub_806F480(target,0);
        }
    }
    switch(trapData->id) {
        case TRAP_TRIP_TRAP:
            HandleTripTrap(pokemon,target);
            break;
        case TRAP_MUD_TRAP:
            HandleMudTrap(pokemon,target);
            break;
        case TRAP_STICKY_TRAP:
            HandleStickyTrap(pokemon,target);
            break;
        case TRAP_GRIMY_TRAP:
            HandleGrimyTrap(pokemon,target);
            break;
        case TRAP_SUMMON_TRAP:
            HandleSummonTrap(pokemon,pos);
            flag1 = TRUE;
            break;
        case TRAP_PITFALL_TRAP:
        case 0x1B: // ???
            HandlePitfallTrap(pokemon,target,tile);
            break;
        case TRAP_WARP_TRAP:
            HandleWarpTrap(pokemon,target);
            break;
        case TRAP_WHIRLWIND_TRAP:
            HandleWhirlwindTrap(entity,target);
            break;
        case TRAP_SPIN_TRAP:
            HandleSpinTrap(pokemon,target);
            break;
        case TRAP_SLUMBER_TRAP:
            HandleSlumberTrap(pokemon,target);
            break;
        case TRAP_SLOW_TRAP:
            HandleSlowTrap(pokemon,target);
            break;
        case TRAP_SEAL_TRAP:
            HandleSealTrap(pokemon,target);
            break;
        case TRAP_POISON_TRAP:
            HandlePoisonTrap(pokemon,target);
            break;
        case TRAP_SELFDESTRUCT_TRAP:
            HandleSelfdestructTrap(pokemon,target);
            break;
        case TRAP_EXPLOSION_TRAP:
            HandleExplosionTrap(pokemon,target);
            break;
        case TRAP_PP_ZERO_TRAP:
            HandlePPZeroTrap(pokemon,target);
            break;
        case TRAP_CHESTNUT_TRAP:
            if (target != NULL) {
                DealDamageToEntity(target,gUnknown_80F4F86,0xf,0x213);
            }
            break;
        case TRAP_WONDER_TILE:
            HandleWonderTile(pokemon,target);
            break;
        case TRAP_POKEMON_TRAP:
            HandlePokemonTrap(pokemon,pos);
            flag1 = TRUE;
            break;
        case TRAP_SPIKE_TRAP:
            if (target != NULL) {
                DealDamageToEntity(target,gUnknown_80F4F84,10,0x206);
            }
    }
    if (EntityExists(target)) {
        sub_8071DA4(target);
    }
    if (flag1) {
        sub_807FE04(pos,1);
    }
}

void HandleMudTrap(Entity *pokemon, Entity *target)
{
    int rand, randDef;

    if (target == NULL)
        return;

    rand = DungeonRandInt(100);
    randDef = rand;
    if (rand < 25) {
        LowerAttackStageTarget(pokemon,target,gUnknown_8106A4C,1,1,1);
    }
    else if (rand < 50) {
        LowerAttackStageTarget(pokemon,target,gUnknown_8106A50,1,1,1);
    }
    else if (randDef < 75) {
        LowerDefenseStageTarget(pokemon,target,gUnknown_8106A4C,1,1,1);
    }
    else {
        LowerDefenseStageTarget(pokemon,target,gUnknown_8106A50,1,1,1);
    }
}

void HandleStickyTrap(Entity *pokemon,Entity *target)
{
    EntityInfo *info;
    int index;
    int itemCount;
    int newIndex;
    Item *itemStack[21];

    info = GetEntInfo(target);
    if (HasHeldItem(target,0xe)) {
        TryDisplayDungeonLoggableMessage3(pokemon,target,*gUnknown_80FDC7C);
    }
    else
    {
        itemCount = 0;
        if (info->isTeamLeader) {
            for (index = 0; index < INVENTORY_SIZE; index++) {
                if (ItemExists(&gTeamInventoryRef->teamItems[index]) && IsNotSpecialItem(gTeamInventoryRef->teamItems[index].id)) {
                    Item *item = &gTeamInventoryRef->teamItems[index];
                    if (!ItemSticky(item)) {
                        itemStack[itemCount] = item;
                        itemCount++;
                    }
                }
            }
        }
        if (ItemExists(&info->heldItem) && IsNotSpecialItem((info->heldItem).id) && !ItemSticky(&info->heldItem)) {
            itemStack[itemCount] = &info->heldItem;
            itemCount++;
        }

        if (itemCount == 0) {
            TryDisplayDungeonLoggableMessage3(pokemon,target,*gUnknown_80FDC40);
        }
        else {
            newIndex = DungeonRandInt(itemCount);
            sub_8045BF8(gFormatBuffer_Items[0], itemStack[newIndex]);
            itemStack[newIndex]->flags |= ITEM_FLAG_STICKY;
            sub_80421C0(target, 0x192);
            TryDisplayDungeonLoggableMessage3(pokemon,target,*gUnknown_80FDC18);
        }
    }
}

void HandleSpinTrap(Entity *pokemon, Entity *target)
{
    if(target != NULL)
        ConfuseStatusTarget(pokemon, target, TRUE);
}

void HandleWarpTrap(Entity *pokemon, Entity *target)
{
    if(target != NULL)
        sub_807D148(pokemon, target, 0, NULL);
}

void HandleSlumberTrap(Entity *pokemon, Entity *target)
{
    s32 turns;

    if(target != NULL)
    {
        turns  = CalculateStatusTurns(target, gUnknown_80F4E74, TRUE);
        sub_8075C58(pokemon, target, turns, TRUE);
    }
}

void HandleSlowTrap(Entity *pokemon, Entity *target)
{
    if(target != NULL)
        LowerMovementSpeedTarget(pokemon, target, 1, TRUE);
}

void HandlePoisonTrap(Entity *pokemon, Entity *target)
{
    if(target != NULL)
        PoisonedStatusTarget(pokemon, target, TRUE);
}

void HandleSelfdestructTrap(Entity *pokemon, Entity *target)
{
    sub_807DF38(pokemon, target, &target->pos, 1, TYPE_NONE, 0x212);
}

void HandleExplosionTrap(Entity *pokemon, Entity *target)
{
    sub_807DF38(pokemon, target, &target->pos, 2, TYPE_NONE, 0x212);
}

void HandleGrimyTrap(Entity *pokemon, Entity *target)
{
    Item *item;
    Item *heldItem;
    int counter;
    int index;
    EntityInfo *info;
    int badFoodCount;
    Item *itemStack [22];

    badFoodCount = 0;
    if (target != NULL) {
        info = GetEntInfo(target);
        counter = 0;
        if (info->isTeamLeader) {
            for(index = 0; index < INVENTORY_SIZE; index++)
            {
                item = &gTeamInventoryRef->teamItems[index];
                if ((item->flags & ITEM_FLAG_EXISTS)) {
                    itemStack[counter] = item;
                    counter++;
                }
            }
        }
        heldItem = &info->heldItem;
        if ((heldItem->flags & ITEM_FLAG_EXISTS)) {
            itemStack[counter] = heldItem;
            counter++;
        }
        for (index = 0; index < counter; index++) {
            if (((GetItemCategory(itemStack[index]->id) == CATEGORY_FOOD_GUMMIES) && (itemStack[index]->id != ITEM_GRIMY_FOOD)) &&
                (DungeonRandInt(100) < gUnknown_80F4E0E)) {
                badFoodCount++;
                sub_8045C28(itemStack[index], ITEM_GRIMY_FOOD, 2);
            }
        }
        if (badFoodCount == 1) {
            TryDisplayDungeonLoggableMessage3(pokemon,target,*gUnknown_80FD788); // A food item went bad.
        }
        else if (badFoodCount == 0) {
            TryDisplayDungeonLoggableMessage3(pokemon,target,*gUnknown_80FD7D4); // Nothing particularly bad happened.
        }
        else
        {
            TryDisplayDungeonLoggableMessage3(pokemon,target,*gUnknown_80FD7AC); // Several food items went bad
        }
    }
}

void HandlePitfallTrap(Entity *pokemon, Entity *target, Tile *tile)
{
    bool8 flag;
    EntityInfo *info;

    flag = FALSE;
    if (target != NULL) {
        if (IsBossFight()) {
            LogMessageByIdWithPopupCheckUser(pokemon,*gUnknown_80FED0C); // But nothing happened...
        }
        else
        {
            info = GetEntInfo(target);
            if (sub_8045888(target)) {
                SetTrap(tile, 0x1B);
                flag = TRUE;
                sub_80421C0(target,0x193);
                sub_803E708(0x1e,0x48);
            }
            if (info->isTeamLeader) {
                if (sub_8043D10() != 2) {
                    info->unk15C = 1;
                    info->unk15E = 1;
                    sub_803E708(0x28,0x4b);
                    DealDamageToEntity(target,gUnknown_80F4F8A,0x11,0x215);
                    gDungeon->unk2 = 2;
                    return;
                }
                LogMessageByIdWithPopupCheckUser(pokemon,*gUnknown_80F9728);
            }
            else
            {
                SubstitutePlaceholderStringTags(gFormatBuffer_Monsters[0],target,0);
                if (info->isNotTeamMember) {
                    TryDisplayDungeonLoggableMessage3(pokemon,target,*gUnknown_80F970C); // $m0 fell into the pitfall!
                }
                else {
                    DisplayDungeonLoggableMessageTrue(pokemon,*gUnknown_80F970C); // $m0 fell into the pitfall!
                }
                sub_8068FE0(target,0x215,pokemon);
            }
            if (flag) {
                SetTrap(tile, TRAP_PITFALL_TRAP);
            }
        }
    }
}

void HandleSummonTrap(Entity *pokemon,DungeonPos *pos)
{
  s32 r4;
  u32 direction;
  int pokemonSummonCount;
  s16 species;
  struct unkStruct_806B7F8 stack;
  s32 i;


  r4 = DungeonRandInt(3) + 2;
  direction = DungeonRandInt(NUM_DIRECTIONS);
  if (IsBossFight()) {
      goto _ret;
  }
  else
  {
    pokemonSummonCount = 0;
    if (pokemonSummonCount < r4) {
        for (i = 0; i < r4; i++)
        {
            species = sub_803D970(0);
            direction &= DIRECTION_MASK;
            stack.species = species;
            if (sub_806AA0C(stack.species,0)) {
              stack.level = 0;
              stack.unk2 = 0;
              stack.pos.x = pos->x + gAdjacentTileOffsets[direction].x;
              stack.pos.y = pos->y + gAdjacentTileOffsets[direction].y;
              stack.unk4 = 0;
              stack.unk10 = 0;
              if (sub_806B7F8(&stack, TRUE) != NULL) {
                pokemonSummonCount++;
              }
            }
            direction++;
        }
    }
    sub_80421EC(pos,0x194);
    if (pokemonSummonCount == 0) {
_ret:
        LogMessageByIdWithPopupCheckUser(pokemon,*gUnknown_80FED04);
    }
    else
    {
        LogMessageByIdWithPopupCheckUser(pokemon,*gUnknown_80FED00);
    }
  }
}

void HandlePPZeroTrap(Entity *param_1,Entity *param_2)
{
  Move *move;
  s32 moveIndex;
  s32 i;
  s32 counter;
  EntityInfo *info;
  Move *moveStack [MAX_MON_MOVES];
  s32 indexStack [MAX_MON_MOVES];
  bool8 flag = FALSE;


  if (param_2 != NULL) {
    info = GetEntInfo(param_2);
    counter = 0;
    for(i = 0;  i < MAX_MON_MOVES; move++, i++)
    {
      move = &info->moves.moves[i];
      if (((move->moveFlags & MOVE_FLAG_EXISTS) != 0) && (move->PP != 0)) {
        moveStack[counter] = move;
        indexStack[counter] = i;
        counter++;
      }
    }
    if (counter != 0) {
      moveIndex = DungeonRandInt(counter);
      moveStack[moveIndex]->PP = 0;
      sub_80928C0(gFormatBuffer_Items[0],moveStack[moveIndex],0);
      sub_806A9B4(param_2, indexStack[moveIndex]);
      flag = TRUE;
    }
    if(flag)
        TryDisplayDungeonLoggableMessage3(param_1,param_2,*gUnknown_80FDA80);
    else
        TryDisplayDungeonLoggableMessage3(param_1,param_2,*gUnknown_80FDAA0);
  }
}

void HandleWonderTile(Entity *pokemon, Entity *target)
{
    sub_8079E34(pokemon, target, FALSE);
}

void HandleSealTrap(Entity *param_1,Entity *param_2)
{
    Move *move;
    s32 moveIndex;
    s32 i;
    s32 counter;
    EntityInfo *info;
    Move *moveStack [MAX_MON_MOVES];
    bool8 flag = FALSE;


    if (param_2 != NULL && !HasSafeguardStatus(param_1, param_2, TRUE)) {
        info = GetEntInfo(param_2);
        counter = 0;
        for(i = 0;  i < MAX_MON_MOVES;i++)
        {
            move = &info->moves.moves[i];
            if ((move->moveFlags & MOVE_FLAG_EXISTS) && !(move->moveFlags2 & MOVE_FLAG_SEALED)) {
                moveStack[counter] = move;
                counter++;
            }
        }
        if (counter != 0) {
            moveIndex = DungeonRandInt(counter);
            moveStack[moveIndex]->moveFlags2 |= MOVE_FLAG_SEALED;
            sub_80928C0(gFormatBuffer_Items[0],moveStack[moveIndex], NULL);
            flag = TRUE;
        }
        if(flag)
            TryDisplayDungeonLoggableMessage3(param_1,param_2,*gUnknown_80FDB04);
        else
            TryDisplayDungeonLoggableMessage3(param_1,param_2,*gUnknown_80FDB2C);
    }
}

void HandleWhirlwindTrap(Entity *pokemon, Entity *target)
{
    if(target)
    {
        sub_807CD9C(pokemon, target, DungeonRandInt(NUM_DIRECTIONS));
    }
}
