/*
 * Copyright (c) 2003-2008 Hypertriton, Inc. 
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * Basic I/O abstraction routines.
 */
#include 
#include 
#include 
#include 
static AG_Object errorMgr;
void
AG_DataSourceInitSubsystem(void)
{
	AG_ObjectInitStatic(&errorMgr, NULL);
}
void
AG_DataSourceDestroySubsystem(void)
{
	AG_ObjectDestroy(&errorMgr);
}
/* Assign an error callback routine to a data source. */
void
AG_DataSourceSetErrorFn(AG_DataSource *ds, AG_EventFn fn, const char *fmt, ...)
{
	AG_ObjectLock(&errorMgr);
	ds->errorFn = AG_SetEvent(&errorMgr, NULL, fn, NULL);
	AG_EVENT_GET_ARGS(ds->errorFn, fmt);
	AG_ObjectUnlock(&errorMgr);
}
/* Raise a data source exception. */
void
AG_DataSourceError(AG_DataSource *ds, const char *fmt, ...)
{
	va_list args;
	char *msg;
	if (fmt != NULL) {
		va_start(args, fmt);
		Vasprintf(&msg, fmt, args);
		va_end(args);
	} else {
		msg = Strdup(AG_GetError());
	}
	
	AG_ObjectLock(&errorMgr);
	AG_PostEvent(NULL, &errorMgr, ds->errorFn->name, "%s", msg);
	AG_ObjectUnlock(&errorMgr);
}
/* Enable checking of debugging information. */
void
AG_DataSourceSetDebug(AG_DataSource *ds, int flag)
{
	ds->debug = flag;
}
/* Write type identifier for type safety checks. */
void
AG_WriteTypeCode(AG_DataSource *ds, Uint32 type)
{
	Uint32 i = (ds->byte_order == AG_BYTEORDER_BE) ? AG_SwapBE32(type) :
	                                                 AG_SwapLE32(type);
	if (AG_Write(ds, &i, sizeof(i), 1) != 0)
		AG_DataSourceError(ds, NULL);
}
/* Write type identifier for type safety checks (offset). */
void
AG_WriteTypeCodeAt(AG_DataSource *ds, Uint32 type, off_t offs)
{
	Uint32 i = (ds->byte_order == AG_BYTEORDER_BE) ? AG_SwapBE32(type) :
	                                                 AG_SwapLE32(type);
	if (AG_WriteAt(ds, &i, sizeof(i), 1, offs) != 0)
		AG_DataSourceError(ds, NULL);
}
/* Write type identifier for type safety checks (error-check). */
int
AG_WriteTypev(AG_DataSource *ds, Uint32 type)
{
	Uint32 i;
	if (!ds->debug) {
		return (0);
	}
	i = (ds->byte_order == AG_BYTEORDER_BE) ? AG_SwapBE32(type) :
	                                          AG_SwapLE32(type);
	return AG_Write(ds, &i, sizeof(i), 1);
}
/* Check type identifier for type safety checks (error-check). */
int
AG_CheckTypev(AG_DataSource *ds, Uint32 type)
{
	Uint32 i;
	if (!ds->debug) {
		return (0);
	}
	if (AG_Read(ds, &i, sizeof(i), 1) != 0) {
		AG_SetError("Reading type ID: %s", AG_GetError());
		return (-1);
	}
	i = ((ds->byte_order == AG_BYTEORDER_BE) ? AG_SwapBE32(i) :
	                                           AG_SwapLE32(i));
	return (i == type) ? 0 : -1;
}
/*
 * File operations.
 */
static AG_IOStatus
FileRead(AG_DataSource *ds, void *buf, size_t size, size_t nmemb, size_t *rv)
{
	FILE *f = AG_FILE_SOURCE(ds)->file;
	clearerr(f);
	*rv = fread(buf, size, nmemb, f) * size;
	if (*rv < (nmemb*size)) {
		if (ferror(f)) {
			AG_SetError(_("Read error"));
			return (AG_IO_ERROR);
		} else if (feof(f)) {
			return (AG_IO_EOF);
		}
	}
	return (AG_IO_SUCCESS);
}
static AG_IOStatus
FileReadAt(AG_DataSource *ds, void *buf, size_t size, size_t nmemb, off_t pos,
    size_t *rv)
{
	FILE *f = AG_FILE_SOURCE(ds)->file;
	long savedPos;
	savedPos = ftell(f);
	if (fseek(f, pos, SEEK_SET) == -1) {
		goto fail_seek;
	}
	clearerr(f);
	*rv = fread(buf, size, nmemb, f) * size;
	if (*rv < (nmemb*size)) {
		if (fseek(f, savedPos, SEEK_SET) == -1) {
			goto fail_seek;
		}
		if (feof(f)) {
			fseek(f, savedPos, SEEK_SET);
			return (AG_IO_EOF);
		} else if (ferror(f)) {
			AG_SetError(_("Write Error"));
			return (AG_IO_ERROR);
		}
	}
	if (fseek(f, savedPos, SEEK_SET) == -1) {
		goto fail_seek;
	}
	return (AG_IO_SUCCESS);
fail_seek:
	AG_SetError("fseek: failed");
	return (AG_IO_ERROR);
}
static AG_IOStatus
FileWrite(AG_DataSource *ds, const void *buf, size_t size, size_t nmemb,
    size_t *rv)
{
	FILE *f = AG_FILE_SOURCE(ds)->file;
	clearerr(f);
	*rv = fwrite(buf, size, nmemb, f) * size;
	if (*rv < (nmemb*size)) {
		if (ferror(f)) {
			AG_SetError(_("Write error"));
			return (AG_IO_ERROR);
		} else {
			return (AG_IO_EOF);
		}
	}
	return (AG_IO_SUCCESS);
}
static AG_IOStatus
FileWriteAt(AG_DataSource *ds, const void *buf, size_t size, size_t nmemb,
    off_t pos, size_t *rv)
{
	FILE *f = AG_FILE_SOURCE(ds)->file;
	long savedPos;
	savedPos = ftell(f);
	if (fseek(f, pos, SEEK_SET) == -1) {
		goto fail_seek;
	}
	clearerr(f);
	*rv = fwrite(buf, size, nmemb, f) * size;
	if (*rv < (nmemb*size)) {
		fseek(f, savedPos, SEEK_SET);
		if (feof(f)) {
			return (AG_IO_EOF);
		} else {
			AG_SetError(_("Write Error"));
			return (AG_IO_ERROR);
		}
	}
	if (fseek(f, savedPos, SEEK_SET) == -1) {
		goto fail_seek;
	}
	return (AG_IO_SUCCESS);
fail_seek:
	AG_SetError("fseek failed");
	return (AG_IO_ERROR);
}
static off_t
FileTell(AG_DataSource *ds)
{
	return ftell(AG_FILE_SOURCE(ds)->file);
}
static int
FileSeek(AG_DataSource *ds, off_t offs, enum ag_seek_mode mode)
{
	FILE *f = AG_FILE_SOURCE(ds)->file;
	if (fseek(f, (long)offs,
	    (mode == AG_SEEK_SET) ? SEEK_SET :
	    (mode == AG_SEEK_CUR) ? SEEK_CUR :
	    SEEK_END) == -1) {
		AG_SetError("fseek failed");
		return (-1);
	}
	return (0);
}
/*
 * Memory operations.
 */
static AG_IOStatus
CoreRead(AG_DataSource *ds, void *buf, size_t msize, size_t nmemb, size_t *rv)
{
	AG_CoreSource *cs = AG_CORE_SOURCE(ds);
	size_t size = msize*nmemb;
	if (cs->offs+size >= cs->size) {
		AG_SetError("EOF (%u+%u >= %u)", (Uint)cs->offs, (Uint)size,
		    (Uint)cs->size);
		return (AG_IO_EOF);
	}
	memcpy(buf, &cs->data[cs->offs], size);
	*rv = size;
	cs->offs += size;
	return (AG_IO_SUCCESS);
}
static AG_IOStatus
CoreReadAt(AG_DataSource *ds, void *buf, size_t msize, size_t nmemb, off_t pos,
    size_t *rv)
{
	AG_CoreSource *cs = AG_CORE_SOURCE(ds);
	size_t size = msize*nmemb;
	if (pos < 0) {
		AG_SetError("Bad position");
		return (AG_IO_ERROR);
	}
	if (pos+size >= cs->size) {
		AG_SetError("EOF (%u+%u >= %u)", (Uint)pos, (Uint)size,
		    (Uint)cs->size);
		return (AG_IO_EOF);
	}
	memcpy(buf, &cs->data[pos], size);
	*rv = size;
	return (AG_IO_SUCCESS);
}
static AG_IOStatus
CoreWrite(AG_DataSource *ds, const void *buf, size_t msize, size_t nmemb,
    size_t *rv)
{
	AG_CoreSource *cs = AG_CORE_SOURCE(ds);
	size_t size = msize*nmemb;
	if (cs->offs+size >= cs->size) {
		AG_SetError("EOF");
		return (AG_IO_EOF);
	}
	memcpy(&cs->data[cs->offs], buf, size);
	*rv = size;
	cs->offs += size;
	return (AG_IO_SUCCESS);
}
static AG_IOStatus
CoreAutoWrite(AG_DataSource *ds, const void *buf, size_t msize, size_t nmemb,
    size_t *rv)
{
	AG_CoreSource *cs = AG_CORE_SOURCE(ds);
	size_t size = msize*nmemb;
	cs->data = Realloc(cs->data, (cs->size+size));
	memcpy(&cs->data[cs->offs], buf, size);
	cs->size += size;
	cs->offs += size;
	*rv = size;
	return (AG_IO_SUCCESS);
}
static AG_IOStatus
CoreWriteAt(AG_DataSource *ds, const void *buf, size_t msize, size_t nmemb,
    off_t pos, size_t *rv)
{
	AG_CoreSource *cs = AG_CORE_SOURCE(ds);
	size_t size = msize*nmemb;
	if (pos < 0) {
		AG_SetError("Bad position");
		return (AG_IO_ERROR);
	}
	if (pos+size >= cs->size) {
		AG_SetError("EOF");
		return (AG_IO_EOF);
	}
	memcpy(&cs->data[pos], buf, size);
	*rv = size;
	return (AG_IO_SUCCESS);
}
static AG_IOStatus
CoreAutoWriteAt(AG_DataSource *ds, const void *buf, size_t msize, size_t nmemb,
    off_t pos, size_t *rv)
{
	AG_CoreSource *cs = AG_CORE_SOURCE(ds);
	size_t size = msize*nmemb;
	if (pos < 0) {
		AG_SetError("Bad position");
		return (AG_IO_ERROR);
	}
	if (pos+size >= cs->size) {
		cs->data = Realloc(cs->data, (pos+size));
		cs->size = pos+size;
	}
	memcpy(&cs->data[pos], buf, size);
	*rv = size;
	return (AG_IO_SUCCESS);
}
static off_t
CoreTell(AG_DataSource *ds)
{
	return AG_CORE_SOURCE(ds)->offs;
}
static int
CoreSeek(AG_DataSource *ds, off_t offs, enum ag_seek_mode mode)
{
	AG_CoreSource *cs = AG_CORE_SOURCE(ds);
	off_t nOffs;
	switch (mode) {
	case AG_SEEK_SET:
		nOffs = offs;
		break;
	case AG_SEEK_CUR:
		nOffs = cs->offs + offs;
		break;
	case AG_SEEK_END:
	default:
		nOffs = cs->size - offs;
		break;
	}
	if (nOffs < 0 || nOffs >= cs->size) {
		AG_SetError("Bad offset %ld", (long)nOffs);
		return (-1);
	}
	cs->offs = nOffs;
	return (0);
}
/* Default error handler */
static void
ErrorDefault(AG_Event *event)
{
	AG_DataSource *ds = AG_SELF();
	char *errorMsg = AG_STRING(1);
	AG_FatalError("Data source (%p): %s", ds, errorMsg);
/*	free(errorMsg); */
}
void
AG_DataSourceInit(AG_DataSource *ds)
{
	ds->debug = 0;
	ds->byte_order = AG_BYTEORDER_BE;
	ds->rdLast = 0;
	ds->wrLast = 0;
	ds->rdTotal = 0;
	ds->wrTotal = 0;
	ds->read = NULL;
	ds->read_at = NULL;
	ds->write = NULL;
	ds->write_at = NULL;
	ds->tell = NULL;
	ds->seek = NULL;
	ds->close = NULL;
	AG_MutexInitRecursive(&ds->lock);
	AG_DataSourceSetErrorFn(ds, ErrorDefault, "%p", ds);
}
void
AG_CloseDataSource(AG_DataSource *ds)
{
	ds->close(ds);
}
void
AG_DataSourceDestroy(AG_DataSource *ds)
{
	AG_MutexDestroy(&ds->lock);
	Free(ds);
}
AG_DataSource *
AG_OpenFileHandle(FILE *f)
{
	AG_FileSource *fs;
	fs = Malloc(sizeof(AG_FileSource));
	AG_DataSourceInit(&fs->ds);
	fs->path = NULL;
	fs->file = f;
	fs->ds.read = FileRead;
	fs->ds.read_at = FileReadAt;
	fs->ds.write = FileWrite;
	fs->ds.write_at = FileWriteAt;
	fs->ds.tell = FileTell;
	fs->ds.seek = FileSeek;
	fs->ds.close = AG_CloseFile;
	return (&fs->ds);
}
AG_DataSource *
AG_OpenFile(const char *path, const char *mode)
{
	FILE *f;
	if ((f = fopen(path, mode)) == NULL) {
		AG_SetError("Unable to open %s", path);
		return (NULL);
	}
	return AG_OpenFileHandle(f);
}
AG_DataSource *
AG_OpenCore(void *data, size_t size)
{
	AG_CoreSource *cs;
	cs = Malloc(sizeof(AG_CoreSource));
	AG_DataSourceInit(&cs->ds);
	cs->data = (Uint8 *)data;
	cs->size = size;
	cs->offs = 0;
	cs->ds.read = CoreRead;
	cs->ds.read_at = CoreReadAt;
	cs->ds.write = CoreWrite;
	cs->ds.write_at = CoreWriteAt;
	cs->ds.tell = CoreTell;
	cs->ds.seek = CoreSeek;
	cs->ds.close = AG_CloseCore;
	return (&cs->ds);
}
AG_DataSource *
AG_OpenConstCore(const void *data, size_t size)
{
	AG_ConstCoreSource *cs;
	cs = Malloc(sizeof(AG_ConstCoreSource));
	AG_DataSourceInit(&cs->ds);
	cs->data = (const Uint8 *)data;
	cs->size = size;
	cs->offs = 0;
	cs->ds.read = CoreRead;
	cs->ds.read_at = CoreReadAt;
	cs->ds.write = NULL;
	cs->ds.write_at = NULL;
	cs->ds.tell = CoreTell;
	cs->ds.seek = CoreSeek;
	cs->ds.close = AG_CloseCore;
	return (&cs->ds);
}
AG_DataSource *
AG_OpenAutoCore(void)
{
	AG_CoreSource *cs;
	cs = Malloc(sizeof(AG_CoreSource));
	AG_DataSourceInit(&cs->ds);
	cs->data = NULL;
	cs->size = 0;
	cs->offs = 0;
	cs->ds.read = CoreRead;
	cs->ds.read_at = CoreReadAt;
	cs->ds.write = CoreAutoWrite;
	cs->ds.write_at = CoreAutoWriteAt;
	cs->ds.tell = CoreTell;
	cs->ds.seek = CoreSeek;
	cs->ds.close = AG_CloseAutoCore;
	return (&cs->ds);
}
void
AG_SetByteOrder(AG_DataSource *ds, enum ag_byte_order order)
{
	AG_MutexLock(&ds->lock);
	ds->byte_order = order;
	AG_MutexUnlock(&ds->lock);
}
void
AG_SetSourceDebug(AG_DataSource *ds, int enable)
{
	AG_MutexLock(&ds->lock);
	ds->debug = enable;
	AG_MutexUnlock(&ds->lock);
}
void
AG_CloseFile(AG_DataSource *ds)
{
	AG_FileSource *fs = AG_FILE_SOURCE(ds);
	fclose(fs->file);
	Free(fs->path);
	AG_DataSourceDestroy(ds);
}
void
AG_CloseCore(AG_DataSource *ds)
{
	AG_DataSourceDestroy(ds);
}
void
AG_CloseAutoCore(AG_DataSource *ds)
{
	Free(AG_CORE_SOURCE(ds)->data);
	AG_DataSourceDestroy(ds);
}
AG_IOStatus
AG_Read(AG_DataSource *ds, void *ptr, size_t size, size_t nmemb)
{
	AG_IOStatus rv;
	
	AG_MutexLock(&ds->lock);
	if (ds->read == NULL) {
		AG_SetError(_("Illegal operation"));
		return (AG_IO_ERROR);
	}
	rv = ds->read(ds, ptr, size, nmemb, &ds->rdLast);
	ds->rdTotal += ds->rdLast;
	AG_MutexUnlock(&ds->lock);
	return (rv);
}
AG_IOStatus
AG_ReadAt(AG_DataSource *ds, void *ptr, size_t size, size_t nmemb, off_t pos)
{
	AG_IOStatus rv;
	
	AG_MutexLock(&ds->lock);
	if (ds->read_at == NULL) {
		AG_SetError(_("Illegal operation"));
		return (AG_IO_ERROR);
	}
	rv = ds->read_at(ds, ptr, size, nmemb, pos, &ds->rdLast);
	ds->rdTotal += ds->rdLast;
	AG_MutexUnlock(&ds->lock);
	return (rv);
}
AG_IOStatus
AG_Write(AG_DataSource *ds, const void *ptr, size_t size, size_t nmemb)
{
	AG_IOStatus rv;
	
	AG_MutexLock(&ds->lock);
	if (ds->write == NULL) {
		AG_SetError(_("Illegal operation"));
		return (AG_IO_ERROR);
	}
	rv = ds->write(ds, ptr, size, nmemb, &ds->wrLast);
	ds->wrTotal += ds->wrLast;
	AG_MutexUnlock(&ds->lock);
	return (rv);
}
AG_IOStatus
AG_WriteAt(AG_DataSource *ds, const void *ptr, size_t size, size_t nmemb,
    off_t pos)
{
	AG_IOStatus rv;
	
	AG_MutexLock(&ds->lock);
	if (ds->write_at == NULL) {
		AG_SetError(_("Illegal operation"));
		return (AG_IO_ERROR);
	}
	rv = ds->write_at(ds, ptr, size, nmemb, pos, &ds->wrLast);
	ds->wrTotal += ds->wrLast;
	AG_MutexUnlock(&ds->lock);
	return (rv);
}
off_t
AG_Tell(AG_DataSource *ds)
{
	off_t pos;
	
	AG_MutexLock(&ds->lock);
	pos = (ds->tell != NULL) ? ds->tell(ds) : 0;
	AG_MutexUnlock(&ds->lock);
	return (pos);
}
int
AG_Seek(AG_DataSource *ds, off_t pos, enum ag_seek_mode mode)
{
	int rv;
	if (ds->seek == NULL) {
		AG_SetError(_("Illegal operation"));
		return (-1);
	}
	AG_MutexLock(&ds->lock);
	rv = ds->seek(ds, pos, mode);
	AG_MutexUnlock(&ds->lock);
	return (rv);
}