#include "NXFileObject.h"
#include <new>
#include "minizip/unzip.h"
#include <nx/nxfile.h>
#include <assert.h>

class NXZipFile : NXFileObject
{
public:
	NXZipFile(unzFile zip_file);
	~NXZipFile();

	/* NXFileObject implementation */
	ns_error_t Read(void *buffer, size_t bytes_requested, size_t *bytes_read);
	ns_error_t Write(const void *buffer, size_t bytes);
	ns_error_t Seek(uint64_t position);
	ns_error_t Tell(uint64_t *position);
	ns_error_t PeekByte(uint8_t *byte);
	ns_error_t Sync();
	ns_error_t Truncate();
	// TODO(benski) implementation EOF
	// TODO(benski) implement region locking
private:
	unzFile zip_file;
};

NXZipFile::NXZipFile(unzFile zip_file) : zip_file(zip_file)
{
}

NXZipFile::~NXZipFile()
{
	if (zip_file) {
		unzCloseCurrentFile(zip_file);
		unzClose(zip_file);
		zip_file=0;
	}
}

/* NXFileObject implementation */
ns_error_t NXZipFile::Read(void *buffer, size_t bytes_requested, size_t *bytes_read)
{
	int zret = unzReadCurrentFile(zip_file, buffer, (unsigned int)bytes_requested);
	if (zret == 0) {
		if (bytes_read) {
			*bytes_read = 0;
		}
		return NErr_EndOfFile;
	} else if (zret > 0) {
		if (bytes_read) {
			*bytes_read = (size_t)zret;
		}
		return NErr_Success;
	} else {
		if (bytes_read) {
			*bytes_read = 0;
		}
		return NErr_Error;
	}
}

ns_error_t NXZipFile::Write(const void *buffer, size_t bytes)
{
	return NErr_NotImplemented;
}

ns_error_t NXZipFile::Seek(uint64_t position)
{
	// TODO(benski) error check)
	unzSetOffset64(zip_file, position);
	return NErr_Success;
}

ns_error_t NXZipFile::Tell(uint64_t *position)
{
	*position = unzGetOffset64(zip_file);
	return NErr_Success;
}

ns_error_t NXZipFile::PeekByte(uint8_t *byte)
{
	return NErr_NotImplemented;
}

ns_error_t NXZipFile::Sync()
{
	return NErr_NotImplemented;
}

ns_error_t NXZipFile::Truncate()
{
	return NErr_NotImplemented;
}

static voidpf ZCALLBACK unzip_nxfile_open OF((voidpf opaque, const void*  filename, int mode))
{
	nx_file_t f;
	if (NXFileOpenFile(&f, (nx_uri_t)filename, nx_file_FILE_read_binary) != NErr_Success) {
		return 0;
	}
	return f;
}

static uLong ZCALLBACK unzip_nxfile_read OF((voidpf opaque, voidpf stream, void* buf, uLong size))
{
	nx_file_t f = (nx_file_t)stream;
	size_t bytes_read;
	if (NXFileRead(f, buf, size, &bytes_read) != NErr_Success) {
		return 0;
	}
	return (uLong)bytes_read;
}

static int ZCALLBACK unzip_nxfile_close OF((voidpf opaque, voidpf stream))
{
	NXFileRelease((nx_file_t)stream);
	return 0;
}


static ZPOS64_T ZCALLBACK unzip_nxfile_tell OF((voidpf opaque, voidpf stream))
{
	nx_file_t f = (nx_file_t)stream;
	uint64_t position;
	if (NXFileTell(f, &position) == NErr_Success) {
		return (int64_t)position;
	} else {
		return -1;
	}
}

static long ZCALLBACK unzip_nxfile_seek OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int whence))
{
		nx_file_t f = (nx_file_t)stream;
	uint64_t position;
	if (whence == SEEK_SET) {
		position = offset;
	} else if (whence == SEEK_CUR) {
		ns_error_t err = NXFileTell(f, &position);
		if (err != NErr_Success) {
			return -1;
		}
		position += offset;
	} else if (whence == SEEK_END) {
		uint64_t length;
		NXFileLength(f, &length);
		position = length + offset;
	} else {
		return -1;
	}
	ns_error_t err = NXFileSeek(f, position);
	if (err == NErr_Success) {
		return 0;
	} else {
		return -1;
	}
}
#if 0
    open64_file_func    zopen64_file;
    read_file_func      zread_file;
    write_file_func     zwrite_file;
    tell64_file_func    ztell64_file;
    seek64_file_func    zseek64_file;
    close_file_func     zclose_file;
    testerror_file_func zerror_file;
#endif

ns_error_t NXFileOpenZip(nx_file_t *out_file, nx_uri_t filename, nx_string_t extension_hint)
{
#if 0
	typedef struct zlib_filefunc_def_s
{
    open_file_func      zopen_file;
    read_file_func      zread_file;
    write_file_func     zwrite_file;
    tell_file_func      ztell_file;
    seek_file_func      zseek_file;
    close_file_func     zclose_file;
    testerror_file_func zerror_file;
    voidpf              opaque;
} zlib_filefunc_def;
#endif

	zlib_filefunc64_def file_func = {0, };
	file_func.zopen64_file = unzip_nxfile_open;
		file_func.zread_file = unzip_nxfile_read;
		file_func.ztell64_file = unzip_nxfile_tell;
		file_func.zseek64_file = unzip_nxfile_seek;
		file_func.zclose_file = unzip_nxfile_close;

	unzFile zip_file = unzOpen2_64(filename, &file_func);
	if (zip_file == NULL) {
		return NErr_Error;
	}

	unzGoToFirstFile(zip_file);
	// TODO(benski): look for filename with extension_hint as extension
	// TODO(benski): search for anything with extension
	unzOpenCurrentFile(zip_file);

	NXZipFile *nx_zip_file = new (std::nothrow) NXZipFile(zip_file);
	if (!nx_zip_file) {
		unzCloseCurrentFile(zip_file);
		unzClose(zip_file);
		return NErr_OutOfMemory;
	}
	*out_file = (nx_file_t)nx_zip_file;
	return NErr_Success;
}