//
// Asura_Zlib.cpp
//
// Created by Richard Rice - 21/08/2007
// 
// Copyright (C) 2007 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"
#include "ChunkSystem/Asura_Zlib.h"

#ifdef ASURA_USE_ZLIB

#include "Core/Asura_Endian.h"
#include "Core/Memory/Asura_Mem.h"
#include "Core/Asura_Maths.h"
#include "Core/Asura_PlatformBasics.h"
#include "FileAccess/Asura_FileAccess.h"

#include "Zlib/zlib.h"

Asura_Zlib_Decoder::Asura_Zlib_Decoder()
:m_pucSource(0)
,m_iFileHandle(ASURA_INVALID_HANDLE)
,m_bStreamOpen(false)
{
	m_pucInputBuffer = new unsigned char[ASURA_ZLIB_DECODER_INPUT_BUFFER_SIZE];
	m_pucOutputBuffer = ASURA_ZLIB_DECODER_OUTPUT_BUFFER_SIZE ? new unsigned char[ASURA_ZLIB_DECODER_OUTPUT_BUFFER_SIZE] : 0;
}

Asura_Zlib_Decoder::~Asura_Zlib_Decoder()
{
	delete [] m_pucInputBuffer;
	delete [] m_pucOutputBuffer;
}

void Asura_Zlib_Decoder::Begin( Asura_Handle iFileHandle )
{
    m_pucSource   = NULL;
    m_iFileHandle = iFileHandle;

    //read the header (just consists of the uncompressed size)
    Asura_FileAccess::Platform_ReadData( m_iFileHandle, &m_xHeader, sizeof(Asura_Zlib_Header) );

    SetupCommonState();
}

void Asura_Zlib_Decoder::Begin( const void* pSource )
{
    m_pucSource   = ((const unsigned char*)pSource)+sizeof(Asura_Zlib_Header);
    m_iFileHandle = ASURA_INVALID_HANDLE;

	//copy the header
    Asura_Mem::Copy( &m_xHeader, pSource, sizeof(Asura_Zlib_Header) );

    SetupCommonState();
}

void Asura_Zlib_Decoder::SetupCommonState( void )
{
	m_pucOutputBufferReadPos = m_pucOutputBuffer;
	m_uOutputBufferBytesLeft = 0;
	
	//initialise zlib decompression
    m_xZlibStream.zalloc = Z_NULL;
    m_xZlibStream.zfree = Z_NULL;
    m_xZlibStream.opaque = Z_NULL;

	#ifdef ASURA_TOOLS
	if(m_xHeader.m_uBigEndian)
	{
		//swap the header back
		m_xHeader.m_uCompressedSize = Asura_Endian::SwapBytes(m_xHeader.m_uCompressedSize);
		m_xHeader.m_uUncompressedSize = Asura_Endian::SwapBytes(m_xHeader.m_uUncompressedSize);

	}
	#endif
		

    if(m_pucSource)
	{
    	//reading directly from memory , so we can fill in the input buffer now
		//Unfortunately the zlib stuff doesn't take const pointers for its input , so
		//we have to 'accidently' lose it
    	m_xZlibStream.avail_in = m_xHeader.m_uCompressedSize;
    	m_xZlibStream.next_in = const_cast<unsigned char*>(m_pucSource);
		
		m_uRemainingSource = 0;
	}
	else
	{
    	m_xZlibStream.avail_in = 0;
    	m_xZlibStream.next_in = Z_NULL;

		m_uRemainingSource = m_xHeader.m_uCompressedSize;
	}

	int iRet = inflateInit2(&m_xZlibStream, ASURA_ZLIB_DEFAULT_WINDOW_BITS);

	if(iRet == Z_OK)
	{
		m_bStreamOpen = true;
	}
	else
	{
		Asura_Assert(0, "Error opening zlib stream for decompression");
		m_bStreamOpen = false;
	}
}

void Asura_Zlib_Decoder::End()
{
	if(m_bStreamOpen)
	{
		inflateEnd(&m_xZlibStream);
		m_bStreamOpen = false;
	}	
}

u_int Asura_Zlib_Decoder::Decompress( void* pDest, u_int uNumBytes )
{
	if(!uNumBytes) return 0;
	if(!m_bStreamOpen)
	{
		Asura_Assert(0,"Zlib stream not open - can't decompress anything");
		return 0;
	}


	u_int uNumBytesStillNeeded = uNumBytes;
	bool bOutputDirectToDest = false;

	do
	{
		if(!bOutputDirectToDest)
		{
			//got any output left from before?
			if(m_uOutputBufferBytesLeft)
			{
				//copy what we can. must use SafeCopy here, since we don't know anything about how
				//the caller has allocated the destination buffer
				u_int uBytesToCopy = Asura_Maths::Min(m_uOutputBufferBytesLeft,uNumBytesStillNeeded);
				Asura_Mem::SafeCopy(pDest, m_pucOutputBufferReadPos, uBytesToCopy);

				uNumBytesStillNeeded -= uBytesToCopy;
				m_uOutputBufferBytesLeft -= uBytesToCopy;
				m_pucOutputBufferReadPos += uBytesToCopy;
				pDest = ((char*)pDest) + uBytesToCopy;

				if(!uNumBytesStillNeeded)
				{
					//read all we need
					return uNumBytes;
				}
			}

			//need some more output
#if !defined ASURA_ZLIB_PREVENT_DIRECT_DECOMPRESS
			if(uNumBytesStillNeeded > ASURA_ZLIB_DECODER_OUTPUT_BUFFER_SIZE)
			{
				//reading a large amount ,so read process directly to the destination buffer
				m_xZlibStream.avail_out = uNumBytesStillNeeded;
				m_xZlibStream.next_out = (Bytef*) pDest;
				bOutputDirectToDest = true;
			}
			else
#endif
			{
				//reading a smaller amount , so read a large chunk to our temporary buffer first
				m_xZlibStream.avail_out = ASURA_ZLIB_DECODER_OUTPUT_BUFFER_SIZE;
				m_xZlibStream.next_out = (Bytef*) m_pucOutputBuffer;
			}
		}
		else
		{
			uNumBytesStillNeeded = m_xZlibStream.avail_out;
		}
		
		//need to provide more input?
		if(!m_xZlibStream.avail_in)
		{
			//determine how much more to read in
			u_int uBytesToRead = Asura_Maths::Min(m_uRemainingSource, (u_int)ASURA_ZLIB_DECODER_INPUT_BUFFER_SIZE);
			if(!uBytesToRead)
			{
				//Too bad , nothing left to read
				return uNumBytes - uNumBytesStillNeeded;
			}

			//should only get here when reading from a file, since a memory buffer is taken in one go
			Asura_Assert(m_iFileHandle != ASURA_INVALID_HANDLE, "");
			bool bOkay = Asura_FileAccess::Platform_ReadData( m_iFileHandle, m_pucInputBuffer, uBytesToRead );
			if(!bOkay)
			{
				Asura_Assert(0,"Error reading file for decompression");
				return uNumBytes - uNumBytesStillNeeded;
			}
			m_uRemainingSource -= uBytesToRead;

			//point the stream at the new input
			m_xZlibStream.avail_in = uBytesToRead;
			m_xZlibStream.next_in = m_pucInputBuffer;
 		}

		//decompress that lot of input
		int iRet = inflate(&m_xZlibStream, Z_NO_FLUSH);
		if(iRet != Z_OK && iRet != Z_STREAM_END)
		{
			Asura_Assert(0,"Error decompressing");
			return uNumBytes - uNumBytesStillNeeded;
		}

		//I think either all the input or all the output space should be used at this stage...
		Asura_Assert(m_xZlibStream.avail_in==0 || m_xZlibStream.avail_out==0, "Zlib decompression not behaving as I'm expecting");

		if(bOutputDirectToDest)
		{
			if(!m_xZlibStream.avail_out)
			{
				//finished reading
				return uNumBytes;
			}
		}
		else
		{
			//determine how much decompressed output we have available
			m_pucOutputBufferReadPos = m_pucOutputBuffer;
			m_uOutputBufferBytesLeft = ASURA_ZLIB_DECODER_OUTPUT_BUFFER_SIZE - m_xZlibStream.avail_out;
		}


	}while(true);

	//shouldn't get here
	return 0;
}




#endif //ASURA_USE_ZLIB
