#include "sfl.h"
#include "sflIoObject.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <assert.h>
#include <pspkernel.h>

// なんかMS上のファイルは10個くらいしか同時に開けないようなので
// これは使えんorz
#if 0
struct sflIoDefault_t {
	sflIoObject super;
	SceUID fd;
};
typedef struct sflIoDefault_t sflIoDefault;

static int sflIoDefaultClose(sflIoObject *self);
static int sflIoDefaultRead(sflIoObject *self, void *data, size_t size);
static int sflIoDefaultWrite(sflIoObject *self, const void *data, size_t size);
static int sflIoDefaultLseek32(sflIoObject *self, int offset, int whence);
static sflIoObject *sflIoDefaultClone(sflIoObject *original);

sflIoObject *sflIoObjectDefaultOpen(const char *file, int flags, int mode, int *res)
{
	sflIoDefault *io = malloc(sizeof(sflIoDefault));
	if (!io)
		return 0;

	io->super.close = sflIoDefaultClose;
	io->super.read = sflIoDefaultRead;
	io->super.write = sflIoDefaultWrite;
	io->super.lseek32 = sflIoDefaultLseek32;
	io->super.clone = sflIoDefaultClone;

	io->fd = sceIoOpen(file, flags, mode);
	*res = io->fd;
	if (io->fd < 0) {
		DEBUG_PRINTF("sceIoOpen failed: %d\n", *res);
		free(io);
		return 0;
	}

	return (sflIoObject*)io;
}

static sflIoObject *sflIoDefaultClone(sflIoObject *original) {
	DEBUG_PRINTF("not imprimented\n");
	assert(0);
	return 0;
}

static int sflIoDefaultClose(sflIoObject *self)
{
	sflIoDefault *io = (sflIoDefault*)self;
	int res = sceIoClose(io->fd);
	free(io);
	return res;
}

static int sflIoDefaultRead(sflIoObject *self, void *data, size_t size)
{
	sflIoDefault* io = (sflIoDefault*)self;
	return sceIoRead(io->fd, data, size);
}

static int sflIoDefaultWrite(sflIoObject *self, const void *data, size_t size)
{
	sflIoDefault *io = (sflIoDefault*)self;
	return sceIoWrite(io->fd, data, size);
}

static int sflIoDefaultLseek32(sflIoObject *self, int offset, int whence)
{
	sflIoDefault *io = (sflIoDefault*)self;
	return sceIoLseek32(io->fd, offset, whence);
}
#endif

// ファイル読み込みは open のときだけ
// ファイル書き込みは close のときだけ
// 実際の読み書きは全部メモリにバッファリングする
struct sflIoFullBuffered_t {
	sflIoObject super;
	char *filename;
	int flags;
	int mode;
	int original;
	int pos;
	size_t size;
	unsigned char *data;
	size_t realSize;
};
typedef struct sflIoFullBuffered_t sflIoFullBuffered;

static int sflIoFullBufferedClose(sflIoObject *self);
static int sflIoFullBufferedRead(sflIoObject *self, void *data, size_t size);
static int sflIoFullBufferedWrite(sflIoObject *self, const void *data, size_t size);
static int sflIoFullBufferedLseek32(sflIoObject *self, int offset, int whence);
static sflIoObject *sflIoFullBufferedClone(sflIoObject *original);
static int sflIoFullBufferedWriteClone(sflIoObject *self, const void *data, size_t size);

sflIoObject *sflIoObjectFullBufferedOpen(const char* file, int flags, int mode, int *res)
{
	sflIoFullBuffered *io = malloc(sizeof(sflIoFullBuffered));
	if (!io) {
		DEBUG_PRINTF("malloc failed\n");
		goto error;
	}

	io->filename = malloc(strlen(file) + 1);
	if (!io->filename) {
		DEBUG_PRINTF("malloc failed\n");
		goto error;
	}
	strcpy(io->filename, file);

	io->super.close = sflIoFullBufferedClose;
	io->super.read = sflIoFullBufferedRead;
	io->super.write = sflIoFullBufferedWrite;
	io->super.lseek32 = sflIoFullBufferedLseek32;
	io->super.clone = sflIoFullBufferedClone;

	// TODO 書き込みモードの検証をする
	io->flags = flags;
	io->mode = mode;
	io->original = 1;
	io->pos = 0;

	if (flags & PSP_O_RDONLY) {
		SceUID fd = sceIoOpen(file, flags, mode);
		*res = fd;
		if (fd < 0) {
			DEBUG_PRINTF("sceIoOpen failed 0x%08x\n", *res);
			goto error;
		}
		io->size = sceIoLseek32(fd, 0, SEEK_END);
		io->realSize = (io->size + 63) & ~63;
		io->data = memalign(64, io->realSize);
		if (!io->data) {
			DEBUG_PRINTF("memalign failed, realSize = %d\n", io->realSize);
			sceIoClose(fd);
			goto error;
		}
		sceIoLseek32(fd, 0, SEEK_SET);
		sceIoRead(fd, io->data, io->size);
		sceIoClose(fd);
	} else if (flags & PSP_O_WRONLY) {
		io->size = 0;
		io->realSize = 32768;
		io->data = memalign(64, io->realSize);
		if (!io->data) {
			DEBUG_PRINTF("memalign failed, realSize = %d\n", io->realSize);
			goto error;
		}
	}

	return (sflIoObject*)io;

error:
	if (io) {
		free(io->filename);
		free(io);
	}
	return 0;
}

static int sflIoFullBufferedClose(sflIoObject *self)
{
	sflIoFullBuffered *io = (sflIoFullBuffered*)self;

	int res = 0;

	// TODO 書き込みモードの検証をする
	if (io->flags & PSP_O_WRONLY) {
		DEBUG_PRINTF("writing data\n");
		SceUID fd = sceIoOpen(io->filename, io->flags, io->mode);
		res = fd;
		if (fd < 0) {
			DEBUG_PRINTF("sceIoOpen failed 0x%08x\n", fd);
		} else {
			sceIoLseek32(fd, 0, SEEK_SET);
			sceIoWrite(fd, io->data, io->size);
			sceIoClose(fd);
		}
	}

	if (io->original) {
		free(io->data);
		free(io->filename);
	}

	free(io);

	return res;
}

static int sflIoFullBufferedRead(sflIoObject *self, void *data, size_t size)
{
	sflIoFullBuffered* io = (sflIoFullBuffered*)self;

	if (io->pos + size > io->size)
		size = io->size - io->pos;
	
	if (size > 0)
		memcpy(data, &io->data[io->pos], size);

	io->pos += size;
	return size;
}

static int sflIoFullBufferedWrite(sflIoObject *self, const void *data, size_t size)
{
	sflIoFullBuffered *io = (sflIoFullBuffered*)self;

	// TODO 書き込みの検証をする
	if ((io->flags & SFL_IO_WRITE_ONLY) == 0) {
		DEBUG_PRINTF("read only!\n");
		return -1;
	}
	
	if (io->pos + size > io->realSize) {
		io->realSize *= 2;
		io->data = realloc(io->data, io->realSize);
	}

	memcpy(&io->data[io->pos], data, size);
	io->pos += size;
	if (io->pos > io->size)
		io->size = io->pos;

	return size;
}

static int sflIoFullBufferedLseek32(sflIoObject *self, int offset, int whence)
{
	sflIoFullBuffered *io = (sflIoFullBuffered*)self;
	
	switch(whence) {
		case SEEK_SET:
			io->pos = offset;
			break;
		case SEEK_CUR:
			io->pos += offset;
			break;
		case SEEK_END:
			io->pos = io->size + offset;
			break;
		default:
			DEBUG_PRINTF("invalid whence\n");
			assert(0);
			return -1;
	}

	if (io->pos < 0) {
		io->pos = 0;
	} else if (io->size < io->pos) {
		io->pos = io->size;
	}

	return io->pos;
}

static sflIoObject *sflIoFullBufferedClone(sflIoObject *original)
{
	sflIoFullBuffered *t = (sflIoFullBuffered*)original;
	sflIoFullBuffered *clone = malloc(sizeof(sflIoFullBuffered));
	if (!clone) {
		DEBUG_PRINTF("malloc failed\n");
		return 0;
	}

	*clone = *t;
	clone->original = 0;
	clone->flags = t->flags & ~PSP_O_WRONLY; // write flag off
	clone->super.write = sflIoFullBufferedWriteClone;
	return (sflIoObject*)clone;
}

static int sflIoFullBufferedWriteClone(sflIoObject *self, const void *data, size_t size)
{
	DEBUG_PRINTF("don't write to clone!\n");
	assert(0);
	return -1;
}

// なんかMS上のファイルは10個くらいしか同時に開けないようなので(ry
#if 0
// FILE を使ってみる
struct sflIoBuffered_t {
	sflIoObject super;
	char *filename;
	FILE *fp;
	int flags;
	int original;
};
typedef struct sflIoBuffered_t sflIoBuffered;

static int sflIoBufferedClose(sflIoObject *self);
static int sflIoBufferedRead(sflIoObject *self, void *data, size_t size);
static int sflIoBufferedWrite(sflIoObject *self, const void *data, size_t size);
static int sflIoBufferedLseek32(sflIoObject *self, int offset, int whence);
static sflIoObject *sflIoBufferedClone(sflIoObject *original);
static int sflIoBufferedWriteClone(sflIoObject *self, const void *data, size_t size);

sflIoObject *sflIoObjectBufferedOpen(const char *file, int flags, int mode, int *res, size_t bufsize)
{
	char *tmpflag;
	if (flags & SFL_IO_READ_WRITE) {
		tmpflag = "rb+";
	} else if (flags & SFL_IO_READ_ONLY) {
		tmpflag = "rb";
	} else if (flags & SFL_IO_WRITE_ONLY) {
		tmpflag = "wb";
	} else {
		DEBUG_PRINTF("bad flags %d\n", flags);
		goto error;
	}

	sflIoBuffered *io = malloc(sizeof(sflIoBuffered));
	if (!io) {
		DEBUG_PRINTF("malloc failed: %s\n", file);
		goto error;
	}

	io->filename = malloc(strlen(file) + 1);
	if (!io->filename) {
		DEBUG_PRINTF("malloc failed\n");
		goto free_io;
	}

	strcpy(io->filename, file);
	io->super.close = sflIoBufferedClose;
	io->super.read = sflIoBufferedRead;
	io->super.write = sflIoBufferedWrite;
	io->super.lseek32 = sflIoBufferedLseek32;
	io->super.clone = sflIoBufferedClone;

	io->fp = fopen(file, tmpflag);
	if (!io->fp) {
		DEBUG_PRINTF("fopen failed: %s\n", file);
		goto free_filename;
	}

	if (setvbuf(io->fp, 0, _IOFBF, bufsize) != 0) {
		DEBUG_PRINTF("setvbuf failed: %s\n", file);
		goto close_fp;
	}

	io->flags = flags;
	io->original = 1;

	return (sflIoObject*)io;

close_fp:
	fclose(io->fp);
free_filename:
	free(io->filename);
free_io:
	free(io);
error:
	*res = -1;
	return 0;
}

static int sflIoBufferedClose(sflIoObject *self)
{
	sflIoBuffered *io = (sflIoBuffered*)self;

	if (io->original) {
		free(io->filename);
	}

	int res = fclose(io->fp);
	free(io);

	return res;
}

static int sflIoBufferedRead(sflIoObject *self, void *data, size_t size)
{
	sflIoBuffered *io = (sflIoBuffered*)self;
	return fread(data, 1, size, io->fp);
}

static int sflIoBufferedWrite(sflIoObject *self, const void *data, size_t size)
{
	sflIoBuffered *io = (sflIoBuffered*)self;
	return fwrite(data, 1, size, io->fp);
}

static int sflIoBufferedLseek32(sflIoObject *self, int offset, int whence)
{
	sflIoBuffered *io = (sflIoBuffered*)self;
	fseek(io->fp, offset, whence);
	return ftell(io->fp);
}

static sflIoObject *sflIoBufferedClone(sflIoObject *original)
{
	sflIoBuffered *t = (sflIoBuffered*)original;
	int tf = t->flags & ~SFL_IO_WRITE_ONLY;
	char *tmpflag;
	if (tf & SFL_IO_READ_ONLY) {
		tmpflag = "rb";
	} else {
		DEBUG_PRINTF("bad flags %d\n", tf);
		goto error;
	}

	sflIoBuffered *clone = malloc(sizeof(sflIoBuffered));
	if (!clone) {
		DEBUG_PRINTF("malloc failed\n");
		return 0;
	}

	*clone = *t;
	clone->original = 0;
	clone->flags = tf;
	clone->super.write = sflIoBufferedWriteClone;
	clone->fp = fopen(clone->filename, tmpflag);
	if (!clone->fp) {
		DEBUG_PRINTF("can't open %s\n", clone->filename);
		goto free_clone;
	}
	return (sflIoObject*)clone;

free_clone:
	free(clone);
error:
	return 0;
}

static int sflIoBufferedWriteClone(sflIoObject *self, const void *data, size_t size)
{
	DEBUG_PRINTF("don't write to clone!\n");
	assert(0);
	return -1;
}
#endif

