#include "stdafx.h"
#include "io.h"

struct IO_CTX {
	int   fd;
	off_t off;
	int   len;
	ACL_FIBER_SEM *sem;
};

struct THREAD {
	pthread_t id;
	int from;
	int to;
	off_t  off;
	size_t len;
	int nfibers;
	char action[128];
	int event_type;
};

struct FIBER {
	struct THREAD *thread;
	off_t  off;
	size_t len;
};

static int __debug = 0;

#define	EQ(x, y)	!strcasecmp((x), (y))

static void  fiber_main(ACL_FIBER *fb acl_unused, void *ctx)
{
	struct FIBER *fiber = (struct FIBER*) ctx;
	ssize_t ret ;

	// For multiple threads with multiple coroutines, pread_pwrite and
	// splice is safety, and sendfile can only be used to copy file
	// in one fiber of one single thread!
	// When copying large file, the splice_copy will be more efficietly
	// than pread_pwrite, because splice_copy won't copy data between
	// kernel and userspace, and pread_pwrite will copy data from kernel
	// to userspace and from userspace to kernel.

	if (EQ(fiber->thread->action, "pread_pwrite")) {
		ret = pread_pwrite(fiber->thread->from, fiber->thread->to,
				fiber->off, fiber->len);
	} else if(EQ(fiber->thread->action, "splice")) {
		int pipefd[2];
		if (pipe(pipefd) == -1) {
			printf("create pipe error %s\r\n", strerror(errno));
			return;
		}
		ret = splice_copy(pipefd, fiber->thread->from,
				fiber->thread->to, fiber->off, fiber->len);
		close(pipefd[0]);
		close(pipefd[1]);
	} else if(EQ(fiber->thread->action, "sendfile")) {
		ret = sendfile_copy(fiber->thread->from, fiber->thread->to,
				fiber->off, fiber->len);
	} else {
		printf("Unkown action=%s\r\n", fiber->thread->action);
		return;
	}

	if (ret != (ssize_t) fiber->len) {
		printf("Copy error %s, ret=%zd, len=%zd\r\n",
			strerror(errno), ret, fiber->len);
		assert(0);
	} else if (__debug) {
		printf("Copy ok ret=%zd, len=%zd, off=%ld\r\n",
			ret, fiber->len, fiber->off);
	}
}

static void *thread_main(void *ctx)
{
	struct THREAD *thr = (struct THREAD*) ctx;
	struct FIBER  *fibers;
	size_t step, mod;
	int i;

	assert(thr->len > 0);
	step = thr->len / thr->nfibers;
	mod  = thr->len % thr->nfibers;

	if (step == 0) {
		thr->nfibers = 1;
		step = mod;
		mod  = 0;
	}

	int n = mod == 0 ? thr->nfibers : (thr->nfibers + 1);
	fibers = (struct FIBER*) calloc(sizeof(struct FIBER), n);

	for (i = 0; i < thr->nfibers; i++) {
		fibers[i].thread = thr;
		fibers[i].off = thr->off + step * i;
		fibers[i].len = step;
		acl_fiber_create(fiber_main, &fibers[i], 320000);
	}

	if (mod > 0) {
		fibers[i].thread = thr;
		fibers[i].off = thr->off + step * i;
		fibers[i].len = mod;
		acl_fiber_create(fiber_main, &fibers[i], 320000);
	}

	acl_fiber_schedule_with(thr->event_type);
	free(fibers);
	return NULL;
}

static size_t get_filesize(const char *filepath)
{
	struct stat statbuf;

	if (stat(filepath, &statbuf) == -1) {
		printf("stat %s error %s\r\n", filepath, strerror(errno));
		return 0;
	}

	return statbuf.st_size;
}

static int start(const char *frompath, const char *topath,
	int nthreads, int nfibers, const char *action, int event_type)
{
	struct THREAD *threads;
	int fromfd, tofd, i;
	size_t size, step, mod;

	size = get_filesize(frompath);
	if (size == 0) {
		printf("Invalid filesize=%zd\r\n", size);
		return -1;
	}

	step = size / nthreads;
	mod  = size % nthreads;
	if (step == 0) {
		nthreads = 1;
		step = mod;
		mod  = 0;
	}

	fromfd = open(frompath, O_RDONLY, 0600);
	if (fromfd == -1) {
		printf("open %s error %s\r\n", frompath, strerror(errno));
		return -1;
	}

	//tofd = open(topath, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600);
	tofd = open(topath, O_WRONLY | O_CREAT | O_TRUNC, 0600);
	if (tofd == -1) {
		printf("open %s error %s\r\n", topath, strerror(errno));
		close(fromfd);
		return -1;
	}

	int n = mod == 0 ? nthreads : (nthreads + 1);
	threads = (struct THREAD*) calloc(sizeof(struct THREAD), n);

	for (i = 0; i < nthreads; i++) {
		threads[i].from = fromfd;
		threads[i].to   = tofd;
		threads[i].off  = step * i;
		threads[i].len  = step;
		threads[i].nfibers = nfibers;
		snprintf(threads[i].action, sizeof(threads[i].action),
			"%s", action);
		threads[i].event_type = event_type;

		int ret = pthread_create(&threads[i].id, NULL,
				thread_main, &threads[i]);
		if (ret != 0) {
			printf("Create thread error=%s\r\n", strerror(ret));
			exit(1);
		}
	}

	if (mod > 0) {
		threads[i].from = fromfd;
		threads[i].to   = tofd;
		threads[i].off  = step * i;
		threads[i].len  = mod;
		threads[i].nfibers = nfibers;
		snprintf(threads[i].action, sizeof(threads[i].action),
			"%s", action);
		threads[i].event_type = event_type;

		int ret = pthread_create(&threads[i].id, NULL,
				thread_main, &threads[i]);
		if (ret != 0) {
			printf("Create thread error=%s\r\n", strerror(ret));
			exit(1);
		}
	}

	for (i = 0; i < n; i++) {
		pthread_join(threads[i].id, NULL);
	}

	free(threads);

	close(fromfd);
	close(tofd);
	return 0;
}

static void usage(const char *proc)
{
	printf("usage: %s -h [help] -d [if show debug info]\r\n"
		"  -e event_type[kernel|select|poll|io_uring]\r\n"
		"  -s from_filepath\r\n"
		"  -d to_filepath\r\n"
		"  -a action[pread_pwrite|sendfile|splice]\r\n"
		"  -t nthreads\r\n"
		"  -c nfibers\r\n"
		, proc);
}

int main(int argc, char *argv[])
{
	int ch, nthreads = 1, nfibers = 1, event_type = FIBER_EVENT_KERNEL;
	char from[256], to[256], action[128];

	snprintf(from, sizeof(from), "from.txt");
	snprintf(to, sizeof(to), "to.txt");
	snprintf(action, sizeof(action), "pread_pwrite");

	while ((ch = getopt(argc, argv, "hDe:s:d:a:t:c:")) > 0) {
		switch (ch) {
		case 'h':
			usage(argv[0]);
			return 0;
		case 'D':
			__debug = 1;
			break;
		case 'e':
			if (EQ(optarg, "io_uring")) {
				event_type = FIBER_EVENT_IO_URING;
			} else if (EQ(optarg, "poll")) {
				event_type = FIBER_EVENT_POLL;
			} else if (EQ(optarg, "select")) {
				event_type = FIBER_EVENT_SELECT;
			}
			break;
		case 's':
			snprintf(from, sizeof(from), "%s", optarg);
			break;
		case 'd':
			snprintf(to, sizeof(to), "%s", optarg);
			break;
		case 'a':
			snprintf(action, sizeof(action), "%s", optarg);
			break;
		case 't':
			nthreads = atoi(optarg);
			break;
		case 'c':
			nfibers = atoi(optarg);
			break;
		default:
			break;
		}
	}

	if (__debug) {
		acl_fiber_msg_stdout_enable(1);
		acl_msg_stdout_enable(1);
	}

	start(from, to, nthreads, nfibers, action, event_type);
	return 0;
}

