#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <limits.h>
#include <dirent.h>
#include <fcntl.h>
#include <stdlib.h>

#include "db.h"
#include "sbs.h"

/**************************************************
 * split_path takes a path as an argument, returns all but the last path
 * element, and sets file to point to the file or directory name at
 * the end of the path.  Returns NULL if the path argument has only 
 * one element.
 **************************************************/
char *
split_path(char *path, char **file) 
{
    char *ptr;
    
    /* If there is no / in the path then path has no directory component */
    if((ptr = rindex(path, '/')) == NULL) {
	*file = path;
	return NULL;
    } else {
	/* terminate the directory part of the path */
	*ptr = '\0';
	ptr++;
	*file = ptr; /* set file to point to the last element on the path */
	return path;
    }
}




/*********************************************
 * unique_name adds a unique version number to path. 
 *     - The version number added is one greater than the largest
 *       version number found in the SBS directory for that file.  
 *     - path contains the full path to a file in the working directory.  
 *********************************************/

char *
unique_name(char *path)
{
    DIR *dir;
    struct dirent *entry;
    char *fname, *ptr;
    char numbuf[PATH_MAX];
    int max_version = 0;
    int found = 0;
    int curr_version;

    if((ptr = split_path(path, &fname)) == NULL) {
	/* This should never happen */
	fprintf(stderr, "Error: expecting an absolute path, got %s\n",
		path); 
	exit(1);
    }
    
    if((dir = opendir(ptr)) == NULL) {
	perror("opendir");
	exit(1);
    }
    
    /* search the directory for the file matching fname, with the
     * largest version number */
    while( (entry = readdir(dir)) != NULL ) {
	if((strncmp(entry->d_name, fname, strlen(fname))) == 0) {
	    found = 1;
	    curr_version = get_version_num(entry->d_name);
	    if(curr_version  > max_version) {
		max_version = curr_version;
	    }
	}
    }

    if(found)
	max_version++;

    /* Constuct the path with version number */
    sprintf(numbuf, "/%s.%d", fname, max_version);

    strncat(path, numbuf, strlen(numbuf) + 1);
    return path;
}

void
get_repository_path(char *rp, char *path, int size) 
{
    char temppath[PATH_MAX];
    char *ptr, *pp;
    int ind;

    strncpy(temppath, path, sizeof(temppath));

    /* continue stripping the last directory off the end of the path 
     * until we find one without a database file.
     */
    while((strlen(temppath) > 1) && db_exists(temppath)) {
	if((ptr = rindex(temppath, '/')) == NULL) {
	    perror("Bad string index"); /* shouldn't happen */
	    exit(1);
	}
	*ptr = '\0';
    }

    /* Now the length of temppath is where the beginning of the 
     * the path from the root of the repository would begin. */
    ind = strlen(temppath);
    ptr = &path[ind+1];

    /* rp will contain the absolute path to the appropriate directory
     * in the repository.  */
    pp = getenv("HOME");
    strncpy(rp, pp, size);
    strncat(rp, ROOTDIR, strlen(ROOTDIR)+1);
    strncat(rp, "/", 2);
    strncat(rp, ptr, strlen(ptr)+1);
}

void
createdirs(char *path) 
{
    char temppath[PATH_MAX];
    char *restofpath = NULL, *ptr, *last;
    struct stat statbuf;
    strncpy(temppath, path, sizeof(temppath));
    

    /* Move up the path until we find a directory that exists */
    while( stat(temppath, &statbuf) == -1 ) {
	last = restofpath;
	if((ptr = split_path(temppath, &restofpath)) == NULL)
	    fprintf(stderr, "Expecting a path to the repository. Got %s\n", 
		    temppath);
	if(last != NULL) {
	    last--;
	    *last = '/';
	}
    }
    if(restofpath == NULL)
	return;

    restofpath--;
    *restofpath = '/';
    restofpath++;

    /* Create all the directories in rest of the path. */
    while((ptr = index(restofpath, '/'))) {
	*ptr = '\0';
	
	if((mkdir(temppath, 0700)) == -1) 
	    perror("mkdir");
	*ptr = '/';
	restofpath = ptr + 1;
    }
    if((mkdir(temppath, 0700)) == -1) 
	perror("mkdir");
    
    
}

void
copy_file(char *path, char *rp)
{
    char buf[4096];
    int numread;
    int numwritten;
    FILE *backupfp;
    FILE *fp = fopen(path, "r");
    if(fp == NULL) {
	fprintf(stderr, "Can't open %s\n", path);
	perror("fopen");
	exit(1);
    }
    backupfp = fopen(rp, "w"); 
    if(backupfp == NULL) {
	fprintf(stderr, "Can't open %s\n", rp);
	perror("fopen");
	exit(1);
    }

    while(!feof(fp)) {
	numread = fread(buf, 1, sizeof(buf), fp);
	numwritten = fwrite(buf, 1, numread, backupfp);
	if(numread != numwritten) {
	    fprintf(stderr, "Incorrect number of bytes written "
		    "to backup file\n");
	}
    }
    fclose(fp);
    fclose(backupfp);
}

void 
copy_dir(char *path, char *rp)
{
    char cwd[PATH_MAX];
    int rplen;
    int pathlen = strlen(path);
    struct stat st;
    struct dirent *entry;
    DIR *dir = opendir(path);

    getcwd(cwd, PATH_MAX);
    if((chdir(path) == -1)) {
	perror("chdir");
	exit(1);
    }

    if(dir == NULL) {
	perror("opendir");
	exit(1);
    }

    rplen = strlen(rp);
    
    /* make sure the directory is there */
    if((mkdir(rp, 0700)) == -1 && errno != EEXIST ) {
	fprintf(stderr, "Could not create the directory %s\n", rp);
	perror("mkdir");
	exit(1);
    }

    while( (entry = readdir(dir)) != NULL ) {
	if(db_find(entry->d_name) == -1 ) {
	    continue;
	}
	strncat(path, "/", 2);
	strncat(path, entry->d_name, strlen(entry->d_name) + 1);
	strncat(rp, "/", 2);
	strncat(rp, entry->d_name, strlen(entry->d_name) + 1);

	stat(path, &st);
	if(S_ISDIR(st.st_mode)) {
	    if(strcmp(entry->d_name, ".") == 0 ||
	       strcmp(entry->d_name, "..") == 0) {
		path[pathlen] = '\0';
		rp[rplen] = '\0';
		continue;
	    } else {
		copy_dir(path, rp);
	    }
	} else {
	    rp = unique_name(rp);
	    copy_file(path, rp);
	}

	path[pathlen] = '\0';
	rp[rplen] = '\0';
    }

    if((chdir(cwd) == -1)) {
	perror("chdir");
	exit(1);
    }
}

void
update_dir(char *path, char *rp)
{
    int pathlen = strlen(path);
    int rplen = strlen(rp);
    char base[PATH_MAX], *sptr, *fname;
    List *head = NULL, *first;
    struct stat st;
    struct dirent *entry;

    DIR *dir = opendir(rp);
    if(dir == NULL) {
	fprintf(stderr, "No such path in the repository: %s\n", path);
	return;
    }

    /* make sure the directory is there */
    if((mkdir(path, 0700)) == -1 && errno != EEXIST ) {
	fprintf(stderr, "Could not create the directory %s\n", rp);
	perror("mkdir");
	exit(1);
    }
    while( (entry = readdir(dir)) != NULL ) {

	
	strncat(rp, "/", 2);
	strncat(rp, entry->d_name, strlen(entry->d_name) + 1);

	strncat(path, "/", 2);
	strncat(path, entry->d_name, strlen(entry->d_name) + 1);

	if((stat(rp, &st)) == -1)
	    continue;
	if(S_ISDIR(st.st_mode)) {
	    if(strcmp(entry->d_name, ".") == 0 ||
	       strcmp(entry->d_name, "..") == 0) {
		path[pathlen] = '\0';
		rp[rplen] = '\0';
		continue;
	    } else {
		update_dir(path, rp);
	    }
	} else {
	    /* Need to keep track of the file names so we can pull out the
	     * most recent */
	    head = add_most_recent(rp, head);
	}

	path[pathlen] = '\0';
	rp[rplen] = '\0';

    }

    /* for each file name in the list of files */
    while((first = dequeue(&head)) != NULL) {
	strncpy(base, first->str, sizeof(base));
	split_path(base, &fname);
	if((sptr = rindex(fname, '.')) == NULL) {
	    perror("Bad string index"); /* shouldn't happen */
	    exit(1);
	}
	*sptr = '\0';
	strncat(path, "/", 2);
	strncat(path, fname, strlen(fname) + 1);

	copy_file(first->str, path);
	path[pathlen] = '\0';
	free(first);
    }

}

char *
get_abs_path(char *name)
{
    char *ptr;
    char *path = (char *)malloc(sizeof(char) * PATH_MAX);
    
    
    if(name[0] == '/') {
	strncpy(path, name, sizeof(char) * PATH_MAX);
    } else {
	if((ptr = getcwd(path, sizeof(char)* PATH_MAX)) == NULL) {
	    perror("getcwd");
	    exit(1);
	} 
	ptr = strncat(path, "/", 2);
	ptr = strncat(path, name, strlen(name) + 1);
    }
    return path;
}

int
set_most_recent(char *path)
{
    char *ptr, *fname;
    DIR *dir;
    struct dirent *entry;
    int max = -1, num;
    char numbuf[PATH_MAX];

    if((ptr = split_path(path, &fname)) == NULL) {
	    fprintf(stderr, "Error from splitpath\n"); /* shouldn't happen */
    }

    if((dir = opendir(path)) == NULL) {
	perror("opendir");
	return -1;
    }

    while((entry = readdir(dir)) != NULL) {
	if((strncmp(fname, entry->d_name, strlen(fname))) == 0) {
	    /* find the last .*/
	    num = get_version_num(entry->d_name);
	    if(num > max) 
		max = num;
		
	}
    }
    fname--;
    *fname= '/';

    sprintf(numbuf, ".%d", num);
    strncat(path, numbuf, strlen(numbuf));
    return 0;
}

int
get_version_num(char *name)
{
    int num;
    char *ptr, *endptr;
    if((ptr = rindex(name, '.')) == NULL) {
	fprintf(stderr, "No version number on: %s\n", name);
	return -1;
    }
    ptr++;

    num = (int)strtol(ptr, &endptr, 10);
    if(*endptr != '\0' || endptr == ptr) {
	fprintf(stderr, "Could not convert %s to a number\n", ptr);
	return -1;
    }
    return num;

}
