#include "entrance.hpp"

#include "fill.hpp"
#include "settings.hpp"
#include "item_list.hpp"
#include "item_pool.hpp"
#include "item_location.hpp"
#include "debug.hpp"
#include "spoiler_log.hpp"
#include "hints.hpp"
#include "location_access.hpp"

#include <vector>
#include <utility>
#include <set>
#include <map>
#include <spdlog/spdlog.h>

std::list<EntranceOverride> entranceOverrides = {};
bool noRandomEntrances = false;
static bool entranceShuffleFailure = false;
static int totalRandomizableEntrances = 0;
static int curNumRandomizedEntrances = 0;

typedef struct {
    EntranceType type;
    AreaKey parentRegion;
    AreaKey connectedRegion;
    int16_t index;
    int16_t blueWarp;
} EntranceLinkInfo;

EntranceLinkInfo NO_RETURN_ENTRANCE = {EntranceType::None, NONE, NONE, -1};

typedef struct {
    std::list<AreaKey> targetRegions;
    std::list<EntranceType> allowedTypes;
} PriorityEntrance;
                                 //primary,          secondary
using EntranceInfoPair = std::pair<EntranceLinkInfo, EntranceLinkInfo>;
using EntrancePair = std::pair<Entrance*, Entrance*>;
using EntrancePools = std::map<EntranceType, std::vector<Entrance*>>;

//The entrance randomization algorithm used here is a direct copy of
//the algorithm used in the original N64 randomizer (except now in C++ instead
//of python). It may be easier to understand the algorithm by looking at the
//base randomizer's code instead:
// https://github.com/TestRunnerSRL/OoT-Randomizer/blob/Dev/EntranceShuffle.py

// Updates the user on how many entrances are currently shuffled
static void DisplayEntranceProgress() {
  std::string dots = ".";
  float progress = (float)curNumRandomizedEntrances / (float)totalRandomizableEntrances;
  if (progress > 0.33) {
    dots += ".";
  } else {
    dots += " ";
  }
  if (progress > 0.66) {
    dots += ".";
  } else {
    dots += " ";
  }
  printf("\x1b[7;29H%s", dots.c_str());
  #ifdef ENABLE_DEBUG
    if (curNumRandomizedEntrances == totalRandomizableEntrances) {
      Areas::DumpWorldGraph("Finish Validation");
    }
  #endif
}

void SetAllEntrancesData(std::vector<EntranceInfoPair>& entranceShuffleTable) {
  for (auto& entrancePair: entranceShuffleTable) {

    auto& forwardEntry = entrancePair.first;
    auto& returnEntry = entrancePair.second;

    //set data
    Entrance* forwardEntrance = AreaTable(forwardEntry.parentRegion)->GetExit(forwardEntry.connectedRegion);
    forwardEntrance->SetIndex(forwardEntry.index);
    forwardEntrance->SetBlueWarp(forwardEntry.blueWarp);
    forwardEntrance->SetType(forwardEntry.type);
    forwardEntrance->SetAsPrimary();
    if (returnEntry.parentRegion != NONE) {
      Entrance* returnEntrance = AreaTable(returnEntry.parentRegion)->GetExit(returnEntry.connectedRegion);
      returnEntrance->SetIndex(returnEntry.index);
      returnEntrance->SetBlueWarp(returnEntry.blueWarp);
      returnEntrance->SetType(returnEntry.type);
      forwardEntrance->BindTwoWay(returnEntrance);
    }
  }
}

static std::vector<Entrance*> AssumeEntrancePool(std::vector<Entrance*>& entrancePool) {
  std::vector<Entrance*> assumedPool = {};
  for (Entrance* entrance : entrancePool) {
    totalRandomizableEntrances++;
    Entrance* assumedForward = entrance->AssumeReachable();
    if (entrance->GetReverse() != nullptr && !Settings::DecoupleEntrances) {
      Entrance* assumedReturn = entrance->GetReverse()->AssumeReachable();
      if (!(Settings::MixedEntrancePools && (Settings::ShuffleOverworldEntrances || Settings::ShuffleInteriorEntrances.Is(SHUFFLEINTERIORS_ALL)))) {
        auto type = entrance->GetType();
        if (((type == EntranceType::Dungeon || type == EntranceType::GrottoGrave) && entrance->GetReverse()->GetName() != "Spirit Temple Entryway -> Desert Colossus From Spirit Entryway") ||
             (type == EntranceType::Interior && Settings::ShuffleInteriorEntrances.Is(SHUFFLEINTERIORS_ALL))) {
               // In most cases, Dungeon, Grotto/Grave and Simple Interior exits shouldn't be assumed able to give access to their parent region
               assumedReturn->SetCondition([]{return false;});
             }
      }
      assumedForward->BindTwoWay(assumedReturn);
    }
    assumedPool.push_back(assumedForward);
  }
  return assumedPool;
}

static std::vector<Entrance*> BuildOneWayTargets(std::vector<EntranceType> typesToInclude, std::vector<std::pair<AreaKey, AreaKey>> exclude = {}/*, target_region_names*/) {
  std::vector<Entrance*> oneWayEntrances = {};
  // Get all entrances of the specified type
  for (EntranceType poolType : typesToInclude) {
    AddElementsToPool(oneWayEntrances, GetShuffleableEntrances(poolType, false));
  }
  // Filter out any that are passed in the exclusion list
  FilterAndEraseFromPool(oneWayEntrances, [&exclude](Entrance* entrance){
    std::pair<AreaKey, AreaKey> entranceBeingChecked (entrance->GetParentRegionKey(), entrance->GetConnectedRegionKey());
    return ElementInContainer(entranceBeingChecked, exclude);
  });

  // The code below is part of the function in ootr, but no use of the function ever provides target_region_names
  // if target_region_names:
  //     return [entrance.get_new_target() for entrance in valid_one_way_entrances
  //             if entrance.connected_region.name in target_region_names]

  std::vector<Entrance*> newTargets = {};
  for (Entrance* entrance : oneWayEntrances) {
    newTargets.push_back(entrance->GetNewTarget());
  }
  return newTargets;
}

//returns restrictive entrances and soft entrances in an array of size 2 (restrictive vector is index 0, soft is index 1)
static std::array<std::vector<Entrance*>, 2> SplitEntrancesByRequirements(std::vector<Entrance*>& entrancesToSplit, std::vector<Entrance*>& assumedEntrances) {
  //First, disconnect all root assumed entrances and save which regions they were originally connected to, so we can reconnect them later
  std::map<Entrance*, AreaKey> originalConnectedRegions = {};
  std::set<Entrance*> entrancesToDisconnect = {};
  for (Entrance* entrance : assumedEntrances) {
    entrancesToDisconnect.insert(entrance);
    if (entrance->GetReverse() != nullptr) {
      entrancesToDisconnect.insert(entrance->GetReverse());
    }
  }

  //disconnect each entrance temporarily to find restrictive vs soft entrances
  //soft entrances are ones that can be accessed by both ages (child/adult) at both times of day (day/night)
  //restrictive entrances are ones that do not meet this criteria
  for (Entrance* entrance : entrancesToDisconnect) {
    if (entrance->GetConnectedRegionKey() != NONE) {
      originalConnectedRegions[entrance] = entrance->Disconnect();
    }
  }

  std::vector<Entrance*> restrictiveEntrances = {};
  std::vector<Entrance*> softEntrances = {};

  Logic::LogicReset();
  // Apply the effects of all advancement items to search for entrance accessibility
  std::vector<ItemKey> items = FilterFromPool(ItemPool, [](const ItemKey i){ return ItemTable(i).IsAdvancement();});
  for (ItemKey unplacedItem : items) {
    ItemTable(unplacedItem).ApplyEffect();
  }
  // run a search to see what's accessible
  GetAccessibleLocations({});

  for (Entrance* entrance : entrancesToSplit) {
    // if an entrance is accessible at all times of day by both ages, it's a soft entrance with no restrictions
    if (entrance->ConditionsMet(true)) {
      softEntrances.push_back(entrance);
    } else {
      restrictiveEntrances.push_back(entrance);
    }
  }

  //Reconnect all disconnected entrances
  for (Entrance* entrance : entrancesToDisconnect) {
    entrance->Connect(originalConnectedRegions[entrance]);
  }

  return {restrictiveEntrances, softEntrances};
}

static bool AreEntrancesCompatible(Entrance* entrance, Entrance* target, std::vector<EntrancePair>& rollbacks) {

  //Entrances shouldn't connect to their own scene, fail in this situation
  if (entrance->GetParentRegion()->scene != "" && entrance->GetParentRegion()->scene == target->GetConnectedRegion()->scene) {
    auto message = "Entrance " + entrance->GetName() + " attempted to connect with own scene target " + target->to_string() + ". Connection failed.\n";
    SPDLOG_DEBUG(message);
    return false;
  }

  // One way entrances shouldn't lead to the same scene as other already chosen one way entrances
  auto type = entrance->GetType();
  const std::vector<EntranceType> oneWayTypes = {EntranceType::OwlDrop, EntranceType::Spawn, EntranceType::WarpSong};
  if (ElementInContainer(type, oneWayTypes)) {
    for (auto& rollback : rollbacks) {
      if (rollback.first->GetConnectedRegion()->scene == target->GetConnectedRegion()->scene) {
        auto message = "A one way entrance already leads to " + target->to_string() + ". Connection failed.\n";
        SPDLOG_DEBUG(message);
        return false;
      }
    }
  }

  return true;
}

//Change connections between an entrance and a target assumed entrance, in order to test the connections afterwards if necessary
static void ChangeConnections(Entrance* entrance, Entrance* targetEntrance) {
  auto message = "Attempting to connect " + entrance->GetName() + " to " + targetEntrance->to_string() + "\n";
  SPDLOG_DEBUG(message);
  entrance->Connect(targetEntrance->Disconnect());
  entrance->SetReplacement(targetEntrance->GetReplacement());
  if (entrance->GetReverse() != nullptr && !Settings::DecoupleEntrances) {
    targetEntrance->GetReplacement()->GetReverse()->Connect(entrance->GetReverse()->GetAssumed()->Disconnect());
    targetEntrance->GetReplacement()->GetReverse()->SetReplacement(entrance->GetReverse());
  }
}

// In the event that we need to retry shuffling an entire group we can restore the
// original connections to reset the entrance and target entrance.
static void RestoreConnections(Entrance* entrance, Entrance* targetEntrance) {
  targetEntrance->Connect(entrance->Disconnect());
  entrance->SetReplacement(nullptr);
  if (entrance->GetReverse() != nullptr && !Settings::DecoupleEntrances) {
    entrance->GetReverse()->GetAssumed()->Connect(targetEntrance->GetReplacement()->GetReverse()->Disconnect());
    targetEntrance->GetReplacement()->GetReverse()->SetReplacement(nullptr);
  }
}

static void DeleteTargetEntrance(Entrance* targetEntrance) {
  if (targetEntrance->GetConnectedRegionKey() != NONE) {
    targetEntrance->Disconnect();
  }
  if (targetEntrance->GetParentRegionKey() != NONE) {
    targetEntrance->GetParentRegion()->RemoveExit(targetEntrance);
    targetEntrance->SetParentRegion(NONE);
  }
}

static void ConfirmReplacement(Entrance* entrance, Entrance* targetEntrance) {
  DeleteTargetEntrance(targetEntrance);
  if (entrance->GetReverse() != nullptr && !Settings::DecoupleEntrances) {
    auto replacedReverse = targetEntrance->GetReplacement()->GetReverse();
    DeleteTargetEntrance(replacedReverse->GetReverse()->GetAssumed());
  }
}

// Returns whether or not we can affirm the entrance can never be accessed as the given age
static bool EntranceUnreachableAs(Entrance* entrance, uint8_t age, std::vector<Entrance*>& alreadyChecked) {

  if (entrance == nullptr) {
    SPDLOG_DEBUG("Entrance is nullptr in EntranceUnreachableAs()");
    return true;
  }

  alreadyChecked.push_back(entrance);
  auto type = entrance->GetType();

  // The following cases determine when we say an entrance is not safe to affirm unreachable as the given age
  if (type == EntranceType::WarpSong || type == EntranceType::Overworld) {
    // Note that we consider all overworld entrances as potentially accessible as both ages, to be completely safe
    return false;
  } else if (type == EntranceType::OwlDrop) {
    return age == AGE_ADULT;
  } else if (type == EntranceType::Spawn && entrance->GetConnectedRegionKey() == KF_LINKS_HOUSE) {
    return age == AGE_ADULT;
  } else if (type == EntranceType::Spawn && entrance->GetConnectedRegionKey() == TEMPLE_OF_TIME) {
    return age == AGE_CHILD;
  }

  // Other entrances such as Interior, Dungeon or Grotto are fine unless they have a parent which is one of the above cases
  // Recursively check parent entrances to verify that they are also not reachable as the wrong age
  auto& parentEntrances = entrance->GetParentRegion()->entrances;
  for (Entrance* parentEntrance : parentEntrances) {

    //if parentEntrance is in alreadyChecked, then continue
    if (ElementInContainer(parentEntrance, alreadyChecked)) {
      continue;
    }

    bool unreachable = EntranceUnreachableAs(parentEntrance, age, alreadyChecked);
    if (!unreachable) {
      return false;
    }
  }

  return true;
}

static bool ValidateWorld(Entrance* entrancePlaced) {
  SPDLOG_DEBUG("Validating world\n");

  //check certain conditions when certain types of ER are enabled
  EntranceType type = EntranceType::None;
  if (entrancePlaced != nullptr) {
    type = entrancePlaced->GetType();
  }

  bool checkPoeCollectorAccess  = (Settings::ShuffleOverworldEntrances || Settings::ShuffleInteriorEntrances.Is(SHUFFLEINTERIORS_ALL)) && (entrancePlaced == nullptr || Settings::MixedEntrancePools ||
                                 type == EntranceType::Interior || type == EntranceType::SpecialInterior || type == EntranceType::Overworld || type == EntranceType::Spawn || type == EntranceType::WarpSong || type == EntranceType::OwlDrop);
  bool checkOtherEntranceAccess = (Settings::ShuffleOverworldEntrances || Settings::ShuffleInteriorEntrances.Is(SHUFFLEINTERIORS_ALL) || Settings::ShuffleOverworldSpawns) && (entrancePlaced == nullptr || Settings::MixedEntrancePools ||
                                 type == EntranceType::SpecialInterior || type == EntranceType::Overworld || type == EntranceType::Spawn || type == EntranceType::WarpSong || type == EntranceType::OwlDrop);

  // Search the world to verify that all necessary conditions are still being held
  // Conditions will be checked during the search and any that fail will be figured out
  // afterwards
  Logic::LogicReset();
  GetAccessibleLocations({}, SearchMode::ValidateWorld, "", checkPoeCollectorAccess, checkOtherEntranceAccess);

  if (!Settings::DecoupleEntrances) {
  // Unless entrances are decoupled, we don't want the player to end up through certain entrances as the wrong age
  // This means we need to hard check that none of the relevant entrances are ever reachable as that age
  // This is mostly relevant when mixing entrance pools or shuffling special interiors (such as windmill or kak potion shop)
  // Warp Songs and Overworld Spawns can also end up inside certain indoors so those need to be handled as well
    std::array<std::string, 2> childForbidden = {"OGC Great Fairy Fountain -> Castle Grounds", "GV Carpenter Tent -> GV Fortress Side"};
    std::array<std::string, 2> adultForbidden = {"HC Great Fairy Fountain -> Castle Grounds", "HC Storms Grotto -> Castle Grounds"};

    auto allShuffleableEntrances = GetShuffleableEntrances(EntranceType::All, false);
    for (auto& entrance: allShuffleableEntrances) {

      std::vector<Entrance*> alreadyChecked = {};

      if (entrance->IsShuffled()) {
        if (entrance->GetReplacement() != nullptr) {

          auto replacementName = entrance->GetReplacement()->GetName();
          alreadyChecked.push_back(entrance->GetReplacement()->GetReverse());

          if (ElementInContainer(replacementName, childForbidden) && !EntranceUnreachableAs(entrance, AGE_CHILD, alreadyChecked)) {
            auto message = replacementName + " is replaced by an entrance with a potential child access\n";
            SPDLOG_DEBUG(message);
            return false;
          } else if (ElementInContainer(replacementName, adultForbidden) && !EntranceUnreachableAs(entrance, AGE_ADULT, alreadyChecked)) {
            auto message = replacementName + " is replaced by an entrance with a potential adult access\n";
            SPDLOG_DEBUG(message);
            return false;
          }
        }
      } else {
        auto name = entrance->GetName();
        alreadyChecked.push_back(entrance->GetReverse());

        if (ElementInContainer(name, childForbidden) && !EntranceUnreachableAs(entrance, AGE_CHILD, alreadyChecked)) {
          auto message = name + " is potentially accessible as child\n";
          SPDLOG_DEBUG(message);
          return false;
        } else if (ElementInContainer(name, adultForbidden) && !EntranceUnreachableAs(entrance, AGE_ADULT, alreadyChecked)) {
          auto message = name + " is potentially accessible as adult\n";
          SPDLOG_DEBUG(message);
          return false;
        }
      }
    }
  }

  if (Settings::ShuffleInteriorEntrances.IsNot(SHUFFLEINTERIORS_OFF) && Settings::GossipStoneHints.IsNot(HINTS_NO_HINTS) &&
  (entrancePlaced == nullptr || type == EntranceType::Interior || type == EntranceType::SpecialInterior)) {
    //When cows are shuffled, ensure both Impa's House entrances are in the same hint area because the cow is reachable from both sides
    if (Settings::ShuffleCows) {
      auto impasHouseFrontHintRegion = GetHintRegionHintKey(KAK_IMPAS_HOUSE);
      auto impasHouseBackHintRegion = GetHintRegionHintKey(KAK_IMPAS_HOUSE_BACK);
      if (impasHouseFrontHintRegion != NONE && impasHouseBackHintRegion != NONE && impasHouseBackHintRegion != LINKS_POCKET && impasHouseFrontHintRegion != LINKS_POCKET && impasHouseBackHintRegion != impasHouseFrontHintRegion) {
        auto message = "Kak Impas House entrances are not in the same hint area\n";
        SPDLOG_DEBUG(message);
        return false;
      }
    }
  }

  // If all locations aren't reachable, that means that one of the conditions failed when searching
  if (!allLocationsReachable) {
    if (checkOtherEntranceAccess) {
      // At least one valid starting region with all basic refills should be reachable without using any items at the beginning of the seed
      if (!AreaTable(KOKIRI_FOREST)->HasAccess() && !AreaTable(KAKARIKO_VILLAGE)->HasAccess()) {
        SPDLOG_DEBUG("Invalid starting area\n");
        return false;
      }

      // Check that a region where time passes is always reachable as both ages without having collected any items
      if (!Areas::HasTimePassAccess(AGE_CHILD) || !Areas::HasTimePassAccess(AGE_ADULT)) {
        SPDLOG_DEBUG("Time passing is not guaranteed as both ages\n");
        return false;
      }

      // The player should be able to get back to ToT after going through time, without having collected any items
      // This is important to ensure that the player never loses access to the pedestal after going through time
      if (Settings::ResolvedStartingAge == AGE_CHILD && !AreaTable(TEMPLE_OF_TIME)->Adult()) {
        SPDLOG_DEBUG("Path to Temple of Time as adult is not guaranteed\n");
        return false;
      } else if (Settings::ResolvedStartingAge == AGE_ADULT && !AreaTable(TEMPLE_OF_TIME)->Child()) {
        SPDLOG_DEBUG("Path to Temple of Time as child is not guaranteed\n");
        return false;
      }
    }

    // The Big Poe shop should always be accessible as adult without the need to use any bottles
    // This is important to ensure that players can never lock their only bottles by filling them with Big Poes they can't sell
    if (checkPoeCollectorAccess) {
      if (!AreaTable(MARKET_GUARD_HOUSE)->Adult()) {
        SPDLOG_DEBUG("Big Poe Shop access is not guarenteed as adult\n");
        return false;
      }
    }
    SPDLOG_DEBUG("All Locations NOT REACHABLE\n");
    return false;
  }
  return true;
}

static bool ReplaceEntrance(Entrance* entrance, Entrance* target, std::vector<EntrancePair>& rollbacks) {

  if (!AreEntrancesCompatible(entrance, target, rollbacks)) {
    return false;
  }
  ChangeConnections(entrance, target);
  if (ValidateWorld(entrance)) {
    #ifdef ENABLE_DEBUG
      std::string ticks = std::to_string(svcGetSystemTick());
      auto message = "Dumping World Graph at " + ticks + "\n";
      //SPDLOG_DEBUG(message);
      //Areas::DumpWorldGraph(ticks);
    #endif
    rollbacks.push_back(EntrancePair{entrance, target});
    curNumRandomizedEntrances++;
    DisplayEntranceProgress();
    return true;
  } else {
    #ifdef ENABLE_DEBUG
      std::string ticks = std::to_string(svcGetSystemTick());
      auto message = "Dumping World Graph at " + ticks + "\n";
      //SPDLOG_DEBUG(message);
      //Areas::DumpWorldGraph(ticks);
    #endif
    if (entrance->GetConnectedRegionKey() != NONE) {
      RestoreConnections(entrance, target);
    }
  }
  DisplayEntranceProgress();
  return false;
}

// Connect one random entrance from entrance pools to one random target in the respective target pool.
// Entrance chosen will have one of the allowed types.
// Target chosen will lead to one of the allowed regions.
static bool PlaceOneWayPriorityEntrance(std::string priorityName, std::list<AreaKey>& allowedRegions, std::list<EntranceType>& allowedTypes, std::vector<EntrancePair>& rollbacks, EntrancePools oneWayEntrancePools, EntrancePools oneWayTargetEntrancePools) {
  // Combine the entrances for allowed types in one list.
  // Shuffle this list.
  // Pick the first one not already set, not adult spawn, that has a valid target entrance.
  // Assemble then clear entrances from the pool and target pools as appropriate.
  std::vector<Entrance*> availPool = {};
  for (auto& pool : oneWayEntrancePools) {
    auto entranceType = pool.first;
    if (ElementInContainer(entranceType, allowedTypes)) {
      AddElementsToPool(availPool, pool.second);
    }
  }
  Shuffle(availPool);

  for (Entrance* entrance : availPool) {
    if (entrance->GetReplacement() != nullptr) {
      continue;
    }
    // Only allow Adult Spawn as sole Nocturne access if hints != mask.
    // Otherwise, child access is required here (adult access assumed or guaranteed later).
    if (entrance->GetParentRegionKey() == ADULT_SPAWN) {
      if (priorityName != "Nocturne" || Settings::GossipStoneHints.Is(HINTS_MASK_OF_TRUTH)) {
        continue;
      }
    }
    // If not shuffling dungeons, Nocturne requires adult access
    if (!Settings::ShuffleDungeonEntrances && priorityName == "Nocturne") {
      if (entrance->GetType() != EntranceType::WarpSong && entrance->GetParentRegionKey() != ADULT_SPAWN) {
        continue;
      }
    }
    for (Entrance* target : oneWayTargetEntrancePools[entrance->GetType()]) {
      AreaKey targetRegionKey = target->GetConnectedRegionKey();
      if (targetRegionKey != NONE && ElementInContainer(targetRegionKey, allowedRegions)) {
        if (ReplaceEntrance(entrance, target, rollbacks)) {
          // Return once the entrance has been replaced
          return true;
        }
      }
    }
  }
  #ifdef ENABLE_DEBUG
    auto message = "ERROR: Unable to place priority one-way entrance for " + priorityName + "\n";
    SPDLOG_DEBUG(message);
    PlacementLog_Write();
  #endif
  return false;
}

// Once the first entrance to Impas House has been placed, try to place the next one immediately to reduce chances of failure.
static bool PlaceOtherImpasHouseEntrance(std::vector<Entrance*> entrances, std::vector<Entrance*> targetEntrances, std::vector<EntrancePair>& rollbacks) {
  // Get the other impas house entrance
  auto otherImpaTargets = FilterFromPool(targetEntrances, [](const Entrance* target){return (target->GetConnectedRegionKey() == KAK_IMPAS_HOUSE || target->GetConnectedRegionKey() == KAK_IMPAS_HOUSE_BACK);});
  if (otherImpaTargets.empty()) {
    return true;
  }

  Entrance* otherImpaTarget = otherImpaTargets[0];
  auto m = "Now Placing Other Impa Target: " + otherImpaTarget->GetName() + "\n";
  SPDLOG_DEBUG(m);
  AreaKey otherImpaRegion = otherImpaTarget->GetConnectedRegionKey() != KAK_IMPAS_HOUSE_BACK ? KAK_IMPAS_HOUSE_BACK : KAK_IMPAS_HOUSE;
  for (Entrance* entrance : entrances) {
    // If the entrance is already connected or it doesn't have the same hint region as the already placed impas house entrance, then don't try to use it
    if (entrance->GetConnectedRegionKey() != NONE || (GetHintRegionHintKey(otherImpaRegion) != GetHintRegionHintKey(entrance->GetParentRegionKey()))) {
      continue;
    }
    // If the placement succeeds, we return true
    if (ReplaceEntrance(entrance, otherImpaTarget, rollbacks)) {
      return true;
    }
  }
  SPDLOG_DEBUG("No available entrances for placing other impa region.\n");
  return false;
}

// Shuffle entrances by placing them instead of entrances in the provided target entrances list
static bool ShuffleEntrances(std::vector<Entrance*>& entrances, std::vector<Entrance*>& targetEntrances, std::vector<EntrancePair>& rollbacks) {

  Shuffle(entrances);

  //place all entrances in the pool, validating after every placement
  for (Entrance* entrance : entrances) {
    if (entrance->GetConnectedRegionKey() != NONE) {
      continue;
    }

    Shuffle(targetEntrances);
    for (Entrance* target : targetEntrances) {
      if (target->GetConnectedRegionKey() == NONE) {
        continue;
      }

      // Store whether or not we're about to attempt placing an entrance to Impas House
      bool attemptedImpasHousePlacement = (target->GetConnectedRegionKey() == KAK_IMPAS_HOUSE || target->GetConnectedRegionKey() == KAK_IMPAS_HOUSE_BACK);

      if (ReplaceEntrance(entrance, target, rollbacks)) {
        // If shuffle cows is enabled and the last entrance was one to Impas House,
        // then immediately attempt to place the other entrance to Impas House
        if (Settings::ShuffleCows && attemptedImpasHousePlacement) {
          if (!PlaceOtherImpasHouseEntrance(entrances, targetEntrances, rollbacks)) {
            return false;
          }
        }
        break;
      }
    }

    if (entrance->GetConnectedRegionKey() == NONE) {
      return false;
    }
  }

  //all entrances were validly connected
  return true;
}

static bool ShuffleOneWayPriorityEntrances(std::map<std::string, PriorityEntrance>& oneWayPriorities, EntrancePools oneWayEntrancePools, EntrancePools oneWayTargetEntrancePools, int retryCount = 2) {
  while (retryCount > 0) {
    retryCount--;
    std::vector<EntrancePair> rollbacks = {};

    bool success = true;
    for (auto& priority : oneWayPriorities) {
      std::string key = priority.first;
      auto& regions = priority.second.targetRegions;
      auto& types = priority.second.allowedTypes;
      success = PlaceOneWayPriorityEntrance(key, regions, types, rollbacks, oneWayEntrancePools, oneWayTargetEntrancePools);
      if (!success) {
        for (auto& pair : rollbacks) {
          RestoreConnections(pair.first, pair.second);
        }
        break;
      }
    }
    if (!success) {
      continue;
    }
    // If there are no issues, log the connections and continue
    for (auto& pair : rollbacks) {
      ConfirmReplacement(pair.first, pair.second);
    }
    break;
  }

  if (retryCount <= 0) {
    SPDLOG_DEBUG("Entrance placement attempt count for one way priorities exceeded. Restarting randomization completely\n");
    entranceShuffleFailure = true;
    return false;
  }
  return true;
}

static void ShuffleEntrancePool(std::vector<Entrance*>& entrancePool, std::vector<Entrance*>& targetEntrances, int retryCount = 20) {
  noRandomEntrances = false;

  auto splitEntrances = SplitEntrancesByRequirements(entrancePool, targetEntrances);

  auto& restrictiveEntrances = splitEntrances[0];
  auto& softEntrances = splitEntrances[1];

  int retries = retryCount;
  while (retries > 0) {
    if (retries != retryCount) {
      #ifdef ENABLE_DEBUG
        std::string ticks = std::to_string(svcGetSystemTick());
        auto message = "Failed to connect entrances. Retrying " + std::to_string(retries) + " more times.\nDumping World Graph at " + ticks + "\n";
        SPDLOG_DEBUG(message);
        //Areas::DumpWorldGraph(ticks);
      #endif
    }
    retries--;

    std::vector<EntrancePair> rollbacks = {};

    //Shuffle Restrictive Entrances first while more regions are available in
    //order to heavily reduce the chances of the placement failing
    bool success = ShuffleEntrances(restrictiveEntrances, targetEntrances, rollbacks);
    if (success) {
      success = ShuffleEntrances(softEntrances, targetEntrances, rollbacks);
      if(!success) {
        for (auto& pair : rollbacks) {
          RestoreConnections(pair.first, pair.second);
          curNumRandomizedEntrances--;
        }
        continue;
      }
    } else {
      for (auto& pair : rollbacks) {
        RestoreConnections(pair.first, pair.second);
        curNumRandomizedEntrances--;
      }
      continue;
    }

    //If there are no issues, log the connections and continue
    for (auto& pair : rollbacks) {
      ConfirmReplacement(pair.first, pair.second);
    }
    break;
  }

  if (retries <= 0) {
    SPDLOG_DEBUG("Entrance placement attempt count exceeded. Restarting randomization completely");
    entranceShuffleFailure = true;
  }
}

static void SetShuffledEntrances(EntrancePools entrancePools) {
  for (auto& pool : entrancePools) {
    for (Entrance* entrance : pool.second) {
      entrance->SetAsShuffled();
      if (entrance->GetReverse() != nullptr) {
        entrance->GetReverse()->SetAsShuffled();
      }
    }
  }
}

//Process for setting up the shuffling of all entrances to be shuffled
int ShuffleAllEntrances() {

  totalRandomizableEntrances = 0;
  curNumRandomizedEntrances = 0;

  std::vector<EntranceInfoPair> entranceShuffleTable = {
                                   //Parent Region                     Connected Region                      index   blue warp
    {{EntranceType::Dungeon,         KF_OUTSIDE_DEKU_TREE,             DEKU_TREE_ENTRYWAY,                   0x0000},
     {EntranceType::Dungeon,         DEKU_TREE_ENTRYWAY,               KF_OUTSIDE_DEKU_TREE,                 0x0209, 0x0457}},
    {{EntranceType::Dungeon,         DEATH_MOUNTAIN_TRAIL,             DODONGOS_CAVERN_ENTRYWAY,             0x0004},
     {EntranceType::Dungeon,         DODONGOS_CAVERN_ENTRYWAY,         DEATH_MOUNTAIN_TRAIL,                 0x0242, 0x047A}},
    {{EntranceType::Dungeon,         ZORAS_FOUNTAIN,                   JABU_JABUS_BELLY_ENTRYWAY,            0x0028},
     {EntranceType::Dungeon,         JABU_JABUS_BELLY_ENTRYWAY,        ZORAS_FOUNTAIN,                       0x0221, 0x010E}},
    {{EntranceType::Dungeon,         SACRED_FOREST_MEADOW,             FOREST_TEMPLE_ENTRYWAY,               0x0169},
     {EntranceType::Dungeon,         FOREST_TEMPLE_ENTRYWAY,           SACRED_FOREST_MEADOW,                 0x0215, 0x0608}},
    {{EntranceType::Dungeon,         DMC_CENTRAL_LOCAL,                FIRE_TEMPLE_ENTRYWAY,                 0x0165},
     {EntranceType::Dungeon,         FIRE_TEMPLE_ENTRYWAY,             DMC_CENTRAL_LOCAL,                    0x024A, 0x0564}},
    {{EntranceType::Dungeon,         LAKE_HYLIA,                       WATER_TEMPLE_ENTRYWAY,                0x0010},
     {EntranceType::Dungeon,         WATER_TEMPLE_ENTRYWAY,            LAKE_HYLIA,                           0x021D, 0x060C}},
    {{EntranceType::Dungeon,         DESERT_COLOSSUS,                  SPIRIT_TEMPLE_ENTRYWAY,               0x0082},
     {EntranceType::Dungeon,         SPIRIT_TEMPLE_ENTRYWAY,           DESERT_COLOSSUS_FROM_SPIRIT_ENTRYWAY, 0x01E1, 0x0610}},
    {{EntranceType::Dungeon,         GRAVEYARD_WARP_PAD_REGION,        SHADOW_TEMPLE_ENTRYWAY,               0x0037},
     {EntranceType::Dungeon,         SHADOW_TEMPLE_ENTRYWAY,           GRAVEYARD_WARP_PAD_REGION,            0x0205, 0x0580}},
    {{EntranceType::Dungeon,         KAKARIKO_VILLAGE,                 BOTTOM_OF_THE_WELL_ENTRYWAY,          0x0098},
     {EntranceType::Dungeon,         BOTTOM_OF_THE_WELL_ENTRYWAY,      KAKARIKO_VILLAGE,                     0x02A6}},
    {{EntranceType::Dungeon,         ZORAS_FOUNTAIN,                   ICE_CAVERN_ENTRYWAY,                  0x0088},
     {EntranceType::Dungeon,         ICE_CAVERN_ENTRYWAY,              ZORAS_FOUNTAIN,                       0x03D4}},
    {{EntranceType::Dungeon,         GERUDO_FORTRESS,                  GERUDO_TRAINING_GROUNDS_ENTRYWAY,     0x0008},
     {EntranceType::Dungeon,         GERUDO_TRAINING_GROUNDS_ENTRYWAY, GERUDO_FORTRESS,                      0x03A8}},
    {{EntranceType::GanonDungeon,    GANONS_CASTLE_GROUNDS,            GANONS_CASTLE_ENTRYWAY,               0x0467},
     {EntranceType::GanonDungeon,    GANONS_CASTLE_ENTRYWAY,           GANONS_CASTLE_GROUNDS,                0x023D}},

    {{EntranceType::Interior,        KOKIRI_FOREST,                    KF_MIDOS_HOUSE,                       0x0433},
     {EntranceType::Interior,        KF_MIDOS_HOUSE,                   KOKIRI_FOREST,                        0x0443}},
    {{EntranceType::Interior,        KOKIRI_FOREST,                    KF_SARIAS_HOUSE,                      0x0437},
     {EntranceType::Interior,        KF_SARIAS_HOUSE,                  KOKIRI_FOREST,                        0x0447}},
    {{EntranceType::Interior,        KOKIRI_FOREST,                    KF_HOUSE_OF_TWINS,                    0x009C},
     {EntranceType::Interior,        KF_HOUSE_OF_TWINS,                KOKIRI_FOREST,                        0x033C}},
    {{EntranceType::Interior,        KOKIRI_FOREST,                    KF_KNOW_IT_ALL_HOUSE,                 0x00C9},
     {EntranceType::Interior,        KF_KNOW_IT_ALL_HOUSE,             KOKIRI_FOREST,                        0x026A}},
    {{EntranceType::Interior,        KOKIRI_FOREST,                    KF_KOKIRI_SHOP,                       0x00C1},
     {EntranceType::Interior,        KF_KOKIRI_SHOP,                   KOKIRI_FOREST,                        0x0266}},
    {{EntranceType::Interior,        LAKE_HYLIA,                       LH_LAB,                               0x0043},
     {EntranceType::Interior,        LH_LAB,                           LAKE_HYLIA,                           0x03CC}},
    {{EntranceType::Interior,        LH_FISHING_ISLAND,                LH_FISHING_HOLE,                      0x045F},
     {EntranceType::Interior,        LH_FISHING_HOLE,                  LH_FISHING_ISLAND,                    0x0309}},
    {{EntranceType::Interior,        GV_FORTRESS_SIDE,                 GV_CARPENTER_TENT,                    0x03A0},
     {EntranceType::Interior,        GV_CARPENTER_TENT,                GV_FORTRESS_SIDE,                     0x03D0}},
    {{EntranceType::Interior,        MARKET_ENTRANCE,                  MARKET_GUARD_HOUSE,                   0x007E},
     {EntranceType::Interior,        MARKET_GUARD_HOUSE,               MARKET_ENTRANCE,                      0x026E}},
    {{EntranceType::Interior,        THE_MARKET,                       MARKET_MASK_SHOP,                     0x0530},
     {EntranceType::Interior,        MARKET_MASK_SHOP,                 THE_MARKET,                           0x01D1}},
    {{EntranceType::Interior,        THE_MARKET,                       MARKET_BOMBCHU_BOWLING,               0x0507},
     {EntranceType::Interior,        MARKET_BOMBCHU_BOWLING,           THE_MARKET,                           0x03BC}},
    {{EntranceType::Interior,        THE_MARKET,                       MARKET_POTION_SHOP,                   0x0388},
     {EntranceType::Interior,        MARKET_POTION_SHOP,               THE_MARKET,                           0x02A2}},
    {{EntranceType::Interior,        THE_MARKET,                       MARKET_TREASURE_CHEST_GAME,           0x0063},
     {EntranceType::Interior,        MARKET_TREASURE_CHEST_GAME,       THE_MARKET,                           0x01D5}},
    {{EntranceType::Interior,        MARKET_BACK_ALLEY,                MARKET_BOMBCHU_SHOP,                  0x0528},
     {EntranceType::Interior,        MARKET_BOMBCHU_SHOP,              MARKET_BACK_ALLEY,                    0x03C0}},
    {{EntranceType::Interior,        MARKET_BACK_ALLEY,                MARKET_MAN_IN_GREEN_HOUSE,            0x043B},
     {EntranceType::Interior,        MARKET_MAN_IN_GREEN_HOUSE,        MARKET_BACK_ALLEY,                    0x0067}},
    {{EntranceType::Interior,        KAKARIKO_VILLAGE,                 KAK_CARPENTER_BOSS_HOUSE,             0x02FD},
     {EntranceType::Interior,        KAK_CARPENTER_BOSS_HOUSE,         KAKARIKO_VILLAGE,                     0x0349}},
    {{EntranceType::Interior,        KAKARIKO_VILLAGE,                 KAK_HOUSE_OF_SKULLTULA,               0x0550},
     {EntranceType::Interior,        KAK_HOUSE_OF_SKULLTULA,           KAKARIKO_VILLAGE,                     0x04EE}},
    {{EntranceType::Interior,        KAKARIKO_VILLAGE,                 KAK_IMPAS_HOUSE,                      0x039C},
     {EntranceType::Interior,        KAK_IMPAS_HOUSE,                  KAKARIKO_VILLAGE,                     0x0345}},
    {{EntranceType::Interior,        KAK_IMPAS_LEDGE,                  KAK_IMPAS_HOUSE_BACK,                 0x05C8},
     {EntranceType::Interior,        KAK_IMPAS_HOUSE_BACK,             KAK_IMPAS_LEDGE,                      0x05DC}},
    {{EntranceType::Interior,        KAK_BACKYARD,                     KAK_ODD_POTION_BUILDING,              0x0072},
     {EntranceType::Interior,        KAK_ODD_POTION_BUILDING,          KAK_BACKYARD,                         0x034D}},
    {{EntranceType::Interior,        THE_GRAVEYARD,                    GRAVEYARD_DAMPES_HOUSE,               0x030D},
     {EntranceType::Interior,        GRAVEYARD_DAMPES_HOUSE,           THE_GRAVEYARD,                        0x0355}},
    {{EntranceType::Interior,        GORON_CITY,                       GC_SHOP,                              0x037C},
     {EntranceType::Interior,        GC_SHOP,                          GORON_CITY,                           0x03FC}},
    {{EntranceType::Interior,        ZORAS_DOMAIN,                     ZD_SHOP,                              0x0380},
     {EntranceType::Interior,        ZD_SHOP,                          ZORAS_DOMAIN,                         0x03C4}},
    {{EntranceType::Interior,        LON_LON_RANCH,                    LLR_TALONS_HOUSE,                     0x004F},
     {EntranceType::Interior,        LLR_TALONS_HOUSE,                 LON_LON_RANCH,                        0x0378}},
    {{EntranceType::Interior,        LON_LON_RANCH,                    LLR_STABLES,                          0x02F9},
     {EntranceType::Interior,        LLR_STABLES,                      LON_LON_RANCH,                        0x042F}},
    {{EntranceType::Interior,        LON_LON_RANCH,                    LLR_TOWER,                            0x05D0},
     {EntranceType::Interior,        LLR_TOWER,                        LON_LON_RANCH,                        0x05D4}},
    {{EntranceType::Interior,        THE_MARKET,                       MARKET_BAZAAR,                        0x052C},
     {EntranceType::Interior,        MARKET_BAZAAR,                    THE_MARKET,                           0x03B8}},
    {{EntranceType::Interior,        THE_MARKET,                       MARKET_SHOOTING_GALLERY,              0x016D},
     {EntranceType::Interior,        MARKET_SHOOTING_GALLERY,          THE_MARKET,                           0x01CD}},
    {{EntranceType::Interior,        KAKARIKO_VILLAGE,                 KAK_BAZAAR,                           0x00B7},
     {EntranceType::Interior,        KAK_BAZAAR,                       KAKARIKO_VILLAGE,                     0x0201}},
    {{EntranceType::Interior,        KAKARIKO_VILLAGE,                 KAK_SHOOTING_GALLERY,                 0x003B},
     {EntranceType::Interior,        KAK_SHOOTING_GALLERY,             KAKARIKO_VILLAGE,                     0x0463}},
    {{EntranceType::Interior,        DESERT_COLOSSUS,                  COLOSSUS_GREAT_FAIRY_FOUNTAIN,        0x0588},
     {EntranceType::Interior,        COLOSSUS_GREAT_FAIRY_FOUNTAIN,    DESERT_COLOSSUS,                      0x057C}},
    {{EntranceType::Interior,        HYRULE_CASTLE_GROUNDS,            HC_GREAT_FAIRY_FOUNTAIN,              0x0578},
     {EntranceType::Interior,        HC_GREAT_FAIRY_FOUNTAIN,          CASTLE_GROUNDS,                       0x0340}},
    {{EntranceType::Interior,        GANONS_CASTLE_GROUNDS,            OGC_GREAT_FAIRY_FOUNTAIN,             0x04C2},
     {EntranceType::Interior,        OGC_GREAT_FAIRY_FOUNTAIN,         CASTLE_GROUNDS,                       0x03E8}}, //0x3E8 is an unused entrance index repruposed to differentiate between the HC and OGC fairy fountain exits (normally they both use 0x340)
    {{EntranceType::Interior,        DMC_LOWER_NEARBY,                 DMC_GREAT_FAIRY_FOUNTAIN,             0x04BE},
     {EntranceType::Interior,        DMC_GREAT_FAIRY_FOUNTAIN,         DMC_LOWER_LOCAL,                      0x0482}},
    {{EntranceType::Interior,        DEATH_MOUNTAIN_SUMMIT,            DMT_GREAT_FAIRY_FOUNTAIN,             0x0315},
     {EntranceType::Interior,        DMT_GREAT_FAIRY_FOUNTAIN,         DEATH_MOUNTAIN_SUMMIT,                0x045B}},
    {{EntranceType::Interior,        ZORAS_FOUNTAIN,                   ZF_GREAT_FAIRY_FOUNTAIN,              0x0371},
     {EntranceType::Interior,        ZF_GREAT_FAIRY_FOUNTAIN,          ZORAS_FOUNTAIN,                       0x0394}},

    {{EntranceType::SpecialInterior, KOKIRI_FOREST,                    KF_LINKS_HOUSE,                       0x0272},
     {EntranceType::SpecialInterior, KF_LINKS_HOUSE,                   KOKIRI_FOREST,                        0x0211}},
    {{EntranceType::SpecialInterior, TOT_ENTRANCE,                     TEMPLE_OF_TIME,                       0x0053},
     {EntranceType::SpecialInterior, TEMPLE_OF_TIME,                   TOT_ENTRANCE,                         0x0472}},
    {{EntranceType::SpecialInterior, KAKARIKO_VILLAGE,                 KAK_WINDMILL,                         0x0453},
     {EntranceType::SpecialInterior, KAK_WINDMILL,                     KAKARIKO_VILLAGE,                     0x0351}},
    {{EntranceType::SpecialInterior, KAKARIKO_VILLAGE,                 KAK_POTION_SHOP_FRONT,                0x0384},
     {EntranceType::SpecialInterior, KAK_POTION_SHOP_FRONT,            KAKARIKO_VILLAGE,                     0x044B}},
    {{EntranceType::SpecialInterior, KAK_BACKYARD,                     KAK_POTION_SHOP_BACK,                 0x03EC},
     {EntranceType::SpecialInterior, KAK_POTION_SHOP_BACK,             KAK_BACKYARD,                         0x04FF}},

     // Grotto Loads use an entrance index of 0x0700 + their grotto id. The id is used as index for the
     // grottoLoadTable in src/grotto.c
     // Grotto Returns use an entrance index of 0x0800 + their grotto id. The id is used as index for the
     // grottoReturnTable in src/grotto.c
    {{EntranceType::GrottoGrave,     DESERT_COLOSSUS,                  COLOSSUS_GROTTO,                      0x0700},
     {EntranceType::GrottoGrave,     COLOSSUS_GROTTO,                  DESERT_COLOSSUS,                      0x0800}},
    {{EntranceType::GrottoGrave,     LAKE_HYLIA,                       LH_GROTTO,                            0x0701},
     {EntranceType::GrottoGrave,     LH_GROTTO,                        LAKE_HYLIA,                           0x0801}},
    {{EntranceType::GrottoGrave,     ZORAS_RIVER,                      ZR_STORMS_GROTTO,                     0x0702},
     {EntranceType::GrottoGrave,     ZR_STORMS_GROTTO,                 ZORAS_RIVER,                          0x0802}},
    {{EntranceType::GrottoGrave,     ZORAS_RIVER,                      ZR_FAIRY_GROTTO,                      0x0703},
     {EntranceType::GrottoGrave,     ZR_FAIRY_GROTTO,                  ZORAS_RIVER,                          0x0803}},
    {{EntranceType::GrottoGrave,     ZORAS_RIVER,                      ZR_OPEN_GROTTO,                       0x0704},
     {EntranceType::GrottoGrave,     ZR_OPEN_GROTTO,                   ZORAS_RIVER,                          0x0804}},
    {{EntranceType::GrottoGrave,     DMC_LOWER_NEARBY,                 DMC_HAMMER_GROTTO,                    0x0705},
     {EntranceType::GrottoGrave,     DMC_HAMMER_GROTTO,                DMC_LOWER_LOCAL,                      0x0805}},
    {{EntranceType::GrottoGrave,     DMC_UPPER_NEARBY,                 DMC_UPPER_GROTTO,                     0x0706},
     {EntranceType::GrottoGrave,     DMC_UPPER_GROTTO,                 DMC_UPPER_LOCAL,                      0x0806}},
    {{EntranceType::GrottoGrave,     GC_GROTTO_PLATFORM,               GC_GROTTO,                            0x0707},
     {EntranceType::GrottoGrave,     GC_GROTTO,                        GC_GROTTO_PLATFORM,                   0x0807}},
    {{EntranceType::GrottoGrave,     DEATH_MOUNTAIN_TRAIL,             DMT_STORMS_GROTTO,                    0x0708},
     {EntranceType::GrottoGrave,     DMT_STORMS_GROTTO,                DEATH_MOUNTAIN_TRAIL,                 0x0808}},
    {{EntranceType::GrottoGrave,     DEATH_MOUNTAIN_SUMMIT,            DMT_COW_GROTTO,                       0x0709},
     {EntranceType::GrottoGrave,     DMT_COW_GROTTO,                   DEATH_MOUNTAIN_SUMMIT,                0x0809}},
    {{EntranceType::GrottoGrave,     KAK_BACKYARD,                     KAK_OPEN_GROTTO,                      0x070A},
     {EntranceType::GrottoGrave,     KAK_OPEN_GROTTO,                  KAK_BACKYARD,                         0x080A}},
    {{EntranceType::GrottoGrave,     KAKARIKO_VILLAGE,                 KAK_REDEAD_GROTTO,                    0x070B},
     {EntranceType::GrottoGrave,     KAK_REDEAD_GROTTO,                KAKARIKO_VILLAGE,                     0x080B}},
    {{EntranceType::GrottoGrave,     HYRULE_CASTLE_GROUNDS,            HC_STORMS_GROTTO,                     0x070C},
     {EntranceType::GrottoGrave,     HC_STORMS_GROTTO,                 CASTLE_GROUNDS,                       0x080C}},
    {{EntranceType::GrottoGrave,     HYRULE_FIELD,                     HF_TEKTITE_GROTTO,                    0x070D},
     {EntranceType::GrottoGrave,     HF_TEKTITE_GROTTO,                HYRULE_FIELD,                         0x080D}},
    {{EntranceType::GrottoGrave,     HYRULE_FIELD,                     HF_NEAR_KAK_GROTTO,                   0x070E},
     {EntranceType::GrottoGrave,     HF_NEAR_KAK_GROTTO,               HYRULE_FIELD,                         0x080E}},
    {{EntranceType::GrottoGrave,     HYRULE_FIELD,                     HF_FAIRY_GROTTO,                      0x070F},
     {EntranceType::GrottoGrave,     HF_FAIRY_GROTTO,                  HYRULE_FIELD,                         0x080F}},
    {{EntranceType::GrottoGrave,     HYRULE_FIELD,                     HF_NEAR_MARKET_GROTTO,                0x0710},
     {EntranceType::GrottoGrave,     HF_NEAR_MARKET_GROTTO,            HYRULE_FIELD,                         0x0810}},
    {{EntranceType::GrottoGrave,     HYRULE_FIELD,                     HF_COW_GROTTO,                        0x0711},
     {EntranceType::GrottoGrave,     HF_COW_GROTTO,                    HYRULE_FIELD,                         0x0811}},
    {{EntranceType::GrottoGrave,     HYRULE_FIELD,                     HF_INSIDE_FENCE_GROTTO,               0x0712},
     {EntranceType::GrottoGrave,     HF_INSIDE_FENCE_GROTTO,           HYRULE_FIELD,                         0x0812}},
    {{EntranceType::GrottoGrave,     HYRULE_FIELD,                     HF_OPEN_GROTTO,                       0x0713},
     {EntranceType::GrottoGrave,     HF_OPEN_GROTTO,                   HYRULE_FIELD,                         0x0813}},
    {{EntranceType::GrottoGrave,     HYRULE_FIELD,                     HF_SOUTHEAST_GROTTO,                  0x0714},
     {EntranceType::GrottoGrave,     HF_SOUTHEAST_GROTTO,              HYRULE_FIELD,                         0x0814}},
    {{EntranceType::GrottoGrave,     LON_LON_RANCH,                    LLR_GROTTO,                           0x0715},
     {EntranceType::GrottoGrave,     LLR_GROTTO,                       LON_LON_RANCH,                        0x0815}},
    {{EntranceType::GrottoGrave,     SFM_ENTRYWAY,                     SFM_WOLFOS_GROTTO,                    0x0716},
     {EntranceType::GrottoGrave,     SFM_WOLFOS_GROTTO,                SFM_ENTRYWAY,                         0x0816}},
    {{EntranceType::GrottoGrave,     SACRED_FOREST_MEADOW,             SFM_STORMS_GROTTO,                    0x0717},
     {EntranceType::GrottoGrave,     SFM_STORMS_GROTTO,                SACRED_FOREST_MEADOW,                 0x0817}},
    {{EntranceType::GrottoGrave,     SACRED_FOREST_MEADOW,             SFM_FAIRY_GROTTO,                     0x0718},
     {EntranceType::GrottoGrave,     SFM_FAIRY_GROTTO,                 SACRED_FOREST_MEADOW,                 0x0818}},
    {{EntranceType::GrottoGrave,     LW_BEYOND_MIDO,                   LW_SCRUBS_GROTTO,                     0x0719},
     {EntranceType::GrottoGrave,     LW_SCRUBS_GROTTO,                 LW_BEYOND_MIDO,                       0x0819}},
    {{EntranceType::GrottoGrave,     THE_LOST_WOODS,                   LW_NEAR_SHORTCUTS_GROTTO,             0x071A},
     {EntranceType::GrottoGrave,     LW_NEAR_SHORTCUTS_GROTTO,         THE_LOST_WOODS,                       0x081A}},
    {{EntranceType::GrottoGrave,     KOKIRI_FOREST,                    KF_STORMS_GROTTO,                     0x071B},
     {EntranceType::GrottoGrave,     KF_STORMS_GROTTO,                 KOKIRI_FOREST,                        0x081B}},
    {{EntranceType::GrottoGrave,     ZORAS_DOMAIN,                     ZD_STORMS_GROTTO,                     0x071C},
     {EntranceType::GrottoGrave,     ZD_STORMS_GROTTO,                 ZORAS_DOMAIN,                         0x081C}},
    {{EntranceType::GrottoGrave,     GERUDO_FORTRESS,                  GF_STORMS_GROTTO,                     0x071D},
     {EntranceType::GrottoGrave,     GF_STORMS_GROTTO,                 GERUDO_FORTRESS,                      0x081D}},
    {{EntranceType::GrottoGrave,     GV_FORTRESS_SIDE,                 GV_STORMS_GROTTO,                     0x071E},
     {EntranceType::GrottoGrave,     GV_STORMS_GROTTO,                 GV_FORTRESS_SIDE,                     0x081E}},
    {{EntranceType::GrottoGrave,     GV_GROTTO_LEDGE,                  GV_OCTOROK_GROTTO,                    0x071F},
     {EntranceType::GrottoGrave,     GV_OCTOROK_GROTTO,                GV_GROTTO_LEDGE,                      0x081F}},
    {{EntranceType::GrottoGrave,     LW_BEYOND_MIDO,                   DEKU_THEATER,                         0x0720},
     {EntranceType::GrottoGrave,     DEKU_THEATER,                     LW_BEYOND_MIDO,                       0x0820}},

    // Graves have their own specified entrance indices
    {{EntranceType::GrottoGrave,     THE_GRAVEYARD,                    GRAVEYARD_SHIELD_GRAVE,               0x004B},
     {EntranceType::GrottoGrave,     GRAVEYARD_SHIELD_GRAVE,           THE_GRAVEYARD,                        0x035D}},
    {{EntranceType::GrottoGrave,     THE_GRAVEYARD,                    GRAVEYARD_HEART_PIECE_GRAVE,          0x031C},
     {EntranceType::GrottoGrave,     GRAVEYARD_HEART_PIECE_GRAVE,      THE_GRAVEYARD,                        0x0361}},
    {{EntranceType::GrottoGrave,     THE_GRAVEYARD,                    GRAVEYARD_COMPOSERS_GRAVE,            0x002D},
     {EntranceType::GrottoGrave,     GRAVEYARD_COMPOSERS_GRAVE,        THE_GRAVEYARD,                        0x050B}},
    {{EntranceType::GrottoGrave,     THE_GRAVEYARD,                    GRAVEYARD_DAMPES_GRAVE,               0x044F},
     {EntranceType::GrottoGrave,     GRAVEYARD_DAMPES_GRAVE,           THE_GRAVEYARD,                        0x0359}},

    {{EntranceType::Overworld,       KOKIRI_FOREST,                    LW_BRIDGE_FROM_FOREST,                0x05E0},
     {EntranceType::Overworld,       LW_BRIDGE,                        KOKIRI_FOREST,                        0x020D}},
    {{EntranceType::Overworld,       KOKIRI_FOREST,                    THE_LOST_WOODS,                       0x011E},
     {EntranceType::Overworld,       LW_FOREST_EXIT,                   KOKIRI_FOREST,                        0x0286}},
    {{EntranceType::Overworld,       THE_LOST_WOODS,                   GC_WOODS_WARP,                        0x04E2},
     {EntranceType::Overworld,       GC_WOODS_WARP,                    THE_LOST_WOODS,                       0x04D6}},
    {{EntranceType::Overworld,       THE_LOST_WOODS,                   ZORAS_RIVER,                          0x01DD},
     {EntranceType::Overworld,       ZORAS_RIVER,                      THE_LOST_WOODS,                       0x04DA}},
    {{EntranceType::Overworld,       LW_BEYOND_MIDO,                   SFM_ENTRYWAY,                         0x00FC},
     {EntranceType::Overworld,       SFM_ENTRYWAY,                     LW_BEYOND_MIDO,                       0x01A9}},
    {{EntranceType::Overworld,       LW_BRIDGE,                        HYRULE_FIELD,                         0x0185},
     {EntranceType::Overworld,       HYRULE_FIELD,                     LW_BRIDGE,                            0x04DE}},
    {{EntranceType::Overworld,       HYRULE_FIELD,                     LAKE_HYLIA,                           0x0102},
     {EntranceType::Overworld,       LAKE_HYLIA,                       HYRULE_FIELD,                         0x0189}},
    {{EntranceType::Overworld,       HYRULE_FIELD,                     GERUDO_VALLEY,                        0x0117},
     {EntranceType::Overworld,       GERUDO_VALLEY,                    HYRULE_FIELD,                         0x018D}},
    {{EntranceType::Overworld,       HYRULE_FIELD,                     MARKET_ENTRANCE,                      0x0276},
     {EntranceType::Overworld,       MARKET_ENTRANCE,                  HYRULE_FIELD,                         0x01FD}},
    {{EntranceType::Overworld,       HYRULE_FIELD,                     KAKARIKO_VILLAGE,                     0x00DB},
     {EntranceType::Overworld,       KAKARIKO_VILLAGE,                 HYRULE_FIELD,                         0x017D}},
    {{EntranceType::Overworld,       HYRULE_FIELD,                     ZR_FRONT,                             0x00EA},
     {EntranceType::Overworld,       ZR_FRONT,                         HYRULE_FIELD,                         0x0181}},
    {{EntranceType::Overworld,       HYRULE_FIELD,                     LON_LON_RANCH,                        0x0157},
     {EntranceType::Overworld,       LON_LON_RANCH,                    HYRULE_FIELD,                         0x01F9}},
    {{EntranceType::Overworld,       LAKE_HYLIA,                       ZORAS_DOMAIN,                         0x0328},
     {EntranceType::Overworld,       ZORAS_DOMAIN,                     LAKE_HYLIA,                           0x0560}},
    {{EntranceType::Overworld,       GV_FORTRESS_SIDE,                 GERUDO_FORTRESS,                      0x0129},
     {EntranceType::Overworld,       GERUDO_FORTRESS,                  GV_FORTRESS_SIDE,                     0x022D}},
    {{EntranceType::Overworld,       GF_OUTSIDE_GATE,                  WASTELAND_NEAR_FORTRESS,              0x0130},
     {EntranceType::Overworld,       WASTELAND_NEAR_FORTRESS,          GF_OUTSIDE_GATE,                      0x03AC}},
    {{EntranceType::Overworld,       WASTELAND_NEAR_COLOSSUS,          DESERT_COLOSSUS,                      0x0123},
     {EntranceType::Overworld,       DESERT_COLOSSUS,                  WASTELAND_NEAR_COLOSSUS,              0x0365}},
    {{EntranceType::Overworld,       MARKET_ENTRANCE,                  THE_MARKET,                           0x00B1},
     {EntranceType::Overworld,       THE_MARKET,                       MARKET_ENTRANCE,                      0x0033}},
    {{EntranceType::Overworld,       THE_MARKET,                       CASTLE_GROUNDS,                       0x0138},
     {EntranceType::Overworld,       CASTLE_GROUNDS,                   THE_MARKET,                           0x025A}},
    {{EntranceType::Overworld,       THE_MARKET,                       TOT_ENTRANCE,                         0x0171},
     {EntranceType::Overworld,       TOT_ENTRANCE,                     THE_MARKET,                           0x025E}},
    {{EntranceType::Overworld,       KAKARIKO_VILLAGE,                 THE_GRAVEYARD,                        0x00E4},
     {EntranceType::Overworld,       THE_GRAVEYARD,                    KAKARIKO_VILLAGE,                     0x0195}},
    {{EntranceType::Overworld,       KAK_BEHIND_GATE,                  DEATH_MOUNTAIN_TRAIL,                 0x013D},
     {EntranceType::Overworld,       DEATH_MOUNTAIN_TRAIL,             KAK_BEHIND_GATE,                      0x0191}},
    {{EntranceType::Overworld,       DEATH_MOUNTAIN_TRAIL,             GORON_CITY,                           0x014D},
     {EntranceType::Overworld,       GORON_CITY,                       DEATH_MOUNTAIN_TRAIL,                 0x01B9}},
    {{EntranceType::Overworld,       GC_DARUNIAS_CHAMBER,              DMC_LOWER_LOCAL,                      0x0246},
     {EntranceType::Overworld,       DMC_LOWER_NEARBY,                 GC_DARUNIAS_CHAMBER,                  0x01C1}},
    {{EntranceType::Overworld,       DEATH_MOUNTAIN_SUMMIT,            DMC_UPPER_LOCAL,                      0x0147},
     {EntranceType::Overworld,       DMC_UPPER_NEARBY,                 DEATH_MOUNTAIN_SUMMIT,                0x01BD}},
    {{EntranceType::Overworld,       ZR_BEHIND_WATERFALL,              ZORAS_DOMAIN,                         0x0108},
     {EntranceType::Overworld,       ZORAS_DOMAIN,                     ZR_BEHIND_WATERFALL,                  0x019D}},
    {{EntranceType::Overworld,       ZD_BEHIND_KING_ZORA,              ZORAS_FOUNTAIN,                       0x0225},
     {EntranceType::Overworld,       ZORAS_FOUNTAIN,                   ZD_BEHIND_KING_ZORA,                  0x01A1}},

    {{EntranceType::Overworld,       GV_LOWER_STREAM,                  LAKE_HYLIA,                           0x0219}, NO_RETURN_ENTRANCE},

    {{EntranceType::OwlDrop,         LH_OWL_FLIGHT,                    HYRULE_FIELD,                         0x027E}, NO_RETURN_ENTRANCE},
    {{EntranceType::OwlDrop,         DMT_OWL_FLIGHT,                   KAK_IMPAS_ROOFTOP,                    0x0554}, NO_RETURN_ENTRANCE},

    {{EntranceType::Spawn,           CHILD_SPAWN,                      KF_LINKS_HOUSE,                       0x00BB}, NO_RETURN_ENTRANCE},
    {{EntranceType::Spawn,           ADULT_SPAWN,                      TEMPLE_OF_TIME,                       0x0282}, NO_RETURN_ENTRANCE}, // 0x282 is an unused entrance index repurposed to differentiate between
                                                                                                                                           // Adult Spawn and prelude of light (normally they both use 0x5F4)
    {{EntranceType::WarpSong,        MINUET_OF_FOREST_WARP,            SACRED_FOREST_MEADOW,                 0x0600}, NO_RETURN_ENTRANCE},
    {{EntranceType::WarpSong,        BOLERO_OF_FIRE_WARP,              DMC_CENTRAL_LOCAL,                    0x04F6}, NO_RETURN_ENTRANCE},
    {{EntranceType::WarpSong,        SERENADE_OF_WATER_WARP,           LAKE_HYLIA,                           0x0604}, NO_RETURN_ENTRANCE},
    {{EntranceType::WarpSong,        REQUIEM_OF_SPIRIT_WARP,           DESERT_COLOSSUS,                      0x01F1}, NO_RETURN_ENTRANCE},
    {{EntranceType::WarpSong,        NOCTURNE_OF_SHADOW_WARP,          GRAVEYARD_WARP_PAD_REGION,            0x0568}, NO_RETURN_ENTRANCE},
    {{EntranceType::WarpSong,        PRELUDE_OF_LIGHT_WARP,            TEMPLE_OF_TIME,                       0x05F4}, NO_RETURN_ENTRANCE},
  };

  std::map<std::string, PriorityEntrance> priorityEntranceTable = {
    {"Bolero",   {{DMC_CENTRAL_LOCAL},                                     {EntranceType::OwlDrop, EntranceType::WarpSong}}},
    {"Nocturne", {{GRAVEYARD_WARP_PAD_REGION},                             {EntranceType::OwlDrop, EntranceType::Spawn, EntranceType::WarpSong}}},
    {"Requiem",  {{DESERT_COLOSSUS, DESERT_COLOSSUS_FROM_SPIRIT_ENTRYWAY}, {EntranceType::OwlDrop, EntranceType::Spawn, EntranceType::WarpSong}}},
  };

  entranceShuffleFailure = false;
  SetAllEntrancesData(entranceShuffleTable);

  EntrancePools oneWayEntrancePools = {};
  EntrancePools entrancePools = {};
  std::map<std::string, PriorityEntrance> oneWayPriorities = {};

  // Owl Drops
  if (Settings::ShuffleOwlDrops) {
    oneWayEntrancePools[EntranceType::OwlDrop] = GetShuffleableEntrances(EntranceType::OwlDrop);
  }

  // Spawns
  if (Settings::ShuffleOverworldSpawns) {
    oneWayEntrancePools[EntranceType::Spawn] = GetShuffleableEntrances(EntranceType::Spawn);
  }

  // Warpsongs
  if (Settings::ShuffleWarpSongs) {
    oneWayEntrancePools[EntranceType::WarpSong] = GetShuffleableEntrances(EntranceType::WarpSong);
    // In Glitchless, there aren't any other ways to access these areas
    if (Settings::Logic.Is(LOGIC_GLITCHLESS)) {
      oneWayPriorities["Bolero"] = priorityEntranceTable["Bolero"];
      oneWayPriorities["Nocturne"] = priorityEntranceTable["Nocturne"];
      if (!Settings::ShuffleDungeonEntrances && !Settings::ShuffleOverworldEntrances) {
        oneWayPriorities["Requiem"] = priorityEntranceTable["Requiem"];
      }
    }
  }

  //Shuffle Dungeon Entrances
  if (Settings::ShuffleDungeonEntrances.IsNot(SHUFFLEDUNGEONS_OFF)) {
    entrancePools[EntranceType::Dungeon] = GetShuffleableEntrances(EntranceType::Dungeon);
	//Add Ganon's Castle, if set to On + Ganon
    if (Settings::ShuffleDungeonEntrances.Is(SHUFFLEDUNGEONS_GANON)) {
      AddElementsToPool(entrancePools[EntranceType::Dungeon], GetShuffleableEntrances(EntranceType::GanonDungeon));
    }
    //If forest is closed don't allow a forest escape via spirit temple hands
    if (Settings::OpenForest.Is(OPENFOREST_CLOSED) && !(Settings::ShuffleOverworldEntrances || Settings::ShuffleInteriorEntrances)) {
      FilterAndEraseFromPool(entrancePools[EntranceType::Dungeon], [](const Entrance* entrance){return entrance->GetParentRegionKey()    == KF_OUTSIDE_DEKU_TREE &&
                                                                                                       entrance->GetConnectedRegionKey() == DEKU_TREE_ENTRYWAY;});
    }
    if (Settings::DecoupleEntrances) {
      for (Entrance* entrance : entrancePools[EntranceType::Dungeon]) {
        entrancePools[EntranceType::DungeonReverse].push_back(entrance->GetReverse());
      }
    }
  }

  // Interior entrances
  if (Settings::ShuffleInteriorEntrances.IsNot(SHUFFLEINTERIORS_OFF)) {
    entrancePools[EntranceType::Interior] = GetShuffleableEntrances(EntranceType::Interior);
    // Special interiors
    if (Settings::ShuffleInteriorEntrances.Is(SHUFFLEINTERIORS_ALL)) {
      AddElementsToPool(entrancePools[EntranceType::Interior], GetShuffleableEntrances(EntranceType::SpecialInterior));
    }
    if (Settings::DecoupleEntrances) {
      for (Entrance* entrance : entrancePools[EntranceType::Interior]) {
        entrancePools[EntranceType::InteriorReverse].push_back(entrance->GetReverse());
      }
    }
  }

  //grotto entrances
  if (Settings::ShuffleGrottoEntrances) {
    entrancePools[EntranceType::GrottoGrave] = GetShuffleableEntrances(EntranceType::GrottoGrave);

    if (Settings::DecoupleEntrances) {
      for (Entrance* entrance : entrancePools[EntranceType::GrottoGrave]) {
        entrancePools[EntranceType::GrottoGraveReverse].push_back(entrance->GetReverse());
      }
    }
  }

  //overworld entrances
  if (Settings::ShuffleOverworldEntrances) {
    bool excludeOverworldReverse = Settings::MixOverworld && !Settings::DecoupleEntrances;
    entrancePools[EntranceType::Overworld] = GetShuffleableEntrances(EntranceType::Overworld, excludeOverworldReverse);
    // Only shuffle GV Lower Stream -> Lake Hylia if decoupled entrances are on
    if (!Settings::DecoupleEntrances) {
      FilterAndEraseFromPool(entrancePools[EntranceType::Overworld], [](const Entrance* entrance){return entrance->GetParentRegionKey()    == GV_LOWER_STREAM &&
                                                                                                         entrance->GetConnectedRegionKey() == LAKE_HYLIA;});
    }
  }

  // Set shuffled entrances as such
  SetShuffledEntrances(entrancePools);
  SetShuffledEntrances(oneWayEntrancePools);

  //combine entrance pools if mixing pools. Only continue if more than one pool is selected.
  int totalMixedPools = (Settings::MixDungeons ? 1 : 0) + (Settings::MixOverworld ? 1 : 0) + (Settings::MixInteriors ? 1 : 0) + (Settings::MixGrottos ? 1 : 0);
  if (totalMixedPools < 2) {
    Settings::MixedEntrancePools.SetSelectedIndex(OFF);
    Settings::MixDungeons.SetSelectedIndex(OFF);
    Settings::MixOverworld.SetSelectedIndex(OFF);
    Settings::MixInteriors.SetSelectedIndex(OFF);
    Settings::MixGrottos.SetSelectedIndex(OFF);
  }
  if (Settings::MixedEntrancePools) {
    std::set<EntranceType> poolsToMix = {};
    if (Settings::MixDungeons) {
      poolsToMix.insert(EntranceType::Dungeon);
      // Insert reverse entrances when decoupled entrances is on
      if (Settings::DecoupleEntrances) {
        poolsToMix.insert(EntranceType::DungeonReverse);
      }
    }
    if (Settings::MixOverworld) {
      poolsToMix.insert(EntranceType::Overworld);
    }
    if (Settings::MixInteriors) {
      poolsToMix.insert(EntranceType::Interior);
      if (Settings::DecoupleEntrances) {
        poolsToMix.insert(EntranceType::InteriorReverse);
      }
    }
    if (Settings::MixGrottos) {
      poolsToMix.insert(EntranceType::GrottoGrave);
      if (Settings::DecoupleEntrances) {
        poolsToMix.insert(EntranceType::GrottoGraveReverse);
      }
    }

    for (auto& pool : entrancePools) {

      auto type = pool.first;

      if (poolsToMix.count(type) > 0) {
        AddElementsToPool(entrancePools[EntranceType::Mixed], pool.second);
        entrancePools[type].clear();
      }
    }
  }

  // Build target entrance pools and set the assumption for entrances being reachable
  EntrancePools oneWayTargetEntrancePools = {};
  for (auto& pool : oneWayEntrancePools) {

    std::vector<EntranceType> validTargetTypes = {};
    EntranceType poolType = pool.first;

    if (poolType == EntranceType::OwlDrop) {
      validTargetTypes = {EntranceType::WarpSong, EntranceType::OwlDrop, EntranceType::Overworld, EntranceType::Extra};
      oneWayTargetEntrancePools[poolType] = BuildOneWayTargets(validTargetTypes, {std::make_pair(PRELUDE_OF_LIGHT_WARP, TEMPLE_OF_TIME)});
      // Owl Drops are only accessible as child, so targets should reflect that
      for (Entrance* target : oneWayTargetEntrancePools[poolType]) {
        target->SetCondition([]{return Logic::IsChild;});
      }

    } else if (poolType == EntranceType::Spawn) {
      validTargetTypes = {EntranceType::Spawn, EntranceType::WarpSong, EntranceType::OwlDrop, EntranceType::Overworld, EntranceType::Interior, EntranceType::SpecialInterior, EntranceType::GrottoGrave, EntranceType::Extra};
      oneWayTargetEntrancePools[poolType] = BuildOneWayTargets(validTargetTypes);

    } else if (poolType == EntranceType::WarpSong) {
      validTargetTypes = {EntranceType::Spawn, EntranceType::WarpSong, EntranceType::OwlDrop, EntranceType::Overworld, EntranceType::Interior, EntranceType::SpecialInterior, EntranceType::GrottoGrave, EntranceType::Extra};
      oneWayTargetEntrancePools[poolType] = BuildOneWayTargets(validTargetTypes);
    }
    // for target in one_way_target_entrance_pools[pool_type]:
    //           target.add_rule((lambda entrances=entrance_pool: (lambda state, **kwargs: any(entrance.connected_region == None for entrance in entrances)))())
  }

  // Disconnect all one way entrances at this point (they need to be connected during all of the above process)
  for (auto& pool : oneWayEntrancePools) {
    for (Entrance* entrance : pool.second) {
      totalRandomizableEntrances++;
      entrance->Disconnect();
    }
  }

  // Assume entrance pools for each type
  EntrancePools targetEntrancePools = {};
  for (auto& pool : entrancePools) {
    targetEntrancePools[pool.first] = AssumeEntrancePool(pool.second);
  }

  //distribution stuff

  //check placed on-way entrances
  //remove replaced entrances so we don't place two in one target
  //remvoe priority targets if any placed entrances point at their regions

  // Place priority entrances
  ShuffleOneWayPriorityEntrances(oneWayPriorities, oneWayEntrancePools, oneWayTargetEntrancePools);
  if (entranceShuffleFailure) {
    return ENTRANCE_SHUFFLE_FAILURE;
  }

  // Delete all targets that we just placed from one way target pools so
  // multiple one way entrances don't use the same target
  std::vector<Entrance*> replacedEntrances = {};
  for (auto& pool : oneWayEntrancePools) {
    for (Entrance* entrance : pool.second) {
      if (entrance->GetReplacement() != nullptr) {
        replacedEntrances.push_back(entrance);
      }
    }
  }
  for (auto& pool : oneWayTargetEntrancePools) {
    for (Entrance* remainingTarget : pool.second) {
      auto replacement = remainingTarget->GetReplacement();
      if (ElementInContainer(replacement, replacedEntrances)) {
        DeleteTargetEntrance(remainingTarget);
      }
    }
  }

  // Shuffle all one way entrances among pools to shuffle
  for (auto& pool : oneWayEntrancePools) {
    ShuffleEntrancePool(pool.second, oneWayTargetEntrancePools[pool.first], 5);
    if (entranceShuffleFailure) {
      return ENTRANCE_SHUFFLE_FAILURE;
    }
    // Delete all targets that we just placed from other one way target pools so
    // multiple one way entrances don't use the same target
    replacedEntrances = FilterFromPool(pool.second, [](Entrance* entrance){
      return entrance->GetReplacement() != nullptr;
    });
    for (auto& targetPool : oneWayTargetEntrancePools) {
      for (Entrance* remainingTarget : targetPool.second) {
        auto replacement = remainingTarget->GetReplacement();
        if (ElementInContainer(replacement, replacedEntrances)) {
          DeleteTargetEntrance(remainingTarget);
        }
      }
    }
    // Delete all unused extra targets after placing a one way pool, since the
    // unused targets won't ever be replaced
    for (Entrance* unusedTarget : oneWayTargetEntrancePools[pool.first]) {
      DeleteTargetEntrance(unusedTarget);
    }
  }

  //shuffle all entrances among pools to shuffle
  for (auto& pool : entrancePools) {
    ShuffleEntrancePool(pool.second, targetEntrancePools[pool.first]);
    if (entranceShuffleFailure) {
      return ENTRANCE_SHUFFLE_FAILURE;
    }
  }

  return ENTRANCE_SHUFFLE_SUCCESS;
}

// Save the set of shuffled entrances that will be sent to the patch
void CreateEntranceOverrides() {
  entranceOverrides.clear();
  if (noRandomEntrances) {
    return;
  }
  SPDLOG_DEBUG("\nCREATING ENTRANCE OVERRIDES\n");
  auto allShuffleableEntrances = GetShuffleableEntrances(EntranceType::All, false);

  for (Entrance* entrance : allShuffleableEntrances) {

    //Double-check to make sure the entrance is actually shuffled
    if (!entrance->IsShuffled()) {
      continue;
    }

    auto message = "Setting " + entrance->to_string() + "\n";
    SPDLOG_DEBUG(message);

    int16_t originalIndex = entrance->GetIndex();
    int16_t originalBlueWarp = entrance->GetBlueWarp();
    int16_t replacementIndex = entrance->GetReplacement()->GetIndex();

    int16_t destinationIndex = -1;
    int16_t replacementDestinationIndex = -1;

    // Only set destination indices for two way entrances and when decouple entrances
    // is off
    if (entrance->GetReverse() != nullptr && !Settings::DecoupleEntrances) {
      replacementDestinationIndex = entrance->GetReplacement()->GetReverse()->GetIndex();
      destinationIndex = entrance->GetReverse()->GetIndex();
    }

    entranceOverrides.push_back({
      .index = originalIndex,
      .destination = destinationIndex,
      .blueWarp = originalBlueWarp,
      .override = replacementIndex,
      .overrideDestination = replacementDestinationIndex,
    });

    message = "\tOriginal: " + std::to_string(originalIndex) + "\n";
    SPDLOG_DEBUG(message);
    message = "\tReplacement " + std::to_string(replacementIndex) + "\n";
    SPDLOG_DEBUG(message);
  }
}

std::vector<std::list<Entrance*>> playthroughEntrances;
