#pragma once

#include <algorithm>
#include <cstdint>
#include <cstring>
#include <stdexcept>
#include <vector>

namespace ipuboot {

constexpr std::size_t frameSize = 1024;
constexpr std::size_t headerSize = 16;
constexpr std::size_t trailerSize = 16;
constexpr std::size_t payloadSize = frameSize - headerSize - trailerSize;
constexpr std::uint32_t finalFrame = 8u << 24;

// Host transport layout consumed by tile_bootloader.S.
constexpr unsigned tilesPerBatch = 64;
constexpr std::size_t descriptorAreaSize = 4096;
constexpr std::size_t transportSize = 0x2842000;
constexpr std::uint8_t executeSentinel = 0xff;

inline std::size_t frameCount(std::size_t imageSize) {
  return (imageSize + payloadSize - 1) / payloadSize;
}

inline std::vector<std::uint8_t>
frameTile(unsigned physicalTile, const std::vector<std::uint8_t> &image) {
  if (image.empty())
    throw std::runtime_error("cannot frame an empty tile image");
  const std::size_t count = frameCount(image.size());
  if (count > UINT32_MAX)
    throw std::runtime_error("tile image requires too many frames");
  std::vector<std::uint8_t> result(count * frameSize, 0);
  for (std::size_t frame = 0; frame < count; ++frame) {
    std::uint8_t *destination = result.data() + frame * frameSize;
    const std::uint32_t index = frame;
    const std::uint32_t tile = physicalTile;
    const std::uint32_t flags = frame + 1 == count ? finalFrame : 0;
    std::memcpy(destination, &index, sizeof(index));
    std::memcpy(destination + 4, &tile, sizeof(tile));
    std::memcpy(destination + 8, &flags, sizeof(flags));
    const std::size_t sourceOffset = frame * payloadSize;
    const std::size_t bytes =
        std::min(payloadSize, image.size() - sourceOffset);
    std::memcpy(destination + headerSize, image.data() + sourceOffset, bytes);
    std::fill(destination + frameSize - trailerSize, destination + frameSize,
              0xff);
  }
  return result;
}

} // namespace ipuboot
