/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only */
/* Copyright (c) 2020-2023 Brett Sheffield <bacs@librecast.net> */

#include "globals.h"
#include "help.h"
#include "log.h"
#include "sec.h"
#include "sync.h"
#include <errno.h>
#include <libgen.h>
#include <librecast/mdex.h>
#include <librecast/mtree.h>
#include <librecast/net.h>
#include <librecast/sync.h>
#include <string.h>

/* fall back to CLOCK_REALTIME when Linux-specific CLOCK_TAI is unavailable */
#ifndef CLOCK_TAI
# define CLOCK_TAI CLOCK_REALTIME
#endif

#define NANO 1000000000

static sem_t sem_done;

static void print_stats(lc_stat_t *stats, struct timespec *start, struct timespec *end)
{
	char SI[] = "GMK";
	uint64_t nss, nse;
	double s, bps, eff;
	size_t B = stats->byt_info;
	int mul = 1000 * 1000 * 1000;
	nss = start->tv_nsec + start->tv_sec * NANO;
	nse = end->tv_nsec + end->tv_sec * NANO;
	s = (double)(nse - nss) / NANO;
	bps = (double)B * 8 / s;
	eff = (double)B / stats->byt_gross * 100;
	DEBUG("%zu bytes synced in %0.2fs", B, s);
	for (int i = 0; i < (int)sizeof SI; i++) {
		if (bps > mul || mul == 1) {
			DEBUG(" (%0.0f ", bps / mul);
			if (mul > 1) DEBUG("%c", SI[i]);
			DEBUG("bps)\n");
			break;
		}
		mul /= 1000;
	}
	DEBUG("%zu gross bytes transferred (%.2f %% efficiency)\n", stats->byt_gross, eff);
}

int file_dump(int *argc, char *argv[])
{
	(void) argc; /* unused */
	char *src = argv[0];
	char *smap = NULL;
	size_t sz_s = 0;
	struct stat sbs = {0};
	int rc = -1;
	mtree_t stree = {0};
	if ((smap = lc_mmapfile(src, &sz_s, PROT_READ, MAP_PRIVATE, 0, &sbs)) != MAP_FAILED) {
		if (mtree_init(&stree, sz_s) == -1) goto err_munmap;
		mtree_build(&stree, smap, NULL);
		mtree_hexdump(&stree, stderr);
		mtree_free(&stree);
		munmap(smap, sz_s);
		rc = 0;
	}
	else if (errno == EISDIR) {
		unsigned char root[HASHSIZE] = "";
		rc = mdex_get_directory_root(src, root);
		if (rc == -1) return -1;
		hash_hex_debug(stderr, root, sizeof root);
	}
	return rc;
err_munmap:
	munmap(smap, sz_s);
	return -1;
}

int file_sync(int *argc, char *argv[])
{
	(void) argc; /* unused */
	return lc_syncfilelocal(argv[1], argv[0], NULL, NULL, NULL, 0);
}

static void sighandler(int signo)
{
	(void) signo;
	sem_post(&sem_done);
}

int net_share(int *argc, char *argv[])
{
	lc_ctx_t *lctx;
	lc_share_t *share;
	mdex_t *mdex;
	int flags = 0;
	int rc = 0;
	int shared = 0;

	if (*argc == 0) return EXIT_FAILURE;
	sem_init(&sem_done, 0, 0);
	if (!batchmode && sec_get_symm_key() == -1) return EXIT_FAILURE;
	lctx = lc_ctx_new();
	if (!batchmode) {
		lc_ctx_coding_set(lctx, LC_CODE_SYMM);
		lc_ctx_set_sym_key(lctx, secretkey, sizeof secretkey);
	}
	lc_ctx_ratelimit(lctx, bpslimit, 0);
	mdex = mdex_init(*argc);
	if (verbose) {
		mdex->stream = stderr;
		mdex->debug = MDEX_DEBUG_FILE;
	}
	for (int i = 0; i < *argc; i++) {
		char *rpath = realpath(argv[i], NULL);
		if (mdex_addfile(mdex, rpath, NULL, MDEX_ALIAS|MDEX_RECURSE) == -1) {
			ERROR("%s: '%s': %s\n", basename(progname), argv[i], strerror(errno));
			rc = -1; break;
		}
		free(rpath);
		shared++;
	}
	if (shared > 0) {
		signal(SIGINT, sighandler);
		DEBUG("sharing %zu files\n", mdex->files);
		if (loopback) flags |= LC_SHARE_LOOPBACK;
		share = lc_share(lctx, mdex, ifx, NULL, NULL, flags);
		if (share) {
			sem_wait(&sem_done);
			lc_unshare(share);
		}
		else if (errno == EPERM) {
			ERROR("%s", strerror(errno));
			ERROR(" (sharing files requires root privileges when binary not setuid)\n");
			rc = -1;
		}
		else {
			ERROR("%s\n", strerror(errno));
			rc = -1;
		}
	}
	mdex_free(mdex);
	lc_ctx_free(lctx);
	sem_destroy(&sem_done);
	DEBUG("exiting\n");
	return (rc == -1) ? EXIT_FAILURE : EXIT_SUCCESS;
}

int net_sync(int *argc, char *argv[])
{
	char *dst;
	lc_ctx_t *lctx;
	ssize_t rc = -1;
	struct timespec ts = {0}, te = {0};
	struct stat sb;
	int flags = 0;
	int max = *argc - 1, dstfree = 0;
	if (*argc > 2) {
		/* more than two arguments, last one must exist and be a directory */
		rc = stat(argv[max], &sb);
		if ((rc == -1) || ((sb.st_mode & S_IFMT) != S_IFDIR))
			return help_usage(), EXIT_FAILURE;
		flags |= SYNC_SUBDIR;
	}
	else {
		rc = stat(argv[0], &sb);
		if (rc == -1 && errno != ENOENT) return help_usage(), EXIT_FAILURE;

	}
	dst = realpath(argv[max], NULL);
	if (!dst) dst = argv[max];
	else dstfree = 1;
	sec_drop_privs();
	lc_stat_t stats = {0};
	clock_gettime(CLOCK_TAI, &ts);
	lctx = lc_ctx_new();
	if (!batchmode) {
		lc_ctx_coding_set(lctx, LC_CODE_SYMM);
		lc_ctx_set_sym_key(lctx, secretkey, sizeof secretkey);
	}
	if (verbose) {
		lc_ctx_stream(lctx, stderr);
		lc_ctx_debug(lctx, LCTX_DEBUG_SYNCFILE);
	}
	if (recurse) flags |= SYNC_RECURSE;
	if (setowner) flags |= SYNC_OWNER;
	if (setgroup) flags |= SYNC_GROUP;
	if (setperms) flags |= SYNC_MODE;
	if (settimes) {
		flags |= SYNC_ATIME;
		flags |= SYNC_MTIME;
	}
	if (ifx) lc_ctx_ifx(lctx, ifx);
	lc_ctx_ratelimit(lctx, 0, bpslimit);
	for (int i = 0; i < max; i++) {
		if (!remote && stat(argv[i], &sb) == 0) {
			rc = lc_syncfilelocal(argv[max], argv[i], NULL, NULL, NULL, flags);
			if (rc == -1) break;
		}
		else {
			remote = 1;
			if (!batchmode && !password && sec_get_symm_key() == -1) goto err_free_dst;
			rc = lc_syncfile_hash(lctx, argv[i], dst, NULL, &stats, NULL, flags);
			if (rc == -1) {
				if (password && errno == EBADMSG) {
					ERROR("Unable to decrypt. Wrong password?\n");
				}
				else perror(basename(progname));
			}
		}
	}
	lc_ctx_free(lctx);
	clock_gettime(CLOCK_TAI, &te);
	if (rc != -1 && verbose) print_stats(&stats, &ts, &te);
err_free_dst:
	if (dstfree) free(dst);
	return (rc == -1) ? EXIT_FAILURE : EXIT_SUCCESS;
}
