#include "BlockAllocationTable.hpp"

#include <algorithm>
#include <vector>

#include "../internal.hpp"
#include "Util.hpp"

namespace aurora::card {
void BlockAllocationTable::swapEndian() {
  m_checksum = bswap(m_checksum);
  m_checksumInv = bswap(m_checksumInv);
  m_updateCounter = bswap(m_updateCounter);
  m_freeBlocks = bswap(m_freeBlocks);
  m_lastAllocated = bswap(m_lastAllocated);
  std::for_each(std::begin(m_map), std::end(m_map), [](uint16_t& val) { val = bswap(val); });
}

void BlockAllocationTable::updateChecksum() {
  swapEndian();
  calculateChecksumBE(reinterpret_cast<uint16_t*>(raw.data() + 4), 0xFFE, &m_checksum, &m_checksumInv);
  swapEndian();
}

bool BlockAllocationTable::valid() const {
  uint16_t ckSum = 0;
  uint16_t ckSumInv = 0;
  const_cast<BlockAllocationTable&>(*this).swapEndian();
  calculateChecksumBE(reinterpret_cast<const uint16_t*>(raw.data() + 4), 0xFFE, &ckSum, &ckSumInv);
  bool res = ckSum == m_checksum && ckSumInv == m_checksumInv;
  const_cast<BlockAllocationTable&>(*this).swapEndian();
  return res;
}

BlockAllocationTable::BlockAllocationTable(uint32_t blockCount)
: m_checksum{0}
, m_checksumInv{0}
, m_updateCounter{0}
, m_freeBlocks{static_cast<uint16_t>(blockCount - FSTBlocks)}
, m_lastAllocated{4}
, m_map{{}} {
  updateChecksum();
}

uint16_t BlockAllocationTable::getNextBlock(uint16_t block) const {
  if ((block < FSTBlocks) || (block > (BATSize - FSTBlocks)))
    return 0xFFFF;

  return m_map[block - FSTBlocks];
}

uint16_t BlockAllocationTable::nextFreeBlock(uint16_t maxBlock, uint16_t startingBlock) const {
  if (m_freeBlocks > 0) {
    maxBlock = std::min(maxBlock, static_cast<uint16_t>(BATSize));
    for (uint16_t i = startingBlock; i < maxBlock; ++i)
      if (m_map[i - FSTBlocks] == 0)
        return i;

    for (uint16_t i = FSTBlocks; i < startingBlock; ++i)
      if (m_map[i - FSTBlocks] == 0)
        return i;
  }

  return 0xFFFF;
}

bool BlockAllocationTable::clear(uint16_t first, uint16_t count) {
  std::vector<uint16_t> blocks;
  while (first != 0xFFFF && first != 0) {
    blocks.push_back(first);
    first = getNextBlock(first);
  }
  if (first > 0) {
    size_t length = blocks.size();
    if (length != count)
      return false;

    for (size_t i = 0; i < length; ++i)
      m_map[blocks.at(i) - FSTBlocks] = 0;
    m_freeBlocks += count;
    return true;
  }
  return false;
}

uint16_t BlockAllocationTable::allocateBlocks(uint16_t count, uint16_t maxBlocks) {
  uint16_t firstBlock = nextFreeBlock(maxBlocks - FSTBlocks, m_lastAllocated + 1);
  uint16_t freeBlock = firstBlock;
  if (freeBlock != 0xFFFF) {
    uint16_t tmpCount = count;
    while ((count--) > 0 && freeBlock != 0xFFFF) {
      m_map[(freeBlock - FSTBlocks)] = 0xFFFF;
      if (count != 0) {
        m_map[(freeBlock - FSTBlocks)] = nextFreeBlock(maxBlocks - FSTBlocks, freeBlock + 1);
        freeBlock = m_map[(freeBlock - FSTBlocks)];
      }
    }
    if (freeBlock == 0xFFFF)
      return 0xFFFF;

    m_lastAllocated = freeBlock;
    m_freeBlocks -= tmpCount;
  }
  return firstBlock;
}
} // namespace aurora::card
