#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <unistd.h>

#include "commands.h"
#include "fs.h"
#include "mkswap.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;

    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;

    if (doMount(dev, dir, fs, 0, 1))
	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 (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 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;
}

int mkswapCommand(int argc, char ** argv) {
    if (argc != 2) {
	fprintf(stderr, "mkswap <device>");
	return 1;
    }

    if (!strncmp(argv[0], "/dev/", 5) && access(argv[0], X_OK)) 
	argv[1] += 5;

    enableswap(argv[1], 0, 0);

    return 0;
}

int swaponCommand(int argc, char ** argv) {
    if (argc != 2) {
	fprintf(stderr, "swapon <file>");
	return 1;
    }

    if (swapon(argv[1])) {
	perror("swapon:");
	return 1;
    }

    return 0;
}
