#include <scsi_layer.h>
#include <tm.h>
#include <chunks.h>
#include <ngssd.h>
#include <hint_data.h>
#include <hint.h>
extern ngssd_t ngssd;

/**
 * Internal scsi requests handler.
 * take regular 1-lba scsi requests, enqueue them with throttling
 * before they are dispatched to the tm.
 *
 * Relevant hint write requests are sent immediately before their
 * respective scsi requests.
 * Therefore they are enqueued and throttled by the same queue
 *
 * Hint reads should be immediate ~> are n-o-t enqueued!!!
 */

req_t* scsiDeferred = 0;

void scsiInit() {}

void scsiHandler(req_t* req, void* response) ;

#define IS_HINT(SPECIFIC)       ((SPECIFIC)->is_hint)
#define IS_HINT_READ(SPECIFIC)  (IS_HINT(SPECIFIC) && (SPECIFIC)->cdb->cmd == SCSI_READ)


req_t* scsiRequest(handler_t notifier, cdb_t* cdb, bool_t is_hint) {
  req_t* req;

//  /*insert to ngssd queue*/
//	ngssd_lock();
//	if(ngssd.shutdown){
//		return 0;
//	}

  req = (req_t*) malloc(sizeof(req_t));

  scsi_specific_t* specific;
  specific = (scsi_specific_t*) malloc(sizeof(scsi_specific_t));

  if (req == 0 || specific == 0) {
    if (req != 0)      free(req);
    if (specific != 0) free(specific);
    return 0;
  }

  req->handler    = scsiHandler;
  req->caller_req = 0;
  req->specific   = specific;
  req->next       = 0; // not in any list
  req->prev       = 0; // not in any list

  specific->cdb = cdb;
  specific->state = SCSI_INITIAL;
  specific->is_hint = is_hint;
  specific->notifier = notifier;

  /* allocate hint for read
   * and return - reads are not enqueued like regular requests*/
  if(IS_HINT_READ(specific)){
  //	ngssd_unlock();
  	return req;
  }

  /*insert to ngssd queue*/
	ngssd_lock();

  /*throttle requests (not hint writes)*/
  int n = 0;
  if(!IS_HINT(specific)){
		while(ngssd.counter >= MAX_SCSI_QUEUED){
			ngssd_unlock();
			sleep(1);//PRINT("ngssd.counter %u. sleep(1)\n", ngssd.counter);
			n++;
			ngssd_lock();
		}
  }
	ngssd.counter+= 1; // counter update
	reqEnqueue(req, &(ngssd.queued));
	ngssd_unlock();
	pthread_cond_signal(&(ngssd.cond));

  return req;
}

void scsiHandler(req_t* req, void* response) {
  scsi_specific_t* specific = (scsi_specific_t*) req->specific;

//  printf("					scsiHandler() - specific->state %u. specific->cdb->start_lba %d\n", specific->state, specific->cdb->start_lba);
  switch (specific->state) {
  case SCSI_INITIAL:
			if(!IS_HINT(specific)){
				specific->state = SCSI_TM_READY;
				tmAlloc(req);
			}
			else{
				specific->state = SCSI_CHM_READY;
				chmAlloc(req);
			}
    break;
  case SCSI_TM_READY:
    specific->state = SCSI_DONE;
//    printf("scsiHandler() - call tmRequest\n");
    tmRequest((req_t*) response,specific->cdb, IDX_EMPTY);
    break;
  case SCSI_CHM_READY:
//		specific->state = (specific->cdb->cmd == SCSI_READ)?SCSI_HINT_READ_DONE:SCSI_DONE;
  	specific->state =SCSI_DONE;
		chmRequest((req_t*) response,(specific->cdb->cmd == SCSI_READ)?CHM_READ:CHM_WRITE, specific->cdb->start_lba, IDX_EMPTY, HINT_FLAG_YES);
		break;
//  case SCSI_HINT_READ_DONE:
//  	hint_send(ngssd.lu, response);
  case SCSI_DONE:
//  	printf("scsiHandler() - SCSI_DONE\n");
  	if(!IS_HINT_READ(specific)){
  		ngssd_lock();
			ngssd.handled_counter-= 1; // counter update
			ngssd_unlock();
  	}
    free(req);
    if(IS_HINT(specific)){
    	(specific->notifier)(ngssd.lu,specific->cdb->private); /*end cmd/hint notify. on read, cdb->private contains relevant hint_data*/
    }
    else{
    	(specific->notifier)(ngssd.lu,specific->cdb); /*end cmd/hint notify. on read, cdb->private contains relevant hint_data*/
    }

    if(IS_HINT_READ(specific)){
      free(specific->cdb);
		}
    free(specific);
    break;
  default: assert(0);
  }

}
