/**
  * UAE - The Un*x Amiga Emulator
  *
  * Hardfile emulation for *nix systems
  *
  * Copyright 2003-2006 Richard Drummond
  * Based on hardfile_win32.c
  */

#include "sysconfig.h"
#include "sysdeps.h"

#include "filesys.h"

//#define HDF_DEBUG
#ifdef  HDF_DEBUG
#define DEBUG_LOG write_log ( "%s: ", __func__); write_log
#else
#define DEBUG_LOG(...) do {} while(0)
#endif


static int hdf_seek (struct hardfiledata *hfd, uae_u64 offset)
{
    off_t ret;

    DEBUG_LOG ("called with offset=0x%llx\n", offset);

    if (hfd->offset != hfd->offset2 || hfd->size != hfd->size2) {
	gui_message ("hd: memory corruption detected in seek");
	abort ();
    }

    if (offset >= hfd->size) {
	gui_message ("hd: tried to seek out of bounds! (0x%llx >= 0x%llx)\n", offset, hfd->size);
	abort ();
    }

    offset += hfd->offset;
    if (offset & (hfd->blocksize - 1)) {
	gui_message ("hd: poscheck failed, offset not aligned to blocksize! (0x%llx & 0x%04.4x = 0x%04.4x)\n", offset, hfd->blocksize, offset & (hfd->blocksize - 1));
	abort ();
    }

  if (offset >= 0x80000000 && sizeof (off_t) < sizeof (uae_u64)) {
	DEBUG_LOG ("Failed to seek passed 2GB limit (0x%llx)\n", offset);
	return -1;
    }

    ret = lseek (hfd->handle, offset, SEEK_SET);

    if (ret == -1) {
	DEBUG_LOG ("seek failed\n");
	return -1;
    }

    DEBUG_LOG ("seek okay\n");
    return 0;
}

static void poscheck (struct hardfiledata *hfd, int len)
{
    off_t pos;

    if (hfd->offset != hfd->offset2 || hfd->size != hfd->size2) {
	gui_message ("hd: memory corruption detected in poscheck");
	abort ();
    }

    pos = lseek (hfd->handle, 0, SEEK_CUR);

    if (pos == -1 ) {
	gui_message ("hd: poscheck failed. seek failure, error %d", errno);
	abort ();
    }
    if (len < 0) {
	gui_message ("hd: poscheck failed, negative length! (%d)", len);
	abort ();
    }

    if ((uae_u64)pos < hfd->offset) {
	gui_message ("hd: poscheck failed, offset out of bounds! (0x%llx < 0x%llx)", pos, hfd->offset);
	abort ();
    }
    if ((uae_u64)pos >= hfd->offset + hfd->size || (uae_u64)pos >= hfd->offset + hfd->size + len) {
	gui_message ("hd: poscheck failed, offset out of bounds! (0x%llx >= 0x%llx, LEN=%d)", pos, hfd->offset + hfd->size, len);
	abort ();
    }
    if (pos & (hfd->blocksize - 1)) {
	gui_message ("hd: poscheck failed, offset not aligned to blocksize! (0x%llx & 0x%04.4x = 0x%04.4x\n", pos, hfd->blocksize, pos & hfd->blocksize);
	abort ();
    }
}

int hdf_open (struct hardfiledata *hfd, const char *name)
{
    int handle;

    DEBUG_LOG ("called with name=%s\n",name);

   if ((handle = open (name, hfd->readonly ? O_RDONLY : O_RDWR)) != -1) {
	int i;
	strcpy (hfd->path, name);
	hfd->handle = handle;
	hfd->cache = 0;

	i = strlen (name) - 1;
	while (i >= 0) {
	    if ((i > 0 && (name[i - 1] == '/' || name[i - 1] == '\\')) || i == 0) {
		strcpy (hfd->vendor_id, "UAE");
		strncpy (hfd->product_id, name + i, 15);
		strcpy (hfd->product_rev, "0.2");
		break;
	    }
	    i--;
	}

	hfd->size = hfd->size2 = lseek (handle, 0, SEEK_END);
	hfd->blocksize = 512;
	lseek (handle, 0, SEEK_SET);

	DEBUG_LOG ("okay\n");
	return 1;
    }

    DEBUG_LOG ("failed\n");
    return 0;
}

extern int hdf_dup (struct hardfiledata *dhfd, const struct hardfiledata *shfd)
{
    DEBUG_LOG ("called\n");

    if (shfd->handle >= 0) {
	dhfd->handle = dup (shfd->handle);
    }

    return 0;
}

void hdf_close (struct hardfiledata *hfd)
{
    DEBUG_LOG ("called\n");

    close (hfd->handle);
    hfd->handle = -1;
}

int hdf_read (struct hardfiledata *hfd, void *buffer, uae_u64 offset, int len)
{
    int n;

    DEBUG_LOG ("called with offset=0x%llx len=%d\n", offset, len);

    hfd->cache_valid = 0;
    hdf_seek (hfd, offset);
    poscheck (hfd, len);
    n = read (hfd->handle, buffer, len);

    DEBUG_LOG ("read %d bytes\n", n);

    return n;
}

int hdf_write (struct hardfiledata *hfd, void *buffer, uae_u64 offset, int len)
{
    int n;

    DEBUG_LOG ("called with offset=0x%llx len=%d\n", offset, len);

    hfd->cache_valid = 0;
    hdf_seek (hfd, offset);
    poscheck (hfd, len);
    n = write (hfd->handle, buffer, len);

    DEBUG_LOG ("Wrote %d bytes\n", n);

    return n;
}
