#include "stdafx.h"
#include "volumeloader.h"
#include <QtZlib\zlib.h>

QDebug operator<<(QDebug dbg, const VolumeMetaData& c)
{
	dbg.nospace() << "(type="<< VolumeMetaData::enumToString(c.type)<<",actualbits="<<c.actualBits<<",width=" << c.width<< ",height=" << c.height << ",depth="<<c.depth<<
		",aspectX="<<c.aspectX<<",aspectY="<<c.aspectY<<",aspectZ="<<c.aspectZ<<
		",dataFile="<<c.dataFile<<")";

	return dbg.space();
}

VolumeLoader::VolumeLoader(QObject *parent)
	: QObject(parent)
{

}

VolumeLoader::~VolumeLoader()
{

}

#define VB_GROUP_NAME "VoxelBlurVolume"

bool VolumeLoader::getMetadata(const QFile& metadataFile,VolumeMetaData& metadata) const
{
	if(!metadataFile.exists())
	{
		qWarning()<<"Metadata file "<<metadataFile.fileName()<<" does not exist.";
		return false;
	}
	//metadata is stored in QSettings compatible format
	QSettings s(metadataFile.fileName(),QSettings::Format::IniFormat);

	if(!s.childGroups().contains(VB_GROUP_NAME))
	{
		qWarning()<<"Metadata file "<<metadataFile.fileName()<<" does not contain correct group name "<<VB_GROUP_NAME;
	}
	s.beginGroup(VB_GROUP_NAME);

	QString typeval = s.value("Type","uchar").toString().trimmed();
	QFileInfo fi(metadataFile);

	metadata.name = fi.completeBaseName();
	metadata.type = VolumeMetaData::stringToEnum(typeval);
	metadata.width = s.value("Width").toInt();
	metadata.height = s.value("Height").toInt();
	metadata.depth = s.value("Depth").toInt();
	metadata.aspectX = s.value("AspectX").toFloat();
	metadata.aspectY = s.value("AspectY").toFloat();
	metadata.aspectZ = s.value("AspectZ").toFloat();
	metadata.skipBytes = s.value("SkipBytes",0).toInt();
	metadata.source = s.value("Source","Unknown source").toString();
	metadata.actualBits = s.value("ActualBits",0).toInt();
	//datafile is relative to metadata file
	metadata.dataFile = fi.absoluteDir().absoluteFilePath(s.value("DataFile").toString().trimmed());

	return true;
}

QByteArray VolumeLoader::readVolume(const VolumeMetaData& d) const
{
	QByteArray b;
	QFile file(d.dataFile);
	QFileInfo fi(file);

	if(!fi.exists())
	{
		qWarning()<<"Volume data file "<<d.dataFile<<" does not exist.";
		return b;
	}

	//open for reading
	if(!file.open(QIODevice::ReadOnly))
	{
		qWarning()<<"Could not open volume file "<<d.dataFile<<". Error: " <<file.errorString();
		return b;
	}

	if(fi.suffix()=="gz")
	{
		//gzip compressed file, handle with zlib
		qDebug()<<"Loading gzipped volume file " <<fi.absoluteFilePath();

		bool ok = readGZippedVolume(file,b);
		if(!ok)
		{
			//error already printed
			return QByteArray();
		}


		//check if byte skipping is desired
		if(d.skipBytes>0)
		{
			//skip from the uncompressed QByteArray
			b = b.mid(d.skipBytes);
		}

		//uncomment this for a quick test of the decompression method
		/*
		QFileInfo fi(d.dataFile);
		QString testFile = fi.absoluteDir().absoluteFilePath(fi.completeBaseName().append(".testinflate"));
		qDebug()<<"Writing test to :"<<testFile;
		QFile file(testFile);
		file.open(QIODevice::WriteOnly);
		file.write(b);
		file.close();
		*/
	}
	else
	{
		qDebug()<<"Loading raw volume file " <<fi.absoluteFilePath();
		//check if byte skipping is desired
		if(d.skipBytes>0)
		{
			file.read(d.skipBytes);
		}

		//raw file, read directly
		b = file.readAll();
		//check errors 
		if(int i = file.error())
		{
			qWarning()<<"Error ("<<i<<") while loading volume: "<<file.errorString();
			return QByteArray();
		}
	}

	//sanity test
	Q_ASSERT_X(b.size() == (d.width * d.height * d.depth * 
		(d.type == VolumeMetaData::VolumeDataType::ushort?2:1)),"volume sanity test","Loaded size does not equal expected size");

	//all ok
	return b;
}

//16k buffer
#define BUFSIZE 16384

bool VolumeLoader::readGZippedVolume(QFile& src,QByteArray& dest) const
{
	//qt has zlib compiled in, we can use it directly without further work
	//based on the basic zlib example at http://www.zlib.net/zlib_how.html

	z_stream strm; //zlib stream object

	int ret;
	uint have;
	uchar inbuffer[BUFSIZE];
	uchar outbuffer[BUFSIZE];

	//set basic state
	strm.zalloc = Z_NULL; //default alloc
	strm.zfree = Z_NULL;
	strm.opaque = Z_NULL;
	strm.avail_in = 0; //how much data is after the input pointer
	strm.next_in = Z_NULL; //input pointer

	//initialize zlib for gzip (see zlib manual)
	ret = inflateInit2(&strm,MAX_WBITS + 16);
	if(ret != Z_OK)
	{
		qWarning()<<"zLib error ("<<ret<<"), cant decompress volume";
		return false;
	}

	//outer decompression loop - read data
	do
	{
		//read a chunk of input data
		qint64 read = src.read((char*)inbuffer,BUFSIZE);
		if(int i = src.error())
		{
			qWarning()<<"Error ("<<i<<") while reading from file: "<<src.errorString();
			inflateEnd(&strm);
			return false;
		}
		else if (read == -1)
		{
			//no idea if this can happen without QFile setting the error bit
			qWarning()<<"Error while reading from file, -1 returned";
			inflateEnd(&strm);
			return false;
		}
		strm.avail_in = read; //amount of bytes available
		if(strm.avail_in == 0) //no more data available, finished
			break;

		strm.next_in = inbuffer;

		//inner decompression loop - inflate as much as possible
		do
		{
			//set output pointer + size
			strm.avail_out = BUFSIZE;
			strm.next_out = outbuffer;

			//do the actual decompression
			ret = inflate(&strm,Z_NO_FLUSH);
			Q_ASSERT(ret != Z_STREAM_ERROR); //must never happen
			switch(ret)
			{
			case Z_NEED_DICT: //needs a decompression dictionary, which we dont have. this shouldnt happen with gz files
			case Z_DATA_ERROR:
				qWarning()<<"zLib reported a data error. File is not in valid .gz format or is corrupted.";
				inflateEnd(&strm);
				return false;
			case Z_MEM_ERROR:
				qWarning()<<"zLib reported not enough memory available.";
				inflateEnd(&strm);
				return false;
			}

			//how much data we actually have now in the buffer
			have = BUFSIZE - strm.avail_out;
			//append data to destination
			dest.append((const char*)outbuffer,have);

			//if the output buffer is not empty, repeat the whole thing
		}while(strm.avail_out == 0);
	}
	while(ret!=Z_STREAM_END); //finished when inflate returns this

	//close zlib
	inflateEnd(&strm);
	return true;
}