#include "ficl.h"

#if FICL_WANT_FILE
/*
 * fileaccess.c
 *
 * Implements all of the File Access word set that can be implemented in
 * portable C.
 */

static void
pushIor(ficlVm *vm, int success)
{
	int ior;
	if (success)
		ior = 0;
	else
		ior = errno;
	ficlStackPushInteger(vm->dataStack, ior);
}

/* ( c-addr u fam -- fileid ior ) */
static void
ficlFileOpen(ficlVm *vm, char *writeMode)
{
	int fam = ficlStackPopInteger(vm->dataStack);
	int length = ficlStackPopInteger(vm->dataStack);
	void *address = (void *)ficlStackPopPointer(vm->dataStack);
	char mode[4];
	FILE *f;
	char *filename = (char *)malloc(length + 1);
	memcpy(filename, address, length);
	filename[length] = 0;

	*mode = 0;

	switch (FICL_FAM_OPEN_MODE(fam)) {
	case 0:
		ficlStackPushPointer(vm->dataStack, NULL);
		ficlStackPushInteger(vm->dataStack, EINVAL);
	goto EXIT;
	case FICL_FAM_READ:
		strcat(mode, "r");
	break;
	case FICL_FAM_WRITE:
		strcat(mode, writeMode);
	break;
	case FICL_FAM_READ | FICL_FAM_WRITE:
		strcat(mode, writeMode);
		strcat(mode, "+");
	break;
	}

	strcat(mode, (fam & FICL_FAM_BINARY) ? "b" : "t");

	f = fopen(filename, mode);
	if (f == NULL)
		ficlStackPushPointer(vm->dataStack, NULL);
	else {
		ficlFile *ff = (ficlFile *)malloc(sizeof (ficlFile));
		strcpy(ff->filename, filename);
		ff->f = f;
		ficlStackPushPointer(vm->dataStack, ff);

		fseek(f, 0, SEEK_SET);
	}
	pushIor(vm, f != NULL);

EXIT:
	free(filename);
}

/* ( c-addr u fam -- fileid ior ) */
static void
ficlPrimitiveOpenFile(ficlVm *vm)
{
	ficlFileOpen(vm, "a");
}

/* ( c-addr u fam -- fileid ior ) */
static void
ficlPrimitiveCreateFile(ficlVm *vm)
{
	ficlFileOpen(vm, "w");
}

/* ( fileid -- ior ) */
static int
ficlFileClose(ficlFile *ff)
{
	FILE *f = ff->f;
	free(ff);
	return (!fclose(f));
}

/* ( fileid -- ior ) */
static void
ficlPrimitiveCloseFile(ficlVm *vm)
{
	ficlFile *ff = (ficlFile *)ficlStackPopPointer(vm->dataStack);
	pushIor(vm, ficlFileClose(ff));
}

/* ( c-addr u -- ior ) */
static void
ficlPrimitiveDeleteFile(ficlVm *vm)
{
	int length = ficlStackPopInteger(vm->dataStack);
	void *address = (void *)ficlStackPopPointer(vm->dataStack);

	char *filename = (char *)malloc(length + 1);
	memcpy(filename, address, length);
	filename[length] = 0;

	pushIor(vm, !unlink(filename));
	free(filename);
}

/* ( c-addr1 u1 c-addr2 u2 -- ior ) */
static void
ficlPrimitiveRenameFile(ficlVm *vm)
{
	int length;
	void *address;
	char *from;
	char *to;

	length = ficlStackPopInteger(vm->dataStack);
	address = (void *)ficlStackPopPointer(vm->dataStack);
	to = (char *)malloc(length + 1);
	memcpy(to, address, length);
	to[length] = 0;

	length = ficlStackPopInteger(vm->dataStack);
	address = (void *)ficlStackPopPointer(vm->dataStack);

	from = (char *)malloc(length + 1);
	memcpy(from, address, length);
	from[length] = 0;

	pushIor(vm, !rename(from, to));

	free(from);
	free(to);
}

/* ( c-addr u -- x ior ) */
static void
ficlPrimitiveFileStatus(ficlVm *vm)
{
	int status;
	int ior;

	int length = ficlStackPopInteger(vm->dataStack);
	void *address = (void *)ficlStackPopPointer(vm->dataStack);

	char *filename = (char *)malloc(length + 1);
	memcpy(filename, address, length);
	filename[length] = 0;

	ior = ficlFileStatus(filename, &status);
	free(filename);

	ficlStackPushInteger(vm->dataStack, status);
	ficlStackPushInteger(vm->dataStack, ior);
}

/* ( fileid -- ud ior ) */
static void
ficlPrimitiveFilePosition(ficlVm *vm)
{
	ficlFile *ff = (ficlFile *)ficlStackPopPointer(vm->dataStack);
	long ud = ftell(ff->f);
	ficlStackPushInteger(vm->dataStack, ud);
	pushIor(vm, ud != -1);
}

/* ( fileid -- ud ior ) */
static void
ficlPrimitiveFileSize(ficlVm *vm)
{
	ficlFile *ff = (ficlFile *)ficlStackPopPointer(vm->dataStack);
	long ud = ficlFileSize(ff);
	ficlStackPushInteger(vm->dataStack, ud);
	pushIor(vm, ud != -1);
}

/* ( i*x fileid -- j*x ) */
#define	nLINEBUF	256
static void
ficlPrimitiveIncludeFile(ficlVm *vm)
{
	ficlFile *ff = (ficlFile *)ficlStackPopPointer(vm->dataStack);
	ficlCell id = vm->sourceId;
	int  except = FICL_VM_STATUS_OUT_OF_TEXT;
	long currentPosition, totalSize;
	long size;
	ficlString s;
	vm->sourceId.p = (void *)ff;

	currentPosition = ftell(ff->f);
	totalSize = ficlFileSize(ff);
	size = totalSize - currentPosition;

	if ((totalSize != -1) && (currentPosition != -1) && (size > 0)) {
		char *buffer = (char *)malloc(size);
		long got = fread(buffer, 1, size, ff->f);
		if (got == size) {
			FICL_STRING_SET_POINTER(s, buffer);
			FICL_STRING_SET_LENGTH(s, size);
			except = ficlVmExecuteString(vm, s);
		}
	}

	if ((except < 0) && (except != FICL_VM_STATUS_OUT_OF_TEXT))
		ficlVmThrow(vm, except);

	/*
	 * Pass an empty line with SOURCE-ID == -1 to flush
	 * any pending REFILLs (as required by FILE wordset)
	 */
	vm->sourceId.i = -1;
	FICL_STRING_SET_FROM_CSTRING(s, "");
	ficlVmExecuteString(vm, s);

	vm->sourceId = id;
	ficlFileClose(ff);
}

/* ( c-addr u1 fileid -- u2 ior ) */
static void
ficlPrimitiveReadFile(ficlVm *vm)
{
	ficlFile *ff = (ficlFile *)ficlStackPopPointer(vm->dataStack);
	int length = ficlStackPopInteger(vm->dataStack);
	void *address = (void *)ficlStackPopPointer(vm->dataStack);
	int result;

	clearerr(ff->f);
	result = fread(address, 1, length, ff->f);

	ficlStackPushInteger(vm->dataStack, result);
	pushIor(vm, ferror(ff->f) == 0);
}

/* ( c-addr u1 fileid -- u2 flag ior ) */
static void
ficlPrimitiveReadLine(ficlVm *vm)
{
	ficlFile *ff = (ficlFile *)ficlStackPopPointer(vm->dataStack);
	int length = ficlStackPopInteger(vm->dataStack);
	char *address = (char *)ficlStackPopPointer(vm->dataStack);
	int error;
	int flag;

	if (feof(ff->f)) {
		ficlStackPushInteger(vm->dataStack, -1);
		ficlStackPushInteger(vm->dataStack, 0);
		ficlStackPushInteger(vm->dataStack, 0);
		return;
	}

	clearerr(ff->f);
	*address = 0;
	fgets(address, length, ff->f);

	error = ferror(ff->f);
	if (error != 0) {
		ficlStackPushInteger(vm->dataStack, -1);
		ficlStackPushInteger(vm->dataStack, 0);
		ficlStackPushInteger(vm->dataStack, error);
		return;
	}

	length = strlen(address);
	flag = (length > 0);
	if (length && ((address[length - 1] == '\r') ||
	    (address[length - 1] == '\n')))
		length--;

	ficlStackPushInteger(vm->dataStack, length);
	ficlStackPushInteger(vm->dataStack, flag);
	ficlStackPushInteger(vm->dataStack, 0); /* ior */
}

/* ( c-addr u1 fileid -- ior ) */
static void
ficlPrimitiveWriteFile(ficlVm *vm)
{
	ficlFile *ff = (ficlFile *)ficlStackPopPointer(vm->dataStack);
	int length = ficlStackPopInteger(vm->dataStack);
	void *address = (void *)ficlStackPopPointer(vm->dataStack);

	clearerr(ff->f);
	fwrite(address, 1, length, ff->f);
	pushIor(vm, ferror(ff->f) == 0);
}

/* ( c-addr u1 fileid -- ior ) */
static void
ficlPrimitiveWriteLine(ficlVm *vm)
{
	ficlFile *ff = (ficlFile *)ficlStackPopPointer(vm->dataStack);
	size_t length = (size_t)ficlStackPopInteger(vm->dataStack);
	void *address = (void *)ficlStackPopPointer(vm->dataStack);

	clearerr(ff->f);
	if (fwrite(address, 1, length, ff->f) == length)
		fwrite("\n", 1, 1, ff->f);
	pushIor(vm, ferror(ff->f) == 0);
}

/* ( ud fileid -- ior ) */
static void
ficlPrimitiveRepositionFile(ficlVm *vm)
{
	ficlFile *ff = (ficlFile *)ficlStackPopPointer(vm->dataStack);
	size_t ud = (size_t)ficlStackPopInteger(vm->dataStack);

	pushIor(vm, fseek(ff->f, ud, SEEK_SET) == 0);
}

/* ( fileid -- ior ) */
static void
ficlPrimitiveFlushFile(ficlVm *vm)
{
	ficlFile *ff = (ficlFile *)ficlStackPopPointer(vm->dataStack);
	pushIor(vm, fflush(ff->f) == 0);
}

#if FICL_PLATFORM_HAS_FTRUNCATE
/* ( ud fileid -- ior ) */
static void
ficlPrimitiveResizeFile(ficlVm *vm)
{
	ficlFile *ff = (ficlFile *)ficlStackPopPointer(vm->dataStack);
	size_t ud = (size_t)ficlStackPopInteger(vm->dataStack);

	pushIor(vm, ficlFileTruncate(ff, ud) == 0);
}
#endif /* FICL_PLATFORM_HAS_FTRUNCATE */
#endif /* FICL_WANT_FILE */

void
ficlSystemCompileFile(ficlSystem *system)
{
#if !FICL_WANT_FILE
	FICL_IGNORE(system);
#else
	ficlDictionary *dictionary = ficlSystemGetDictionary(system);
	ficlDictionary *environment = ficlSystemGetEnvironment(system);

	FICL_SYSTEM_ASSERT(system, dictionary);
	FICL_SYSTEM_ASSERT(system, environment);

	ficlDictionarySetPrimitive(dictionary, "create-file",
	    ficlPrimitiveCreateFile,  FICL_WORD_DEFAULT);
	ficlDictionarySetPrimitive(dictionary, "open-file",
	    ficlPrimitiveOpenFile,  FICL_WORD_DEFAULT);
	ficlDictionarySetPrimitive(dictionary, "close-file",
	    ficlPrimitiveCloseFile,  FICL_WORD_DEFAULT);
	ficlDictionarySetPrimitive(dictionary, "include-file",
	    ficlPrimitiveIncludeFile,  FICL_WORD_DEFAULT);
	ficlDictionarySetPrimitive(dictionary, "read-file",
	    ficlPrimitiveReadFile,  FICL_WORD_DEFAULT);
	ficlDictionarySetPrimitive(dictionary, "read-line",
	    ficlPrimitiveReadLine,  FICL_WORD_DEFAULT);
	ficlDictionarySetPrimitive(dictionary, "write-file",
	    ficlPrimitiveWriteFile,  FICL_WORD_DEFAULT);
	ficlDictionarySetPrimitive(dictionary, "write-line",
	    ficlPrimitiveWriteLine,  FICL_WORD_DEFAULT);
	ficlDictionarySetPrimitive(dictionary, "file-position",
	    ficlPrimitiveFilePosition,  FICL_WORD_DEFAULT);
	ficlDictionarySetPrimitive(dictionary, "file-size",
	    ficlPrimitiveFileSize,  FICL_WORD_DEFAULT);
	ficlDictionarySetPrimitive(dictionary, "reposition-file",
	    ficlPrimitiveRepositionFile,  FICL_WORD_DEFAULT);
	ficlDictionarySetPrimitive(dictionary, "file-status",
	    ficlPrimitiveFileStatus,  FICL_WORD_DEFAULT);
	ficlDictionarySetPrimitive(dictionary, "flush-file",
	    ficlPrimitiveFlushFile,  FICL_WORD_DEFAULT);

	ficlDictionarySetPrimitive(dictionary, "delete-file",
	    ficlPrimitiveDeleteFile,  FICL_WORD_DEFAULT);
	ficlDictionarySetPrimitive(dictionary, "rename-file",
	    ficlPrimitiveRenameFile,  FICL_WORD_DEFAULT);

#if FICL_PLATFORM_HAS_FTRUNCATE
	ficlDictionarySetPrimitive(dictionary, "resize-file",
	    ficlPrimitiveResizeFile,  FICL_WORD_DEFAULT);

	ficlDictionarySetConstant(environment, "file", FICL_TRUE);
	ficlDictionarySetConstant(environment, "file-ext", FICL_TRUE);
#else /*  FICL_PLATFORM_HAS_FTRUNCATE */
	ficlDictionarySetConstant(environment, "file", FICL_FALSE);
	ficlDictionarySetConstant(environment, "file-ext", FICL_FALSE);
#endif /* FICL_PLATFORM_HAS_FTRUNCATE */

#endif /* !FICL_WANT_FILE */
}