#include <stdio.h>
#include <apr_getopt.h>
#include <apr_strings.h>
#include <apr_escape.h>
#include <curl/curl.h>

#include "htdavlock.h"

struct htdavlock_args {
	apr_pool_t *p;
	const char *lockdb;
	const char *prefix;
	const char *base_url;
};

struct htdavlock_fileinfo {
	const char *url;
	const char *locktoken;
	const char *user;
};

static char *
htdavlock_escape(apr_pool_t *p, const char *const_path) {
	char *path = apr_pstrdup(p, const_path);
	const char *sep = "/";
	char *last;
	char *part;
	char *esc_filename = "";

	for (part = apr_strtok(path, sep, &last); 
	     part != NULL;
	     part = apr_strtok(NULL, sep, &last)) {
		const char *esc_part;

		esc_part = apr_pescape_path_segment(p, part);
		esc_filename = apr_pstrcat(p, esc_filename, "/",
					   esc_part, NULL);
	}

	return esc_filename;

}

static int
htdavlock_unlock(void *v, const char *path, const char *val)
{
	struct htdavlock_args *args = v;
	struct htdavlock_fileinfo *hfi;

	hfi = (struct htdavlock_fileinfo *)val;
	(void)htdavlock_httpclient(args->p, hfi->url,
				   hfi->locktoken, hfi->user);

	return TRUE;
}

static void
htdavlock_queue_unlock(apr_pool_t *p, struct htdavlock_args *args, 
		       apr_table_t *filetounlock, const char *path,
		       unsigned int rectype, unsigned char *val)
{
	const char *cpath;
	struct htdavlock_fileinfo *hfi;
	const char *locktoken;
	const char *user;
	const char *url;

	/* Skip prefix */
	if (strstr(path, args->prefix) == path)
		path += strlen(args->prefix);

	cpath = apr_pstrdup(p, path);

	hfi = apr_palloc(p, sizeof(hfi));
	hfi->locktoken = apr_pstrdup(p, htdavlock_locktoken(p, rectype, val));
	hfi->user = apr_pstrdup(p, htdavlock_auth_user(val));
	hfi->url = apr_pstrcat(p, args->base_url,
			       htdavlock_escape(p, path), NULL);

	apr_table_setn(filetounlock, cpath, (const char *)hfi);

	return;
}

int
main(int argc, const char *const argv[])
{
	enum { HTDAVLOCK_LIST, HTDAVLOCK_UNLOCK, HTDAVLOCK_USAGE } mode;
	char errbuf[8192];
	apr_pool_t *p;
	apr_dbm_t *dbm;
	struct htdavlock_args args;
	apr_getopt_t *state;
	char opt;
	const char *opt_arg;
	int verbose = 1;
	apr_table_t *filetodo;
	apr_table_t *filefound;
	apr_table_t *filetounlock;
	apr_datum_t key, val;
	unsigned char rectype;
	const char *path;
	apr_status_t rv;
	int i;

	curl_global_init(CURL_GLOBAL_DEFAULT);

	apr_app_initialize(&argc, &argv, NULL);
	apr_pool_create(&p, NULL);

	args.p = p;
	args.lockdb = HTDAVLOCK_DB_DEFAULT;
	args.prefix = "";
	args.base_url = "http://localhost";

	if ((rv = apr_getopt_init(&state, p, argc, argv)) != APR_SUCCESS)
		errx(1, "apr_getopt_init failed: %s\n",
		     apr_strerror(rv, errbuf, sizeof(errbuf)));


	while ((rv = apr_getopt(state, "d:H:lp:qu",
				&opt, &opt_arg)) == APR_SUCCESS) {
		switch (opt) {
		case 'd':
			args.lockdb = apr_pstrdup(p, opt_arg);
			break;
		case 'H':
			args.base_url = apr_pstrdup(p, opt_arg);
			break;
		case 'l':
			mode = HTDAVLOCK_LIST;
			break;
		case 'p':
			args.prefix = apr_pstrdup(p, opt_arg);
			break;
		case 'q':
			verbose = 0;
			break;
		case 'u':
			mode = HTDAVLOCK_UNLOCK;
			break;
		default:
			mode = HTDAVLOCK_USAGE;
			break;
		}
	}

	if (rv != APR_EOF)
		mode = HTDAVLOCK_USAGE;

	filetodo = apr_table_make(p, argc - state->ind);
	filefound = apr_table_make(p, argc - state->ind);
	filetounlock = apr_table_make(p, argc - state->ind);

	for (i = state->ind; i < argc; i++) 
		apr_table_set(filetodo, argv[i], "");

	if (mode == HTDAVLOCK_UNLOCK && apr_is_empty_table(filetodo))
		mode = HTDAVLOCK_USAGE;

	if (mode == HTDAVLOCK_USAGE)
		errx(1, "Usage:\n"
			"  list locks %s [-d lockdb] -l [file ...]\n"
			"  unlock     %s [-d lockdb] -u [-H url] "
					"[-p prefix] file ...",
		     argv[0], argv[0]);

	rv = apr_dbm_open(&dbm, args.lockdb,
			  APR_DBM_READONLY, APR_OS_DEFAULT, p);
	if (rv != APR_SUCCESS)
		errx(1, "Error opening database %s: %s", args.lockdb,
		     apr_strerror(rv, errbuf, sizeof(errbuf)));
	
	/* 
	 * Iterate
	 */
	if ((rv = apr_dbm_firstkey(dbm, &key)) != APR_SUCCESS)
		errx(1, "No data in %s", args.lockdb);

	while (key.dptr != NULL) {
		char ptype;
		int i;
		int reclen; 

		if ((rv = apr_dbm_fetch(dbm, key, &val)) != APR_SUCCESS)
			errx(1, "Failed to read data from %s", args.lockdb);

		ptype = key.dptr[0];
		path = apr_pstrndup(p, key.dptr + 1, key.dsize - 1);

		if (!apr_is_empty_table(filetodo) && 
		    apr_table_get(filetodo, path) == NULL)
			goto next;

		apr_table_set(filefound, path, "");

		for (i = 0; i < val.dsize; i += reclen) {
			dav_lock_indirect *ind;
			unsigned char rectype = val.dptr[i++];

			switch (rectype) {
			case DAV_LOCK_DIRECT:
				reclen = sizeof(dav_lock_discovery)
				  + strlen(htdavlock_owner(val.dptr + i))
				  + strlen(htdavlock_auth_user(val.dptr + i));
				break;
			case DAV_LOCK_INDIRECT:
				ind = (dav_lock_indirect *)(val.dptr + i);
				reclen = sizeof(dav_lock_indirect)
				  + ind->key_size;
				break;
			default:
				errx(1, "unexpected data from %s", args.lockdb);
				break;
			}

			switch (mode) {
			case HTDAVLOCK_LIST:
				htdavlock_dump(p, key.dptr, rectype,
					       val.dptr + i, verbose);
				break;
			case HTDAVLOCK_UNLOCK:
				if (rectype == DAV_LOCK_INDIRECT) {
					warnx("Indirect lock on \"%s\", "
					      "from \"%s\"",
					      path, htdavlock_key(ind));
					break;
				}
				htdavlock_queue_unlock(p, &args, filetounlock,
						       path, rectype,
						       val.dptr + i);
				break;
			default:
				errx(1, "unexpected mode %d", mode);
				break;
			}
		}

next:
		if ((rv = apr_dbm_nextkey(dbm, &key)) != APR_SUCCESS)
			errx(1, "Failed to iterate in %s", args.lockdb);
	}

	apr_dbm_close(dbm);

	if (mode == HTDAVLOCK_UNLOCK) {
		rv = apr_table_do(htdavlock_unlock, &args, filetounlock, NULL);
		if (rv != TRUE)
			warnx("Some file were not unlock");
	}

	for (i = state->ind; i < argc; i++) { 
		if (apr_table_get(filefound, argv[i]) == NULL)
			warnx("file \"%s\" not found", argv[i]);
	}

	return 0;
}
		

