#define _DARWIN_C_SOURCE 1
#define _GNU_SOURCE 1
#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/clonefile.h>

off_t
rpl_lseek (int fd, off_t offset, int whence)
{
#if defined __APPLE__ && defined __MACH__ && defined SEEK_DATA
  if (whence == SEEK_DATA)
    {
      /* If OFFSET points to data, macOS lseek+SEEK_DATA returns the
         start S of the first data region that begins *after* OFFSET,
         where the region from OFFSET to S consists of possibly-empty
         data followed by a possibly-empty hole.  To work around this
         portability glitch, check whether OFFSET is within data by
         using lseek+SEEK_HOLE, and if so return to OFFSET by using
         lseek+SEEK_SET.  Also, contrary to the macOS documentation,
         lseek+SEEK_HOLE can fail with ENXIO if there are no holes on
         or after OFFSET.  What a mess!  */
      off_t next_hole = lseek (fd, offset, SEEK_HOLE);
      if (next_hole < 0)
        return errno == ENXIO ? offset : next_hole;
      if (next_hole != offset)
        whence = SEEK_SET;
    }
#endif
  return lseek (fd, offset, whence);
}
#define lseek rpl_lseek

#define MAX_SIZE (1024 * 1024 * 100)
char msg[MAX_SIZE];

int main(int argc, char ** argv)
{
	// sparse copy test
	int src = open("cc1", O_RDONLY);
	int dst = open("cc1-sparse", O_CREAT | O_TRUNC | O_RDWR, 0700);
	printf("__APPLE__ %u  __MACH__ %u  SEEK_DATA %u\n", __APPLE__, __MACH__, SEEK_DATA);
	printf("src %i  dst %i\n", src, dst);
	//- printf("SET %i  CUR %i  END %i  HOLE %i  DATA %i\n", SEEK_SET, SEEK_CUR, SEEK_END, SEEK_HOLE, SEEK_DATA);

	long long a = 0;
	long long b = 0;
	long long d = 0;
	long long e = 0;
	long long h = 0;
	long long p = 0;
	long long s = -1;
	long long t = 0;
	ssize_t c = 0;
	ssize_t i = 0;
	ssize_t r = 0;
	ssize_t w = 0;
	int ea = 0;
	int eb = 0;
	int ed = 0;
	int ee = 0;
	int eh = 0;
	int ep = 0;

	do
	{
		if (++i >= 10)
		{
			printf("LOOP %zi\n", i);
			break;
		}

		errno = 0;
		d = lseek(src, d, SEEK_DATA);
		ed = errno;
		h = lseek(src, d, SEEK_HOLE);
		eh = errno;
		a = lseek(src, d, SEEK_SET);
		ea = errno;
		b = lseek(dst, d, SEEK_SET);
		eb = errno;
		c = h - d;

		if ((a == -1) || (b == -1) || (d == -1) || (h == -1))
		{
			int handled = 0;

			if ((d == -1) && (ed == ENXIO))
			{
				p = lseek(src, 0, SEEK_END);
				ep = errno;

				if (p == -1)
				{
					printf(
						"lseek(SEEK_END) failed  p %lli %2i %s\n",
						p, ep, strerror(ep)
					);
				}
				else
				{
					e = ftruncate(dst, p);
					ee = errno;

					if (e == -1)
					{
						printf(
							"ftruncate(%lli) failed  %lli %2i %s\n",
							p, e, ee, strerror(ee)
						);
					}
					else
					{
						handled = 1;
					}
				}
			}

			if (!handled)
			{
				printf(
					"lseek failed"
					"  ENXIO %i  EBADF %i  EINVAL %i  EOVERFLOW %i  ESPIPE %i\n"
					"  p %llx\n"
					"  d %llx %2i %s\n"
					"  h %llx %2i %s\n"
					"  a %llx %2i %s\n"
					"  b %llx %2i %s\n",
					ENXIO, EBADF, EINVAL, EOVERFLOW, ESPIPE,
					p,
					d, ed, strerror(ed),
					h, eh, strerror(eh),
					a, ea, strerror(ea),
					b, eb, strerror(eb)
				);
			}

			break;
		}

		if (c < 0)
		{
			printf("negative size c %zi = h %lli - d %lli\n", c, h, d);
			continue;
		}

		if (s == d)
		{
			printf("EOF %llu %llu\n", s, d);
			break;
		}

		s = d;

		if (c > sizeof(msg))
		{
			printf("msg too small %zu %zi\n", sizeof(msg), c);
			break;
		}

		r = read(src, msg, c);
		w = write(dst, msg, c);

		if ((r != c) || (w != c))
		{
			printf("expected %zi  got %zi %zi\n", c, r, w);
			break;
		}

		p = lseek(src, 0, SEEK_CUR);
		t += c;
		printf("c %zx  p %llx  h %llx  d %llx\n", c, p, h, d);
		d = p;
	} while (1);

	close(src);
	close(dst);

	printf("total bytes copied %llx / %llx\n", t, p);

	return 0;
}
