/*
 * Copyright (c) 2005-2006 Tom N Harris.
 *
 *     This program is free software; you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation; either version 2 of the License, or
 *     (at your option) any later version.
 *
 *     A copy of the GNU General Public License can be found at
 *     http://www.gnu.org/
 *
 * Written by Tom N Harris <telliamed@whoopdedo.org>.
 *
 */

#include "ZipArchive.h"

#include <cstring>
#include <cstdlib>

#include "zlib/ioapi.h"
#include "zlib/wioapi.h"

using namespace zLib;
using namespace std;

ZipArchive::~ZipArchive()
{
	Close();
}

ZipArchive::ZipArchive()
	: m_zf(NULL),
	  m_globalcomment(NULL),
	  m_method(Z_DEFLATED), m_level(8),
	  m_filename(NULL),
	  m_eflocal(NULL), m_eflocalsize(0),
	  m_efglobal(NULL), m_efglobalsize(0),
	  m_comment(NULL),
	  m_status(zip_archive_closed)
{
	memset(&m_currfile, 0, sizeof(m_currfile));
}

int ZipArchive::Close(void)
{
	int err = ZIP_OK;
	switch (m_status)
	{
	  case zip_archive_error:
	  case zip_archive_closed:
		break;
	  case zip_archive_fileopen:
	  {
		int e = openfile();
		if (e != ZIP_OK && errno == ZIP_OK)
			err = e;
	  }
	  case zip_archive_filewrite:
	  {
		int e = closefile();
		if (e != ZIP_OK && errno == ZIP_OK)
			err = e;
	  }
	  case zip_archive_open:
	  {
		int e = zipClose(m_zf, m_globalcomment);
		if (e != ZIP_OK && errno == ZIP_OK)
			err = e;
		m_zf = NULL;
		if (m_globalcomment)
		{
			free(m_globalcomment);
			m_globalcomment = NULL;
		}
		m_status = zip_archive_closed;
		break;
	  }
	}
	return err;
}

int ZipArchive::Open(const char* fname, int append)
{
	int err;
	err = Close();
	if (err != ZIP_OK)
		return err;

	zlib_filefunc_def filefuncs;
	fill_fopen_filefunc(&filefuncs);
	m_zf = zipOpen2(fname, append, NULL, &filefuncs);
	if (m_zf == NULL)
	{
		return ZIP_ERRNO;
	}

	m_status = zip_archive_open;
	return ZIP_OK;
}

int ZipArchive::Open(const wchar_t* fname, int append)
{
	int err;
	err = Close();
	if (err != ZIP_OK)
		return err;

	zlib_filefunc_def filefuncs;
	fill_wfopen_filefunc(&filefuncs);
	m_zf = zipOpen2(reinterpret_cast<const char*>(fname), append, NULL, &filefuncs);
	if (m_zf == NULL)
	{
		return ZIP_ERRNO;
	}

	m_status = zip_archive_open;
	return ZIP_OK;
}

int ZipArchive::SetComment(const char* comment)
{
	if (m_status == zip_archive_error 
	 || m_status == zip_archive_closed)
		return ZIP_ERRNO;

	unsigned long len = strlen(comment) + 1;
	char* buf = reinterpret_cast<char*>(malloc(len));
	strcpy(buf, comment);
	if (m_globalcomment)
		free(m_globalcomment);
	m_globalcomment = buf;
	return ZIP_OK;
}

int ZipArchive::AddFile(const char* fname, unsigned long ftime, unsigned long attr)
{
	int err = ZIP_OK;
	switch (m_status)
	{
	case zip_archive_error:
	case zip_archive_closed:
		return ZIP_ERRNO;
	case zip_archive_fileopen:
		err = openfile();
		if (err != ZIP_OK)
		{
			return err;
		}
	case zip_archive_filewrite:
		err = closefile();
		if (err != ZIP_OK)
		{
			return err;
		}
	case zip_archive_open:
		break;
	}

	unsigned long len = strlen(fname) + 1;
	char* buf = reinterpret_cast<char*>(malloc(len));
	strcpy(buf, fname);
	if (m_filename)
		free(m_filename);
	m_filename = buf;

	m_currfile.dosDate = ftime;
	m_currfile.external_fa = attr;

	m_status = zip_archive_fileopen;
	return ZIP_OK;
}

int ZipArchive::SetFileComment(const char* comment)
{
	if (m_status != zip_archive_fileopen) 
		return ZIP_ERRNO;

	unsigned long len = strlen(comment) + 1;
	char* buf = reinterpret_cast<char*>(malloc(len));
	strcpy(buf, comment);
	if (m_comment)
		free(m_comment);
	m_comment = buf;
	return ZIP_OK;
}

int ZipArchive::CloseFile(void)
{
	int err = ZIP_OK;
	switch (m_status)
	{
	case zip_archive_error:
	case zip_archive_closed:
	case zip_archive_open:
		return ZIP_ERRNO;
	case zip_archive_fileopen:
		err = openfile();
		if (err != ZIP_OK)
		{
			return err;
		}
	case zip_archive_filewrite:
		break;
	}
	return closefile();
}

int ZipArchive::AddExtraField(unsigned short tag, const char* ldata, unsigned long lsize, const char* gdata, unsigned long gsize)
{
	if (m_status != zip_archive_fileopen)
		return ZIP_ERRNO;

	zip_extrafield_tag loc, cid;
	cid.tag = loc.tag = tag;
	loc.blksz = lsize;
	cid.blksz = gsize;

	unsigned long locsz, cidsz;
	char* locbuf = NULL;
	char* cidbuf = NULL;

	if (ldata)
	{
		locsz = m_eflocalsize + lsize + sizeof(zip_extrafield_tag);
		locbuf = reinterpret_cast<char*>(realloc(m_eflocal, locsz));
		if (!locbuf)
		{
			return Z_MEM_ERROR;
		}
		char* p = locbuf + m_eflocalsize;
		memcpy(p, &loc, sizeof(zip_extrafield_tag));
		p += sizeof(zip_extrafield_tag);
		memcpy(p, ldata, lsize);
	}
	if (gdata)
	{
		cidsz = m_efglobalsize + gsize + sizeof(zip_extrafield_tag);
		cidbuf = reinterpret_cast<char*>(realloc(m_efglobal, cidsz));
		if (!cidbuf)
		{
			return Z_MEM_ERROR;
		}
		char* p = cidbuf + m_efglobalsize;
		memcpy(p, &cid, sizeof(zip_extrafield_tag));
		p += sizeof(zip_extrafield_tag);
		memcpy(p, gdata, gsize);
	}
	if (locbuf)
	{
		m_eflocal = locbuf;
		m_eflocalsize = locsz;
	}
	if (cidbuf)
	{
		m_efglobal = cidbuf;
		m_efglobalsize = cidsz;
	}

	return ZIP_OK;
}

int ZipArchive::Write(const char* data, unsigned long nbytes)
{
	int err = ZIP_OK;
	switch (m_status)
	{
	case zip_archive_error:
	case zip_archive_closed:
	case zip_archive_open:
		return ZIP_ERRNO;
	case zip_archive_fileopen:
		err = openfile();
		if (err != ZIP_OK)
		{
			return err;
		}
	case zip_archive_filewrite:
		break;
	}

	err = zipWriteInFileInZip(m_zf, data, nbytes);
	return err;
}

int ZipArchive::openfile(void)
{
	if (m_status != zip_archive_fileopen)
		return ZIP_INTERNALERROR;
	if (m_filename == NULL)
		return ZIP_INTERNALERROR;

	int err = zipOpenNewFileInZip3(m_zf, m_filename, &m_currfile,
		  m_eflocal, m_eflocalsize, m_efglobal, m_efglobalsize,
		  m_comment, m_method, m_level, 
		  0, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
		  NULL, 0);
	
	m_status = (err == ZIP_OK) ? zip_archive_filewrite : zip_archive_open;
	if (m_comment != NULL)
	{
		free(m_comment);
		m_comment = NULL;
	}
	m_efglobalsize = 0;
	if (m_efglobal != NULL)
	{
		free(m_efglobal);
		m_efglobal = NULL;
	}
	m_eflocalsize = 0;
	if (m_eflocal != NULL)
	{
		free(m_eflocal);
		m_eflocal = NULL;
	}
	free(m_filename);
	m_filename = NULL;
	memset(&m_currfile, 0, sizeof(m_currfile));

	return err;
}

int ZipArchive::closefile(void)
{
	if (m_status != zip_archive_filewrite)
		return ZIP_INTERNALERROR;
	
	int err = zipCloseFileInZip(m_zf);

	m_status = zip_archive_open;
	return err;
}

