void FreeSpaceListing::giveSpace(
                 int address,
                 int length) {
  // Check to see if we can add this space to an existing entry
  for (FreeSpaceList::iterator it = freeSpaceList_.begin();
       it != freeSpaceList_.end();
       it++) {
    // Ensure that we don't cross bank boundaries
    // (needs more thorough checking)
    if ((it->address() % LoadedROM::bankSize)) {
      // If this space comes exactly before an existing one,
      // modify the existing space with the new amount
      if (it->address() == address + length) {
        it->setAddress(address);
        it->setLength(it->length() + length);
        return;
      }
      // If this space comes exactly after an existing one,
      // modify the existing space with the new amount
      // as long as this would not cross a bank boundary
      else if ((address % LoadedROM::bankSize)
               && (it->address() + it->length() == address)) {
        it->setLength(it->length() + length);
        return;
      }
    }
  }
                 
  // No existing space found: add new entry
  freeSpaceList_.push_back(FreeSpaceListPair(address, length));
}
FreeSpaceList::iterator FreeSpaceListing::getFreeSpace(int length) {
  FreeSpaceList::iterator it = freeSpaceList_.begin();
  
  // Iterate over free space list until end is reached
  // or a sufficiently large space is found
  while ((it != freeSpaceList_.end())
         && (it->length() < length)) {
    it++;
  }
  
  // Return result, or freeSpaceList_.end() on failure
  return it;
}
void FreeSpaceListing::claimSpace(
                FreeSpaceList::iterator& position,
                int length) {
  // Throw if trying to claim more space than exists
  if (length > position->length()) {
    throw TGenericException(TALES_SRCANDLINE,
                           "FreeSpaceListing::claimSpace("
                           "FreeSpaceList::iterator,int",
                           std::string("Tried to claim ")
                           + StringConversion::toString(length)
                           + std::string(" bytes at position ")
                           + StringConversion::toString(position->address())
                           + std::string(" when only ")
                           + StringConversion::toString(position->length())
                           + std::string(" were available"));
  }
  
  // Increase address
  position->setAddress(position->address() + length);
  
  // Decrease length
  position->setLength(position->length() - length);
  
  // If all space at this address is claimed, remove it from the list
  if (position->length() == 0) {
    freeSpaceList_.erase(position);
  }
}
FreeSpaceList::iterator FreeSpaceListing::getFreeSpace(
                                     int length,
                                     int low,
                                     int high) {
  FreeSpaceList::iterator it = freeSpaceList_.begin();
  
  while ((it != freeSpaceList_.end())) {
    if (it->address() >= high) {
      break;
    }
    
    if ((it->address() >= low)
        && (it->address() < high)
        && (it->length() >= length)) {
      return it;
    }
  
    it++;
  }
  
  return freeSpaceList_.end();
}
void MetatileStructureSet::exportToROM(WritableROM& rom) {
  // TODO: support for adding metatile definitions
  // search freespace &c
  
  FreeSpaceList::iterator spaceIt = rom.freeSpace().getFreeSpace(
    exportSize());
    
  if (spaceIt == rom.freeSpace().freeSpaceList().end()) {
    throw NotEnoughSpaceException(TALES_SRCANDLINE,
                                  "MetatileStructureSet::exportToROM("
                                  "WritableROM&)",
                                  exportSize());
  }
  
  int writeAddress = spaceIt->address();
  address_ = spaceIt->address();
  
  rom.freeSpace().claimSpace(spaceIt, exportSize());
  
//  std::cout << "addr: " << writeAddress << std::endl;
  
  // Starting address of structure definitions
  int contentStartAddress = writeAddress
        + (index_.size() * ByteSizes::uint16Size);

  // Write the index
  for (MetatileIndexToStructureMap::iterator it = index_.begin();
       it != index_.end();
       it++) {
    int contentIndex = it->second;
    
    // Compute the address of the content
    Taddress contentAddress = (contentStartAddress 
                  + (contentIndex * MetatileStructure::dataSize));
    
    // Convert to banked address
    Taddress contentBankedAddress = LoadedROM
          ::directToBankedAddress(contentAddress);
    
    // Write to index
    Tbyte buffer[ByteSizes::uint16Size];
    ByteConversion::toBytes(contentBankedAddress,
                            buffer,
                            ByteSizes::uint16Size,
                            EndiannessTypes::little,
                            SignednessTypes::nosign);
    rom.directWrite(writeAddress + (it->first * ByteSizes::uint16Size),
                    buffer,
                    ByteSizes::uint16Size);
  }
  
  // Write content
  int metatileNum = 0;
  for (MetatileStructureCollection::iterator it = primaryStorage_.begin();
       it != primaryStorage_.end();
       it++) {
    // Write metatile to buffer
    Tbyte buffer[MetatileStructure::dataSize];
    it->writeToData(buffer);
    // Write buffer to table
    rom.directWrite(contentStartAddress
                      + (metatileNum * MetatileStructure::dataSize),
                    buffer,
                    MetatileStructure::dataSize);
    ++metatileNum;
  }
  
//  std::cout << "metatiles: " << metatileNum << std::endl;
//  std::cout << std::endl;
}
Taddress EditableLevelObjectEntryGroups::moveToNewBank(WritableROM& rom) {

//  std::cout << "Preparing to move to new bank" << std::endl;
  
  // More for convenience than anything else, we grab a whole bank of data
  // for use by this table (and a few bits of associated code).
  // Note that this basically requires expanding the ROM to 1 MB
  FreeSpaceList::iterator spaceIt
      = rom.freeSpace().getFreeSpace(LoadedROM::bankSize);
  
  // Throw if a full bank isn't available
  if (spaceIt == rom.freeSpace().freeSpaceList().end()) {
    throw NotEnoughSpaceException(TALES_SRCANDLINE,
                                  "EditableLevelObjectEntryGroups::"
                                  "exportToROM(WritableROM&)",
                                  LoadedROM::bankSize);
  }
  
//  std::cout << spaceIt->address() << " " << spaceIt->length() << std::endl;
                             
  Taddress newBaseAddress = spaceIt->address();
  
  // Claim the bank
  rom.freeSpace().claimSpace(spaceIt,
                             LoadedROM::bankSize);
  
//  std::cout << "New base address: " << newBaseAddress << std::endl;
  
  // Copy code segments C1 and C3 to new bank.
  // C1 and C3 both contain code that references the object table,
  // so they must (to avoid more complicated hacking) be in the same bank.
  // After rearranging the code, the new bank will contain C1, C3, and
  // the object table, in that order, starting from the beginning of the
  // bank
  
  // Copy C1 to start of bank
  Taddress newC1Address = newBaseAddress;
  std::memcpy(rom.directWrite(newC1Address),
              codeSegmentC1,
              lengthOfC1Segment);
              
  // Copy C3 to directly after C1
  Taddress newC3Address = newC1Address
                            + lengthOfC1Segment;
  std::memcpy(rom.directWrite(newC3Address),
              codeSegmentC3,
              lengthOfC3Segment);
  
  // Set new export address for table (directly after C3)
  int exportAddress = newBaseAddress
                    + lengthOfC1Segment
                    + lengthOfC3Segment;
                    
//  std::cout << "New C1 address: " << newC1Address << std::endl;
//  std::cout << "New C3 address: " << newC3Address << std::endl;
//  std::cout << "Export address: " << exportAddress << std::endl;
  
  // Update all the code that references the old locations of C1 and C3
  
  // Get the banked address for the new direct address of C1
  int newC1BankNum = LoadedROM::directToBankNum(newC1Address);
  int newC1BankedAddress = LoadedROM::directToBankedAddress(newC1Address);
  
  // Get the banked address for the new direct address of C3
  int newC3BankNum = LoadedROM::directToBankNum(newC3Address);
  int newC3BankedAddress = LoadedROM::directToBankedAddress(newC3Address);
  
  // Update bank number in code that calls C1
  ByteConversion::toBytes(newC1BankNum,
                          rom.directWrite(callReferenceToC1Bank),
                          ByteSizes::uint8Size,
                          EndiannessTypes::little,
                          SignednessTypes::nosign);
  
  // C1's banked address is the same (0000), so we don't need to update it
  // ... but do anyway for consistency
  ByteConversion::toBytes(newC1BankedAddress,
                          rom.directWrite(callReferenceToC1Address),
                          ByteSizes::uint16Size,
                          EndiannessTypes::little,
                          SignednessTypes::nosign);
  
  // Update banked address in code that calls C3
  ByteConversion::toBytes(newC3BankNum,
                          rom.directWrite(callReferenceToC3Bank),
                          ByteSizes::uint8Size,
                          EndiannessTypes::little,
                          SignednessTypes::nosign);
  
  // Update bank number in code that calls C3.
  ByteConversion::toBytes(newC3BankedAddress,
                          rom.directWrite(callReferenceToC3Address),
                          ByteSizes::uint16Size,
                          EndiannessTypes::little,
                          SignednessTypes::nosign);
  
  // C3 contains a hardcoded JP instruction into itself. Since we moved
  // C3, we have to update the JP to correspond to the new location.
  
  // Get new address of the JP instruction's address parameter
  Taddress addressOfNewJpParameter = newC3Address
                                      + offsetOfJpInC3;
  
  // Get new address of the JP instruction's jump point base
  Taddress addressOfNewJpBase = newC3Address
                                      + baseOffsetOfJpInC3;
  
  // Compute the new target address
  Taddress newJpTargetAddress = addressOfNewJpBase
                                  - absoluteLengthOfJpInC3;
                                  
//  std::cout << "New JP target: " << newJpTargetAddress << std::endl;
                                  
  // Convert to banked form
  Taddress newJpTargetBankedAddress
      = LoadedROM::directToBankedAddress(newJpTargetAddress);
  
  // Write the new target address to the JP parameter
  ByteConversion::toBytes(newJpTargetBankedAddress,
                          rom.directWrite(addressOfNewJpParameter),
                          ByteSizes::uint16Size,
                          EndiannessTypes::little,
                          SignednessTypes::nosign);
  
  // Get the banked address for the new direct address of the object table
//    int newTableBankNum
//        = LoadedROM::directToBankNum(exportAddress);
  int newTableBankedAddress
      = LoadedROM::directToBankedAddress(exportAddress);
  
  // Update C1 to refer to the new location of the object table
  ByteConversion::toBytes(newTableBankedAddress,
                          rom.directWrite(newC1Address
                                          + offsetOfTableReferenceInC1),
                          ByteSizes::uint16Size,
                          EndiannessTypes::little,
                          SignednessTypes::nosign);
  
  // Update C3 to refer to the new location of the object table
  ByteConversion::toBytes(newTableBankedAddress,
                          rom.directWrite(newC3Address
                                          + offsetOfTableReferenceInC3),
                          ByteSizes::uint16Size,
                          EndiannessTypes::little,
                          SignednessTypes::nosign);
  
  // Update table information
//    initialTableAddress_ = exportAddress;

//  initialTableContentSize_ = spaceIt->length()
//                              - exportAddress
//                              - tableHeaderSize_;

//  initialTableContentSize_ = exportAddress - spaceIt->address()
//                              - tableHeaderSize_;

//  initialTableContentSize_ = LoadedROM::bankSize;
                              
//  std::cout << initialTableContentSize_ << std::endl;
  
  // Mark that we've moved to a new bank
  movedToNewBank_ = true;
  
  return exportAddress;
}
void TailsAdvBank0Hacks::addUseAllInventoryHack(
                   WritableROM& rom) {
  // Find free space
  FreeSpaceList::iterator spaceIt
    = rom.freeSpace().getFreeSpace(allInventoryHackCodeDataSize);
  
  // Throw if not enough space
  if (spaceIt == rom.freeSpace().freeSpaceList().end()) {
    throw NotEnoughSpaceException(TALES_SRCANDLINE,
                                  "TailsAdvBank0Hacks::addUseAllInventoryHack("
                                  "WritableROM&)",
                                  allInventoryHackCodeDataSize);
  }
  
  Taddress writeAddress = spaceIt->address();
  
  rom.freeSpace().claimSpace(spaceIt,
                             allInventoryHackCodeDataSize);
                             
//  std::cout << writeAddress << std::endl;
  
  // Write main code and data
  rom.directWrite(writeAddress,
                  allInventoryHackMainData,
                  allInventoryHackMainLength);
  
  // Fill in addresses of data tables
  
  ByteConversion::toBytes(LoadedROM::directToBankedAddress(writeAddress
                            + allInventoryHackMainTable1Offset),
                          rom.directWrite(writeAddress
                            + allInventoryHackMainTable1ReferenceOffset),
                          ByteSizes::uint16Size,
                          EndiannessTypes::little,
                          SignednessTypes::nosign);
  
  ByteConversion::toBytes(LoadedROM::directToBankedAddress(writeAddress
                            + allInventoryHackMainTable2Offset),
                          rom.directWrite(writeAddress
                            + allInventoryHackMainTable2ReferenceOffset),
                          ByteSizes::uint16Size,
                          EndiannessTypes::little,
                          SignednessTypes::nosign);
  
  // Add trigger 1
  
  rom.directWrite(allInventoryHackTrigger1Address,
                  allInventoryHackTrigger1Data,
                  allInventoryHackTrigger1Length);
                  
  // Fill in bank
  ByteConversion::toBytes(LoadedROM::directToBankNum(writeAddress
                            + allInventoryHackMainCodeStartOffset),
                          rom.directWrite(
                            allInventoryHackTrigger1Address
                            + allInventoryHackTrigger1BankReferenceOffset),
                          ByteSizes::uint8Size,
                          EndiannessTypes::little,
                          SignednessTypes::nosign);
  
  
  // Fill in address
  ByteConversion::toBytes(LoadedROM::directToBankedAddress(writeAddress
                            + allInventoryHackMainCodeStartOffset),
                          rom.directWrite(
                            allInventoryHackTrigger1Address
                            + allInventoryHackTrigger1AddressReferenceOffset),
                          ByteSizes::uint16Size,
                          EndiannessTypes::little,
                          SignednessTypes::nosign);
  
  // Add trigger 2
  
  rom.directWrite(allInventoryHackTrigger2Address,
                  allInventoryHackTrigger2Data,
                  allInventoryHackTrigger2Length);
                  
  // Fill in bank
  ByteConversion::toBytes(LoadedROM::directToBankNum(writeAddress
                            + allInventoryHackMainCodeStartOffset),
                          rom.directWrite(
                            allInventoryHackTrigger2Address
                            + allInventoryHackTrigger2BankReferenceOffset),
                          ByteSizes::uint8Size,
                          EndiannessTypes::little,
                          SignednessTypes::nosign);
  
  // Fill in address
  ByteConversion::toBytes(LoadedROM::directToBankedAddress(writeAddress
                            + allInventoryHackMainCodeStartOffset),
                          rom.directWrite(
                            allInventoryHackTrigger2Address
                            + allInventoryHackTrigger2AddressReferenceOffset),
                          ByteSizes::uint16Size,
                          EndiannessTypes::little,
                          SignednessTypes::nosign);
  
  // Add initializer 1
  rom.directWrite(allInventoryHackInitializer1Address,
                  allInventoryHackInitializer1Data,
                  allInventoryHackInitializer1Length);
  
  // Add initializer 2
  rom.directWrite(allInventoryHackInitializer2Address,
                  allInventoryHackInitializer2Data,
                  allInventoryHackInitializer2Length);
  
  // Add initializer 3
  rom.directWrite(allInventoryHackInitializer3Address,
                  allInventoryHackInitializer3Data,
                  allInventoryHackInitializer3Length);
  
  // Add initializer 4
  rom.directWrite(allInventoryHackInitializer4Address,
                  allInventoryHackInitializer4Data,
                  allInventoryHackInitializer4Length);
  
}