#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <inttypes.h>
#include <linux/types.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <errno.h>

//#include "../client/ioctl.h"

#include <linux/ioctl.h>
#define CEPH_IOCTL_MAGIC 0x97
#define CEPH_IOC_SYNCIO _IO(CEPH_IOCTL_MAGIC, 5)

void write_pattern()
{
	printf("writing pattern\n");

	uint64_t i;
	int r;

	int fd = open("foo", O_CREAT|O_WRONLY, 0644);
	if (fd < 0) {
	   r = errno;
	   printf("write_pattern: error: open() failed with: %d (%s)\n", r, strerror(r));
	   exit(r);
	}
	for (i=0; i<1048576 * sizeof(i); i += sizeof(i)) {
		r = write(fd, &i, sizeof(i));
		if (r == -1) {
			r = errno;
			printf("write_pattern: error: write() failed with: %d (%s)\n", r, strerror(r));
			break;
		}
	}

	close(fd);
}

int verify_pattern(char *buf, size_t len, uint64_t off)
{
	size_t i;

	for (i = 0; i < len; i += sizeof(uint64_t)) {
		uint64_t expected = i + off;
		uint64_t actual = *(uint64_t*)(buf + i);
		if (expected != actual) {
			printf("error: offset %llu had %llu\n", (unsigned long long)expected,
			       (unsigned long long)actual);
			exit(1);
		}
	}
	return 0;
}

void generate_pattern(void *buf, size_t len, uint64_t offset)
{
	uint64_t *v = buf;
	size_t i;

	for (i=0; i<len / sizeof(v); i++)
		v[i] = i * sizeof(v) + offset;
	verify_pattern(buf, len, offset);
}

int read_file(int buf_align, uint64_t offset, int len, int direct) {

	printf("read_file buf_align %d offset %llu len %d\n", buf_align,
	       (unsigned long long)offset, len);
	void *rawbuf;
	int r;
        int flags;
	int err = 0;

	if(direct)
	   flags = O_RDONLY|O_DIRECT;
	else
	   flags = O_RDONLY;

	int fd = open("foo", flags);
	if (fd < 0) {
	   err = errno;
	   printf("read_file: error: open() failed with: %d (%s)\n", err, strerror(err));
	   exit(err);
	}

	if (!direct)
	   ioctl(fd, CEPH_IOC_SYNCIO);

	if ((r = posix_memalign(&rawbuf, 4096, len + buf_align)) != 0) {
	   printf("read_file: error: posix_memalign failed with %d", r);
	   close(fd);
	   exit (r);
	}

	void *buf = (char *)rawbuf + buf_align;
	memset(buf, 0, len);
	r = pread(fd, buf, len, offset);
	if (r == -1) {
	   err = errno;
	   printf("read_file: error: pread() failed with: %d (%s)\n", err, strerror(err));
	   goto out;
	}
	r = verify_pattern(buf, len, offset);

out:
	close(fd);
	free(rawbuf);
	return r;
}

int read_direct(int buf_align, uint64_t offset, int len)
{
	printf("read_direct buf_align %d offset %llu len %d\n", buf_align,
	       (unsigned long long)offset, len);
	return read_file(buf_align, offset, len, 1);
}

int read_sync(int buf_align, uint64_t offset, int len)
{
	printf("read_sync buf_align %d offset %llu len %d\n", buf_align,
	       (unsigned long long)offset, len);
	return read_file(buf_align, offset, len, 0);
}

int write_file(int buf_align, uint64_t offset, int len, int direct)
{
	printf("write_file buf_align %d offset %llu len %d\n", buf_align,
	       (unsigned long long)offset, len);
	void *rawbuf;
	int r;
        int err = 0;
	int flags;
	if (direct)
	   flags = O_WRONLY|O_DIRECT|O_CREAT;
        else
	   flags = O_WRONLY|O_CREAT;

	int fd = open("foo", flags, 0644);
	if (fd < 0) {
	   int err = errno;
	   printf("write_file: error: open() failed with: %d (%s)\n", err, strerror(err));
	   exit(err);
	}

	if ((r = posix_memalign(&rawbuf, 4096, len + buf_align)) != 0) {
	   printf("write_file: error: posix_memalign failed with %d", r);
	   err = r;
	   goto out_close;
	}

	if (!direct)
	   ioctl(fd, CEPH_IOC_SYNCIO);

	void *buf = (char *)rawbuf + buf_align;

	generate_pattern(buf, len, offset);

	r = pwrite(fd, buf, len, offset);
	close(fd);

	fd = open("foo", O_RDONLY);
	if (fd < 0) {
	   err = errno;
	   printf("write_file: error: open() failed with: %d (%s)\n", err, strerror(err));
	   free(rawbuf);
	   goto out_unlink;
	}
	void *buf2 = malloc(len);
	if (!buf2) {
	   err = -ENOMEM;
	   printf("write_file: error: malloc failed\n");
	   goto out_free;
	}

	memset(buf2, 0, len);
	r = pread(fd, buf2, len, offset);
	if (r == -1) {
	   err = errno;
	   printf("write_file: error: pread() failed with: %d (%s)\n", err, strerror(err));
	   goto out_free_buf;
	}
	r = verify_pattern(buf2, len, offset);

out_free_buf:
	free(buf2);
out_free:
	free(rawbuf);
out_close:
	close(fd);
out_unlink:
	unlink("foo");
	if (err)
	   exit(err);
	return r;
}

int write_direct(int buf_align, uint64_t offset, int len)
{
	printf("write_direct buf_align %d offset %llu len %d\n", buf_align,
	       (unsigned long long)offset, len);
	return write_file (buf_align, offset, len, 1);
}

int write_sync(int buf_align, uint64_t offset, int len)
{
	printf("write_sync buf_align %d offset %llu len %d\n", buf_align,
	       (unsigned long long)offset, len);
	return write_file (buf_align, offset, len, 0);
}

int main(int argc, char **argv)
{
	uint64_t i, j, k;
	int read = 1;
	int write = 1;

	if (argc >= 2 && strcmp(argv[1], "read") == 0)
		write = 0;
	if (argc >= 2 && strcmp(argv[1], "write") == 0)
		read = 0;

	if (read) {
		write_pattern();
		
		for (i = 0; i < 4096; i += 512)
			for (j = 4*1024*1024 - 4096; j < 4*1024*1024 + 4096; j += 512)
				for (k = 1024; k <= 16384; k *= 2) {
					read_direct(i, j, k);
					read_sync(i, j, k);
				}
		
	}
	unlink("foo");
	if (write) {
		for (i = 0; i < 4096; i += 512)
			for (j = 4*1024*1024 - 4096 + 512; j < 4*1024*1024 + 4096; j += 512)
				for (k = 1024; k <= 16384; k *= 2) {
					write_direct(i, j, k);
					write_sync(i, j, k);
				}
	}
	

	return 0;
}