#include <stdint.h>
#include <stddef.h>
#include "storedev.h"
#include "spinlock/spinlock.h"
#include "kprintf.h"
#include "errors.h"
#include "dlmalloc/malloc.h"
#include "ramsd.h"
#include "atasd.h"
#include "util/util.h"
#include "dev/dev.h"
#include "hshtb.h"
#include "hal/hal.h"
#include "randcrypto/uniqid.h"
#include "sysdefs/dev.h"

StoreDevList STOREDEV_LIST;

void storedev_init(void) {
  spinlock_init(&STOREDEV_LIST.spinlock);
  STOREDEV_LIST.head = NULL;

  LOG("storedev", "init\n");

  ata_probe();
}

int32_t storedev_dev_read(Dev *dev, uint8_t *buffer, size_t len, uint64_t pid) {
  (void)pid;

  StoreDev *sd = (StoreDev *)dev->extra;

  if (len != sizeof(StoreDevRead)) {
    return E_INVALIDARGUMENT;
  }

  StoreDevRead *cfg = (StoreDevRead *)buffer;

  return sd->read(sd, cfg->buffer, cfg->sector, cfg->off, cfg->size);
}

int32_t storedev_dev_write(Dev *dev, uint8_t *buffer, size_t len, uint64_t pid) {
  (void)pid;

  StoreDev *sd = (StoreDev *)dev->extra;

  if (len != sizeof(StoreDevRead)) {
    return E_INVALIDARGUMENT;
  }

  StoreDevWrite *cfg = (StoreDevWrite *)buffer;

  return sd->write(sd, cfg->buffer, cfg->sector, cfg->off, cfg->size);
}

int32_t storedev_dev_seccount(Dev *dev, uint8_t *buffer, size_t len, uint64_t pid) {
  (void)pid; (void)buffer; (void)len;

  StoreDev *sd = (StoreDev *)dev->extra;

  return (int32_t)(sd->capacity(sd) / sd->sectorsize);
}

int32_t storedev_dev_sectsize(Dev *dev, uint8_t *buffer, size_t len, uint64_t pid) {
  (void)pid; (void)buffer; (void)len;

  StoreDev *sd = (StoreDev *)dev->extra;

  return (int32_t)sd->sectorsize;
}

void storedev_register_dev_entry(StoreDev *sd, char *name) {
  spinlock_acquire(&DEVTABLE.spinlock);
  Dev *dev = NULL;
  HSHTB_ALLOC(DEVTABLE.devs, ident, name, dev);
  spinlock_release(&DEVTABLE.spinlock);

  if (dev == NULL) {
    ERR("storedev", "could not register dev entry for storedev");
    return;
  }

  spinlock_init(&dev->spinlock);
  dev->extra = (void *)sd;

  dev->fns[DEV_STOREDEV_READ]     = &storedev_dev_read;
  dev->fns[DEV_STOREDEV_WRITE]    = &storedev_dev_write;
  dev->fns[DEV_STOREDEV_SECCOUNT] = &storedev_dev_seccount;
  dev->fns[DEV_STOREDEV_SECTSIZE] = &storedev_dev_sectsize;
}

void storedev_unregister_dev_entry(Dev *dev) {
  spinlock_acquire(&DEVTABLE.spinlock);
  HSHTB_DELETE(DEVTABLE.devs, ident, dev->ident);
  spinlock_release(&DEVTABLE.spinlock);
}

StoreDev *storedev_create(int32_t sdtype, char *name, void *extra) {
  StoreDev *sd = dlmalloc(sizeof(*sd));
  if (sd == NULL) {
    return NULL;
  }

  spinlock_acquire(&STOREDEV_LIST.spinlock);

  sd->_magic = STOREDEV_MAGIC;
  spinlock_init(&sd->spinlock);

  switch (sdtype) {
    case STOREDEV_RAMSD: {
      sd->sdtype     = STOREDEV_RAMSD;
      sd->init       = &ramsd_init;
      sd->cleanup    = &ramsd_cleanup;
      sd->read       = &ramsd_read;
      sd->write      = &ramsd_write;
      sd->capacity   = &ramsd_capacity;
      sd->sectorsize = STOREDEV_RAMSD_SECTORSIZE;
    } break;
    case STOREDEV_ATASD: {
      sd->sdtype     = STOREDEV_ATASD;
      sd->init       = &atasd_init;
      sd->cleanup    = &atasd_cleanup;
      sd->read       = &atasd_read;
      sd->write      = &atasd_write;
      sd->capacity   = &atasd_capacity;
      sd->sectorsize = STOREDEV_ATASD_SECTORSIZE;
    } break;
    case STOREDEV_PARTSD: {
      sd->sdtype     = STOREDEV_PARTSD;
      sd->init       = &partsd_init;
      sd->cleanup    = &partsd_cleanup;
      sd->read       = &partsd_read;
      sd->write      = &partsd_write;
      sd->capacity   = &partsd_capacity;
      sd->sectorsize = 0; // lazy
    } break;
    default:
      dlfree(sd);
      spinlock_release(&STOREDEV_LIST.spinlock);
      return NULL;
  }
  int32_t err = sd->init(sd, extra);
  if (err != E_OK) {
    dlfree(sd);
    spinlock_release(&STOREDEV_LIST.spinlock);
    return NULL;
  }
  LL_APPEND(STOREDEV_LIST.head, sd);
  spinlock_release(&STOREDEV_LIST.spinlock);

  storedev_register_dev_entry(sd, name);

  return sd;
}

int32_t storedev_delete(StoreDev *sd) {
  spinlock_acquire(&STOREDEV_LIST.spinlock);
  LL_REMOVE(STOREDEV_LIST.head, sd);
  int32_t err = sd->cleanup(sd);
  if (err < 0) {
    spinlock_release(&STOREDEV_LIST.spinlock);
    return err;
  }
  spinlock_release(&STOREDEV_LIST.spinlock);
  return E_OK;
}
