/*
 * Copyright 2017, Jérôme Duval.
 * Copyright 2014, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */
 
 
#include <ZstdCompressionAlgorithm.h>
 
#include <errno.h>
#include <string.h>
 
#include <algorithm>
#include <new>
 
#ifdef ZSTD_ENABLED
  #include <zstd.h>
  #include <zstd_errors.h>
#endif
 
#include <DataIO.h>
 
 
// build compression support only for userland
#if defined(ZSTD_ENABLED) && !defined(_KERNEL_MODE) && !defined(_BOOT_MODE)
#	define B_ZSTD_COMPRESSION_SUPPORT 1
#endif
 
 
static const size_t kMinBufferSize		= 1024;
static const size_t kMaxBufferSize		= 1024 * 1024;
static const size_t kDefaultBufferSize	= 4 * 1024;
 
 
static size_t
sanitize_buffer_size(size_t size)
{
	if (size < kMinBufferSize)
		return kMinBufferSize;
	return std::min(size, kMaxBufferSize);
}
 
 
// #pragma mark - BZstdCompressionParameters
 
 
BZstdCompressionParameters::BZstdCompressionParameters(
	int compressionLevel)
	:
	BCompressionParameters(),
	fCompressionLevel(compressionLevel),
	fBufferSize(kDefaultBufferSize)
{
}
 
 
BZstdCompressionParameters::~BZstdCompressionParameters()
{
}
 
 
int32
BZstdCompressionParameters::CompressionLevel() const
{
	return fCompressionLevel;
}
 
 
void
BZstdCompressionParameters::SetCompressionLevel(int32 level)
{
	fCompressionLevel = level;
}
 
 
size_t
BZstdCompressionParameters::BufferSize() const
{
	return fBufferSize;
}
 
 
void
BZstdCompressionParameters::SetBufferSize(size_t size)
{
	fBufferSize = sanitize_buffer_size(size);
}
 
 
// #pragma mark - BZstdDecompressionParameters
 
 
BZstdDecompressionParameters::BZstdDecompressionParameters()
	:
	BDecompressionParameters(),
	fBufferSize(kDefaultBufferSize)
{
}
 
 
BZstdDecompressionParameters::~BZstdDecompressionParameters()
{
}
 
 
size_t
BZstdDecompressionParameters::BufferSize() const
{
	return fBufferSize;
}
 
 
void
BZstdDecompressionParameters::SetBufferSize(size_t size)
{
	fBufferSize = sanitize_buffer_size(size);
}
 
 
// #pragma mark - CompressionStrategy
 
 
#ifdef B_ZSTD_COMPRESSION_SUPPORT
 
 
struct BZstdCompressionAlgorithm::CompressionStrategy {
	typedef BZstdCompressionParameters Parameters;
 
	static const bool kNeedsFinalFlush = true;
 
	static size_t Init(ZSTD_CStream **stream,
		const BZstdCompressionParameters* parameters)
	{
		int32 compressionLevel = B_ZSTD_COMPRESSION_DEFAULT;
		if (parameters != NULL) {
			compressionLevel = parameters->CompressionLevel();
		}
 
		*stream = ZSTD_createCStream();
		return ZSTD_initCStream(*stream, compressionLevel);
	}
 
	static void Uninit(ZSTD_CStream *stream)
	{
		ZSTD_freeCStream(stream);
	}
 
	static size_t Process(ZSTD_CStream *stream, ZSTD_inBuffer *input,
		ZSTD_outBuffer *output, bool flush)
	{
		if (flush)
			return ZSTD_flushStream(stream, output);
		else
			return ZSTD_compressStream(stream, output, input);
	}
};
 
 
#endif	// B_ZSTD_COMPRESSION_SUPPORT
 
 
// #pragma mark - DecompressionStrategy
 
 
#ifdef ZSTD_ENABLED
 
 
struct BZstdCompressionAlgorithm::DecompressionStrategy {
	typedef BZstdDecompressionParameters Parameters;
 
	static const bool kNeedsFinalFlush = false;
 
	static size_t Init(ZSTD_DStream **stream,
		const BZstdDecompressionParameters* /*parameters*/)
	{
		*stream = ZSTD_createDStream();
		return ZSTD_initDStream(*stream);
	}
 
	static void Uninit(ZSTD_DStream *stream)
	{
		ZSTD_freeDStream(stream);
	}
 
	static size_t Process(ZSTD_DStream *stream, ZSTD_inBuffer *input,
		ZSTD_outBuffer *output, bool flush)
	{
		return ZSTD_decompressStream(stream, output, input);
	}
 
};
 
 
// #pragma mark - Stream
 
 
template<typename BaseClass, typename Strategy, typename StreamType>
struct BZstdCompressionAlgorithm::Stream : BaseClass {
	Stream(BDataIO* io)
		:
		BaseClass(io),
		fStreamInitialized(false)
	{
	}
 
	~Stream()
	{
		if (fStreamInitialized) {
			if (Strategy::kNeedsFinalFlush)
				this->Flush();
			Strategy::Uninit(fStream);
		}
	}
 
	status_t Init(const typename Strategy::Parameters* parameters)
	{
		status_t error = this->BaseClass::Init(
			parameters != NULL ? parameters->BufferSize() : kDefaultBufferSize);
		if (error != B_OK)
			return error;
 
		size_t zstdError = Strategy::Init(&fStream, parameters);
		if (ZSTD_getErrorCode(zstdError) != ZSTD_error_no_error)
			return _TranslateZstdError(zstdError);
 
		fStreamInitialized = true;
		return B_OK;
	}
 
	virtual status_t ProcessData(const void* input, size_t inputSize,
		void* output, size_t outputSize, size_t& bytesConsumed,
		size_t& bytesProduced)
	{
		return _ProcessData(input, inputSize, output, outputSize,
			bytesConsumed, bytesProduced, false);
	}
 
	virtual status_t FlushPendingData(void* output, size_t outputSize,
		size_t& bytesProduced)
	{
		size_t bytesConsumed;
		return _ProcessData(NULL, 0, output, outputSize,
			bytesConsumed, bytesProduced, true);
	}
 
	template<typename BaseParameters>
	static status_t Create(BDataIO* io, BaseParameters* _parameters,
		BDataIO*& _stream)
	{
		const typename Strategy::Parameters* parameters
#ifdef _BOOT_MODE
			= static_cast<const typename Strategy::Parameters*>(_parameters);
#else
			= dynamic_cast<const typename Strategy::Parameters*>(_parameters);
#endif
		Stream* stream = new(std::nothrow) Stream(io);
		if (stream == NULL)
			return B_NO_MEMORY;
 
		status_t error = stream->Init(parameters);
		if (error != B_OK) {
			delete stream;
			return error;
		}
 
		_stream = stream;
		return B_OK;
	}
 
private:
	status_t _ProcessData(const void* input, size_t inputSize,
		void* output, size_t outputSize, size_t& bytesConsumed,
		size_t& bytesProduced, bool flush)
	{
		inBuffer.src = input;
		inBuffer.pos = 0;
		inBuffer.size = inputSize;
		outBuffer.dst = output;
		outBuffer.pos = 0;
		outBuffer.size = outputSize;
 
		size_t zstdError = Strategy::Process(fStream, &inBuffer, &outBuffer, flush);
		if (ZSTD_getErrorCode(zstdError) != ZSTD_error_no_error)
			return _TranslateZstdError(zstdError);
 
		bytesConsumed = inBuffer.pos;
		bytesProduced = outBuffer.pos;
		return B_OK;
	}
 
private:
	bool		fStreamInitialized;
	StreamType	*fStream;
	ZSTD_inBuffer inBuffer;
	ZSTD_outBuffer outBuffer;
};
 
 
#endif	// ZSTD_ENABLED
 
 
// #pragma mark - BZstdCompressionAlgorithm
 
 
BZstdCompressionAlgorithm::BZstdCompressionAlgorithm()
	:
	BCompressionAlgorithm()
{
}
 
 
BZstdCompressionAlgorithm::~BZstdCompressionAlgorithm()
{
}
 
 
status_t
BZstdCompressionAlgorithm::CreateCompressingInputStream(BDataIO* input,
	const BCompressionParameters* parameters, BDataIO*& _stream)
{
#ifdef B_ZSTD_COMPRESSION_SUPPORT
	return Stream<BAbstractInputStream, CompressionStrategy, ZSTD_CStream>::Create(
		input, parameters, _stream);
#else
	return B_NOT_SUPPORTED;
#endif
}
 
 
status_t
BZstdCompressionAlgorithm::CreateCompressingOutputStream(BDataIO* output,
	const BCompressionParameters* parameters, BDataIO*& _stream)
{
#ifdef B_ZSTD_COMPRESSION_SUPPORT
	return Stream<BAbstractOutputStream, CompressionStrategy, ZSTD_CStream>::Create(
		output, parameters, _stream);
#else
	return B_NOT_SUPPORTED;
#endif
}
 
 
status_t
BZstdCompressionAlgorithm::CreateDecompressingInputStream(BDataIO* input,
	const BDecompressionParameters* parameters, BDataIO*& _stream)
{
#ifdef ZSTD_ENABLED
	return Stream<BAbstractInputStream, DecompressionStrategy, ZSTD_DStream>::Create(
		input, parameters, _stream);
#else
	return B_NOT_SUPPORTED;
#endif
}
 
 
status_t
BZstdCompressionAlgorithm::CreateDecompressingOutputStream(BDataIO* output,
	const BDecompressionParameters* parameters, BDataIO*& _stream)
{
#ifdef ZSTD_ENABLED
	return Stream<BAbstractOutputStream, DecompressionStrategy, ZSTD_DStream>::Create(
		output, parameters, _stream);
#else
	return B_NOT_SUPPORTED;
#endif
}
 
 
status_t
BZstdCompressionAlgorithm::CompressBuffer(const void* input,
	size_t inputSize, void* output, size_t outputSize, size_t& _compressedSize,
	const BCompressionParameters* parameters)
{
#ifdef B_ZSTD_COMPRESSION_SUPPORT
	const BZstdCompressionParameters* zstdParameters
		= dynamic_cast<const BZstdCompressionParameters*>(parameters);
	int compressionLevel = zstdParameters != NULL
		? zstdParameters->CompressionLevel()
		: B_ZSTD_COMPRESSION_DEFAULT;
 
	size_t zstdError = ZSTD_compress(output, outputSize, input,
		inputSize, compressionLevel);
	if (ZSTD_isError(zstdError))
		return _TranslateZstdError(zstdError);
 
	_compressedSize = zstdError;
	return B_OK;
#else
	return B_NOT_SUPPORTED;
#endif
}
 
 
status_t
BZstdCompressionAlgorithm::DecompressBuffer(const void* input,
	size_t inputSize, void* output, size_t outputSize,
	size_t& _uncompressedSize, const BDecompressionParameters* parameters)
{
#ifdef ZSTD_ENABLED
	size_t zstdError = ZSTD_decompress(output, outputSize, input,
		inputSize);
	if (ZSTD_isError(zstdError))
		return _TranslateZstdError(zstdError);
 
	_uncompressedSize = zstdError;
	return B_OK;
#else
	return B_NOT_SUPPORTED;
#endif
}
 
 
/*static*/ status_t
BZstdCompressionAlgorithm::_TranslateZstdError(size_t error)
{
#ifdef ZSTD_ENABLED
	switch (ZSTD_getErrorCode(error)) {
		case ZSTD_error_no_error:
			return B_OK;
		case ZSTD_error_seekableIO:
			return B_BAD_VALUE;
		case ZSTD_error_corruption_detected:
		case ZSTD_error_checksum_wrong:
			return B_BAD_DATA;
		case ZSTD_error_version_unsupported:
			return B_BAD_VALUE;
		case ZSTD_error_dstSize_tooSmall:
			return B_BUFFER_OVERFLOW;
		default:
			return B_ERROR;
	}
#else
	return B_NOT_SUPPORTED;
#endif
}

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: inBuffer, outBuffer.