/*------------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


		#include "io_zip/io_zip.h"
		#include "unzip.h"
		#include "filesystem/io_handle_segment.h"
		#include "filesystem/filesystem.h"
		#include "platform.h"
		#include "log/log.h"

		using namespace GS::IO;


//------------------------------------------------------------------------------
// I/O wrapper for unzip (allows archive access from various I/O systems).
static voidpf	Zip_open_file_func(voidpf opaque, const char *filename, int mode)
{
	__LOG__ << "ZipOpenFileFunc(); filename: " << filename << ", mode: " << mode << ".\n";
	if	(mode & ZLIB_FILEFUNC_MODE_READ)
		return GS::Platform::Get().io->Open(filename);
	if	(mode & ZLIB_FILEFUNC_MODE_WRITE)
		return GS::Platform::Get().io->Open(filename, ModeWrite);
	return NULL;
}
static uLong	Zip_read_file_func(voidpf opaque, voidpf stream, void *buf, uLong size)
{	return ((Handle *)stream)->Read(buf, size);	}
static uLong	Zip_write_file_func(voidpf opaque, voidpf stream, const void *buf, uLong size)
{	return ((Handle *)stream)->Write(buf, size);	}
static int		Zip_close_file_func(voidpf opaque, voidpf stream)
{
	__LOG__ << "ZipCloseFileFunc();\n";
	delete ((Handle *)stream);
	return UNZ_OK;
}
static int		Zip_testerror_file_func(voidpf opaque, voidpf stream)
{	return UNZ_OK;	}

static long		Zip_tell_file_func(voidpf opaque, voidpf stream)
{	return ((Handle *)stream)->Tell();	}
static long		Zip_seek_file_func(voidpf opaque, voidpf stream, uLong offset, int origin)
{
	static Base::SeekRef ref[] = { Base::SeekStart, Base::SeekCurrent, Base::SeekEnd };
	return ((Handle *)stream)->Seek(offset, ref[origin]) == -1 ? -1 : 0;
}
//------------------------------------------------------------------------------

static zlib_filefunc_def Zip_filefunc =
{ Zip_open_file_func, Zip_read_file_func, Zip_write_file_func, Zip_tell_file_func, Zip_seek_file_func, Zip_close_file_func, Zip_testerror_file_func, 0 };

//------------------------------------------------------------------------------
bool			Zip::SetArchive(const char *uri, const char *pass)
{
	if	(zfile)
		unzClose(zfile);
	zfile = NULL;

	if	(!uri)
		return true;

	__LOG_H__ << "Zip::SetArchive() connect to archive '" << uri << "', password: " << (pass ? pass : "(empty)") << ".\n";

	password = pass;
	return uri ? (zfile = unzOpen2(uri, &Zip_filefunc)) != NULL : false;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
uint			Zip::GetCaps() const
{	return CanRead | CanSeek| IsCaseSensitive;	}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Handle			*Zip::Open(const char *path, Mode mode)
{
	if	(!zfile)
		__ERR__(__LOG_E__ << "Cannot open file with no archive support.\n", NULL)

	if	(mode == ModeWrite)
		return NULL;	// unsupported

	/*
		Query the memory file system so that multiple accesses to the same
		compressed file will share a single uncompressed memory buffer.
	*/
	bool is_memory_handle = false;
	Handle *h = memfs->Open(path, mode);

	if	(!h)
		if	(unzLocateFile(zfile, path, 1) == UNZ_OK)		// case-sensitive
		{
			// Get file info.
			unz_file_info info;
			unzGetCurrentFileInfo(zfile, &info, 0, 0, 0, 0, 0, 0);

			if	(info.compression_method == 0)
			{
				unzOpenCurrentFile(zfile);
				size_t offset = (size_t)unzGetCurrentFileZStreamPos64(zfile);
				unzCloseCurrentFile(zfile);

				__LOG__ << "Mapping zip segment to '" << path << "' @" << int(offset) << "\n";
				Handle *zh = (Handle *)unzGetFileStream(zfile);
				h = new HandleSegment(zh, offset, info.uncompressed_size);
			}
			else
			{
				__LOG__ << "Mapping unzipped '" << path << "' to memory...\n";

				// Load the whole file into memory.
				Array <char> data(info.uncompressed_size, Alloc::Filesystem);
				if	(data.GetSize() != info.uncompressed_size)
					__ERR__(__LOG_E__ << "Failed to allocate decompression space for '" << path << "'.\n", NULL)

				int r = password.IsEmpty() ? unzOpenCurrentFile(zfile) : unzOpenCurrentFilePassword(zfile, password);
				if	(r != UNZ_OK)
					__ERR__(__LOG_E__ << "Failed to open file '" << path << "'.\n", NULL)
				if	(unzReadCurrentFile(zfile, (voidp)data.c_ptr(), info.uncompressed_size) != (int)info.uncompressed_size)
					__ERR__(__LOG_E__ << "Failed to read file '" << path << "'.\n", NULL)
				unzCloseCurrentFile(zfile);

				// Write to the support I/O memory filesystem.
				AutoPtr <Handle> wh(memfs->Open(path, ModeWrite));
				if	(wh.IsValid())
					wh->Write(data.c_ptr(), data.GetSize());

				// Open memory based uncompressed file.
				h = memfs->Open(path, mode);
				is_memory_handle = true;
			}
		}

	if	(h)
		if	(ZipHandle *z = new ZipHandle(this))
		{
			// Wrap memory I/O handle.
			if	(is_memory_handle)
			{
				// Increase refcount for this file.
				Pair <String, int> *p = refc_map.Get(path);
				if	(!p)
					p = refc_map.Add(path, 0);
				++p->value;

				z->p = p;
			}

			z->h = h;
			return z;
		}

	return NULL;
}
void			Zip::Close(Handle *h)
{
	if	(ZipHandle *z = (ZipHandle *)h)
	{
		if	(z->p)
		{
			// If refcount for this entry reaches 0, drop it from the support I/O.
			if	(--z->p->value == 0)
			{
				memfs->Delete(z->p->key);
				refc_map.Delete(z->p);
			}
			z->p = NULL;
		}
		z->h = NULL;
	}
}

bool			Zip::Delete(const char *uri)
{	return false;	}

size_t			Zip::Seek(Handle *h, ptrdiff_t offset, SeekRef ref)
{
	if	(ZipHandle *z = (ZipHandle *)h)
		return z->h->Seek(offset, ref);
	return (size_t)-1;
}
size_t			Zip::Tell(Handle *h)
{
	if	(ZipHandle *z = (ZipHandle *)h)
		return z->h->Tell();
	return (size_t)-1;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
size_t			Zip::Read(Handle *h, void *b, size_t size)
{
	if	(ZipHandle *z = (ZipHandle *)h)
		return z->h->Read(b, size);
	return 0;
}
size_t			Zip::Write(Handle *h, const void *b, size_t size)
{	return 0;	}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Zip::Zip(const char *uri, const char *password)
{
	zfile = NULL;
	memfs = new Memory;
	SetArchive(uri, password);
}
Zip::~Zip()
{	SetArchive(NULL, NULL);	}
//------------------------------------------------------------------------------
