#include "cardslot.h"

#include <cstring>

CardSlot::CardSlot(lib::Flash& flash, uint16_t card_index) :
  card_index(card_index)
{
  load_data(flash);
}

uint16_t CardSlot::get_image_start_page_index() const
{
  return card_index * TOTAL_PAGE_COUNT + 1;
}

void CardSlot::set_name(const uint8_t* name, uint8_t name_length)
{
  if (name_length > 32)
  {
    name_length = 32;
  }

  memcpy(this->name, name, name_length);
  status.name_length = name_length;
}

void CardSlot::set_cmc(uint8_t cmc) { status.cmc = cmc > 20 ? 20 : cmc; }

void CardSlot::set_colors(uint8_t colors) { status.colors = colors & 0b11111; }

void CardSlot::mark_unused(lib::Flash& flash)
{
  flash.read_page(card_index * TOTAL_PAGE_COUNT);
  flash.page()[0] &= 0xFE;
  flash.write_page(card_index * TOTAL_PAGE_COUNT);
}

void CardSlot::save_data(lib::Flash& flash)
{
  // if we're saving data, the slot is in use
  status.in_use = true;

  // and the erase count should be updated
  status.times_erased += 1;

  uint32_t raw_status = (status.in_use << 0) | (status.name_length << 1)
                        | (status.cmc << 6) | (status.colors << 11);
  auto& page = flash.page();

  page[0] = (raw_status >> 0) & 0xFF;
  page[1] = (raw_status >> 8) & 0xFF;
  page[2] = (raw_status >> 16) & 0xFF;
  page[3] = (raw_status >> 24) & 0xFF;

  page[4] = (status.times_erased >> 0) & 0xFF;
  page[5] = (status.times_erased >> 8) & 0xFF;
  page[6] = (status.times_erased >> 16) & 0xFF;
  page[7] = (status.times_erased >> 24) & 0xFF;

  memcpy(page + 8, name, status.name_length);

  flash.write_page(card_index * TOTAL_PAGE_COUNT);
}

void CardSlot::erase(lib::Flash& flash)
{
  // 16 pages per sector, one command erases them all
  for (int i = 0; i < TOTAL_PAGE_COUNT; i += 16)
  {
    flash.erase_sector(card_index * TOTAL_PAGE_COUNT + i);
  }
}

bool CardSlot::is_used() const { return status.in_use; }

uint32_t CardSlot::times_erased() const { return status.times_erased; }

uint8_t CardSlot::get_colors() const { return status.colors; }
uint8_t CardSlot::get_cmc() const { return status.cmc; }

const uint8_t* CardSlot::get_name() const { return name; }
uint8_t CardSlot::get_name_length() const { return status.name_length; }

bool CardSlot::operator<=(const CardSlot& other) const
{
  return status.times_erased <= other.status.times_erased;
}

bool CardSlot::operator>=(const CardSlot& other) const
{
  return status.times_erased >= other.status.times_erased;
}

void CardSlot::each_pixel(lib::Flash& flash, void (*fn)(void*, uint8_t),
                          void* userdata)
{
  for (int page_index = 0; page_index < IMAGE_PAGE_COUNT; page_index++)
  {
    flash.read_page(get_image_start_page_index() + page_index);
    const auto& page = flash.page();

    for (uint8_t value : page)
    {
      fn(userdata, value);
    }
  }
}

void CardSlot::load_data(lib::Flash& flash)
{
  flash.read_page(card_index * TOTAL_PAGE_COUNT);
  const auto& page = flash.page();

  const uint32_t raw_status
    = page[0] | (page[1] << 8) | (page[2] << 16) | (page[3] << 24);

  status.in_use = (raw_status & 0x01) >> 0;
  status.name_length = (raw_status & 0x3E) >> 1;
  status.cmc = (raw_status & 0x7C0) >> 6;
  status.colors = (raw_status & 0xF800) >> 11;

  status.times_erased
    = page[4] | (page[5] << 8) | (page[6] << 16) | (page[7] << 24);

  memcpy(name, page + 8, status.name_length);
}

int32_t CardSlot::get_unused(lib::Flash& flash)
{
  uint32_t min_unused = UINT32_MAX;
  int min_unused_index = -1;

  for (int i = 0; i < MAX_CARDS; i++)
  {
    CardSlot c(flash, i);
    if (!c.is_used() && c.times_erased() <= min_unused)
    {
      min_unused = c.times_erased();
      min_unused_index = i;
    }
  }

  return min_unused_index;
}
