
#include <linux/device.h>
#include <linux/slab.h>

#include "hint_storage.h"
#include "hint_log.h"
#include "hint_error.h"


hint_storage_info_t hint_storage_info;

static int hint_storage_is_matching_hint(uint32_t lba, uint32_t sectors_count,
                                         lba_hint_t *lba_hint)
{
  if (lba >= lba_hint->lba &&
      lba < (lba_hint->lba + lba_hint->sectors_count)) {
    return 1;
  }
  else {
    return 0;
  }
}
                                         

int hint_storage_init()
{
  memset(&hint_storage_info, 0, sizeof(hint_storage_info));
  INIT_LIST_HEAD(&hint_storage_info.lbas_list);
  spin_lock_init(&hint_storage_info.lock);
  return H_OK;
}

void hint_storage_fini()
{
  lba_hint_t *lba_hint;
  unsigned long flags;

  spin_lock_irqsave(&hint_storage_info.lock, flags);
  while (!list_empty(&hint_storage_info.lbas_list)) {
		lba_hint = list_first_entry(&hint_storage_info.lbas_list,
                                lba_hint_t,
                                list);
		list_del(&lba_hint->list);
		kfree(lba_hint);
	}
  spin_unlock_irqrestore(&hint_storage_info.lock, flags);
}

int hint_storage_store_lba_hint(uint32_t lba, uint32_t sectors_count,
                                hint_data_t *hint)
{
  lba_hint_t *lba_hint;
  struct list_head *node;
  unsigned long flags;
  
  /*see if hint is already in list*/
  spin_lock_irqsave(&hint_storage_info.lock, flags);
  list_for_each(node, &hint_storage_info.lbas_list) {
    lba_hint = list_entry(node, lba_hint_t, list);
    /*verify lba covered by hint*/
    if (hint_storage_is_matching_hint(lba, sectors_count, lba_hint)) {
      hint_log("overwrite existing hint (lba=%d, sectors_count=%d)",
               lba_hint->lba, lba_hint->sectors_count);
      memcpy(&lba_hint->hint_data, hint, sizeof(*hint));
      lba_hint->lba = lba;
      lba_hint->sectors_count = sectors_count;
      spin_unlock_irqrestore(&hint_storage_info.lock, flags);
      return H_OK;
    }
  }
  spin_unlock_irqrestore(&hint_storage_info.lock, flags);

  hint_log("add new hint (lba=%d, sectors_count=%d)", lba, sectors_count);
  
  /* fresh hint. add to list*/
  lba_hint = kmalloc(sizeof(lba_hint_t), GFP_KERNEL);
  if (lba_hint == NULL) {
    return H_KMALLOC;
  }

  lba_hint->lba = lba;
  lba_hint->sectors_count = sectors_count;
  memcpy(&lba_hint->hint_data, hint, sizeof(*hint));

  spin_lock_irqsave(&hint_storage_info.lock, flags);
  list_add_tail(&lba_hint->list, &hint_storage_info.lbas_list);
  hint_storage_info.lbas_count++;
  spin_unlock_irqrestore(&hint_storage_info.lock, flags);

  return H_OK;
  
}

int hint_storage_get_lba_hint(uint32_t lba, uint32_t sectors_count,
                              hint_data_t **hint)
{
  lba_hint_t *lba_hint;
  struct list_head *node;
  unsigned long flags;
  
  hint_log("lba=%d, sectors_count=%d", lba, sectors_count);
  spin_lock_irqsave(&hint_storage_info.lock, flags);
  list_for_each(node, &hint_storage_info.lbas_list) {
    lba_hint = list_entry(node, lba_hint_t, list);
    /*verify lba covered by hint*/
    if (hint_storage_is_matching_hint(lba, sectors_count, lba_hint)) {
      hint_log("found, lba=%d, sectors_count=%d, version=%d",
               lba_hint->lba, lba_hint->sectors_count,
               ((hint_payload_t*)&(lba_hint->hint_data.hint_payload))->version);
      *hint = &lba_hint->hint_data;
      spin_unlock_irqrestore(&hint_storage_info.lock, flags);
      return H_OK;
    }
  }

  spin_unlock_irqrestore(&hint_storage_info.lock, flags);
  hint_log("not_found, lba=%d, sectors_count=%d", lba, sectors_count);
  return H_NOT_FOUND;
}
