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

#include <scsi/scsi_device.h>
#include <linux/slab.h>

int hint_manager_init(hint_manager_t *hint_manager, hint_dev_t *hint_dev)
{
  hint_manager->hint_dev = hint_dev;
  hint_manager->buffer = kmalloc(sizeof(hint_data_t), GFP_KERNEL);
  hint_manager->buffer_size = sizeof(hint_data_t);
  if (!hint_manager->buffer) {
    hint_error("kmalloc");
    return H_KMALLOC;
  }

  return H_OK;
}

void hint_manager_fini(hint_manager_t *hint_manager)
{
  if (hint_manager->buffer) {
    kfree(hint_manager->buffer);
  }
}

void hint_manager_on_scsi_request(hint_manager_t *hint_manager,
                                  struct scsi_cmnd *sc)
{
  hint_data_t *hint_data = NULL;
  int ret;
  uint32_t lba = 0;
  uint32_t sectors_count = 0;
  uint32_t sectors_to_next_chunk;
  
  HINT_SCSI_DUMP_CDB(sc);

  if (!hint_device_is_ready(hint_manager->hint_dev))
    return;

  if (sc->cmnd[0] == READ_6 || sc->cmnd[0] == WRITE_6) {
    lba = ((u32)sc->cmnd[1] << 16) | ((u32)sc->cmnd[2] << 8) | (u32)sc->cmnd[3];
    sectors_count = (u32)sc->cmnd[4];
  }
  else if (sc->cmnd[0] == READ_10 || sc->cmnd[0] == WRITE_10){
    lba = ((u32)sc->cmnd[2] << 24) | ((u32)sc->cmnd[3] << 16) | ((u32)sc->cmnd[4] << 8) | (u32)sc->cmnd[5];
    sectors_count = (u32)sc->cmnd[8] | ((u32)sc->cmnd[7] << 8);
  }
  else {
    hint_error("invalid scsi command");
    return;
  }

  hint_log("cmd=0x%x, lba=%d, sectors_count=%d", sc->cmnd[0], lba, sectors_count);
  
  do {
    sectors_to_next_chunk = min(HINT_SECTORS_IN_DATA - (lba % HINT_SECTORS_IN_DATA),
                             sectors_count);
                             

    hint_log("lba=%d, sectors_to_next_chunk=%d", lba, sectors_to_next_chunk);
    ret = hint_storage_get_lba_hint(lba, sectors_to_next_chunk, &hint_data);

    sectors_count -= sectors_to_next_chunk;
    lba += sectors_to_next_chunk;
    
    /*check for errors and send hint*/
    if (ret == H_NOT_FOUND) {
      continue;// try finding next hint
    }
  
    if (ret != H_OK) {
      hint_error_ret(ret, "hint_storage_get");
      return;
    }

    memcpy(&hint_data->cdb, sc->cmnd, sc->cmd_len);
    hint_data->lun = sc->device->lun;

    ret = hint_device_send_fast(hint_manager->hint_dev,
                                (char*)hint_data, sizeof(*hint_data), 60*HZ);
    if (ret != H_OK) {
      hint_error_ret(ret, "hint_device_send_fast");
      return;
    }
  } while(sectors_count > 0);
}

void hint_manager_on_idle_time(hint_manager_t *hint_manager)
{
  int ret;
  hint_data_t *hint_data;
  hint_payload_t *hint_payload;
  
  if (!hint_device_is_ready(hint_manager->hint_dev))
    return;
  
  ret = hint_device_recv(hint_manager->hint_dev,
                         hint_manager->buffer,
                         hint_manager->buffer_size,
                         60*HZ);
  if (ret != H_OK) {
    goto exit;
  }
  
  if (hint_manager->buffer_size < sizeof(hint_data_t)) {
    hint_error("received invalid hint, size=%d, expected_size=%d",
               hint_manager->buffer_size,
               sizeof(hint_data_t));
  }
  else {
    hint_data = (hint_data_t*)hint_manager->buffer;
    hint_payload = (hint_payload_t*)&hint_data->hint_payload;
    hint_storage_store_lba_hint(hint_payload->start_lba, HINT_SECTORS_IN_DATA, hint_data);
  }
  
exit:
  (void)0;
}
