#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <asm/page.h>
#include <sys/swap.h>
#include <sys/sysmacros.h>
#include <sys/statfs.h>
#include <unistd.h>
#include <zlib.h>

#include "../isys/imount.h"
#include "../isys/isys.h"

#include "commands.h"
#include "idmap.h"
#include "ls.h"
#include "popt.h"
#include "../isys/cpio.h"

static int copyfd(int to, int from);

static int copyfd(int to, int from) {
   char buf[1024];
   int size;

   while ((size = read(from, buf, sizeof(buf))) > 0) {
       if (write(to, buf, size) != size) {
           fprintf(stderr, "error writing output: %s\n", strerror(errno));
           return 1;
       }
   }

   if (size < 0) {
       fprintf(stderr, "error reading input: %s\n", strerror(errno));
       return 1;
   }

   return 0;
}

static int catFile(char * filename) {
   int fd;
   int rc;

   fd = open(filename, O_RDONLY);
   if (fd < 0) {
       fprintf(stderr, "cannot open %s: %s\n", filename, strerror(errno));
       return 1;
   }

   rc = copyfd(1, fd);
   close(fd);

   return rc;
}

int catCommand(int argc, char ** argv) {
   char ** argptr = argv + 1;
   int rc;

   if (!*argptr) {
       return copyfd(1, 0);
   } else {
       while (*argptr) {
           rc = catFile(*argptr);
           if (rc) return rc;
           argptr++;
       }
   }

   return 0;
}

int lsmodCommand(int argc, char ** argv) {
   puts("Module:        #pages:  Used by:");
   catFile("/proc/modules");

   return 0;
}

#define MOUNT_USAGE fprintf(stderr, "usage: mount -t <fs> <device> <dir>\n" \
                       "       (if /dev/ is left off the device name, a " \
                       "temporary node will be created)\n")

int mountCommand(int argc, char ** argv) {
   char * dev, * dir;
   char * fs, * buf;

   if (argc < 2) {
       return catFile("/proc/mounts");
   } else if (argc == 3) {
       if (strchr(argv[1], ':'))
           fs = "nfs";
       else
           fs = "ext2";
       dev = argv[1];
       dir = argv[2];
   } else if (argc != 5) {
       MOUNT_USAGE;
       return 1;
   } else {
       if (strcmp(argv[1], "-t")) {
           MOUNT_USAGE;
           return 1;
       }

       fs = argv[2];
       dev = argv[3];
       dir = argv[4];
   }

   if (!strncmp(dev, "/dev/", 5) && access(dev, X_OK)) {
       dev += 5;
       buf = alloca(strlen(dev) + 10);
       sprintf(buf, "/tmp/%s", dev);
       devMakeInode(dev, buf);
       dev = buf;
   }

   if (doPwMount(dev, dir, fs, 0, 1, NULL, NULL))
       return 1;

   return 0;
}

int umountCommand(int argc, char ** argv) {
   if (argc != 2) {
       fprintf(stderr, "umount expects a single argument\n");
       return 1;
   }

   if (umount(argv[1])) {
       fprintf(stderr, "error unmounting %s: %s\n", argv[1], strerror(errno));
       return 1;
   }

   return 0;
}

int mkdirCommand(int argc, char ** argv) {
   char ** argptr = argv + 1;

   if (argc < 2) {
       fprintf(stderr, "umount expects one or more arguments\n");
       return 1;
   }

   while (*argptr) {
       if (mkdir(*argptr, 0755)) {
           fprintf(stderr, "error creating directory %s: %s\n", *argptr,
                       strerror(errno));
           return 1;
       }

       argptr++;
   }

   return 0;
}

int mknodCommand(int argc, char ** argv) {
   int major, minor;
   char * path;
   int mode = 0600;
   char *end;

   if (argc != 5 && argc != 2) {
       fprintf(stderr, "usage: mknod <path> [b|c] <major> <minor> or mknod <path>\n");
       return 1;
   }

   path = argv[1];

   if (argc == 2) {
       end = path + strlen(path) - 1;
       while (end > path && *end != '/') end--;
       if (*end == '/') end++;

       if (devMakeInode(end, path)) {
           return 1;
       }

       return 0;
   }

   if (!strcmp(argv[2], "b"))
       mode |= S_IFBLK;
   else if (!strcmp(argv[2], "c"))
       mode |= S_IFCHR;
   else {
       fprintf(stderr, "unknown node type %s\n", argv[2]);
       return 1;
   }

   major = strtol(argv[3], &end, 0);
   if (*end) {
       fprintf(stderr, "bad major number %s\n", argv[3]);
       return 1;
   }

   minor = strtol(argv[4], &end, 0);
   if (*end) {
       fprintf(stderr, "bad minor number %s\n", argv[4]);
       return 1;
   }

   if (mknod(path, mode, makedev(major, minor))) {
       fprintf(stderr, "mknod failed: %s\n", strerror(errno));
       return 1;
   }

   return 0;
}

int lnCommand(int argc, char ** argv) {
   char ** argptr = argv + 1;
   int force = 0, soft = 0;
   int rc;

   while (*argptr && **argptr == '-') {
       if (!strcmp(*argptr, "-f"))
          force = 1;
       else if (!strcmp(*argptr, "-s"))
          soft = 1;
       else if (!strcmp(*argptr, "-fs") || !strcmp(*argptr, "-sf"))
          force = soft = 1;
       else {
          fprintf(stderr, "ln: unknown argument %s\n", *argptr);
          return 1;
       }

       argptr++;
   }

   if (!*argptr || !(*argptr + 1) || *(argptr + 2)) {
       fprintf(stderr, "ln requires exactly two filenames\n");
       return 1;
   }

   if (force) unlink(*(argptr + 1));
   if (soft)
       rc = symlink(*argptr, *(argptr + 1));
   else
       rc = link(*argptr, *(argptr + 1));

   if (rc) {
       perror("error");
       return 1;
   }

   return 0;
}

int rmCommand(int argc, char ** argv) {
   char ** argptr = argv + 1;

   if (argc < 2) {
       fprintf(stderr, "rm expects one or more arguments "
               "(no flags are supported");
       return 1;
   }

   while (*argptr) {
       if (unlink(*argptr)) {
           fprintf(stderr, "unlink of %s failed: %s\n", *argptr,
                       strerror(errno));
           return 1;
       }

       argptr++;
   }

   return 0;
}

int chmodCommand(int argc, char ** argv) {
   char ** argptr = argv + 2;
   int mode;
   char * end;

   if (argc < 3) {
       fprintf(stderr, "usage: chmod <mode> <one or files>\n");
       return 1;
   }

   mode = strtol(argv[1], &end, 8);
   if (*end) {
       fprintf(stderr, "illegal mode %s\n", argv[1]);
       return 1;
   }

   while (*argptr) {
       if (chmod(*argptr, mode)) {
           fprintf(stderr, "error in chmod of %s to 0%o: %s\n", *argptr,
                       mode, strerror(errno));
           return 1;
       }

       argptr++;
   }

   return 0;
}

#define CPIOERR_CHECK_ERRNO     0x00008000

int uncpioCommand(int argc, char ** argv) {
   int rc;
   char * fail;
   CFD_t cfd;

   if (argc != 1) {
       fprintf(stderr, "uncpio reads from stdin");
       return 1;
   }

   cfd.cpioPos = 0;
   cfd.cpioIoType = cpioIoTypeGzFd;
   cfd.cpioGzFd = gzdFdopen(fdDup(0), "r");

   rc = cpioInstallArchive(&cfd, NULL, 0, NULL, NULL, &fail);

   if (rc) {
       fprintf(stderr, "cpio failed on %s: ", fail);
       if (rc & CPIOERR_CHECK_ERRNO)
           fprintf(stderr, "%s\n", strerror(errno));
       else
           fprintf(stderr, "(internal)\n");
   }

   return (rc != 0);
}

int dfCommand(int argc, char ** argv) {
   int fd;
   char * buf = alloca(2048);
   char * end;
   struct statfs fs;
   int i;
   int badjust;

   if ((fd = open("/proc/mounts", O_RDONLY)) < 0) {
       perror("failed to open /proc/mounts");
       return 1;
   }

   i = read(fd, buf, 2048);
   buf[i] = '\0';

   printf("%-30s %-10s %-10s %-10s\n",
          "Mount Point", "1k-blocks", "Used", "Available");

   while (buf && *buf) {
       end = strchr(buf, ' ');
       if (!end) return 1;
       buf = end + 1;

       end = strchr(buf, ' ');
       if (!end) return 1;
       *end = '\0';

       statfs(buf, &fs);

       badjust = fs.f_bsize / 1024;

       printf("%-30s %-10d %-10d %-10d\n",
               buf, fs.f_blocks * badjust,
               (fs.f_blocks - fs.f_bfree) * badjust,
               fs.f_bfree * badjust);

       buf = strchr(end + 1, '\n');
       if (buf) buf++;
   }

   return 0;
}

int lsCommand(int argc, char ** argv) {
   poptContext optCon;
   int flags = 0;
   int rc;
   char path[1024];
   struct poptOption ksOptions[] = {
       { NULL, 'l', 0, NULL, 'l' },
       { NULL, 'C', 0, NULL, 'C' },
       { NULL, 'd', 0, NULL, 'd' },
       { NULL, 'g', 0, NULL, 'g' },
       { NULL, 'n', 0, NULL, 'n' },
       { NULL, 'p', 0, NULL, 'p' },
       { NULL, 'a', 0, NULL, 'a' },
       { NULL, 'L', 0, NULL, 'L' },
       { NULL, 'f', 0, NULL, 'f' },
       { NULL, 'r', 0, NULL, 'r' },
       { NULL, 't', 0, NULL, 't' },
       { NULL, 'S', 0, NULL, 'S' },
       { NULL, 'R', 0, NULL, 'R' },
       { NULL, '\0', 0, NULL, '\0' }
   };

   optCon = poptGetContext(NULL, argc, argv, ksOptions, 0);
   if (isatty(1)) flags |= SENDDIR_MULTICOLUMN;

   while ((rc = poptGetNextOpt(optCon)) >= 0) {
       switch (rc) {
         case 'l':
           flags |= SENDDIR_LONG; flags &= ~SENDDIR_MULTICOLUMN;
           break;
         case 'C':
           flags |= SENDDIR_MULTICOLUMN; flags &= ~SENDDIR_LONG;
           break;
         case 'd': flags |= SENDDIR_SIMPLEDIRS;        break;
         case 'g': /* ignored */                       break;
         case 'n': flags |= SENDDIR_NUMIDS;            break;
         case 'p': case 'F': flags |= SENDDIR_FILETYPE; break;
         case 'a': flags |= SENDDIR_ALL;               break;
         case 'L': flags |= SENDDIR_FOLLOWLINKS;       break;
         case 'f': flags |= SENDDIR_SORTNONE;          break;
         case 'r': flags |= SENDDIR_SORTREVERSE;       break;
         case 't': flags |= SENDDIR_SORTMTIME;         break;
         case 'S': flags |= SENDDIR_SORTSIZE;          break;
         case 'R': flags |= SENDDIR_RECURSE;           break;
       }
   }

   getcwd(path, 1000);

   if (rc < -1) {
       fprintf(stderr, "argument error: %s %s",
                  poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
                  poptStrerror(rc));
   } else {
       idInit();

       argv = poptGetArgs(optCon);
       if (argv) {
           while (*argv) {
               if (argv[0][0] == '/')
                   listFiles("", *argv, flags);
               else
                   listFiles(path, *argv, flags);
               argv++;
           }
       } else {
           listFiles(path, "", flags);
       }
   }

   return 0;
}

int gunzipCommand(int argc, char ** argv) {
   gzFile f;
   char buf[16384];
   int i;

   f = gzdopen(0, "r");
   while ((i = gzread(f, buf, sizeof(buf))) > 0)
       write(1, buf, i);

   gzclose(f);

   return 0;
}