#include "ac_set_manager.h"
#include "m_name_table.h"
#include "m_lib.h"
#include "m_field_info.h"
#include "m_player_lib.h"
#include "libultra/libultra.h"
#include "ac_set_ovl_insect.h"
#include "ac_set_ovl_gyoei.h"

/**
 * @brief Gets the X & Z acre the player is currently in.
 * 
 * @param bx Out player X-acre
 * @param bz Out player Z-acre
 * @return TRUE/FALSE successfully updated the acre
 **/
static int aSetMgr_get_player_block(int* bx, int* bz, GAME_PLAY* play) {
  int res = FALSE;
  PLAYER_ACTOR* player_actor = get_player_actor_withoutCheck(play);

  if (player_actor != NULL) {
    res = mFI_Wpos2BlockNum(bx, bz, player_actor->actor_class.world.position);
  }

  return res;
}

/**
 * @brief Gets the next X & Z acre which the player is currently transitioning into.
 * 
 * @param next_bx Out next player X-acre
 * @param next_bz Out next player Z-acre
 * @return TRUE/FALSE successfully updated next acre
 **/
static int aSetMgr_renewal_player_next_pos(int* next_bx, int* next_bz) {
  int wade_end_pos_res;
  int wpos_2_blocknum_res;
  xyz_t endpos = { 0.0f, 0.0f, 0.0f };
  GAME_PLAY* play = (GAME_PLAY*)gamePT;

  wade_end_pos_res = get_player_actor_withoutCheck(play)->Get_WadeEndPos_proc((GAME*)play, &endpos); // maybe make this a macro like GET_PLAYER_ACTOR() ?
  wpos_2_blocknum_res = mFI_Wpos2BlockNum(next_bx, next_bz, endpos);
  return wpos_2_blocknum_res | wade_end_pos_res;
}

/**
 * @brief Updates the set manager's internal player acre and updates the previous player acre.
 * 
 * @param play GAME_PLAY pointer
 * @param player_pos Pointer to SET_MANAGER internal player position structure
 **/
static void aSetMgr_renewal_player_pos(GAME_PLAY* play, aSetMgr_player_pos_c* player_pos) {
  int bx, bz;
  
  player_pos->last_bx = player_pos->now_bx;
  player_pos->last_bz = player_pos->now_bz;

  if (aSetMgr_get_player_block(&bx, &bz, play) == TRUE) {
    player_pos->now_bx = bx;
    player_pos->now_bz = bz;
  }
}

/**
 * @brief Checks if the player's wade state is 'starting'.
 * 
 * @return TRUE when the player is beginning an acre transition, FALSE otherwise
 **/
static int aSetMgr_check_player_wade_start() {
  return mFI_CheckPlayerWade(mFI_WADE_START);
}

/**
 * @brief Checks if the player's wade state is 'ending'.
 * 
 * @return TRUE when the player is finishing an acre transition, FALSE otherwise
 **/
static int aSetMgr_check_player_wade_end() {
  return mFI_CheckPlayerWade(mFI_WADE_END);
}

/**
 * @brief Clears the set overlay buffer.
 * 
 * @param set_ovl Pointer to SET_MANAGER's internal aSetMgr_set_ovl_c overlay structure
 **/
static void aSetMgr_clear_set_ovl(aSetMgr_set_ovl_c* set_ovl) {
  bzero(set_ovl->buf, aSetMgr_SET_OVERLAY_BUF_SIZE);
  set_ovl->ovl_proc = NULL;
}

/**
 * @brief Updates the SET_MANAGER's current set overlay process.
 * 
 * @param set_ovl Pointer to SET_MANAGER's internal aSetMgr_set_ovl_c overlay structure
 * @param type The aSetMgr_OVERLAY_* type which will be executed
 * @return TRUE
 **/
static int aSetMgr_ovl(aSetMgr_set_ovl_c* set_ovl, int type) {
  static aSetMgr_ovl_proc proc_table[aSetMgr_OVERLAY_NUM] = { &aSOI_insect_set, &aSOG_gyoei_set };

  if (type < aSetMgr_OVERLAY_BEGIN || type >= aSetMgr_OVERLAY_NUM) {
    type = aSetMgr_OVERLAY_BEGIN;
  }

  set_ovl->ovl_proc = proc_table[type];
  return TRUE;
}

/**
 * @brief Clears the SET_MANAGER's "kept" data buffer.
 * 
 * @param keep Pointer to the SET_MANAGER's internal aSetMgr_keep_c structure
 **/
static void aSetMgr_clear_keep(aSetMgr_keep_c* keep) {
  bzero(keep, sizeof(aSetMgr_keep_c));
}

/**
 * @brief Check if the 'move' func state should be updated to 'check_wait'.
 * 
 * This check is succeeded by the player entering an acre transition state.
 * 
 * @param play GAME_PLAY pointer
 * @param set_manager SET_MANAGER pointer
 * @return TRUE when updated, FALSE otherwise
 **/
static int aSetMgr_move_check_set(GAME_PLAY* play, SET_MANAGER* set_manager) {
  int wading = FALSE;
  aSetMgr_renewal_player_pos(play, &set_manager->player_pos);

  /* update the next acre position if player is starting acre transition state */
  if (aSetMgr_check_player_wade_start() == TRUE) {
    aSetMgr_renewal_player_next_pos(&set_manager->player_pos.next_bx, &set_manager->player_pos.next_bz);
    set_manager->next_move_proc = aSetMgr_MOVE_move_check_wait;
    wading = TRUE;
  }

  return wading;
}

typedef int (*aSetMgr_move_proc)(GAME_PLAY*, SET_MANAGER*);

/**
 * @brief Checks whether or not to update the move proc to 'set'.
 * 
 * The SET_MANAGER's internal wait timer must be zero to pass this check.
 * 
 * @param play GAME_PLAY pointer
 * @param set_manager SET_MANAGER pointer
 * @return TRUE when updated, FALSE otherwise
 **/
static int aSetMgr_move_check_wait(GAME_PLAY* play, SET_MANAGER* set_manager) {
  int wait_timer;
  int res = FALSE;

  if (set_manager->next_move_proc == aSetMgr_MOVE_move_check_wait) {
    if (set_manager->wait_timer == 0) {
      wait_timer = 0;
    }
    else {
      wait_timer = --set_manager->wait_timer;
    }

    if (wait_timer == 0) {
      set_manager->next_move_proc = aSetMgr_MOVE_move_set;
      set_manager->wait_timer = aSetMgr_WAIT_TIME;
      res = TRUE;
    }
  }
  return res;
}

/**
 * @brief Processes SET_MANAGER's overlay actors & checks whether to update move func state.
 * 
 * All SET_MANAGER overlay processes must have been executed OR the player must
 * finish transitioning between acres for this check to pass.
 * 
 * @param play GAME_PLAY pointer
 * @param set_manager SET_MANAGER pointer
 * @return TRUE when updated, FALSE otherwise
 **/
static int aSetMgr_move_set(GAME_PLAY* play, SET_MANAGER* set_manager) {
  int res = FALSE;

  if (aSetMgr_ovl(&set_manager->set_overlay, set_manager->set_ovl_type) == TRUE &&
      set_manager->set_overlay.ovl_proc != NULL) {
      set_manager->set_overlay.ovl_proc(set_manager, play);
      set_manager->set_ovl_type++;
  }
  else {
    set_manager->set_ovl_type++;
  }

  if (set_manager->set_ovl_type >= aSetMgr_OVERLAY_NUM) {
    set_manager->next_move_proc = aSetMgr_MOVE_move_check_set;
    set_manager->set_ovl_type = aSetMgr_OVERLAY_BEGIN;
    res = TRUE;
  }

  if (aSetMgr_check_player_wade_end() == TRUE) {
    set_manager->next_move_proc = aSetMgr_MOVE_move_check_set;
    set_manager->set_ovl_type = aSetMgr_OVERLAY_BEGIN;
    res = TRUE;
  }

  return res;
}

/**
 * @brief SET_MANAGER move process.
 * 
 * @param actor Pointer to the SET_MANAGER actor
 * @param game GAME pointer
 **/
static void aSetMgr_move(ACTOR* actor, GAME* game) {
  static aSetMgr_move_proc move[aSetMgr_MOVE_PROC_NUM] = {
    &aSetMgr_move_check_set,
    &aSetMgr_move_check_wait,
    &aSetMgr_move_set
  };

  SET_MANAGER* set_manager = (SET_MANAGER*)actor;
  GAME_PLAY* play = (GAME_PLAY*)game;

  if ((*move[set_manager->move_proc])(play, set_manager) == TRUE) {
    set_manager->move_proc = set_manager->next_move_proc;
  }
}

/**
 * @brief SET_MANAGER constructor.
 * 
 * @param actor Pointer to the SET_MANAGER actor
 * @param game GAME pointer
 **/
static void aSetMgr_ct(ACTOR* actor, GAME* game) {
  SET_MANAGER* set_manager = (SET_MANAGER*)actor;
  GAME_PLAY* play = (GAME_PLAY*)game;

  set_manager->move_proc = aSetMgr_MOVE_move_check_set;
  set_manager->next_move_proc = aSetMgr_MOVE_move_check_set;
  set_manager->set_ovl_type = aSetMgr_OVERLAY_BEGIN;

  aSetMgr_clear_set_ovl(&set_manager->set_overlay);

  set_manager->player_pos.next_bx = 0;
  set_manager->player_pos.next_bz = 0;
  set_manager->player_pos.now_bx = 0;
  set_manager->player_pos.now_bz = 0;
  set_manager->player_pos.last_bx = 0;
  set_manager->player_pos.last_bz = 0;

  set_manager->wait_timer = aSetMgr_WAIT_TIME;

  aSetMgr_clear_keep(&set_manager->keep);
}

/**
 * @brief SET_MANAGER destructor.
 * 
 * @param actor Pointer to the SET_MANAGER actor
 * @param game GAME pointer
 **/
static void aSetMgr_dt(ACTOR* actor, GAME* game) { }

/* actor profile for SET_MANAGER */ 
ACTOR_PROFILE Set_Manager_Profile = {
  mAc_PROFILE_SET_MANAGER,
  ACTOR_PART_CONTROL, /* control actor type */
  ACTOR_STATE_NO_MOVE_WHILE_CULLED | ACTOR_STATE_NO_DRAW_WHILE_CULLED | ACTOR_STATE_CAN_MOVE_IN_DEMO_SCENES,
  EMPTY_NO,
  ACTOR_OBJ_BANK_KEEP,
  sizeof(SET_MANAGER),
  aSetMgr_ct,
  aSetMgr_dt,
  aSetMgr_move,
  mActor_NONE_PROC1,
  NULL
};
