#include <inc/fs.h>
#include <inc/string.h>
#include <inc/lib.h>

#define debug 0

static int file_close(struct Fd *fd);
static ssize_t file_read(struct Fd *fd, void *buf, size_t n, off_t offset);
static ssize_t file_write(struct Fd *fd, const void *buf, size_t n, off_t offset);
static int file_stat(struct Fd *fd, struct Stat *stat);
static int file_trunc(struct Fd *fd, off_t newsize);



struct Dev devfile =
{
	.dev_id =	'f',
	.dev_name =	"file",
	.dev_read =	file_read,
	.dev_write =	file_write,
	.dev_close =	file_close,
	.dev_stat =	file_stat,
	.dev_trunc =	file_trunc
};


// Is this virtual address mapped?
bool
check_if_va_mapped(void *va)
{
	return (vpd[PDX(va)] & PTE_P) && (vpt[VPN(va)] & PTE_P);
}

// Helper functions for file access
static int fmap(struct Fd *fd, off_t oldsize, off_t newsize);
static int funmap(struct Fd *fd, off_t oldsize, off_t newsize, bool dirty);

// Open a file (or directory),
// returning the file descriptor index on success, < 0 on failure.
int
open(const char *path, int mode)
{
	// Find an unused file descriptor page using fd_alloc.
	// Then send a message to the file server to open a file
	// using a function in fsipc.c.
	// (fd_alloc does not allocate a page, it just returns an
	// unused fd address.  Do you need to allocate a page?  Look
	// at fsipc.c if you aren't sure.)
	// Then map the file data (you may find fmap() helpful).
	// Return the file descriptor index.
	// If any step fails, use fd_close to free the file descriptor.

	// LAB 5: Your code here.
	
	struct Fd * fd;
	int r;
	
	if ((r = fd_alloc(&fd)) < 0) {
		cprintf("fd_alloc failed\n");
		fd_close(fd,0);
		return r;
	}
	
	if ((r = fsipc_open(path, mode, fd)) < 0) {
		cprintf("fsipc_open returns negative value \n");
		fd_close(fd,0);
		return r;
	}

	if (!lazy_map) {
		if ((r = fmap(fd, 0, fd->fd_file.file.f_size)) < 0) {
			cprintf("fmap returns negative value \n");
			fd_close(fd,0);
			return r;
		}
	} else {
		//do nothing - we can tell if a page is mapped or not by the present bit	
	}
	return fd2num(fd);
	
}

// Clean up a file-server file descriptor.
// This function is called by fd_close.
static int
file_close(struct Fd *fd)
{
	// Unmap any data mapped for the file,
	// then tell the file server that we have closed the file
	// (to free up its resources).

	// LAB 5: Your code here.
	int r;
	int fileid = fd->fd_dev_id;
	envid_t my_id;

	
	//first tell the file system to close the file
	if ((r = fsipc_close(fd->fd_dev_id)) < 0) {
		cprintf("fsipc_close has failed\n");
		return -1;
	}
	
	if ((my_id = sys_getenvid()) < 0) {
		cprintf("could not get envid\n");
		return -1;
	}

	
	//unmap data
	if ((r = funmap(fd, fd->fd_file.file.f_size, 0, 0)) < 0) {
		cprintf("funmap fails \n");
		return -1;
	}
	
	//unmap fd itself
	if ((r=sys_page_unmap(my_id, (void *)fd)) < 0) {
		cprintf("unmapping fd fails \n");
		return -1;
	}

	return 0;
}

// Maps the pages of the file indicated by fd in the user space. The data to 
// map should start from start_in_file in the file indicated by fd and is being mapped in user begining
// with start_va and for as many pages as npages.
// Do not map pages that are already mapped.
//start_in_file and start_va need to be page aligned
 int lazy_map_pages(struct Fd *fd, uint32_t start_in_file, uint32_t start_va, int npages) {
	
	int i, r;

	for (i = 0; i < npages; i++) {
		if (!check_if_va_mapped((void *)start_va)) {
			if ((r = fsipc_map(fd->fd_file.id, start_in_file + i*PGSIZE, (void *)start_va + i*PGSIZE)) < 0) {
				// unmap anything we may have mapped so far
				funmap(fd, start_in_file + i*PGSIZE, start_in_file,0);
				return r;
			}
		}
	}

	return 0;
}


// Read 'n' bytes from 'fd' at the current seek position into 'buf'.
// Since files are memory-mapped, this amounts to a memmove()
// surrounded by a little red tape to handle the file size and seek pointer.
static ssize_t
file_read(struct Fd *fd, void *buf, size_t n, off_t offset)
{
	size_t size;
	char * va; 
	int i, r;

	// avoid reading past the end of file
	size = fd->fd_file.file.f_size;
	if (offset > size)
		return 0;
	if (offset + n > size)
		n = size - offset;
	
	if (lazy_map) {
		//need to map the pages we need for reading if they are not already mapped
		va = fd2data(fd);
		//determine pages we need in this read
		int pages_to_read = ROUNDUP(PGOFF(va + offset) + n, PGSIZE) / PGSIZE;
		if ((r = lazy_map_pages(fd, ROUNDDOWN(offset, PGSIZE), ROUNDDOWN((uint32_t)va + offset, PGSIZE), pages_to_read)) < 0) 			{
			cprintf("problem with mazy map pages \n");
			return r;
		}
				
	}

	// read the data by copying from the file mapping
	memmove(buf, fd2data(fd) + offset, n);
	
	return n;
}

// Find the page that maps the file block starting at 'offset',
// and store its address in '*blk'.
int
read_map(int fdnum, off_t offset, void **blk)
{
	int r;
	char *va;
	struct Fd *fd;

	if ((r = fd_lookup(fdnum, &fd)) < 0)
		return r;
	if (fd->fd_dev_id != devfile.dev_id)
		return -E_INVAL;
	va = fd2data(fd) + offset;
	if (offset >= MAXFILESIZE)
		return -E_NO_DISK;

	if (lazy_map) {	
		if ((r = lazy_map_pages(fd, ROUNDDOWN(offset, PGSIZE), ROUNDDOWN((uint32_t)va, PGSIZE), 1)) < 0) {
			cprintf("problem with lazy map pages in read_map \n");
			return r;
		}
	}

	if (!(vpd[PDX(va)] & PTE_P) || !(vpt[VPN(va)] & PTE_P)) {
			return -E_NO_DISK;
	}

	*blk = (void*) va;
	return 0;
}

// Write 'n' bytes from 'buf' to 'fd' at the current seek position.
static ssize_t
file_write(struct Fd *fd, const void *buf, size_t n, off_t offset)
{
	int r;
	size_t tot;

	// don't write past the maximum file size
	tot = offset + n;
	if (tot > MAXFILESIZE)
		return -E_NO_DISK;

	// increase the file's size if necessary
	if (tot > fd->fd_file.file.f_size) {
		if ((r = file_trunc(fd, tot)) < 0)
			return r;
	}

	if (lazy_map) {	
		int npages = ROUNDUP(PGOFF(offset) + n, PGSIZE) / PGSIZE;
		if ((r = lazy_map_pages(fd, ROUNDDOWN(offset, PGSIZE), ROUNDDOWN((uint32_t)fd2data(fd) + offset, PGSIZE), npages)) < 0) {
			cprintf("problem with lazy map pages in read_map \n");
			return r;
		}
	}

	// write the data
	memmove(fd2data(fd) + offset, buf, n);
	return n;
}

static int
file_stat(struct Fd *fd, struct Stat *st)
{
	strcpy(st->st_name, fd->fd_file.file.f_name);
	st->st_size = fd->fd_file.file.f_size;
	st->st_isdir = (fd->fd_file.file.f_type == FTYPE_DIR);
	return 0;
}

// Truncate or extend an open file to 'size' bytes
static int
file_trunc(struct Fd *fd, off_t newsize)
{
	int r;
	off_t oldsize;
	uint32_t fileid;

	if (newsize > MAXFILESIZE)
		return -E_NO_DISK;

	fileid = fd->fd_file.id;
	oldsize = fd->fd_file.file.f_size;
	if ((r = fsipc_set_size(fileid, newsize)) < 0)
		return r;
	assert(fd->fd_file.file.f_size == newsize);

	if (!lazy_map) {
		if ((r = fmap(fd, oldsize, newsize)) < 0)
			return r;
		funmap(fd, oldsize, newsize, 0);
	}
	return 0;
}

// Call the file system server to obtain and map file pages
// when the size of the file as mapped in our memory increases.
// Harmlessly does nothing if oldsize >= newsize.
// Returns 0 on success, < 0 on error.
// If there is an error, unmaps any newly allocated pages.
static int
fmap(struct Fd* fd, off_t oldsize, off_t newsize)
{
	size_t i;
	char *va;
	int r;

	va = fd2data(fd);
	for (i = ROUNDUP(oldsize, PGSIZE); i < newsize; i += PGSIZE) {
		if ((r = fsipc_map(fd->fd_file.id, i, va + i)) < 0) {
			// unmap anything we may have mapped so far
			funmap(fd, i, oldsize, 0);
			return r;
		}
	}
	return 0;
}

// Unmap any file pages that no longer represent valid file pages
// when the size of the file as mapped in our address space decreases.
// Harmlessly does nothing if newsize >= oldsize.
static int
funmap(struct Fd* fd, off_t oldsize, off_t newsize, bool dirty)
{
	size_t i;
	char *va;
	int r, ret;

	va = fd2data(fd);

	// Check vpd to see if anything is mapped.
	if (!(vpd[VPD(va)] & PTE_P))
		return 0;

	ret = 0;
	for (i = ROUNDUP(newsize, PGSIZE); i < oldsize; i += PGSIZE)
		if (vpt[VPN(va + i)] & PTE_P) {
			if (dirty
			    && (vpt[VPN(va + i)] & PTE_D)
			    && (r = fsipc_dirty(fd->fd_file.id, i)) < 0)
				ret = r;
			sys_page_unmap(0, va + i);
		}
  	return ret;
}

// Delete a file
int
remove(const char *path)
{
	return fsipc_remove(path);
}

// Synchronize disk with buffer cache
int
sync(void)
{
	return fsipc_sync();
}

