#include "jnet.hpp"
#include "specs.hpp"
#include "nfserver.hpp"
#include "dprint.hpp"
#include "timing.hpp"
#include "cache.hpp"
#include "crc.hpp"

nfs_server *file_server=NULL;

class nfs_server_client_node
{
  public :
  out_socket *nd;
  nfs_server_client_node *next;
  bFILE **file_list;
  int file_list_size;


  nfs_server_client_node(out_socket *descriptor, nfs_server_client_node *Next)
  { nd=descriptor; next=Next; 
    file_list_size=0;
    file_list=NULL;
  }
  int add_file(bFILE *fp);  // returns id for jFILE
  bFILE *get_file(int id);
  int delete_file(int id);  // returns 1=success

  ~nfs_server_client_node();

} ;

nfs_server_client_node::~nfs_server_client_node()
{
  delete nd;
  for (int i=0;i<file_list_size;i++)
    if (file_list[i])
     delete file_list[i];
  if (file_list)
    jfree(file_list);
}

int nfs_server_client_node::add_file(bFILE *fp)  // returns id for bFILE
{
  for (int i=0;i<file_list_size;i++)   // search for a free spot
  {
    if (!file_list[i])
    {
      file_list[i]=fp;
      return i;
    }
  }
  // we need to enlarge the file_list
  file_list_size++;
  file_list=(bFILE **)jrealloc(file_list,sizeof(bFILE *)*file_list_size,"client file list");
  file_list[file_list_size-1]=fp;
  return file_list_size-1;
}

bFILE *nfs_server_client_node::get_file(int id)
{
  if (id<file_list_size)
    return file_list[id];
  else return NULL;
}

int nfs_server_client_node::delete_file(int id)
{
  if (id<file_list_size && file_list[id])
  {
    delete file_list[id];
    file_list[id]=NULL;
    return 1;
  }
  return 0;
}

char *squash_path(char *path, char *buffer)
{
  strcpy(buffer,path);
  return buffer;
}

int nfs_server::process_packet(packet &pk, nfs_server_client_node *c)
{
  uchar cmd;
  if (pk.read(&cmd,1)!=1) 
  {
    dprintf("Could not read command from nfs packet\n");
    return 0;
  }
  dprintf("cmd : %d\n",cmd);
  switch (cmd)
  {
    case NFS_CLOSE_CONNECTION : 
    { return 0; } break;
    case NFS_CRC_OPEN :
    {
      uchar fn_len;
      char fn[255],newfn[255],perm[255];
      ulong crc;
      if (pk.read((uchar *)&crc,4)!=4) return 0; crc=lltl(crc);
      if (pk.read(&fn_len,1)!=1) return 0;
      if (pk.read((uchar *)fn,fn_len)!=fn_len) return 0;      
      if (pk.read((uchar *)&fn_len,1)!=1) return 0;
      if (pk.read((uchar *)perm,fn_len)!=fn_len) return 0;      // read permission string
      dprintf("nfs open %s,%s\n",fn,perm);
      packet opk;
      int fail;
      ulong my_crc=crc_man.get_crc(crc_man.get_filenumber(fn),fail);
      if (fail)
      {
	jFILE *fp=new jFILE(squash_path(fn,newfn),perm);
	if (fp->open_failure())
	{
	  delete fp;
	  opk.write_long((long)-1);
	  if (!c->nd->send(opk)) return 0;
	  return 1;
	} else      
	{
	  my_crc=crc_file(fp);
	  crc_man.set_crc(crc_man.get_filenumber(fn),my_crc);
	  delete fp;
	}	
      }

      if (my_crc==crc)
      {
	opk.write_long((long)-2);
	if (!c->nd->send(opk)) return 0;
	return 1;
      }

      jFILE *fp=new jFILE(squash_path(fn,newfn),perm);
      if (fp->open_failure())
      {
	delete fp;
	opk.write_long((long)-1);
      } else      
	opk.write_long(c->add_file(fp));      
      if (!c->nd->send(opk)) return 0;
      return 1;
    } break;
    case NFS_OPEN :
    { 
      uchar fn_len;
      char fn[255],newfn[255],perm[255];
      if (pk.read(&fn_len,1)!=1) return 0;
      if (pk.read((uchar *)fn,fn_len)!=fn_len) return 0;      
      if (pk.read((uchar *)&fn_len,1)!=1) return 0;
      if (pk.read((uchar *)perm,fn_len)!=fn_len) return 0;      // read permission string
      dprintf("nfs open %s,%s\n",fn,perm);
      packet opk;
      jFILE *fp=new jFILE(squash_path(fn,newfn),perm);
      if (fp->open_failure())
      {
	delete fp;
	opk.write_long((long)-1);
      } else      
	opk.write_long(c->add_file(fp));      
      if (!c->nd->send(opk)) return 0;
      return 1;
    } break;
    case NFS_CLOSE :
    {
      long fd;
      if (pk.read((uchar *)&fd,4)!=4) return 0;  fd=lltl(fd);
      dprintf("nfs close %d\n",fd);
      if (!c->delete_file(fd)) 
      {
	dprintf("nfs : bad fd for close\n");
	return 0;
      }
      return 1;
    } break;
    case NFS_READ :
    {
      long fd,size;
      if (pk.read((uchar *)&fd,4)!=4) return 0;    fd=lltl(fd);
      if (pk.read((uchar *)&size,4)!=4) return 0;  size=lltl(size);
      dprintf("nfs read %d,%d\n",fd,size);
      bFILE *fp=c->get_file(fd);
      uchar buf[NFSFILE_BUFFER_SIZE];
      packet opk;
      if (!fp) return 0;
      int total;
      do
      {
	opk.reset();
	int to_read=NFSFILE_BUFFER_SIZE < size ? NFSFILE_BUFFER_SIZE : size;
	total=fp->read(buf,to_read);
	opk.write_short(total);
	opk.write(buf,total);
	printf("sending %d bytes\n",total);
	if (!c->nd->send(opk)) 
	{
	  dprintf("failed on write to client\n");
	  return 0;
	}
	if (total<to_read) size=0;
	else size-=total;
      } while (size>0 && total);	       
      return 1;
    } break;

    case NFS_WRITE : // not supported
    { dprintf("got write command..., not good\n"); 
      return 0;
    } break;
    case NFS_SEEK :
    {
      long fd,off,type;
      if (pk.read((uchar *)&fd,4)!=4) return 0;   fd=lltl(fd);
      if (pk.read((uchar *)&off,4)!=4) return 0;  off=lltl(off);    
      if (pk.read((uchar *)&type,4)!=4) return 0; type=lltl(type);
      dprintf("seek %d %d %d\n",fd,off,type);
      bFILE *fp=c->get_file(fd);
      if (!fp) { dprintf("bad fd for seek\n"); return 0; }
      fp->seek(off,type);
      return 1;
    } break;
    case NFS_FILESIZE :
    {
      long fd,off,type;
      if (pk.read((uchar *)&fd,4)!=4) return 0;   fd=lltl(fd);
      bFILE *fp=c->get_file(fd);
      if (!fp) return 0;      
      packet opk;
      opk.write_long(fp->file_size());
      if (!c->nd->send(opk)) return 0;
      return 1;
    } break;
    case NFS_TELL :
    {
      long fd,off,type;
      if (pk.read((uchar *)&fd,4)!=4) return 0;   fd=lltl(fd);
      bFILE *fp=c->get_file(fd);
      if (!fp) return 0;      
      packet opk;
      opk.write_long(fp->tell());
      if (!c->nd->send(opk)) return 0;
      return 1;
    } break;

  }
  return 0;   // bad command,  all above return 1 when complete
}

int nfs_server::service_request()
{
  int ret=0;
  out_socket *c=listen->check_for_connect();   // check for new clients
  if (c)  
  {
    nodes=new nfs_server_client_node(c,nodes);
    ret=1;
  }

  nfs_server_client_node *last=NULL;
  for (nfs_server_client_node *nc=nodes;nc;)    // loop through all clients and check for request
  {
    if (nc->nd->ready_to_read())  // request pending?
    {
      packet pk;
      int kill=0;
      do
      {
	int ret;
	if (!nc->nd->get(pk))
          kill=1;
	else if (!process_packet(pk,nc))
	  kill=1;
      } while (!kill && nc->nd->ready_to_read());


      if (kill)
      {
	nfs_server_client_node *p=nc;
	nc=nc->next;
	if (last)
	  last->next=nc;
	else
	  nodes=NULL;
	delete p;
      } else nc=nc->next;      
    } else nc=nc->next;
    last=nc;
  }  
  return ret;
}

nfs_server::nfs_server(int Port)
{
  nodes=NULL;
  port=Port;
  listen=NULL;
  int i=0;
  do
  {
    current_sock_err=-1;
    listen=create_in_socket(port);
    if (current_sock_err!=-1)
    {
      dprintf("Port %d already in use, trying next up\n",port);
      port++;
      i++;
      delete listen;
    }
  } while (current_sock_err!=-1 && i<10);
  if (current_sock_err!=-1)
  {
    dprintf("Could not create a listening socket!\n");
    exit(0);
  }

}



nfs_server::~nfs_server()
{
  nfs_server_client_node *p;
  while (nodes)
  {
    p=nodes;
    nodes=nodes->next;
    delete p;
  }
  delete listen;
}



