//TO FIX:
// extent_server should not use get contents because it should not know the layout, use other method

/*
 * receive request from fuse and sends them along. use yfs_client to
 * to reach yfs_server.
 *
 * started life as low-level example in the fuse distribution.  we
 * have to use low-level interface in order to get i-numbers.  the
 * high-level interface only gives us complete paths.
 */
 
 

#include <fuse_lowlevel.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <arpa/inet.h>
#include "rpc/rpc.h"
#include "yfs_protocol.h"
#include "yfs_client.h"

#define LARGE_NUMBER 2000000000

yfs_client *yfsc;


yfs_protocol::status
getattr(yfs_protocol::inum inum, struct stat &st)
{
  
  yfs_protocol::status ret;

  bzero(&st, sizeof(st));

  st.st_ino = inum;
  //printf("getattr %016llx %d\n", inum, yfsc->isfile(inum));
  if(yfsc->isfile(inum)){
    yfs_protocol::fileinfo info;
    ret = yfsc->getfile(inum, info);
    if(ret != yfs_protocol::OK) {
	   // write(1,"returning from fuse get attr with error!", 200);
      return ret;
    }
    st.st_mode = S_IFREG | 0666;
    st.st_nlink = 1;
    st.st_atime = info.atime;
    st.st_mtime = info.mtime;
    st.st_ctime = info.ctime;
    st.st_size = info.size;
    //printf("   getattr -> %llu\n", info.size);
  } else {
    yfs_protocol::dirinfo info;
    ret = yfsc->getdir(inum, info);
    if(ret != yfs_protocol::OK)
      return ret;
    st.st_mode = S_IFDIR | 0777;
    st.st_nlink = 2;
    st.st_atime = info.atime;
    st.st_mtime = info.mtime;
    st.st_ctime = info.ctime;
    //printf("   getattr -> %lu %lu %lu\n", info.atime, info.mtime, info.ctime);
  }
	
  return yfs_protocol::OK;
  
}


void
fuseserver_getattr(fuse_req_t req, fuse_ino_t ino,
                             struct fuse_file_info *fi)
{
	
   
    struct stat st;
    yfs_protocol::inum inum = ino; // req->in.h.nodeid;
//    printf("\n in fuse get attr for inode %llu\n ", inum);
    yfs_protocol::status ret;

    ret = getattr(inum, st);
    if(ret != yfs_protocol::OK){
      fuse_reply_err(req, ENOENT);
      return;
    }
    
    fuse_reply_attr(req, &st, 0);
//    printf("\n leaving fuse get attr \n ");
    
}



struct dirbuf {
    char *p;
    size_t size;
};

void dirbuf_add(struct dirbuf *b, const char *name, fuse_ino_t ino)
{
    struct stat stbuf;
    size_t oldsize = b->size;
    b->size += fuse_dirent_size(strlen(name));
    b->p = (char *) realloc(b->p, b->size);
    memset(&stbuf, 0, sizeof(stbuf));
    stbuf.st_ino = ino;
    fuse_add_dirent(b->p + oldsize, name, &stbuf, b->size);
}

#define min(x, y) ((x) < (y) ? (x) : (y))
#define max(x, y) ((x) > (y) ? (x) : (y))

int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
                             off_t off, size_t maxsize)
{
    if (off < bufsize)
        return fuse_reply_buf(req, buf + off, min(bufsize - off, maxsize));
    else
        return fuse_reply_buf(req, NULL, 0);
}

yfs_protocol::status
		readdir(yfs_protocol::inum inum, std::list<yfs_protocol::dirent> &lst)
{
	
	
	yfs_protocol::status ret;

	lst.clear();

	if(yfsc->isfile(inum)){
		return yfs_protocol::IOERR; //not ok to read a file as a directory
	} else { //it is indeed a directory
		
		ret = yfsc->readdir(inum, lst);
		
		if(ret != yfs_protocol::OK)
			return ret;
		
		
	}
	return yfs_protocol::OK;


}


void
fuseserver_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
                             off_t off, struct fuse_file_info *fi)
{

	
 
	
  yfs_protocol::inum inum = ino; // req->in.h.nodeid;
  
  //printf("\n entering fuse: Read dir for inum %llu \n",inum);
  
  struct dirbuf b;
  yfs_protocol::dirent e;


 
 if(!yfsc->isdir(inum)){
    fuse_reply_err(req, ENOTDIR);
    return;
  }

  memset(&b, 0, sizeof(b));


  // fill in the b data structure using dirbuf_add

  std::list<yfs_protocol::dirent> list_of_entries;
  yfs_protocol::status ret = readdir(inum, list_of_entries);//list_of_entries is passed by reference
  if (ret != yfs_protocol::OK) {
	  fuse_reply_err(req, ENOTDIR);
	  return;
  }
  
  std::list<yfs_protocol::dirent>::iterator it = list_of_entries.begin();
  //printf("in fuse read dir, directory has %d entries\n", list_of_entries.size());
  while (it != list_of_entries.end()) {
	  dirbuf_add(&b, it->name.c_str(), it->inum);
	  it++;
  }

  reply_buf_limited(req, b.p, b.size, off, size);
  free(b.p);
  //printf("\n leaving Read dir from fuse \n");
  
  
}



void
fuseserver_statfs(fuse_req_t req)
{
    struct statvfs buf;
    //printf("entering STATFS! \n");

    printf("statfs\n");

    memset(&buf, 0, sizeof(buf));

    buf.f_namemax = 255;
    buf.f_bsize = 512;

    fuse_reply_statfs(req, &buf);
}




yfs_protocol::status mknod(yfs_protocol::inum parent, const char * name, mode_t mode, dev_t rdev, yfs_protocol::inum & inum, struct stat & st)
{	
	
	yfs_protocol::status ret;

	bzero(&st, sizeof(st));
	
	
	ret = yfsc->mknod(parent, name, mode, rdev, inum);//needs to also add an entry to the appropriate dir
	if (ret != yfs_protocol::OK) {
		return ret;
	}
	
	
	st.st_ino = inum;
	//printf("getattr %016llx %d\n", inum, yfsc->isfile(inum));
	
	yfs_protocol::fileinfo info;
	ret = yfsc->getfile(inum, info);
	if(ret != yfs_protocol::OK)
		return ret;
	st.st_mode = S_IFREG | 0666;
	st.st_nlink = 1;
	st.st_atime = info.atime;
	st.st_mtime = info.mtime;
	st.st_ctime = info.ctime;
	st.st_size = info.size;
	//printf("   getattr -> %llu\n", info.size);
	
	
	
	
	return yfs_protocol::OK;
	
}

void fuseserver_mknod(fuse_req_t req, fuse_ino_t parent, const char *name,
		      mode_t mode, dev_t rdev )
{
	
	//printf("\n entering fuse mkmod  name %s \n", name);
		
	
	yfs_protocol::inum inum; //the inode of the created file
	yfs_protocol::status ret;
	struct stat attr;
	
	ret = mknod(parent, name, mode, rdev, inum, attr);//inum and attr are passed by reference
	if (ret != yfs_protocol::OK) {
		fuse_reply_err(req, ENOENT); 
		return;
	}
	struct fuse_entry_param * e = new struct fuse_entry_param;
	e->ino = inum;
	e->generation = 0;
	e->attr = attr;
	e->attr_timeout = 0;
	e->entry_timeout = 0;
	fuse_reply_entry(req, e);
	//printf( "\n leaving fuse mknod, inum created is %llu \n ", inum);
	
	
	
	
}
/*
void fuseserver_create (fuse_req_t req, fuse_ino_t parent, const char *name,
			mode_t mode, struct fuse_file_info *fi) {

	
	write(1,"\n \ni in fuseserver create - empty function!\n",50);
	
	
	write(1,"\n \n leaving fuseserver create - empty function!\n",50);		
			}*/

yfs_protocol::status lookup(yfs_protocol::inum parent, const char * name, yfs_protocol::inum & inum, struct stat & st) 
{
	
	yfs_protocol::status ret;

	bzero(&st, sizeof(st));
	//printf("in fuse, lookup: parent inum %lld, name %s\n", parent, name);
	ret = yfsc->lookup(parent, name, inum);//inum is passed by reference
	if (ret != yfs_protocol::OK) {
		return ret;
	}
	
	 st.st_ino = inum;
	
	 if (yfsc->isfile(inum)) {
		yfs_protocol::fileinfo info;
		ret = yfsc->getfile(inum, info);
		if(ret != yfs_protocol::OK)
			return ret;
		st.st_mode = S_IFREG | 0666;
		st.st_nlink = 1;
		st.st_atime = info.atime;
		st.st_mtime = info.mtime;
		st.st_ctime = info.ctime;
		st.st_size = info.size;
	 } else {
		 yfs_protocol::dirinfo info;
		 ret = yfsc->getdir(inum, info);
		 if(ret != yfs_protocol::OK)
			 return ret;
		 st.st_mode = S_IFDIR | 0777;;
		 st.st_nlink = 2;
		 st.st_atime = info.atime;
		 st.st_mtime = info.mtime;
		 st.st_ctime = info.ctime;
	
	 
	 
	 
	 }
	
	
	return yfs_protocol::OK;
	
}

void fuseserver_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) 
{
	
	
	struct stat attr;
	yfs_protocol::inum inum;//the inode of the directory
	yfs_protocol::status ret;
	yfs_protocol::inum parent_inum = parent;
	//printf("\n entering fuse lookup parent inode %llu  and name to lookup %s \n", parent_inum,name );
	ret = lookup(parent_inum, name, inum, attr);//inum and attr passed by reference
	if (ret != yfs_protocol::OK) {
		fuse_reply_err(req, ENOENT);
		printf( " leaving fuse lookup with ENOENT\n ");
		return;
	}
	struct fuse_entry_param * e = new struct fuse_entry_param;
	e->ino = inum;
	e->generation = 0;
	e->attr = attr;
	e->attr_timeout = 0;
	e->entry_timeout = 0;
	fuse_reply_entry(req, e);
	//printf( " leaving fuse lookup, found inode %llu\n ", inum);
	

	
}

yfs_protocol::status setattr(yfs_protocol::inum inode, const struct stat *attr, int to_set, struct stat & st) 
{
	
	int new_to_set = 0;
	
	
	if ((to_set & FUSE_SET_ATTR_SIZE) != 0) {
		printf("setting size to %lld\n", attr->st_size);
		new_to_set = new_to_set | (1<<3);
	}
	if ((to_set & FUSE_SET_ATTR_ATIME) != 0) {
		new_to_set = new_to_set | (1<<4);
	}
	if ((to_set & FUSE_SET_ATTR_MTIME) != 0) {
		new_to_set = new_to_set | (1<<5);
	}
	if ((to_set & FUSE_SET_ATTR_GID) != 0) {
		new_to_set = new_to_set | (1<<2);
	}
	if ((to_set & FUSE_SET_ATTR_MODE) != 0) {
		new_to_set = new_to_set | (1<<0);
	}
	if ((to_set & FUSE_SET_ATTR_UID) != 0) {
		new_to_set = new_to_set | (1<<1);
	}
	
	//convert the st to fileinfo
	yfs_protocol::fileinfo data;
	data.ctime = attr->st_ctime;
	data.atime =  attr->st_atime;
	data.mtime = attr->st_mtime;
	data.size  = attr->st_size;
	
	st.st_ino = inode;
	yfs_protocol::status ret;
	
	if(yfsc->isfile(inode)){
		yfs_protocol::fileinfo info;
		ret = yfsc->setattr(inode,  new_to_set, data); //none passed by reference
		if(ret != yfs_protocol::OK) {
			return ret;
		}
		//now get the new attributes
		ret = yfsc->getfile(inode, info); //only info is passed by reference
		if(ret != yfs_protocol::OK) {
			return ret;
		}
		
		st.st_mode = S_IFREG | 0666;
		st.st_nlink = 1;
		st.st_atime = info.atime;
		st.st_mtime = info.mtime;
		st.st_ctime = info.ctime;
		st.st_size = info.size;
    
	} else {
		yfs_protocol::dirinfo info;
		ret = yfsc->setattr(inode, new_to_set, data); //none passed by reference
		if(ret != yfs_protocol::OK) {
			return ret;
		}
		//now get the new attributes
		ret = yfsc->getdir(inode, info); //only info is passed by reference
		if (ret != yfs_protocol::OK) {
			return ret;
		}
		
		st.st_mode = S_IFDIR | 0777;
		st.st_nlink = 2;
		st.st_atime = info.atime;
		st.st_mtime = info.mtime;
		st.st_ctime = info.ctime;
    
	}
	
	return yfs_protocol::OK;
}

void fuseserver_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
			int to_set, struct fuse_file_info *fi)
{
	//fi  not needed for anything?	
	
	//printf("\n entering fuse setattr for inode %llu \n ", (unsigned long long) ino);
	
	
	
	
	struct stat st;
	yfs_protocol::inum inum = ino; // req->in.h.nodeid;
	yfs_protocol::status ret;	
	
	ret = setattr(inum, attr, to_set, st); //st passed by reference and should contain new attributes
	if(ret != yfs_protocol::OK){
		fuse_reply_err(req, yfs_protocol::IOERR);
		return;
	}
    
	fuse_reply_attr(req, &st, 0);
	//printf("\n leaving fuse setattr \n ");	
	
}

yfs_protocol::status read(yfs_protocol::inum inum, std::string & value) {
	value.clear();
	yfs_protocol::status r = yfsc->read(inum, value);
	if (r != yfs_protocol::OK) {
		return r;
	}
	
	return yfs_protocol::OK;
}

void fuseserver_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
		     struct fuse_file_info *fi) {
			     
	//just testing
			     
			   /*  char bufa[size];
			     bufa[0] = 'r';
			     bufa[1] = 'a';
			     
			     for (int i = 2; i<size; i++) {
				     bufa[i] = '\0';
			     }
			     fuse_reply_buf(req, bufa, size);
			     return;
			   */
			     
	//printf("size requested is %u", size);
			    
	yfs_protocol::inum inum = ino;
	//printf("\n in fuse read for inode %llu\n ", inum); 
	
	std::string value;
	yfs_protocol::status ret = read(inum, value);
	if (ret != yfs_protocol::OK) {
		fuse_reply_err(req, yfs_protocol::IOERR);
		return;
	}
	
	//printf("in fuse, here is value %s \n and value size is %u \n", value.c_str(), value.size());
	
	if (off >= value.size()) {
		fuse_reply_buf(req, NULL, 0);
		return;
	}
	
	size_t new_size = min(value.size()-off, size); //how much to read from the buffer: min(value.size - off, size)	
	
	char buf[max(value.size(),size)+1];
	
	memset(buf, 0, (max(value.size(), size) + 1) * sizeof(char));
	
	for (int i = 0 ; i < value.size() ; i++) {
		buf[i] = value.at(i);
	}	
	
	buf[value.size()]= '\0';
	
	//printf("\n in fuse read, here is what I read %s, it has length %u and new size is %u \n", buf, strlen(buf), new_size);
		
	fuse_reply_buf(req, buf + off, new_size);
	
	//printf("\n in fuse read after reply buf, here is what I read %s, it has length %u and new size is %u \n", buf, strlen(buf), new_size);
	
	//printf("\n leaving fuse read \n ");
	return;

}

yfs_protocol::status write(yfs_protocol::inum inum, std::string to_copy, off_t off) {
	int res;
	//need to convert in string
	yfs_protocol::status r = yfsc->write(inum, to_copy, off,  res);
	if (r != yfs_protocol::OK) {
		return r;
	}
	return yfs_protocol::OK;
}

void fuseserver_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
		      size_t size, off_t off, struct fuse_file_info *fi) {
	//printf("entering fuse write\n");
			     
	size_t new_size;
	if (size < strlen(buf)) {
		new_size = size;
	} else {
		new_size = strlen(buf);
	}	
	std::string to_copy = "";
	for (int i = 0; i < size; i++) 
	{
		to_copy.push_back(buf[i]);
	}
	
	yfs_protocol::inum inum = ino;
	//printf("\n in fuse write, size of to_copy is %u, inode is %llu , offset is %lld , to_copy is %s, size of buf is %u \n ",to_copy.size(), inum, off,  to_copy.c_str(), strlen(buf));
	
	yfs_protocol::status ret = write(inum, to_copy,  off); 
	if (ret != yfs_protocol::OK) {
		printf("returning with IOERR from fuse write\n");
	    fuse_reply_err(req, yfs_protocol::IOERR);
	    return;
	}
	
	fuse_reply_write(req, size);
	//printf("\n leaving fuse write \n ");

 }
 
 yfs_protocol::status mkdir(yfs_protocol::inum parent, const char * name, mode_t mode,  yfs_protocol::inum & inum, struct stat & st)
 {	
	
	 yfs_protocol::status ret;

	 bzero(&st, sizeof(st));

	 ret = yfsc->mkdir(parent, name, mode, inum);//needs to also add an entry to the appropriate dir
	 if (ret != yfs_protocol::OK) {
		 
		 return ret;
	 }	
	
	 st.st_ino = inum;
	//printf("getattr %016llx %d\n", inum, yfsc->isfile(inum));
	
	 yfs_protocol::dirinfo info;
	 ret = yfsc->getdir(inum,info);
	 if(ret != yfs_protocol::OK) {
		 return ret;
	 }
	 st.st_mode = S_IFDIR | 0777;
	 st.st_nlink = 2;
	 st.st_atime = info.atime;
	 st.st_mtime = info.mtime;
	 st.st_ctime = info.ctime;
    
	return yfs_protocol::OK;
 }
 
 void fuseserver_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
		       mode_t mode) {
 
//	printf("entering fuse mkdir\n");
	yfs_protocol::inum inum;
	 struct stat attr;
	 yfs_protocol::status ret = mkdir(parent, name, mode, inum, attr);//inum and attr passed by reference
 	
	 if (ret != yfs_protocol::OK) {
		 printf("leaving fuse mkdir with err1");
		 fuse_reply_err(req, yfs_protocol::IOERR);
		 return;
	 }	

	 struct fuse_entry_param * e = new struct fuse_entry_param;
	 e->ino = inum;
	 e->generation = 0;
	 e->attr = attr;
	 e->attr_timeout = 0;
	 e->entry_timeout = 0;
	 fuse_reply_entry(req, e);
//	printf( "\n leaving fuse mkdir, inum created is %llu \n ", inum);
	 

 
 }
 
 yfs_protocol::status unlink(yfs_protocol::inum inum_parent, yfs_protocol::inum inum) {
	 return yfsc->unlink(inum_parent, inum);
 }
 
 void fuseserver_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) {
	 
	 yfs_protocol::inum inum_parent = parent;
	// printf("entering remove in fuse, parent inode is %llu and name of file is %s\n", inum_parent, name);
	 yfs_protocol::inum inum;
	 struct stat aux;
	 yfs_protocol::status ret = lookup(inum_parent, name, inum, aux);
			 
	 if (ret!=yfs_protocol::OK) {
		 fuse_reply_err(req, ENOENT);
		 return;
	 }
	 
	 ret = unlink(inum_parent, inum);
	 if (ret != yfs_protocol::OK) {
		 fuse_reply_err(req, yfs_protocol::IOERR);
		 return;
	 }
	 fuse_reply_err(req, 0);
	// printf("leaving remove in fuse\n");
	 
 }



struct fuse_lowlevel_ops fuseserver_oper;

int
main(int argc, char *argv[])
{
  char *mountpoint = 0;
  int err = -1;
  int fd;
  struct sockaddr_in dst;

 // test_server_connection();
  
  setvbuf(stdout, NULL, _IONBF, 0);

  if(argc != 3){
    fprintf(stderr, "Usage: fuseserver /mountpoint port-fuse-server\n");
    exit(1);
  }
  mountpoint = argv[1];

  make_sockaddr(argv[2], &dst);

  yfsc = new yfs_client(dst);

  fuseserver_oper.getattr    = fuseserver_getattr;
  fuseserver_oper.statfs     = fuseserver_statfs;
  fuseserver_oper.readdir    = fuseserver_readdir;
  fuseserver_oper.mknod      = fuseserver_mknod;
  //fuseserver_oper.create = fuseserver_create; 
  fuseserver_oper.lookup     = fuseserver_lookup; 
  fuseserver_oper.setattr    = fuseserver_setattr;
  fuseserver_oper.read       = fuseserver_read;
  fuseserver_oper.write	     = fuseserver_write;
  fuseserver_oper.mkdir      = fuseserver_mkdir;
  fuseserver_oper.unlink     = fuseserver_unlink;
 
  char *fuse_argv[20];
  int fuse_argc = 0;
  fuse_argv[fuse_argc++] = argv[0];
#ifdef __APPLE__
  fuse_argv[fuse_argc++] = "-o"; 
  fuse_argv[fuse_argc++] = "nolocalcaches"; // no dir entry caching
#endif
 fuse_argv[fuse_argc++] = "-d";
  fuse_argv[fuse_argc++] = mountpoint;

  fuse_args args = FUSE_ARGS_INIT( fuse_argc, fuse_argv );
  int foreground;
  int res = fuse_parse_cmdline( &args, &mountpoint, 0 /*multithreaded*/, 
				&foreground );
  if( res == -1 ) {
    fprintf(stderr, "fuse_parse_cmdline failed\n");
    return 0;
  }
  
  args.allocated = 0;

  fd = fuse_mount(mountpoint, &args);
  if(fd == -1){
    fprintf(stderr, "fuse_mount failed\n");
    exit(1);
  }

  struct fuse_session *se;

  se = fuse_lowlevel_new(&args, &fuseserver_oper, sizeof(fuseserver_oper),
			 NULL);
  if(se == 0){
    fprintf(stderr, "fuse_lowlevel_new failed\n");
    exit(1);
  }

  struct fuse_chan *ch = fuse_kern_chan_new(fd);
  if (ch == NULL) {
    fprintf(stderr, "fuse_kern_chan_new failed\n");
    exit(1);
  }

  fuse_session_add_chan(se, ch);
  err = fuse_session_loop(se);
    
  fuse_session_destroy(se);
  close(fd);
  fuse_unmount(mountpoint);

  return err ? 1 : 0;
}
