/*
For general Scribus (>=1.3.2) copyright and licensing information please refer
to the COPYING file provided with the program. Following this notice may exist
a copyright and/or license notice that predates the release of Scribus 1.3.2
for which a new license (GPL+exception) is in place.
*/

#include <cstdio>
#include <cstdlib>
#include <zlib.h>
#include <QDir>
#include <QFileInfo>

#include "scconfig.h"
#include "scgzfile.h"

const  int ScGzFile::gzipExpansionFactor = 8;

struct  ScGzFileDataPrivate
{
	FILE*  file;
	gzFile gzfile;
};

ScGzFile::ScGzFile(const QString& fileName) : QIODevice(), m_fileName(fileName)
{
	m_fileName = fileName;
	m_data     = NULL;
}

ScGzFile::~ScGzFile(void)
{
	freeData();
}

int ScGzFile::error(void) const
{
	int errCode = 0;
	if (m_data)
		gzerror(m_data->gzfile, &errCode);
	return errCode;
}

bool ScGzFile::errorOccurred(void) const
{
	bool errorOccurred = false;
	if (m_data)
	{
		int errCode = error();
		errorOccurred = (errCode != Z_OK && errCode != Z_STREAM_END);
	}
	return errorOccurred;
}

ScGzFileDataPrivate* ScGzFile::newPrivateData(void)
{
	freeData();
	m_data = (ScGzFileDataPrivate*) malloc(sizeof(ScGzFileDataPrivate));
	if (m_data)
	{
		m_data->file   = NULL;
		m_data->gzfile = NULL;
	}
	return m_data;
}

void ScGzFile::freeData(void)
{
	if (m_data)
	{
		free(m_data);
		m_data = NULL;
	}
}

bool ScGzFile::gzFileOpen(QString fileName, ScGzFileDataPrivate* data, QIODevice::OpenMode mode)
{
	bool success = false;
	FILE* file   = NULL;
	gzFile gzf   = NULL;
	QString localPath = QDir::toNativeSeparators(fileName);
#if defined(_WIN32)
	if (mode == QIODevice::ReadOnly)
		file = _wfopen((const wchar_t*) localPath.utf16(), L"rb");
	else if (mode == QIODevice::WriteOnly)
		file = _wfopen((const wchar_t*) localPath.utf16(), L"wb");
	if (file)
	{
		int fno = _fileno(file);
		gzf = gzdopen(fno,(mode == QIODevice::ReadOnly) ? "rb" : "wb");
		if (!gzf) fclose(file);
	}
#else
	if (mode == QIODevice::ReadOnly)
		file = fopen(localPath.toLocal8Bit().data(), "rb");
	else if (mode == QIODevice::WriteOnly)
		file = fopen(localPath.toLocal8Bit().data(), "wb");
	if (file)
	{
		int fno = fileno(file);
		gzf = gzdopen(fno,(mode == QIODevice::ReadOnly) ? "rb" : "wb");
		if (!gzf) fclose(file);
	}
#endif
	if (gzf)
	{
		data->file   = file;
		data->gzfile = gzf;
		success = true;
	}
	return success;
}

bool ScGzFile::open(QIODevice::OpenMode mode)
{
	bool success = true;
	if (isOpen()) return false;

	freeData();
	if (mode == QIODevice::ReadOnly || mode == QIODevice::WriteOnly)
		m_data = newPrivateData();
	if (!m_data) return false;

	bool opened = gzFileOpen(m_fileName, m_data, mode);
	if (opened)
	{
		opened &= QIODevice::open(mode);
		if (!opened)
			this->close();
		success = opened;
	}
	return success;
}

bool ScGzFile::reset(void)
{
	if (!m_data || (openMode() != QIODevice::ReadOnly))
		return false;

	int res = gzrewind(m_data->gzfile);
	QIODevice::reset();
	return (res == 0);
}

bool ScGzFile::atEnd() const
{
	if (m_data)
	{
		if (openMode() == QIODevice::ReadOnly)
		{
			int result = gzeof(m_data->gzfile);
			return result;
		}
		return QIODevice::atEnd();
	}
	return QIODevice::atEnd();
}

qint64 ScGzFile::readData (char * data, qint64 maxSize)
{
	if (!m_data || (openMode() != QIODevice::ReadOnly))
		return 0;

	int result = gzread(m_data->gzfile, data, maxSize);
	return result;
}

qint64 ScGzFile::writeData (const char * data, qint64 dataLen)
{
	if (!m_data || (openMode() != QIODevice::WriteOnly))
		return false;

    int result = gzwrite(m_data->gzfile, data, dataLen);
	return result;
}

void ScGzFile::close()
{
	if (!isOpen() || !m_data)
		return;

	gzclose(m_data->gzfile);
	QIODevice::close();
	freeData();
}

bool ScGzFile::readFromFile(const QString& filename, QByteArray& bArray, uint maxBytes)
{
	int i;
	uint maxB = 0xFFFFFFFF;
	if (maxBytes != 0)
		maxB = maxBytes;
	bArray.resize(0);
	ScGzFile gzFile(filename);
	if(!gzFile.open(QIODevice::ReadOnly))
	{ 
		return false; // FIXME: Needs better error return
	}
	// Allocate a buffer of a multiple of the compressed size of the file
	// as a starting point for loading. We'll expand this buffer by powers
	// of two if we run out of space.
	const QFileInfo fi(filename);
	uint bufSize = qMin<uint>(4096, fi.size()*gzipExpansionFactor);
	bArray.resize(bufSize);
	char* buf = bArray.data();
	uint bytesRead = 0;
	// While there's free space, read into the buffer....
	while ((i = gzFile.read(buf,bufSize-bytesRead-1)) > 0)
	{
		// Ensure the string is null-terminated and move the
		// write pointer to the current position.
		buf[i]=0;
		buf+=i;
		bytesRead += i;
		// And check that there's free space to work with, expanding the
		// buffer if there's not.
		if (bufSize - bytesRead < 4096)
		{
			bufSize *= 2;
			bArray.resize(bufSize);
			buf = bArray.data() + bytesRead;
		}
		if (maxB >= 0 && bytesRead >= maxB)
			break;
	}
	gzFile.close();
	return (bArray.size() > 0);
}

bool ScGzFile::writeToFile(const QString& filename, const QByteArray& bArray)
{
	ScGzFile gzFile(filename);
	if (gzFile.open(QIODevice::WriteOnly))
	{
		int res = gzFile.write(bArray.data(), bArray.size());
		gzFile.close();
		return (res > 0 && (res == bArray.size()));
	}
	return false;
}

bool ScGzFile::writeToFile(const QString& filename, const QByteArray& bArray, const char* header)
{
	ScGzFile gzFile(filename);
	if(gzFile.open(QIODevice::WriteOnly))
	{
		int res1 = gzFile.write(header, strlen(header));
		int res2 = gzFile.write(bArray.data(), bArray.size());
		gzFile.close();
		bool done1 = (res1 > 0 && (res1 == static_cast<int>(strlen(header))));
		bool done2 = (res2 > 0 && (res2 == bArray.size()));
		return (done1 && done2);
	}
	return false;
}
