#include "compat.h"
#include "backuppcd-common.h"
#include <libbackuppcd.h>
#include <libconfig.h>

/* XXX: include safety */
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#define BPC_CLIENT_LIST 0
#define BPC_CLIENT_GET_INCR 1
#define BPC_CLIENT_GET_FULL 2

static char *bpc_hashstr(const unsigned char hash[16]) {
	static char ret[33];
	int i;

	for (i = 0; i < 16; i++) {
		sprintf(ret + (i * 2), "%02x", hash[i]);
	}

	return(ret);
}

static char *bpc_poolpath(const char *pooldir, const unsigned char hash[16], int collidx) {
	static char ret[8192];
	char *hashstr;
	int snprintf_ret;

	hashstr = bpc_hashstr(hash);

	if (collidx < 0) {
		snprintf_ret = snprintf(ret, sizeof(ret), "%s/%s", pooldir, hashstr);
	} else {
		snprintf_ret = snprintf(ret, sizeof(ret), "%s/%s_%i", pooldir, hashstr, collidx);
	}

	if (snprintf_ret >= sizeof(ret)) {
		return(NULL);
	}

	return(ret);
}

static char *bpc_mungepath(const char *path) {
	static char ret[8192];
	char *path_cp, *path_cp_s;
	char *retptr, *tmpbuf;

	if (!path) {
		return(NULL);
	}

	path_cp_s = path_cp = strdup(path);
	retptr = ret;
	ret[0] = '\0';

	if (path[0] == '/') {
		strcat(retptr, "f%2f");
		retptr += 4;
	}

	for (; (tmpbuf = strsep(&path_cp, "/")) != NULL;) {
		if (tmpbuf[0] == '\0') {
			continue;
		}

		*retptr = '/'; retptr++;
		*retptr = 'f'; retptr++;
		*retptr = '\0';
		strcat(retptr, tmpbuf);
		retptr += strlen(tmpbuf);
	}

	if (*retptr == '/') {
		retptr--;
	}
	*retptr = '\0';

	free(path_cp_s);

	return(ret);
}

static int bpc_fileuptodate(const char *pooldir, const char *datadir, struct bpc_fileinfo *finfo) {
	struct stat stbuf;
	char localfile[8192];
	char *poolfile;
	ino_t localinode, poolinode;
	int retval = 0;
	int snprintf_ret, stat_ret;
	int i;

	snprintf_ret = snprintf(localfile, sizeof(localfile), "%s/%s", datadir, bpc_mungepath(finfo->name));
	if (snprintf_ret >= sizeof(localfile)) {
		CHECKPOINT;
		return(0);
	}

	/*
	 * If the local file does not exist, do no further checking.
	 */
	stat_ret = stat(localfile, &stbuf);
	if (stat_ret < 0) {
		CHECKPOINT;
		return(0);
	}

	localinode = stbuf.st_ino;

	/*
	 * Iterate through all the available pool files to see if one matches
	 * our local file's inode
	 */
	for (i = -1; i < 0xffff; i++) {
		poolfile = bpc_poolpath(pooldir, finfo->hash_bpc, i);

		stat_ret = stat(poolfile, &stbuf);
		if (stat_ret < 0) {
			CHECKPOINT;
			break;
		}

		poolinode = stbuf.st_ino;

		if (poolinode == localinode) {
			CHECKPOINT;
			retval = 1;
			break;
		}
	}

	CHECKPOINT;
	return(retval);
}
static int bpc_updatenewfilelist(FILE *fp, struct bpc_fileinfo *finfo) {
	if (fp == NULL || finfo == NULL) {
		return(-1);
	}

	/* XXX: Check to make sure finfo->hash_bpc isn't non-sense */
	

	/*
	 * Print out the required data
	 */
	fprintf(fp, "%s %llu %s\n", bpc_hashstr(finfo->hash_bpc), finfo->size, bpc_mungepath(finfo->name));

	return(0);
}

static int bpc_updatexferlog(FILE *fp, struct bpc_fileinfo *finfo, ...) {
	return(0);
}

static int bpc_updateattrib(const char *pathname, struct bpc_fileinfo *finfo) {
	static FILE *fp;
	static char *lastdir = NULL;
	size_t pathname_len;
	char pathnameparent[16384], attribfile[16384], *tmp;
	int snprintf_ret;
	int needopen;

	if (pathname == NULL || finfo == NULL) {
		return(-1);
	}

	pathname_len = strlen(pathname) + 1;

	if (pathname_len >= sizeof(pathnameparent)) {
		return(-1);
	}

	memcpy(pathnameparent, pathname, pathname_len);

	tmp = strrchr(pathnameparent, '/');
	if (!tmp) {
		return(-1);
	}

	*tmp = '\0';

	needopen = 1;
	if (lastdir) {
		if (strcmp(lastdir, pathnameparent) == 0) {
			needopen = 0;
		}
	}

	if (needopen) {
		snprintf_ret = snprintf(attribfile, sizeof(attribfile), "%s/attrib", lastdir);
		if (snprintf_ret >= sizeof(attribfile)) {
			return(-1);
		}

		if (lastdir) {
			free(lastdir);
		}
		if (fp) {
			fclose(fp);
		}

		lastdir = strdup(pathnameparent);
		fp = fopen(attribfile, "w");
	}

	/* XXX: Write to attrib file */

	return(0);
}

static int print_help(const char *msg) {
	if (msg) {
		fprintf(stderr, "%s\n", msg);
	}

	return(0);
}

int main(int argc, char **argv) {
	BPC_CONN *conn1, *conn2 = NULL;
	struct bpc_fileinfo *finfo = NULL;
	char *host = NULL, *command, *username = "anonymous", *password = NULL;
	char localfile[16384];
	int snprintf_ret;
	int port = BPC_TCP_PORT;
	int mode;
	int ret = 0;
	char *run_curr = "new", *run_last = NULL;
	int lc_ret;
	int uptodate;
	char type_string[][7] = {"dir", "file", "syml", "sock", "fifo", "blk", "chr", "hrdl"};
	char *pooldir = NULL;
	char *datadir = NULL;
	char lastdir[16384], currdir[16384];
	char newfilelistname[16384], xferfilename[16384];
	FILE *newfilelistfp = NULL;

	lc_register_var("Host", LC_VAR_STRING, &host, 'H');
	lc_register_var("Port", LC_VAR_INT, &port, 'P');
	lc_register_var("User", LC_VAR_STRING, &username, 'U');
	lc_register_var("Pass", LC_VAR_STRING, &password, 'p');
	lc_register_var("Pool", LC_VAR_STRING, &pooldir, 'S');
	lc_register_var("DataDir", LC_VAR_STRING, &datadir, 'D');
	lc_register_var("LastRun", LC_VAR_STRING, &run_last, 'r');
	lc_register_var("CurrRun", LC_VAR_STRING, &run_curr, 'R');

	lc_ret = lc_process(argc, argv, "backuppcdc", LC_CONF_SPACE, NULL);

	lc_cleanup();

	if (lc_ret != 0) {
		fprintf(stderr, "Error processing configuration: %s\n", lc_geterrstr());
		return(EXIT_FAILURE);
	}

	if (host == NULL) {
		print_help("Host is required");
		return(EXIT_FAILURE);
	}
	if (password == NULL) {
		print_help("Password is required");
		return(EXIT_FAILURE);
	}
	if (port < 0) {
		print_help("Port is required");
		return(EXIT_FAILURE);
	}
	if (argc == lc_optind) {
		print_help("Command is required");
		return(EXIT_FAILURE);
	}

	command = argv[lc_optind];

	if (strcasecmp(command, "list") == 0) {
		mode = BPC_CLIENT_LIST;
	} else if (strcasecmp(command, "get") == 0) {
		mode = BPC_CLIENT_GET_INCR;
	} else if (strcasecmp(command, "getfull") == 0) {
		mode = BPC_CLIENT_GET_FULL;
	} else {
		fprintf(stderr, "Invalid operation: %s\n", command);
		return(EXIT_FAILURE);
	}

	switch (mode) {
		case BPC_CLIENT_GET_INCR:
			if (run_last == NULL) {
				print_help("Last Run is required");
				return(EXIT_FAILURE);
			}
			if (pooldir == NULL) {
				print_help("PoolDir is required");
				return(EXIT_FAILURE);
			}

		case BPC_CLIENT_GET_FULL:
			if (datadir == NULL) {
				print_help("DataDir is required");
				return(EXIT_FAILURE);
			}

			if (run_last != NULL) {
				snprintf(lastdir, sizeof(lastdir), "%s/%s", datadir, run_last);
			}
			snprintf(currdir, sizeof(currdir), "%s/%s", datadir, run_curr);
			snprintf(newfilelistname, sizeof(newfilelistname), "%s/NewFileList", datadir);
			snprintf(xferfilename, sizeof(xferfilename), "%s/XferLOG", datadir);
			newfilelistfp = fopen(newfilelistname, "w");
	}

	conn1 = bpc_connect(host, port, username, password);
	if (!conn1) {
		fprintf(stderr, "Failed (connect)\n");
		return(EXIT_FAILURE);
	}

	/*
	 * Begin operation
	 */
	switch (mode) {
		case BPC_CLIENT_LIST:
			ret = bpc_list_open(conn1, "/", 1, BPC_HASH_NONE, NULL, NULL);
			break;
		case BPC_CLIENT_GET_INCR:
			ret = bpc_list_open(conn1, "/", 1, BPC_HASH_BPC, NULL, NULL);

			conn2 = bpc_connect(host, port, username, password);
			if (!conn2) {
				fprintf(stderr, "Failed (connect)\n");
				return(EXIT_FAILURE);
			}
			break;
		case BPC_CLIENT_GET_FULL:
			ret = bpc_get_open(conn1, "/", 1, BPC_HASH_NONE, NULL, NULL);
			break;
	}

	if (!ret) {
		fprintf(stderr, "Failed (open)\n");
		return(EXIT_FAILURE);
	}

	/*
	 * Process every entry.
	 */
	while (1) {
		switch (mode) {
			case BPC_CLIENT_LIST:
				finfo = bpc_list(conn1);
				break;
			case BPC_CLIENT_GET_INCR:
				finfo = bpc_list(conn1);
				break;
			case BPC_CLIENT_GET_FULL:
				finfo = bpc_get_head(conn1);
				break;
		}

		if (!finfo) {
			printf("--- end ---\n");
			break;
		}

		printf("[%4s] %06o %6lu %6lu %10llu %12lu (%s) %s",
		       type_string[finfo->type],
		       (unsigned int) finfo->mode,
		       (unsigned long) finfo->uid,
		       (unsigned long) finfo->gid,
		       (unsigned long long) finfo->size,
		       (unsigned long) finfo->mtime,
		       bpc_hashstr(finfo->hash_bpc),
		       finfo->name);
		if (finfo->type == BPC_FILE_SYMLINK || finfo->type == BPC_FILE_HRDLINK) {
			printf(" -> %s", finfo->linkdest);
		}
		printf("\n");

		if (mode == BPC_CLIENT_LIST) {
			continue;
		}

		snprintf_ret = snprintf(localfile, sizeof(localfile), "%s/%s", currdir, bpc_mungepath(finfo->name));
		if (snprintf_ret < 0 || snprintf_ret >= sizeof(localfile)) {
			PRINTERR("Filename too long.  Something is almost definitely wrong, aborting.");
			ret = 0;
			CHECKPOINT;
			break;
		}

		switch (mode) {
			case BPC_CLIENT_GET_INCR:
				if (finfo->type == BPC_FILE_DIR) {
					backuppc_mkdir(localfile);
				}

				/*
				 * Update attrib file
				 */
				bpc_updateattrib(currdir, finfo);

				if (finfo->type != BPC_FILE_REG) {
					break;
				}

				/*
				 * Check to see if the file is up-to-date
				 */
				uptodate = bpc_fileuptodate(pooldir, lastdir, finfo);
				if (uptodate) {
					/* If so, don't copy it again. */
					break;
				}

				/*
				 * Update NewFileList
				 */
				bpc_updatenewfilelist(newfilelistfp, finfo);

				CHECKPOINT;
				SPOTVAR_S(finfo->name);
				ret = bpc_get_open(conn2, finfo->name, 0, BPC_HASH_NONE, NULL, NULL);
				CHECKPOINT;
				if (!ret) {
					CHECKPOINT;
					break;
				}

				CHECKPOINT;
				finfo = bpc_get_head(conn2);
				CHECKPOINT;
				if (!finfo) {
					ret = bpc_get_close(conn2);
					CHECKPOINT;
					break;
				}

				ret = bpc_copyfile(conn2, finfo, localfile, 0);
				if (!ret) {
					CHECKPOINT;
				}

				finfo = bpc_get_head(conn2);
				if (finfo != NULL) {
					ret = 0;
					break;
				}

				ret = bpc_get_close(conn2);
				CHECKPOINT;
				break;
			case BPC_CLIENT_GET_FULL:
				ret = bpc_copyfile(conn1, finfo, localfile, 0);

				CHECKPOINT;
				break;
		}

		if (!ret) {
			printf("---- failed during file copy ----\n");
			break;
		}
	}

	/*
	 * Cleanup
	 */
	switch (mode) {
		case BPC_CLIENT_LIST:
			ret = bpc_list_close(conn1);
			break;
		case BPC_CLIENT_GET_INCR:
			ret = bpc_list_close(conn1);
			break;
		case BPC_CLIENT_GET_FULL:
			ret = bpc_get_close(conn1);
			break;
	}

	if (!ret) {
		fprintf(stderr, "Failed (close).\n");
		return(EXIT_FAILURE);
	}

	bpc_disconnect(conn1);
	if (conn2) {
		bpc_disconnect(conn2);
	}

	fprintf(stderr, "Done.\n");
	return(EXIT_SUCCESS);
}
