#include <stdio.h>
#include <string.h>
#include <errno.h>

#ifdef __CYGWIN__
#include <sys/cygwin.h>
#endif
#include "hackerlab/vu/vu.h"
#include "hackerlab/vu/vu-sys.h"
#include "hackerlab/char/str.h"

#define DEBUG 0

#if DEBUG
#define Dprintf printf
#else
#define Dprintf do_nothing
#endif


#include "pathcompress.c"

#define NUM_INFO 1024
struct pathcompress_info {
  DIR* dir;
  char* comp_dir;
  char* uncomp_dir;
} pathcompress_info[NUM_INFO];
int pathcompress_numinfo = 0;


struct pathcompress_info* pathcompress_grab_info()
{
  struct pathcompress_info* p = NULL;
  int i;
  for (i=0 ; i<pathcompress_numinfo; i++) {
    p = &pathcompress_info[i];
    if (p->dir == NULL) {
      memset(p, 0, sizeof(*p));
      return p;
    }
  }

  p = &pathcompress_info[pathcompress_numinfo++];
  memset(p, 0, sizeof(*p));
  return p;
}

struct pathcompress_info* pathcompress_find_info(DIR* dir)
{
  struct pathcompress_info* p = NULL;
  int i;
  for (i=0 ; i<pathcompress_numinfo; i++) {
    p = &pathcompress_info[i];
    if (p->dir == dir) {
      return p;
    }
  }
  return NULL;
}

void pathcompress_free_info(DIR* dir)
{
  struct pathcompress_info* p = NULL;
  int i;
  for (i=0 ; i<pathcompress_numinfo; i++) {
    p = &pathcompress_info[i];
    if (p->dir == dir) {
      p->dir = NULL;
      if (i == pathcompress_numinfo-1) {
	pathcompress_numinfo--;
      }
    }
  }
}

void* pathcompress_make_closure(void * closure)
{
  //Dprintf("pathcompress_make_closure(%p)\n", closure);
  return vu_sys_make_closure(closure);
}

void pathcompress_free_closure(void * closure)
{
  //Dprintf("pathcompress_free_closure(%p)\n", closure);
  vu_sys_free_closure(closure);
}

int pathcompress_access(int* errn, char* path, int mode, void* closure)
{
  int rvl;
  char compressed_path[PATH_LEN]="";
  Dprintf("pathcompress_access(%s,%x)",path,mode);
  pathcompress_compress_path(path, compressed_path);
  rvl = vu_sys_access(errn, compressed_path, mode, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_chdir(int * errn, char * path, void * closure)
{
  int rvl;
  char compressed_path[PATH_LEN]="";
  Dprintf("pathcompress_chdir(%s)", path);
  pathcompress_compress_path(path, compressed_path);
  rvl = vu_sys_chdir(errn, compressed_path, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_chmod(int * errn, char * path, int mode, void * closure)
{
  int rvl;
  char compressed_path[PATH_LEN]="";
  Dprintf("pathcompress_chmod(%s,0%o)", path, mode);
  pathcompress_compress_path(path, compressed_path);
  rvl = vu_sys_chmod(errn, compressed_path, mode, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_chown(int * errn, char * path, int owner, int group, void * closure)
{
  int rvl;
  char compressed_path[PATH_LEN]="";
  Dprintf("pathcompress_chown(%s,%d,%d)", path, owner, group);
  pathcompress_compress_path(path, compressed_path);
  rvl = vu_sys_chown(errn, compressed_path, owner, group, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_chroot(int * errn, char * path, void * closure)
{
  int rvl;
  char compressed_path[PATH_LEN]="";
  Dprintf("pathcompress_chroot(%s)", path);
  pathcompress_compress_path(path, compressed_path);
  rvl = vu_sys_chroot(errn, compressed_path, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_closedir(int * errn, DIR * dir, void * closure)
{
  int rvl;
  struct pathcompress_info* p;

  Dprintf("pathcompress_closedir(%p)", dir);
  p = pathcompress_find_info(dir);
  if (p->uncomp_dir != NULL) {
    lim_free(0, p->uncomp_dir);
    p->uncomp_dir = NULL;
  }
  if (p->comp_dir != NULL) {
    lim_free(0, p->comp_dir);
    p->comp_dir = NULL;
  }
  pathcompress_free_info(dir);
  rvl = vu_sys_closedir(errn, dir, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_close(int * errn, int fd, void * closure)
{
  int rvl;
  Dprintf("pathcompress_close(%d)", fd);
  rvl = vu_sys_close(errn, fd, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_fchdir(int * errn, int fd, void * closure)
{
  int rvl;
  Dprintf("pathcompress_fchdir(%d)", fd);
  rvl = vu_sys_fchdir(errn, fd, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_fchmod(int * errn, int fd, int mode, void * closure)
{
  int rvl;
  Dprintf("pathcompress_fchmod(%d,%x)", fd, mode);
  rvl = vu_sys_fchmod(errn, fd, mode, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_fchown(int * errn, int fd, int owner, int group, void * closure)
{
  int rvl;
  Dprintf("pathcompress_fchown(%d,%d,%d)", fd, owner, group);
  rvl = vu_sys_fchown(errn, fd, owner, group, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_fstat(int * errn, int fd, struct stat * buf, void * closure)
{
  int rvl;
  Dprintf("pathcompress_fstat(%d,%p)", fd, buf);
  rvl = vu_sys_fstat(errn, fd, buf, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_fsync(int * errn, int fd, void * closure)
{
  int rvl;
  Dprintf("pathcompress_fsync(%d)", fd);
  rvl = vu_sys_fsync(errn, fd, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_ftruncate(int * errn, int fd, off_t where, void * closure)
{
  int rvl;
  Dprintf("pathcompress_ftruncate(%d,%d)", fd, (int)where);
  rvl = vu_sys_ftruncate(errn, fd, where, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_getcwd(char* cwd, size_t size)
{
  char wd[PATH_LEN]="";
  char compressed_wd[PATH_LEN]="";
  char* c;
  int rvl;
  int len;
  Dprintf("pathcompress_getcwd(%p, %d)", cwd, size);
  c = getcwd(compressed_wd, sizeof(compressed_wd));
  if (c == NULL) {
    rvl = -1;
  } else {
    if (pathcompress_uncompress_path(compressed_wd, wd)==NULL) {
      rvl = -1;
    } else {
      len = strlen(wd);
      if (strlen(wd)>size-1) {
	errno = ERANGE;
	rvl = 0;
      } else {
	strcpy(cwd, wd);
	rvl = 1;
      }
    }
  }
  Dprintf("=%d '%s'\n", rvl, cwd);
  return rvl;
}

int pathcompress_link(int * errn, char * from, char * to, void * closure)
{
  int rvl;
  char compressed_from[PATH_LEN]="";
  char compressed_to[PATH_LEN]="";
  Dprintf("pathcompress_link(%s,%s)", from, to);
  pathcompress_compress_path(from, compressed_from);
  pathcompress_compress_path(to, compressed_to);
  rvl = vu_sys_link(errn, compressed_from, compressed_to, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

off_t pathcompress_lseek(int * errn, int fd, off_t offset, int whence, void * closure)
{
  int rvl;
  Dprintf("pathcompress_lseek(%d,%d,%d)", fd, (int)offset, whence);
  rvl = vu_sys_lseek(errn, fd,offset, whence, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_lstat(int * errn, char * path, struct stat * buf, void * closure)
{
  int rvl;
  char compressed_path[PATH_LEN]="";
  char* cpath;
  Dprintf("pathcompress_lstat(%s,%p)", path, buf);
  cpath = pathcompress_compress_path(path, compressed_path);
  if (cpath != NULL) {
    rvl = vu_sys_lstat(errn, cpath, buf, closure);
  } else {
    *errn = ENOENT;
    rvl = -1;
  }
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_mkdir(int * errn, char * path, int mode, void * closure)
{
  int rvl;
  char abspath[PATH_LEN]="";
  char compressed_path[PATH_LEN]="";

  Dprintf("pathcompress_mkdir(%s,0%o)", path, mode);

  pathcompress_add_path(path, abspath);
  //printf("abspath(%s)\n", abspath);

  pathcompress_compress_path(path, compressed_path);
  //printf("compressed(%s)\n", path);

  rvl = vu_sys_mkdir(errn, compressed_path, mode, closure);
  if (rvl == 0) {
    pathcompress_add_pathid(path);
  }
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_open(int * errn, char * path, int flags, int mode, void * closure)
{
  int rvl;
  char* p;
  char* q;
  char intermediary[PATH_LEN]="";
  char compressed_intermediary[PATH_LEN]="";
  char compressed_path[PATH_LEN]="";

  struct stat buf;
  int err;

  p = path;
  q = intermediary;

  //we might need to create intermediary directories...
  if ((flags & O_CREAT) == O_CREAT) {
    //printf("pathcompress_open(%s)\n", path);
    if (*p == '/') {
      *q++ = *p++;
    }
    while (*p != '\0') {
      while ((*p != '/') && (*p != '\0')) {
	*q++ = *p++;
      }
      //printf("p=%x p=%s\n", *p, p);
      *q = '\0';
      if (*p == '/') {
	pathcompress_compress_path(intermediary, compressed_intermediary);
	//printf("intermediary(%s) p=%s c=%s\n", intermediary, p, compressed_intermediary);
	rvl = stat(compressed_intermediary, &buf);
	//printf("stat(%s)=%d\n", compressed, rvl);
	if (rvl!=0) {
	  //printf("create(%s)\n", compressed);
	  pathcompress_mkdir(&err, intermediary, 0777, closure);
	  //printf("created(%s)\n", compressed);
	}
	*q++=*p++;
      }
    }
  }

  pathcompress_compress_path(path, compressed_path);

#ifdef __CYGWIN__
  Dprintf("pathcompress_open(%s,0x%x,0%03o)", compressed_path, flags, mode);
  rvl =  vu_sys_open(errn, compressed_path, flags | O_BINARY, mode, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
#endif
  return 0;
}

int pathcompress_opendir(int * errn, DIR ** retv, char * path, void * closure)
{
  int rvl;
  char compressed_path[PATH_LEN]="";
  struct pathcompress_info* p;

  p = pathcompress_grab_info();
  p->uncomp_dir = str_save(0, path);
  Dprintf("pathcompress_opendir(%p,%s)", retv, path);
  pathcompress_compress_path(path, compressed_path);
  p->comp_dir = str_save(0, compressed_path);
  Dprintf("pathcompress_opendir(%p,%s)", retv, compressed_path);
  rvl = vu_sys_opendir(errn, retv, compressed_path, closure);
  p->dir = *retv;

  Dprintf("=%d\n", rvl);
  return rvl;
}

ssize_t pathcompress_read(int * errn, int fd, char * buf, size_t count, void * closure)
{
  int rvl;
  Dprintf("pathcompress_read(%d,%p,%d)", fd, buf, count);
  rvl = vu_sys_read(errn, fd, buf, count, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_readdir(int * errn, struct alloc_limits * limits, char ** file_ret, DIR * dir, void * closure)
{
  struct pathcompress_info* p = pathcompress_find_info(dir);
  int rvl;
  char* path;
  char abspath[PATH_LEN]="";
  int err;
  struct stat stat;
  char uncompressed_path[PATH_LEN]="";

  //Dprintf("pathcompress_readdir(%p,%p,%p)", limits, file_ret, dir);

  p = pathcompress_find_info(dir);
  Dprintf("pathcompress_readdir(%s)", p->uncomp_dir);

 next:
  rvl = vu_sys_readdir(errn, limits, file_ret, dir, closure);
  if (rvl < 0) {
    goto exit;
  }
  path = *file_ret;
  if (strcmp(path,".")==0 || strcmp(path, "..")==0) {
    goto exit;
  }

  sprintf(abspath, "%s/%s", p->comp_dir, path);

  vu_sys_stat(&err, abspath, &stat, NULL);
  if (!S_ISDIR(stat.st_mode)) {
    //printf("[NOTDIR st_mode=%x]", stat.st_mode);
    goto exit;
  }

  pathcompress_uncompress_path(abspath, uncompressed_path);

  /*
  printf("\nuncompressed: %s\n", uncompressed_path);
  printf("uncomp_dir: %s\n", p->uncomp_dir);
  printf("comp_dir: %s\n", p->comp_dir);
  printf("norm_dir: %s\n", p->norm_dir);
  */

  path = &uncompressed_path[strlen(uncompressed_path)];
  while (path[-1] != '/') path--;

  lim_free(0, *file_ret);
  *file_ret = str_save(0, path);

 exit:
  if (rvl==0) {
    Dprintf("=%d (%s)\n", rvl, *file_ret);
  } else {
    Dprintf("=%d\n", rvl);
  }
  return rvl;
}

int pathcompress_readlink(int * errn, char * path, char * buf, int bufsize, void * closure)
{
  int rvl;
  char compressed_path[PATH_LEN]="";

  Dprintf("pathcompress_readlink(%s,%p,%d)", path, buf, bufsize);

  pathcompress_compress_path(path, compressed_path);
  rvl = vu_sys_readlink(errn, compressed_path, buf, bufsize, closure);

  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_rename(int * errn, char * from, char * to, void * closure)
{
  struct stat stat;
  int err;
  int rvl;
  char abspath[PATH_LEN]="";
  char cwd[PATH_LEN]="";
  char compressed_from[PATH_LEN]="";
  char compressed_to[PATH_LEN]="";
  getcwd(cwd, sizeof(cwd));
  if (*from != '/') {
    Dprintf("pathcompress_rename([cwd=%s]'%s','%s')\n", cwd, from, to);
  } else {
    Dprintf("pathcompress_rename('%s','%s')\n", from, to);
  }
  pathcompress_compress_path(from, compressed_from);
  pathcompress_compress_path(to, compressed_to);

  vu_sys_stat(&err, compressed_from, &stat, NULL);
  if (S_ISDIR(stat.st_mode)) {
    pathcompress_add_path(to, abspath);
    //to = abspath;
    pathcompress_compress_path(to, compressed_to);
    //printf("cmp(\na=%s\nc=%s\n)=%d\n", abspath, compressed_to, strcmp(abspath, compressed_to));
  }
  Dprintf("pathcompress_rename('%s','%s')", compressed_from, compressed_to);
  rvl = vu_sys_rename(errn, compressed_from, compressed_to, closure);
  if (rvl != 0) {
    Dprintf("=%d, err=%d\n", rvl, *errn);
  }
  pathcompress_remove_path(from);
  pathcompress_update_pathid(to);
  return rvl;
}

int pathcompress_rmdir(int * errn, char * path, void * closure)
{
  int rvl;
  char dirnames[PATH_LEN]="";
  char compressed_path[PATH_LEN]="";

  pathcompress_compress_path(path, compressed_path);

  Dprintf("pathcompress_rmdir(%s)", compressed_path);
  rvl = vu_sys_rmdir(errn, compressed_path, closure);
  pathcompress_remove_path(path);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_stat(int * errn, char * path, struct stat * buf, void * closure)
{
  int rvl;
  char compressed_path[PATH_LEN]="";
  char* cpath;

  Dprintf("pathcompress_stat(%s,%p)", path, buf);

  cpath = pathcompress_compress_path(path, compressed_path);
  //Dprintf("pathcompress_stat(%s,%p)", compressed_path, buf);
  if (cpath != NULL) {
    rvl = vu_sys_stat(errn, compressed_path, buf, closure);
  } else {
    *errn = ENOENT;
    rvl = -1;
  }
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_symlink(int * errn, char * from, char * to, void * closure)
{
  int rvl;
  char compressed_from[PATH_LEN]="";
  char compressed_to[PATH_LEN]="";

  Dprintf("pathcompress_symlink(%s,%s)", from, to);

  pathcompress_compress_path(from, compressed_from);
  pathcompress_compress_path(to, compressed_to);

  rvl = vu_sys_symlink(errn, compressed_from, compressed_to, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_truncate(int * errn, char * path, off_t where, void * closure)
{
  int rvl;
  char compressed_path[PATH_LEN]="";

  pathcompress_compress_path(path, compressed_path);

  Dprintf("pathcompress_truncate(%s,%d)", compressed_path, (int)where);
  rvl = vu_sys_truncate(errn, compressed_path, where, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_unlink(int * errn, char * path, void * closure)
{
  int rvl;
  char compressed_path[PATH_LEN]="";
  pathcompress_compress_path(path, compressed_path);

  Dprintf("pathcompress_unlink(%s)", compressed_path);
  rvl = vu_sys_unlink(errn, compressed_path, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_utime(int * errn, char * path, struct utimbuf * times, void * closure)
{
  int rvl;
  char compressed_path[PATH_LEN]="";
  pathcompress_compress_path(path, compressed_path);

  Dprintf("pathcompress_utime(%s,%p)", compressed_path, times);
  rvl = vu_sys_utime(errn, compressed_path, times, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

ssize_t pathcompress_write(int * errn, int fd, char * buf, size_t count, void * closure)
{
  int rvl;
  Dprintf("pathcompress_write(%d,%p,%d)", fd, buf, count);
  rvl = vu_sys_write(errn, fd, buf, count, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_fcntl(int * errn, int fd, int cmd, long arg, void * closure)
{
  int rvl;
  Dprintf("pathcompress_fcntl(%d,0x%x,%ld)", fd, cmd, arg);
  rvl = vu_sys_fcntl(errn, fd, cmd, arg, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_dup(int * errn, int fd, void * closure)
{
  int rvl;
  Dprintf("pathcompress_dup(%d)", fd);
  rvl = vu_sys_dup(errn, fd, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_dup2(int * errn, int fd, int newfd, void * closure)
{
  int rvl;
  Dprintf("pathcompress_dup2(%d,%d)", fd, newfd);
  rvl = vu_sys_dup2(errn, fd, newfd, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

int pathcompress_move_state(int * errn, int fd, int newfd, void * closure)
{
  int rvl;
  Dprintf("pathcompress_move_state(%d,%d)", fd, newfd);
  rvl = vu_sys_move_state(errn, fd, newfd, closure);
  Dprintf("=%d\n", rvl);
  return rvl;
}

struct vu_fs_discipline pathcompress_fs_vtable
 	 = { VU_FS_DISCIPLINE_INITIALIZERS (pathcompress_) };
