#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#include <system.h>
#include <nand.h>
#include <nandbus.h>
#include <ngssd.h>
#include <defs.h>
#include <req.h>
#include <string.h>

extern int maintid;
extern int tids[NAND_CHIP_COUNT];
static char mode[10] = "a+";

extern uint32_t program[NAND_PAGE_COUNT];
extern ngssd_t ngssd;
static FILE* open_file(char* fname){
	return fopen(fname, mode);
}

static void close_file(FILE* file){
	fclose(file);
}

static char filename[10] = "nandbus.c";

//#define LOG(fmt, ...) printf("TRACE %s:%d. %s() - ", __FILE__, __LINE__, __func__);printf(fmt, ## __VA_ARGS__);

extern req_t* nandbusDeferred;

error_t nandbusCmd(req_t *req, uint8_t* buf,
									 uint32_t bus, uint32_t chip,
									 uint32_t cmd,  uint32_t page, uint32_t offset, uint32_t len,
									 uint8_t* spare_buf, bool_t with_spare) {
	LOG("starting\n");
//	LOG("nandbusCmd() - buf %x\n", buf);
  nandbus_specific_t* specific = (nandbus_specific_t*) req->specific;

  specific->cmd    = cmd;
  specific->bus    = bus;
  specific->chip   = chip;
  specific->page   = page;
  specific->offset = offset;
  specific->len    = len;
  specific->buf    = buf;
  if(with_spare){
  	specific->spare  = spare_buf;
  }
  else{
  	specific->spare  = 0;
  }
//  LOG("nandbusCmd() - starting\n", pthread_self());
//  fflush(stdout);
  pthread_mutex_lock(&(nandbus[bus].chip[chip].mutex));

//  LOG("nandbusCmd() - inc counter\n", pthread_self());
  nandbus[bus].chip[chip].counter+= 1; // counter update
//  LOG("nandbusCmd() - specific->buf %x\n", specific->buf);

  LOG("%s page=%u bus=%u chip=%u (buf[0] %u). queue counter=%u specific->spare %x\n",
  		(specific->cmd ==NAND_CMD_WRITE)?"WRITE":(specific->cmd ==NAND_CMD_READ)?"READ":"ERASE", page, bus, chip,
  		(specific->buf != 0)?((uint32_t*)(specific->buf))[0]:0xffff, nandbus[bus].chip[chip].counter, specific->spare);
  assert(bus<NAND_BUSES);assert(chip<NAND_CHIPS_PER_BUS);assert(page<NAND_PAGE_COUNT);
  /* make req head of queue*/

  //  LOG("nandbusCmd() - reqEnqueue\n", pthread_self());
  reqEnqueue(req, &(nandbus[bus].chip[chip].queued));
//  LOG("nandbusCmd() - tid %d mutex unlock, then signal\n\n\n\n", pthread_self());
//    fflush(stdout);
  LOG("signal chip bus %u chip %u\n\n\n\n", bus, chip);
  pthread_mutex_unlock(&(nandbus[bus].chip[chip].mutex));
  pthread_cond_signal(&(nandbus[bus].chip[chip].cond));
}

void* nandbusThread(void* argument) {
  long int page_count = NAND_PAGE_COUNT;
  int* bus_chip = (int*) argument;
  int bus  = bus_chip[0];
  int chip = bus_chip[1];
  uint8_t* buf;
  int i;
  int tid = (int)pthread_self();
  tids[bus*NAND_CHIPS_PER_BUS+chip] = tid;
  LOG("Starting nandbus thread for bus=%u chip=%u. tids[%d]=%d\n",bus,chip, bus*NAND_CHIPS_PER_BUS+chip, tids[bus*NAND_CHIPS_PER_BUS+chip]);
  while (1) {
    pthread_mutex_lock(&(nandbus[bus].chip[chip].mutex));

//    LOG("nandbusThread() - tid %d . after mutex lock. nandbus[bus].chip[chip].queued %x\n", pthread_self(), nandbus[bus].chip[chip].queued);fflush(stdout);
    /* wait for signal if:
     * 1. queue empty
     * 2. current (=R/B) is not 0*/
    while (nandbus[bus].chip[chip].queued == 0
    		   ||
    		   nandbus[bus].chip[chip].current != 0){
    	LOG("nandbusThread() - tid %d. wait on signal\n", pthread_self());fflush(stdout);
      pthread_cond_wait(&(nandbus[bus].chip[chip].cond),
                        &(nandbus[bus].chip[chip].mutex));
    }
    nandbus[bus].chip[chip].rb = RB_BUSY; /* BUSY*/
    /* remove tail of queue*/
//    LOG("start handling request bus %u chip %u\n", bus, chip);
    nandbus[bus].chip[chip].current = reqDequeue(&(nandbus[bus].chip[chip].queued));
//    {
//    	nandbus_specific_t* specific;
//    	req_t *p = nandbus[bus].chip[chip].queued;
//    	LOG("nand thread queue tid %u bus=%u chip=%u contents:\n", pthread_self(),bus,chip);
//    	while(p){
//    		specific = (nandbus_specific_t*) p->specific;
//    		LOG("iterare queue - %s %u\n", (specific->cmd ==NAND_CMD_WRITE)?"WRITE":((specific->cmd ==NAND_CMD_READ)?"READ":"ERASE"),specific->page);
//    		p = p->next;
//    	}
//    	LOG("queue contents done\n");
//    }
    nandbus[bus].chip[chip].counter -= 1; // counter update

    /* lock bus to transfer relevant data (read page, status,...)
		 * from the MCU*/
    pthread_mutex_lock(&(nandbus[bus].mutex));
		while(nandbus[bus].current_dma_transfer){
			pthread_cond_wait(&(nandbus[bus].cond),
												&(nandbus[bus].mutex));
		}

		/* do the transfer...in the future this may require some timing issues.
		 * baiscally a no-op now
		 * then unlock bus mutex*/
		nandbus[bus].current_dma_transfer = 1;
		nandbus[bus].current_dma_transfer = 0;
		pthread_mutex_unlock(&(nandbus[bus].mutex));
		pthread_cond_signal(&(nandbus[bus].cond));

//    LOG("bus=%u chip=%u, queue counter=%u\n", bus, chip, nandbus[bus].chip[chip].counter);
    pthread_mutex_unlock(&(nandbus[bus].chip[chip].mutex));

    /* handle request*/
//    sleep(1);
    req_t* req = nandbus[bus].chip[chip].current;
//    LOG("req %x\n", req);
    /* do what the current request tells you to do.*/
    nandbus_specific_t* specific = (nandbus_specific_t*) req->specific;

    /* do we have spare area?*/
    buf = specific->buf;
//    int i;
//    for(i=0;i<NAND_PAGE_SIZE;i++){
//    	if(buf[i] != 0x63){
//    		LOG("buf[%d]=%x\n", i, buf[i]);
//    	}
//    }
//    LOG("buf %x specific->spare %x\n", buf, specific->spare);
    if(specific->spare != 0){
    	assert(specific->spare !=0);
    	buf = nandbus[bus].chip[chip].buf;
//    	LOG("buf %x specific->spare %x, specific->len %u specific->offset %u spare lba %u\n",
//    			buf, specific->spare, specific->len, specific->offset, ((uint32_t*)(&(specific->spare[NAND_SPARE_LBA_OFFSET])))[0]);

    	if(specific->cmd == NAND_CMD_WRITE){
				memcpy(buf, specific->buf, specific->len);
				memcpy(&(buf[NAND_PAGE_SIZE]), specific->spare, NAND_SPARE_SIZE);
				specific->len = NAND_TOTAL_SIZE;
    	}
//    	LOG("buf spare lba %u specific->len %u\n", ((uint32_t*)(&(nandbus[bus].chip[chip].buf[NAND_SPARE_LBA])))[0], specific->len);
    }

//    LOG("nandbusThread() - req %x req->caller_req %x\n", req, req->caller_req);
    LOG("nandbusThread() - %s from page %u (program[%u] %u)\n\n\n\n", (specific->cmd ==NAND_CMD_WRITE)?"WRITE":(specific->cmd ==NAND_CMD_READ)?"READ":"ERASE",specific->page, specific->page,program[specific->page]);
    switch (specific->cmd) {
    case NAND_CMD_READ:
//    	LOG("nandbusThread() - read specific->page %u begin\n", specific->page);
////    	LOG("read page %u begin. req %x req->caller_req %x. specific->buf %x. specific->offset %u, specific->len %u\n", specific->page, req, req->caller_req, specific->buf, specific->offset, specific->len);
    	nandReadPage(buf, specific->page, specific->offset, specific->len);
    	if(specific->spare != 0){
				memcpy(specific->buf, buf, specific->len);
				memcpy(specific->spare, &(buf[NAND_PAGE_SIZE]), NAND_SPARE_SIZE);
    	}
////    	LOG("read page %u done. req %x req->caller_req %x.\n", specific->page, req, req->caller_req);
    	break;
    case NAND_CMD_WRITE:
//    	LOG("nandbusThread() - write specific->page %u begin\n", specific->page);
    	nandProgramPage(buf, specific->page, specific->offset, specific->len);
    	break;
    case NAND_CMD_ERASE:
//    	LOG("nandbusThread() - erase eu in specific->page %u begin\n", specific->page);
    	nandErase(specific->page);
//    	LOG("write page %u done\n", specific->page);
    	break;
    default:
    	assert(0);
    }

    /* chip is ready for next request.*/
    pthread_mutex_lock(&(nandbus[bus].chip[chip].mutex));
    nandbus[bus].chip[chip].rb = RB_READY; /* READY*/
    pthread_mutex_unlock(&(nandbus[bus].chip[chip].mutex));

//    LOG("nandbusThread() - before caller_req. req %x req->caller_req %x\n", req, req->caller_req);
//    fflush(stdout);

//    {
//    	uint8_t buf[NAND_PAGE_SIZE];
//    	nandReadPage(buf, 0, 0, NAND_PAGE_SIZE);
//    	LOG("@@testing() - buf[0] %x\n", buf[0]);
//    }
//    LOG("done. call req->caller_req\n\n\n");
//    (req->caller_req->handler)(req->caller_req,0);
  }

  return 0;
}

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

void nandbusAlloc(req_t* caller_req){
	req_t* req;
	req = (req_t*) malloc(sizeof(req_t));

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

	/* allocation successful*/
	if (req != 0 && specific != 0) {
		req->handler    = nandbusHandler;
//		LOG("caller_req %x\n", caller_req);
		fflush(stdout);
		req->caller_req = caller_req;
		req->specific   = specific;
		req->next       = 0; // not in any list
		req->prev       = 0; // not in any list
		(caller_req->handler)(caller_req, req);
		return;
	}

	/* allocation failure - defer*/
	if (req != 0)      free(req);
	if (specific != 0) free(specific);

	reqEnqueue(caller_req, &(ngssd.nandbusDeferred));

	return;
}

void nandbusHandler(req_t* req, void* response) {
//	LOG("nandbusHandler() - do nothing");
//	(req->caller_req->handler)(req->caller_req,0);
  return;
}

void nandbusInit() {
  int bus,chip;

  for (bus=0; bus<NAND_BUSES; bus++) {
    pthread_mutex_init(&(nandbus[bus].mutex), NULL);
    pthread_cond_init (&(nandbus[bus].cond ), NULL);
    for (chip=0; chip<NAND_CHIPS_PER_BUS; chip++) {
      pthread_mutex_init(&(nandbus[bus].chip[chip].mutex), NULL);
      pthread_cond_init (&(nandbus[bus].chip[chip].cond ), NULL);
      uint32_t* bus_chip = (uint32_t*) malloc(2*sizeof(uint32_t));
      bus_chip[0] = bus;
      bus_chip[1] = chip;
      pthread_create(&(nandbus[bus].chip[chip].thread), NULL,
                     nandbusThread, bus_chip);

      nandbus[bus].chip[chip].queued  = 0;
      nandbus[bus].chip[chip].counter = 0;
    }
  }
}
