#include <devices.h>
#include <fs_types.h>
#include <libfat.h>
#include <status.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <system.h>

static void translate (size_t fs_block, size_t fs_block_count, size_t fs_block_size,
                       size_t device_sector_size, size_t* out_phys_sector,
                       size_t* out_sector_count) {
  size_t ratio = fs_block_size / device_sector_size;

  if (out_phys_sector != NULL)
    *out_phys_sector = fs_block * ratio;

  if (out_sector_count != NULL)
    *out_sector_count = fs_block_count * ratio;
}

static int fat_diskio_read (struct fatfs_ctx* ctx, uint32_t sector, uint8_t* buffer,
                            uint32_t sector_count) {
  (void)ctx;

  int ret;
  size_t sector_size;
  size_t phys_sector, phys_sector_count;

  if (sector_count == 0)
    return 0;

  if ((ret = device_do (ctx->device_name, XDRV_GET_SECTOR_SIZE, &sector_size, NULL, NULL, NULL)) <
      0)
    return 0;

  translate (sector, sector_count, FAT_SECTOR_SIZE, sector_size, &phys_sector, &phys_sector_count);

  if ((ret = device_do (ctx->device_name, XDRV_READ, &phys_sector, &phys_sector_count, buffer,
                        NULL)) < 0)
    return 0;

  return 1;
}

static int fat_diskio_write (struct fatfs_ctx* ctx, uint32_t sector, uint8_t* buffer,
                             uint32_t sector_count) {
  (void)ctx;

  int ret;
  size_t sector_size;
  size_t phys_sector, phys_sector_count;

  if (sector_count == 0)
    return 0;

  if ((ret = device_do (ctx->device_name, XDRV_GET_SECTOR_SIZE, &sector_size, NULL, NULL, NULL)) <
      0)
    return 0;

  translate (sector, sector_count, FAT_SECTOR_SIZE, sector_size, &phys_sector, &phys_sector_count);

  if ((ret = device_do (ctx->device_name, XDRV_WRITE, &phys_sector, &phys_sector_count, buffer,
                        NULL)) < 0)
    return 0;

  return 1;
}

int fat_format_drive (struct fatfs_ctx* ctx, const char* device_key, int fs_type) {
  memset (ctx, 0, sizeof (*ctx));
  memcpy (ctx->device_name, device_key, sizeof (ctx->device_name) - 1);

  size_t total_size = 0;
  device_do (device_key, XDRV_GET_SIZE, &total_size, NULL, NULL, NULL);

  if (total_size == 0)
    return -ST_NOT_FOUND;

  size_t sectors = ((total_size + FAT_SECTOR_SIZE) / FAT_SECTOR_SIZE);

  fl_init (ctx);
  ctx->_fs.disk_io.read_media = &fat_diskio_read;
  ctx->_fs.disk_io.write_media = &fat_diskio_write;

  int r = -1;

  switch (fs_type) {
  case FS_FAT16:
    r = fatfs_format_fat16 (ctx, &ctx->_fs, sectors, "mop3 fat16");
    break;
  case FS_FAT32:
    r = fatfs_format_fat32 (ctx, &ctx->_fs, sectors, "mop3 fat32");
    break;
  default:
    break;
  }

  if (r < 0)
    return -ST_FORMAT_ERROR;

  return ST_OK;
}
