/**

	\file	lzma_compressor.cpp

	

	LZMA compressor interface.

*/



#include "edge/edge_stdint.h"

#include "edgelzma_compressor.h"



#ifndef LZMA_MEMCPY

//! Controls whether we use memcpy or just a simple byte-copy loop.

# define LZMA_MEMCPY     1

#endif



#ifndef LZMA_ASSERT

//! Controls whether we use assert or not.

# define LZMA_ASSERT     1

#endif





/* Standard C includes */

#if LZMA_MEMCPY

# include <string.h>

#endif



#if LZMA_ASSERT

# include <assert.h>

#else

# undef assert

# define assert(x)    ((void)0)

#endif



/* LZMA compilation controls */

#ifndef _NO_EXCEPTIONS

# define _NO_EXCEPTIONS

#endif



#ifndef INITGUID

# define INITGUID

#endif



/* C++ compressor */

#include "lzma_sdk/CPP/Common/MyString.cpp"

#include "lzma_sdk/CPP/Common/StringConvert.cpp"

#include "lzma_sdk/CPP/7zip/Common/CreateCoder.cpp"

#include "lzma_sdk/CPP/7zip/Common/FilterCoder.cpp"

#include "lzma_sdk/CPP/7zip/Common/InBuffer.cpp"

#include "lzma_sdk/CPP/7zip/Common/OutBuffer.cpp"

#include "lzma_sdk/CPP/7zip/Common/StreamUtils.cpp"

#include "lzma_sdk/CPP/7zip/Compress/LZ/LZOutWindow.cpp"

#include "lzma_sdk/CPP/7zip/Compress/LZMA/LZMAEncoder.cpp"

#include "lzma_sdk/CPP/7zip/Compress/LZMA/LZMADecoder.cpp"

#include "lzma_sdk/CPP/7zip/Compress/RangeCoder/RangeCoderBit.cpp"



// ---------------------------------------------------------

//	Extracted from lzma_common.h

// ---------------------------------------------------------



/** \brief LZMA format definition

	LZMA does not have a single well-defined file or memory format, so

	we've explicitly documented the format we use, which is based on the

	.lzma files generated by lzma.exe.



	Our format:

	* LZMA_PROPERTIES_SIZE (5) bytes of properties

	* 8 bytes of output size, stored in little-endian order

	* the stream of compressed data

*/

enum e_LZMA_FORMAT_OFFSETS

{

	kOffsetToProperties       = (uint32_t) 0,

	kOffsetToSize             = (uint32_t) 5,

	kOffsetToCompressedStream = (uint32_t) 13

};



/** \brief Maximum reasonable values for LZMA compression parameters.

	It's far too easy for LZMA to require ridiculous amounts of memory if you

	simplistically max out its settings. These are reasonable limits that

	still provide very good compression ratios without taking up

	unreasonable amounts of RAM.

	

	The #LZMACompressor and #LZMADecompressor classes use these parameters

	when compressing and decompressing.

	

	\note You MUST limit the compression parameters to these limits if

	you want to decompress on the PS3 SPU. */

enum e_LZMA_REASONABLE_PARAMETERS

{

	kMaxAlgorithm        = 1,

	kMaxDictSize         = 65536,

	kMaxFastBytes        = 64,

	kMaxLitContextBits   = 3,

	kMaxLitPosBits       = 0,

	kMaxPosStateBits     = 2

};



// ---------------------------------------------------------

//

// ---------------------------------------------------------



// Property keys that control the behavior of the compressor.

const PROPID kCompressionPropertyKeys[] = {

	NCoderPropID::kAlgorithm,

	NCoderPropID::kDictionarySize,

	NCoderPropID::kNumFastBytes,

	NCoderPropID::kLitContextBits,

	NCoderPropID::kLitPosBits,

	NCoderPropID::kPosStateBits

};

const size_t kNumCompressionPropertyKeys = sizeof(kCompressionPropertyKeys) / sizeof(kCompressionPropertyKeys[0]);





// Property values that control the behavior of the compressor.

// We define multiple sets of these, one for each level.

// These are not PROPVARIANT structs, because PROPVARIANT contains

// a union which we can't statically initialize with C++ (sigh).

// So we convert them into PROPVARIANT structs just before using.

const unsigned long kCompressionPropertyValues[9][kNumCompressionPropertyKeys] = {

	// algorithm     dict-size        fast-bytes       lit-ctxt              lit-pos         pos-bits

	{ 0,             kMaxDictSize/16, kMaxFastBytes/2, 0,                    kMaxLitPosBits, 0 }, // level 1 (fastest)

	{ kMaxAlgorithm, kMaxDictSize/16, kMaxFastBytes/2, 0,                    kMaxLitPosBits, 0 },

	{ kMaxAlgorithm, kMaxDictSize/8,  kMaxFastBytes/2, 0,                    kMaxLitPosBits, kMaxPosStateBits/2 },

	{ kMaxAlgorithm, kMaxDictSize/8,  kMaxFastBytes,   kMaxLitContextBits/2, kMaxLitPosBits, kMaxPosStateBits/2 },

	{ kMaxAlgorithm, kMaxDictSize/4,  kMaxFastBytes,   kMaxLitContextBits/2, kMaxLitPosBits, kMaxPosStateBits/2 },

	{ kMaxAlgorithm, kMaxDictSize/4,  kMaxFastBytes,   kMaxLitContextBits/2, kMaxLitPosBits, kMaxPosStateBits },

	{ kMaxAlgorithm, kMaxDictSize/2,  kMaxFastBytes,   kMaxLitContextBits/2, kMaxLitPosBits, kMaxPosStateBits },

	{ kMaxAlgorithm, kMaxDictSize,    kMaxFastBytes,   kMaxLitContextBits,   kMaxLitPosBits, kMaxPosStateBits/2 },

	{ kMaxAlgorithm, kMaxDictSize,    kMaxFastBytes,   kMaxLitContextBits,   kMaxLitPosBits, kMaxPosStateBits }  // level 9 (best)

};

const int kNumCompressionLevels = (int)(sizeof(kCompressionPropertyValues) / sizeof(kCompressionPropertyValues[0]));



// Utility to convert our static values (above) into a PROPVARIANT at runtime.

PROPVARIANT * GetPropertyValuesForLevel(int level, PROPVARIANT vals[kNumCompressionPropertyKeys]) {

	for (size_t i=0; i<kNumCompressionPropertyKeys; ++i) {

		vals[i].vt = VT_UI4;

		vals[i].ulVal = (UINT)kCompressionPropertyValues[level-1][i];

	}

	return vals;

}



// Support class to use a RAM-based input stream.

class RAMInputStream : public ISequentialInStream, public CMyUnknownImp

{

public:

	MY_UNKNOWN_IMP

	RAMInputStream(const uint8_t *pBuffer, uint32_t size) : m_pBuffer(pBuffer), m_size(size), m_offset(0) {}

	STDMETHOD(Read)(void *data, uint32_t size, uint32_t *processedSize)

	{

		uint32_t remaining = m_size - m_offset;

		if (size > remaining) size = remaining;

		if (processedSize) *processedSize = size;

		uint8_t *pOut = (uint8_t*)data;

#if LZMA_MEMCPY

		memcpy(pOut, &m_pBuffer[m_offset], size);

		m_offset += size;

#else

		while (size-- > 0) // simple memcpy loop

			*pOut++ = m_pBuffer[m_offset++];

#endif

		return S_OK;

	}



	const uint8_t *  m_pBuffer;

	uint32_t         m_size;

	uint32_t         m_offset;

};



// Support class to use a RAM-based output stream.

class RAMOutputStream : public ISequentialOutStream, public CMyUnknownImp

{

public:

	MY_UNKNOWN_IMP

	RAMOutputStream(uint8_t* pBuffer, uint32_t size) : m_pBuffer(pBuffer), m_size(size), m_offset(0), m_overflow(false) {}

	STDMETHOD(Write)(const void *data, uint32_t size, uint32_t *processedSize)

	{

		uint32_t remaining = m_size - m_offset;

		if (size > remaining) { m_overflow = true; size = remaining; }

		if (processedSize) *processedSize = size;

		const uint8_t *pIn = (const uint8_t*)data;

#if LZMA_MEMCPY

		memcpy(&m_pBuffer[m_offset], pIn, size);

		m_offset += size;

#else

		while (size-- > 0) // simple memcpy loop

			m_pBuffer[m_offset++] = *pIn++;

#endif

		return (m_overflow ? E_FAIL:S_OK);

	}

	

	uint8_t *        m_pBuffer;

	uint32_t         m_size;

	uint32_t         m_offset;

	bool        m_overflow;

};



// ---------------------------------------------------------

//	LZMACompressor::compress

// ---------------------------------------------------------



extern "C" int32_t

lzmaCompress

(	uint8_t*		pCompr,

	uint32_t*		pComprSize,

	const uint8_t*	pUncompr,

	uint32_t		uncomprSize,

	int32_t			level

)

{

	uint32_t comprAlloc = *pComprSize;

	

	assert( comprAlloc >= kOffsetToCompressedStream);



	// Create an encoder object.

	NCompress::NLZMA::CEncoder *pEncoder = new NCompress::NLZMA::CEncoder;

	assert(pEncoder != NULL);

	if (pEncoder != NULL)

	{

		CMyComPtr<ICompressCoder> pEncoderCOM = pEncoder;



		// Set up two streams.

		RAMInputStream  *pIn = new RAMInputStream(pUncompr, uncomprSize);

		CMyComPtr<ISequentialInStream> pInCOM = pIn;

		RAMOutputStream *pOut = new RAMOutputStream(pCompr, comprAlloc);

		CMyComPtr<ISequentialOutStream> pOutCOM = pOut;

		

		// Set the properties on the coder object, and write them out.

		PROPVARIANT values[kNumCompressionPropertyKeys];

		HRESULT setCoderPropertiesErr = pEncoder->SetCoderProperties(kCompressionPropertyKeys, GetPropertyValuesForLevel(level,values), kNumCompressionPropertyKeys);

		(void)&setCoderPropertiesErr;

		assert(setCoderPropertiesErr == S_OK);

		assert(pOut->m_offset == kOffsetToProperties);

		HRESULT writeCoderPropertiesErr = pEncoder->WriteCoderProperties(pOut);

		(void)&writeCoderPropertiesErr;

		assert(writeCoderPropertiesErr == S_OK);

		

		// Write the size.

		assert(pOut->m_offset == kOffsetToSize);

		uint64_t size = (uint64_t)uncomprSize;

		for (int i=0; i<8; ++i) {

			uint8_t byte = (uint8_t)((size >> (i*8)) & 0xFF);

			HRESULT writeErr = pOut->Write(&byte, sizeof(byte), NULL);

			(void)&writeErr;

			assert(writeErr == S_OK);

		}



		// Compress the stream.

		assert(pOut->m_offset == kOffsetToCompressedStream);

		HRESULT lzmaResult = pEncoderCOM->Code(pInCOM, pOutCOM, 0, 0, 0);

		if (lzmaResult == S_OK)

		{

			// Success!

			*pComprSize = pOut->m_offset;

			return 0; // OK

		}

		else

		{

			return 1; // error

		}

	}



	return 1; // error

}

