/**
 *
 * nand simulator driver
 *
 * Aviad Tsuck
 *
 */


#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <system.h>
#include <nand.h>
//#include <test/tests.h>

extern int fds[NUM_SIM_FILES];
#ifdef PROFILING
extern int nand_total_reads;
extern int nand_total_writes;
extern int nand_erases;
extern pthread_mutex_t profiling_lock;
#endif


uint8_t buf[NAND_TOTAL_SIZE];

//#ifdef REAL_PROGRAM
extern uint32_t program[NAND_PAGE_COUNT];
//#endif

void zeroize_buf(uint8_t *buf, uint32_t size){
	while(size>0){
		buf[size-1] = 0;
		size--;
	}
}

void init_buf(uint8_t *buf){
	uint32_t i = 0;
	while(i>NAND_TOTAL_SIZE){
		buf[i] = 0xff;
		i++;
	}
}

error_t nandInit(void){
	uint32_t i, fd_idx;
	char sim_temp_name[100];

	// init erase buf
	for(i=0; i<NAND_TOTAL_SIZE;i++){
		buf[i] = 0xff;
	}

	printf("\nnandInit() - starting. NUM_SIM_FILES %u (POWER_PAGES_PER_GIGA %u)",
			                         NUM_SIM_FILES,
			                         POWER_PAGES_PER_GIGA);

	strcpy(sim_temp_name, SIM_FILE_NAME);
	sim_temp_name[strlen(SIM_FILE_NAME)]   = '0';
	sim_temp_name[strlen(SIM_FILE_NAME)+1] = '\0';

	for(fd_idx=0; fd_idx< NUM_SIM_FILES; fd_idx++){
		/* prepare sim file name, and current fp*/
//		fds[fd_idx] = NULL;
		fds[fd_idx] = 0;
		sim_temp_name[strlen(SIM_FILE_NAME)] = '0' + fd_idx;

		// open for reading just to check that it exists
		fds[fd_idx] = open(sim_temp_name, O_RDWR);
		printf("open errno %s\n", strerror(errno));

		if(fds[fd_idx] == -1){
			printf("file did not exist, create %s with %u pages\n", sim_temp_name, SIM_FILE_PAGE_COUNT);
			fds[fd_idx] = open(sim_temp_name, O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU | S_IRWXG | S_IRWXO);
			//verify no error
			if(fds[fd_idx] == -1){
				printf("couldn't open file (errno %s)\n", strerror(errno));
				fflush(stdout);
				assert(0);
			}

			// set file contents to all 0xff
			for(i=0; i< SIM_FILE_PAGE_COUNT; i++){
				if(pwrite(fds[fd_idx], buf, NAND_TOTAL_SIZE, i * NAND_TOTAL_SIZE) != NAND_TOTAL_SIZE){
					printf("couldn't erase EU's (errno %s)\n", strerror(errno));
					fflush(stdout);
					assert(0);
				}
			}
			close(fds[fd_idx]);

			// file exists, open it
			fds[fd_idx] = open(sim_temp_name, O_RDWR, S_IRWXU | S_IRWXG | S_IRWXO);
			struct stat sbuf;
			stat(sim_temp_name, &sbuf);
			printf("%s size %d\n", sim_temp_name, sbuf.st_size);
			//verify no error
			if(fds[fd_idx] == -1){
				printf("couldn't open file\n");
				fflush(stdout);
				assert(0);
			}
		}
		else{
#ifndef LOAD_STATE
			//printf("\nreopen file");
			// close the old file pointer
			close(fds[fd_idx]);

			// file exists, open it
//			printf("\nfile exists, opening %s", sim_temp_name);
			fds[fd_idx] = open(sim_temp_name, O_RDWR | O_TRUNC);

			//verify no error
			if(fds[fd_idx] == -1){
				printf("couldn't open file\n");
				fflush(stdout);
				assert(0);
			}

			/* delete file contents on re-opening */
			for(i=0; i< SIM_FILE_PAGE_COUNT; i++){
				if(pwrite(fds[fd_idx], buf, NAND_TOTAL_SIZE, i * NAND_TOTAL_SIZE) != NAND_TOTAL_SIZE){
					printf("couldn't erase EU's\n");
					fflush(stdout);
					assert(0);
				}
			}
#endif
			struct stat sbuf;
			stat(sim_temp_name, &sbuf);
			printf("%s size %d\n", sim_temp_name, sbuf.st_size);
		}
	}

	/* print fds*/
	for(fd_idx=0; fd_idx< NUM_SIM_FILES; fd_idx++){
		printf("fd[%d]=%d\n", fd_idx, fds[fd_idx]);
	}

#ifndef LOAD_STATE
	for(i=0; i< NAND_PAGE_COUNT; i++){
		program[i] = 0;
	}
#endif

	return 0;
}

static int
get_fd(uint32_t page){
	uint32_t fds_offset = page / PAGES_PER_GIGA;
	if(fds_offset > NUM_SIM_FILES-1){
		printf("\nget_fd() - fds_offset > NUM_SIM_FILES-1 (page=%u)", page);
//		fflush(stdout);
		exit(-1);
		return -1;
	}

	return fds[fds_offset];
}

error_t
nandReadPage(uint8_t *buf, uint32_t page, uint32_t offset, uint32_t len){
	uint32_t i;
//	printf("nandReadPage() - page %u offset %u len %u\n", page, offset, len);

	int fd = get_fd(page);
	if(fd == -1){
		printf("nandReadPage() - fd is null\n");
		fflush(stdout);
		assert(0);
	}

	// cant read non-existing page
	if (page >= NAND_PAGE_COUNT){
		printf("\nnandReadPage() - page >= NAND_PAGE_COUNT (page=%u)\n", page);
		return 1;
	}

//	printf("\nnandReadPageSpare() - page * NAND_TOTAL_SIZE+ offset=%u", page * NAND_TOTAL_SIZE+ offset);
//	assert((page % PAGES_PER_GIGA) * NAND_TOTAL_SIZE+offset < GIGA_SIZE);
//
//	int x = (page % PAGES_PER_GIGA) * NAND_TOTAL_SIZE+offset;

//	if (fseek(fp, (page % PAGES_PER_GIGA) * NAND_TOTAL_SIZE+offset, SEEK_SET) != 0){
//		printf("\nfseek(fp, page (=%u)* NAND_TOTAL_SIZE+offset (=%u), SEEK_SET) != 0", page, offset);
//		return 1;
//	}

//	assert( ftell(fp) == x );

	//printf("\nfile position=%d",page * NAND_TOTAL_SIZE+offset);
	int rc = pread(fd, buf, len, (page % PAGES_PER_GIGA) * NAND_TOTAL_SIZE+offset);
	if(rc != len){
		printf("\nnandReadPage() pread() != len (rc %d). errno=%s (fd %d page %u, offset %u, len %u)\n", rc, strerror(errno), fd, page, offset, len);
		fflush(stdout);
		assert(0);
		return 1;
	}

//	assert( ftell(fp) == x+len );
//	//if ( x == 0 ) {
//		printf("read from %d %x\n",x,buf[0]);
	//}

#ifdef PROFILING
	pthread_mutex_lock(&(profiling_lock));
	nand_total_reads++;
	pthread_mutex_unlock(&(profiling_lock));
#endif

	return 0;
}

error_t
nandProgramPage(uint8_t *buf, uint32_t page, uint32_t offset, uint32_t len){
	int fd = get_fd(page);
//	printf("nandProgramPage() - page %u offset %u len %u\n", page, offset, len);

	if(fd == -1){
		printf("nandProgramPage() - fd is null\n");
		fflush(stdout);
		assert(0);
	}

	/* cant program non-existing page */
	if (page >= NAND_PAGE_COUNT){
		return 1;
	}

	/* can't program page when following pages in erase unit were written*/
	int i;
	for(i=page;i<(page & NAND_EU_MASK) + NAND_PAGES_PER_ERASE_UNIT;i++){
		if(program[page]){
			printf("nandProgramPage() - page %u not erased as expected. exiting\n", page);
			fflush(stdout);
			assert(program[page] == 0);
		}
	}

	program[page] = 1;
//	/* can only program erased page*/
//	if(program[page] != 0){
//		printf("nandProgramPage() - page %u not erased as expected. exiting\n", page);
//		fflush(stdout);
//		assert(program[page] == 0);
//	}
//	else{
//		program[page] = 1;
//	}

	// verify not overflowing page with len
	if(offset % NAND_TOTAL_SIZE + len > NAND_TOTAL_SIZE){
		len = NAND_TOTAL_SIZE - offset % NAND_TOTAL_SIZE;
	}
	assert((page % PAGES_PER_GIGA) * NAND_TOTAL_SIZE+offset < GIGA_SIZE);
//	if (fseek(fp, (page % PAGES_PER_GIGA) * NAND_TOTAL_SIZE+offset, SEEK_SET) != 0)
//		return 1;

//	printf("buf %x\n", buf);
//	printf("len %u\n", len);
//	printf("page %u\n", page);
//	printf("(page % PAGES_PER_GIGA) * NAND_TOTAL_SIZE+offset) %u\n", (page % PAGES_PER_GIGA) * NAND_TOTAL_SIZE+offset);
	if(pwrite(fd, buf, len, (page % PAGES_PER_GIGA) * NAND_TOTAL_SIZE+offset) != len){
		return 1;
	}

//	fflush(fd);

#ifdef PROFILING
	pthread_mutex_lock(&(profiling_lock));
	nand_total_writes++;
	pthread_mutex_unlock(&(profiling_lock));
#endif

	return 0;
}

//
//
//error_t markEuAsBad(uint32_t page_addr){
//	uint8_t bad_byte = 0x00;
////	PRINT_MSG_AND_NUM("\nmarkEuAsBad() - marking eu as bad in address ",page_addr);
//	// mark eu as bad  (if not already marked)
//	if(!nandCheckEuStatus(page_addr)){
////		PRINT("\nmarkEuAsBad() - EU is already bad");
//		return 1;
//	}
//
////	PRINT_MSG_AND_NUM("\nmarkEuAsBad() - mark page as bad ",page_addr & NAND_EU_MASK);
//	if(nandProgramPageC(&bad_byte, page_addr & NAND_EU_MASK, NAND_BAD_EU_FLAG_BYTE_NUM-1, 1)){
//		return 0;
//	}
////	PRINT("\nmarkEuAsBad() - EU is marked as bad");
//	return 1;
//}

#if 0
/**
 * @brief
 * erase an EU regardless of it's status (bad or not)
 */
error_t nandBruteErase(uint32_t page_in_erase_unit){
	uint32_t j;
	FILE *fp = get_fd(page_in_erase_unit);
	if(fp == NULL){
		printf("fp is null");
		return 1;
	}

	/* cant read non-existing page */
	if (page_in_erase_unit >= NAND_PAGE_COUNT){
		printf("\nnandErase() - page_in_erase_unit(%u) >= NAND_PAGE_COUNT", page_in_erase_unit);
		return 1;
	}

	/* get address of first page in EU */
	page_in_erase_unit &= NAND_EU_MASK;
	assert((page_in_erase_unit % PAGES_PER_GIGA) * NAND_TOTAL_SIZE < GIGA_SIZE);
	if (fseek(fp, (page_in_erase_unit % PAGES_PER_GIGA) * NAND_TOTAL_SIZE, SEEK_SET) != 0){
		printf("\nnandErase() - failed fseek");
		return 1;
	}

	/* erase page by page in erase unit */
	for(j=0; j< NAND_PAGES_PER_ERASE_UNIT;j++){
		main_area_writes[page_in_erase_unit+j] = 0;
		spare_writes[page_in_erase_unit+j]     = 0;

		if(fwrite(buf, 1, NAND_TOTAL_SIZE, fp) == 0){
			printf("\nnandErase() - failed fwrite");
			return 1;
		}
	}
//	fflush(fp);
#ifdef Profiling
	nand_erases++;
#ifdef PROFILING_ARRAYS
	erases[page_in_erase_unit/NAND_PAGES_PER_ERASE_UNIT]++;
#endif
#endif

	return 0;
}
#endif

error_t nandErase(uint32_t page_in_erase_unit){
	uint32_t res, j;
	int fd = get_fd(page_in_erase_unit);
	if(fd == -1){
		printf("nandErase() - fd is null\n");
		fflush(stdout);
		assert(0);
	}

	/* cant read non-existing page */
	if (page_in_erase_unit >= NAND_PAGE_COUNT){
		printf("\nnandErase() - page_in_erase_unit(%u) >= NAND_PAGE_COUNT", page_in_erase_unit);
		return 1;
	}

	/* get address of first page in EU */
//	page_in_erase_unit &= NAND_EU_MASK;

//	res = nandCheckEuStatus(page_in_erase_unit); /*31.3.10 - donr check eu status b4 erasing*/
//	if(!res){
//		return 0;
//	}
	assert((page_in_erase_unit % PAGES_PER_GIGA) * NAND_TOTAL_SIZE < GIGA_SIZE);

	/* erase page by page in erase unit */
	for(j=0; j< NAND_PAGES_PER_ERASE_UNIT;j++){
//		main_area_writes[page_in_erase_unit+j] = 0;
//		spare_writes[page_in_erase_unit+j]     = 0;
		/* set in memory array */
		program[page_in_erase_unit+j] = 0;

		if(pwrite(fd, buf, NAND_TOTAL_SIZE, ((page_in_erase_unit % PAGES_PER_GIGA)+j) * NAND_TOTAL_SIZE) != NAND_TOTAL_SIZE){
			return 1;
		}
	}

#ifdef PROFILING
	pthread_mutex_lock(&(profiling_lock));
	nand_erases++;
	pthread_mutex_unlock(&(profiling_lock));
#endif

	return 0;
}

void nandTerminate(void){
	uint32_t fd_idx;

	for(fd_idx=0; fd_idx< NUM_SIM_FILES; fd_idx++){
		if(fds[fd_idx] != -1){
			close(fds[fd_idx]);
		}
	}

}

///**
// * verify eu status. read phy_addr of an eu, and perform eu validiy check
// * return 1 if eu is ok, 0 otherwise
// */
//error_t nandCheckEuStatus(uint32_t phy_addr){
//	uint8_t spare_buf[NAND_SPARE_SIZE];
//
//	FILE *fp = get_fd(phy_addr);
//	if(fp == NULL){
//		printf("fp is null");
//		return 1;
//	}
//
////	L("starting. phy_addr=%x", phy_addr);
////	printf("\nread first apge spare (%u)", phy_addr & NAND_EU_MASK);
//	nandReadPageSpare(spare_buf, phy_addr & NAND_EU_MASK, 0, NAND_SPARE_SIZE);
////	printf("\nverify it");
//	/* verify the eu isn't bad
//	 * check bad eu flag in first two pages of the EU (as required by the datasheet)*/
//	if (spare_buf[NAND_BAD_EU_FLAG_BYTE_NUM-1] == 0xff){
////		printf("\nread second apge spare (%u)", (phy_addr & NAND_EU_MASK) + 1);
//		nandReadPageSpare(spare_buf, (phy_addr & NAND_EU_MASK) + 1,0,NAND_SPARE_SIZE);
////		printf("\nverified first page in the eu of %u. verifying second", phy_addr);
//
////		L("is EU ok = %d", spare_buf[NAND_BAD_EU_FLAG_BYTE_NUM-1] == 0xff);
////		printf("\nfailed verifying first page in the eu of %u", phy_addr);
//		return (spare_buf[NAND_BAD_EU_FLAG_BYTE_NUM-1] == 0xff);
//	}
//
////	L("failed verifying first page in the eu of %u", phy_addr);
//	return 0;
//}
