// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/***************************************************************************

    chdcodec.c

    Codecs used by the CHD format

***************************************************************************/

#include "chdcodec.h"

#include "avhuff.h"
#include "cdrom.h"
#include "chd.h"
#include "flac.h"
#include "hashing.h"

#include "lzma/C/LzmaDec.h"
#include "lzma/C/LzmaEnc.h"

#include <zlib.h>

#include <new>


namespace {

//**************************************************************************
//  GLOBAL VARIABLES
//**************************************************************************

constexpr uint8_t f_cd_sync_header[12] = { 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00 };



//**************************************************************************
//  TYPE DEFINITIONS
//**************************************************************************

// ======================> chd_zlib_allocator

// allocation helper class for zlib
class chd_zlib_allocator
{
public:
	// construction/destruction
	chd_zlib_allocator();
	~chd_zlib_allocator();

	// installation
	void install(z_stream &stream);

private:
	// internal helpers
	static voidpf fast_alloc(voidpf opaque, uInt items, uInt size);
	static void fast_free(voidpf opaque, voidpf address);

	static constexpr int MAX_ZLIB_ALLOCS = 64;

	uint32_t *                m_allocptr[MAX_ZLIB_ALLOCS];
};


// ======================> chd_zlib_compressor

// ZLIB compressor
class chd_zlib_compressor : public chd_compressor
{
public:
	// construction/destruction
	chd_zlib_compressor(chd_file &chd, uint32_t hunkbytes, bool lossy);
	~chd_zlib_compressor();

	// core functionality
	virtual uint32_t compress(const uint8_t *src, uint32_t srclen, uint8_t *dest) override;

private:
	// internal state
	z_stream                m_deflater;
	chd_zlib_allocator      m_allocator;
};


// ======================> chd_zlib_decompressor

// ZLIB decompressor
class chd_zlib_decompressor : public chd_decompressor
{
public:
	// construction/destruction
	chd_zlib_decompressor(chd_file &chd, uint32_t hunkbytes, bool lossy);
	~chd_zlib_decompressor();

	// core functionality
	virtual void decompress(const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen) override;

private:
	// internal state
	z_stream                m_inflater;
	chd_zlib_allocator      m_allocator;
};


// ======================> chd_lzma_allocator

// allocation helper clas for zlib
class chd_lzma_allocator : public ISzAlloc
{
public:
	// construction/destruction
	chd_lzma_allocator();
	~chd_lzma_allocator();

private:
	// internal helpers
	static void *fast_alloc(void *p, size_t size);
	static void fast_free(void *p, void *address);

	static constexpr int MAX_LZMA_ALLOCS = 64;
	uint32_t *                m_allocptr[MAX_LZMA_ALLOCS];
};


// ======================> chd_lzma_compressor

// LZMA compressor
class chd_lzma_compressor : public chd_compressor
{
public:
	// construction/destruction
	chd_lzma_compressor(chd_file &chd, uint32_t hunkbytes, bool lossy);
	~chd_lzma_compressor();

	// core functionality
	virtual uint32_t compress(const uint8_t *src, uint32_t srclen, uint8_t *dest) override;

	// helpers
	static void configure_properties(CLzmaEncProps &props, uint32_t hunkbytes);

private:
	// internal state
	CLzmaEncProps           m_props;
	chd_lzma_allocator      m_allocator;
};


// ======================> chd_lzma_decompressor

// LZMA decompressor
class chd_lzma_decompressor : public chd_decompressor
{
public:
	// construction/destruction
	chd_lzma_decompressor(chd_file &chd, uint32_t hunkbytes, bool lossy);
	~chd_lzma_decompressor();

	// core functionality
	virtual void decompress(const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen) override;

private:
	// internal state
	CLzmaDec                m_decoder;
	chd_lzma_allocator      m_allocator;
};


// ======================> chd_huffman_compressor

// Huffman compressor
class chd_huffman_compressor : public chd_compressor
{
public:
	// construction/destruction
	chd_huffman_compressor(chd_file &chd, uint32_t hunkbytes, bool lossy);

	// core functionality
	virtual uint32_t compress(const uint8_t *src, uint32_t srclen, uint8_t *dest) override;

private:
	// internal state
	huffman_8bit_encoder    m_encoder;
};


// ======================> chd_huffman_decompressor

// Huffman decompressor
class chd_huffman_decompressor : public chd_decompressor
{
public:
	// construction/destruction
	chd_huffman_decompressor(chd_file &chd, uint32_t hunkbytes, bool lossy);

	// core functionality
	virtual void decompress(const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen) override;

private:
	// internal state
	huffman_8bit_decoder    m_decoder;
};


// ======================> chd_flac_compressor

// FLAC compressor
class chd_flac_compressor : public chd_compressor
{
public:
	// construction/destruction
	chd_flac_compressor(chd_file &chd, uint32_t hunkbytes, bool lossy);

	// core functionality
	virtual uint32_t compress(const uint8_t *src, uint32_t srclen, uint8_t *dest) override;

	// static helpers
	static uint32_t blocksize(uint32_t bytes);

private:
	// internal state
	bool            m_big_endian;
	flac_encoder    m_encoder;
};


// ======================> chd_flac_decompressor

// FLAC decompressor
class chd_flac_decompressor : public chd_decompressor
{
public:
	// construction/destruction
	chd_flac_decompressor(chd_file &chd, uint32_t hunkbytes, bool lossy);

	// core functionality
	virtual void decompress(const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen) override;

private:
	// internal state
	bool            m_big_endian;
	flac_decoder    m_decoder;
};


// ======================> chd_cd_flac_compressor

// CD/FLAC compressor
class chd_cd_flac_compressor : public chd_compressor
{
public:
	// construction/destruction
	chd_cd_flac_compressor(chd_file &chd, uint32_t hunkbytes, bool lossy);
	~chd_cd_flac_compressor();

	// core functionality
	virtual uint32_t compress(const uint8_t *src, uint32_t srclen, uint8_t *dest) override;

	// static helpers
	static uint32_t blocksize(uint32_t bytes);

private:
	// internal state
	bool                m_swap_endian;
	flac_encoder        m_encoder;
	z_stream            m_deflater;
	chd_zlib_allocator  m_allocator;
	std::vector<uint8_t>      m_buffer;
};


// ======================> chd_cd_flac_decompressor

// FLAC decompressor
class chd_cd_flac_decompressor : public chd_decompressor
{
public:
	// construction/destruction
	chd_cd_flac_decompressor(chd_file &chd, uint32_t hunkbytes, bool lossy);
	~chd_cd_flac_decompressor();

	// core functionality
	virtual void decompress(const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen) override;

private:
	// internal state
	bool                m_swap_endian;
	flac_decoder        m_decoder;
	z_stream            m_inflater;
	chd_zlib_allocator  m_allocator;
	std::vector<uint8_t>      m_buffer;
};


// ======================> chd_cd_compressor

template<class BaseCompressor, class SubcodeCompressor>
class chd_cd_compressor : public chd_compressor
{
public:
	// construction/destruction
	chd_cd_compressor(chd_file &chd, uint32_t hunkbytes, bool lossy)
		: chd_compressor(chd, hunkbytes, lossy),
			m_base_compressor(chd, (hunkbytes / CD_FRAME_SIZE) * CD_MAX_SECTOR_DATA, lossy),
			m_subcode_compressor(chd, (hunkbytes / CD_FRAME_SIZE) * CD_MAX_SUBCODE_DATA, lossy),
			m_buffer(hunkbytes + (hunkbytes / CD_FRAME_SIZE) * CD_MAX_SUBCODE_DATA)
	{
		// make sure the CHD's hunk size is an even multiple of the frame size
		if (hunkbytes % CD_FRAME_SIZE != 0)
			throw std::error_condition(chd_file::error::CODEC_ERROR);
	}

	// core functionality
	virtual uint32_t compress(const uint8_t *src, uint32_t srclen, uint8_t *dest) override
	{
		// determine header bytes
		uint32_t frames = srclen / CD_FRAME_SIZE;
		uint32_t complen_bytes = (srclen < 65536) ? 2 : 3;
		uint32_t ecc_bytes = (frames + 7) / 8;
		uint32_t header_bytes = ecc_bytes + complen_bytes;

		// clear out destination header
		memset(dest, 0, header_bytes);

		// copy audio data followed by subcode data
		for (uint32_t framenum = 0; framenum < frames; framenum++)
		{
			memcpy(&m_buffer[framenum * CD_MAX_SECTOR_DATA], &src[framenum * CD_FRAME_SIZE], CD_MAX_SECTOR_DATA);
			memcpy(&m_buffer[frames * CD_MAX_SECTOR_DATA + framenum * CD_MAX_SUBCODE_DATA], &src[framenum * CD_FRAME_SIZE + CD_MAX_SECTOR_DATA], CD_MAX_SUBCODE_DATA);

			// clear out ECC data if we can
			uint8_t *sector = &m_buffer[framenum * CD_MAX_SECTOR_DATA];
			if (memcmp(sector, f_cd_sync_header, sizeof(f_cd_sync_header)) == 0 && ecc_verify(sector))
			{
				dest[framenum / 8] |= 1 << (framenum % 8);
				memset(sector, 0, sizeof(f_cd_sync_header));
				ecc_clear(sector);
			}
		}

		// encode the base portion
		uint32_t complen = m_base_compressor.compress(&m_buffer[0], frames * CD_MAX_SECTOR_DATA, &dest[header_bytes]);
		if (complen >= srclen)
			throw std::error_condition(chd_file::error::COMPRESSION_ERROR);

		// write compressed length
		dest[ecc_bytes + 0] = complen >> ((complen_bytes - 1) * 8);
		dest[ecc_bytes + 1] = complen >> ((complen_bytes - 2) * 8);
		if (complen_bytes > 2)
			dest[ecc_bytes + 2] = complen >> ((complen_bytes - 3) * 8);

		// encode the subcode
		return header_bytes + complen + m_subcode_compressor.compress(&m_buffer[frames * CD_MAX_SECTOR_DATA], frames * CD_MAX_SUBCODE_DATA, &dest[header_bytes + complen]);
	}

private:
	// internal state
	BaseCompressor     m_base_compressor;
	SubcodeCompressor  m_subcode_compressor;
	std::vector<uint8_t>      m_buffer;
};


// ======================> chd_cd_decompressor

template<class BaseDecompressor, class SubcodeDecompressor>
class chd_cd_decompressor : public chd_decompressor
{
public:
	// construction/destruction
	chd_cd_decompressor(chd_file &chd, uint32_t hunkbytes, bool lossy)
		: chd_decompressor(chd, hunkbytes, lossy),
			m_base_decompressor(chd, (hunkbytes / CD_FRAME_SIZE) * CD_MAX_SECTOR_DATA, lossy),
			m_subcode_decompressor(chd, (hunkbytes / CD_FRAME_SIZE) * CD_MAX_SUBCODE_DATA, lossy),
			m_buffer(hunkbytes)
	{
		// make sure the CHD's hunk size is an even multiple of the frame size
		if (hunkbytes % CD_FRAME_SIZE != 0)
			throw std::error_condition(chd_file::error::CODEC_ERROR);
	}

	// core functionality
	virtual void decompress(const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen) override
	{
		// determine header bytes
		uint32_t frames = destlen / CD_FRAME_SIZE;
		uint32_t complen_bytes = (destlen < 65536) ? 2 : 3;
		uint32_t ecc_bytes = (frames + 7) / 8;
		uint32_t header_bytes = ecc_bytes + complen_bytes;

		// extract compressed length of base
		uint32_t complen_base = (src[ecc_bytes + 0] << 8) | src[ecc_bytes + 1];
		if (complen_bytes > 2)
			complen_base = (complen_base << 8) | src[ecc_bytes + 2];

		// reset and decode
		m_base_decompressor.decompress(&src[header_bytes], complen_base, &m_buffer[0], frames * CD_MAX_SECTOR_DATA);
		m_subcode_decompressor.decompress(&src[header_bytes + complen_base], complen - complen_base - header_bytes, &m_buffer[frames * CD_MAX_SECTOR_DATA], frames * CD_MAX_SUBCODE_DATA);

		// reassemble the data
		for (uint32_t framenum = 0; framenum < frames; framenum++)
		{
			memcpy(&dest[framenum * CD_FRAME_SIZE], &m_buffer[framenum * CD_MAX_SECTOR_DATA], CD_MAX_SECTOR_DATA);
			memcpy(&dest[framenum * CD_FRAME_SIZE + CD_MAX_SECTOR_DATA], &m_buffer[frames * CD_MAX_SECTOR_DATA + framenum * CD_MAX_SUBCODE_DATA], CD_MAX_SUBCODE_DATA);

			// reconstitute the ECC data and sync header
			uint8_t *sector = &dest[framenum * CD_FRAME_SIZE];
			if ((src[framenum / 8] & (1 << (framenum % 8))) != 0)
			{
				memcpy(sector, f_cd_sync_header, sizeof(f_cd_sync_header));
				ecc_generate(sector);
			}
		}
	}

private:
	// internal state
	BaseDecompressor   m_base_decompressor;
	SubcodeDecompressor m_subcode_decompressor;
	std::vector<uint8_t>      m_buffer;
};


// ======================> chd_avhuff_compressor

// A/V compressor
class chd_avhuff_compressor : public chd_compressor
{
public:
	// construction/destruction
	chd_avhuff_compressor(chd_file &chd, uint32_t hunkbytes, bool lossy);

	// core functionality
	virtual uint32_t compress(const uint8_t *src, uint32_t srclen, uint8_t *dest) override;

private:
	// internal helpers
	void postinit();

	// internal state
	avhuff_encoder              m_encoder;
	bool                        m_postinit;
};


// ======================> chd_avhuff_decompressor

// A/V decompressor
class chd_avhuff_decompressor : public chd_decompressor
{
public:
	// construction/destruction
	chd_avhuff_decompressor(chd_file &chd, uint32_t hunkbytes, bool lossy);

	// core functionality
	virtual void decompress(const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen) override;
	virtual void configure(int param, void *config) override;

private:
	// internal state
	avhuff_decoder              m_decoder;
};



//**************************************************************************
//  CODEC LIST
//**************************************************************************

// an entry in the list
struct codec_entry
{
	chd_codec_type         m_type;
	bool                   m_lossy;
	const char *           m_name;
	chd_compressor::ptr    (*m_construct_compressor)(chd_file &, uint32_t, bool);
	chd_decompressor::ptr  (*m_construct_decompressor)(chd_file &, uint32_t, bool);

	template <class CompressorClass>
	static chd_compressor::ptr construct_compressor(chd_file &chd, uint32_t hunkbytes, bool lossy)
	{
		return std::make_unique<CompressorClass>(chd, hunkbytes, lossy);
	}

	template <class DecompressorClass>
	static chd_decompressor::ptr construct_decompressor(chd_file &chd, uint32_t hunkbytes, bool lossy)
	{
		return std::make_unique<DecompressorClass>(chd, hunkbytes, lossy);
	}
};


// static list of available known codecs
const codec_entry f_codec_list[] =
{
	// general codecs
	{ CHD_CODEC_ZLIB,       false,  "Deflate",              &codec_entry::construct_compressor<chd_zlib_compressor>,     &codec_entry::construct_decompressor<chd_zlib_decompressor> },
	{ CHD_CODEC_LZMA,       false,  "LZMA",                 &codec_entry::construct_compressor<chd_lzma_compressor>,     &codec_entry::construct_decompressor<chd_lzma_decompressor> },
	{ CHD_CODEC_HUFFMAN,    false,  "Huffman",              &codec_entry::construct_compressor<chd_huffman_compressor>,  &codec_entry::construct_decompressor<chd_huffman_decompressor> },
	{ CHD_CODEC_FLAC,       false,  "FLAC",                 &codec_entry::construct_compressor<chd_flac_compressor>,     &codec_entry::construct_decompressor<chd_flac_decompressor> },

	// general codecs with CD frontend
	{ CHD_CODEC_CD_ZLIB,    false,  "CD Deflate",           &codec_entry::construct_compressor<chd_cd_compressor<chd_zlib_compressor, chd_zlib_compressor> >,        &codec_entry::construct_decompressor<chd_cd_decompressor<chd_zlib_decompressor, chd_zlib_decompressor> > },
	{ CHD_CODEC_CD_LZMA,    false,  "CD LZMA",              &codec_entry::construct_compressor<chd_cd_compressor<chd_lzma_compressor, chd_zlib_compressor> >,        &codec_entry::construct_decompressor<chd_cd_decompressor<chd_lzma_decompressor, chd_zlib_decompressor> > },
	{ CHD_CODEC_CD_FLAC,    false,  "CD FLAC",              &codec_entry::construct_compressor<chd_cd_flac_compressor>,                                              &codec_entry::construct_decompressor<chd_cd_flac_decompressor> },

	// A/V codecs
	{ CHD_CODEC_AVHUFF,     false,  "A/V Huffman",          &codec_entry::construct_compressor<chd_avhuff_compressor>,   &codec_entry::construct_decompressor<chd_avhuff_decompressor> },
};


//-------------------------------------------------
//  find_in_list - create a new compressor
//  instance of the given type
//-------------------------------------------------

const codec_entry *find_in_list(chd_codec_type type)
{
	// find in the list and construct the class
	for (auto & elem : f_codec_list)
		if (elem.m_type == type)
			return &elem;
	return nullptr;
}

} // anonymous namespace



//**************************************************************************
//  CHD CODEC
//**************************************************************************

//-------------------------------------------------
//  chd_codec - constructor
//-------------------------------------------------

chd_codec::chd_codec(chd_file &chd, uint32_t hunkbytes, bool lossy)
	: m_chd(chd),
		m_hunkbytes(hunkbytes),
		m_lossy(lossy)
{
}


//-------------------------------------------------
//  ~chd_codec - destructor
//-------------------------------------------------

chd_codec::~chd_codec()
{
}


//-------------------------------------------------
//  configure - configuration
//-------------------------------------------------

void chd_codec::configure(int param, void *config)
{
	// if not overridden, it is always a failure
	throw std::error_condition(std::errc::invalid_argument);
}



//**************************************************************************
//  CHD COMPRESSOR
//**************************************************************************

//-------------------------------------------------
//  chd_compressor - constructor
//-------------------------------------------------

chd_compressor::chd_compressor(chd_file &chd, uint32_t hunkbytes, bool lossy)
	: chd_codec(chd, hunkbytes, lossy)
{
}



//**************************************************************************
//  CHD DECOMPRESSOR
//**************************************************************************

//-------------------------------------------------
//  chd_decompressor - constructor
//-------------------------------------------------

chd_decompressor::chd_decompressor(chd_file &chd, uint32_t hunkbytes, bool lossy)
	: chd_codec(chd, hunkbytes, lossy)
{
}



//**************************************************************************
//  CHD CODEC LIST
//**************************************************************************

//-------------------------------------------------
//  new_compressor - create a new compressor
//  instance of the given type
//-------------------------------------------------

chd_compressor::ptr chd_codec_list::new_compressor(chd_codec_type type, chd_file &chd)
{
	// find in the list and construct the class
	codec_entry const *const entry = find_in_list(type);
	return entry ? (*entry->m_construct_compressor)(chd, chd.hunk_bytes(), entry->m_lossy) : nullptr;
}


//-------------------------------------------------
//  new_compressor - create a new decompressor
//  instance of the given type
//-------------------------------------------------

chd_decompressor::ptr chd_codec_list::new_decompressor(chd_codec_type type, chd_file &chd)
{
	// find in the list and construct the class
	const codec_entry *entry = find_in_list(type);
	return entry ? (*entry->m_construct_decompressor)(chd, chd.hunk_bytes(), entry->m_lossy) : nullptr;
}


//-------------------------------------------------
//  codec_exists - determine whether a codec type
//  corresponds to a supported codec
//-------------------------------------------------

bool chd_codec_list::codec_exists(chd_codec_type type)
{
	// find in the list and construct the class
	return bool(find_in_list(type));
}


//-------------------------------------------------
//  codec_name - return the name of the given
//  codec
//-------------------------------------------------

const char *chd_codec_list::codec_name(chd_codec_type type)
{
	// find in the list and construct the class
	const codec_entry *entry = find_in_list(type);
	return entry ? entry->m_name : nullptr;
}



//**************************************************************************
//  CODEC INSTANCE
//**************************************************************************

//-------------------------------------------------
//  chd_compressor_group - constructor
//-------------------------------------------------

chd_compressor_group::chd_compressor_group(chd_file &chd, uint32_t compressor_list[4])
	: m_hunkbytes(chd.hunk_bytes())
	, m_compress_test(m_hunkbytes)
#if CHDCODEC_VERIFY_COMPRESSION
	, m_decompressed(m_hunkbytes)
#endif
{
	// verify the compression types and initialize the codecs
	for (int codecnum = 0; codecnum < std::size(m_compressor); codecnum++)
	{
		if (compressor_list[codecnum] != CHD_CODEC_NONE)
		{
			m_compressor[codecnum] = chd_codec_list::new_compressor(compressor_list[codecnum], chd);
			if (!m_compressor[codecnum])
				throw std::error_condition(chd_file::error::UNKNOWN_COMPRESSION);
#if CHDCODEC_VERIFY_COMPRESSION
			m_decompressor[codecnum] = chd_codec_list::new_decompressor(compressor_list[codecnum], chd);
			if (!m_decompressor[codecnum])
				throw std::error_condition(chd_file::error::UNKNOWN_COMPRESSION);
#endif
		}
	}
}


//-------------------------------------------------
//  ~chd_compressor_group - destructor
//-------------------------------------------------

chd_compressor_group::~chd_compressor_group()
{
	// codecs and test buffer deleted automatically
}


//-------------------------------------------------
//  find_best_compressor - iterate over all codecs
//  to determine which one produces the best
//  compression for this hunk
//-------------------------------------------------

int8_t chd_compressor_group::find_best_compressor(const uint8_t *src, uint8_t *compressed, uint32_t &complen)
{
	// determine best compression technique
	complen = m_hunkbytes;
	int8_t compression = -1;
	for (int codecnum = 0; codecnum < std::size(m_compressor); codecnum++)
		if (m_compressor[codecnum])
		{
			// attempt to compress, swallowing errors
			try
			{
				// if this is the best one, copy the data into the permanent buffer
				uint32_t compbytes = m_compressor[codecnum]->compress(src, m_hunkbytes, &m_compress_test[0]);
#if CHDCODEC_VERIFY_COMPRESSION
				try
				{
					memset(m_decompressed, 0, m_hunkbytes);
					m_decompressor[codecnum]->decompress(m_compress_test, compbytes, m_decompressed, m_hunkbytes);
				}
				catch (...)
				{
				}

				if (memcmp(src, m_decompressed, m_hunkbytes) != 0)
				{
					compbytes = m_compressor[codecnum]->compress(src, m_hunkbytes, m_compress_test);
					try
					{
						m_decompressor[codecnum]->decompress(m_compress_test, compbytes, m_decompressed, m_hunkbytes);
					}
					catch (...)
					{
						memset(m_decompressed, 0, m_hunkbytes);
					}
				}
printf("   codec%d=%d bytes            \n", codecnum, compbytes);
#endif
				if (compbytes < complen)
				{
					compression = codecnum;
					complen = compbytes;
					memcpy(compressed, &m_compress_test[0], compbytes);
				}
			}
			catch (...)
			{
			}
		}

	// if the best is none, copy it over
	if (compression == -1)
		memcpy(compressed, src, m_hunkbytes);
	return compression;
}



//**************************************************************************
//  ZLIB ALLOCATOR HELPER
//**************************************************************************

//-------------------------------------------------
//  chd_zlib_allocator - constructor
//-------------------------------------------------

chd_zlib_allocator::chd_zlib_allocator()
{
	// reset pointer list
	memset(m_allocptr, 0, sizeof(m_allocptr));
}


//-------------------------------------------------
//  ~chd_zlib_allocator - destructor
//-------------------------------------------------

chd_zlib_allocator::~chd_zlib_allocator()
{
	// free our memory
	for (auto & elem : m_allocptr)
		delete[] elem;
}


//-------------------------------------------------
//  install - configure the allocators for a
//  stream
//-------------------------------------------------

void chd_zlib_allocator::install(z_stream &stream)
{
	stream.zalloc = &chd_zlib_allocator::fast_alloc;
	stream.zfree = &chd_zlib_allocator::fast_free;
	stream.opaque = this;
}


//-------------------------------------------------
//  zlib_fast_alloc - fast malloc for ZLIB, which
//  allocates and frees memory frequently
//-------------------------------------------------

voidpf chd_zlib_allocator::fast_alloc(voidpf opaque, uInt items, uInt size)
{
	auto *codec = reinterpret_cast<chd_zlib_allocator *>(opaque);

	// compute the size, rounding to the nearest 1k
	size = (size * items + 0x3ff) & ~0x3ff;

	// reuse a hunk if we can
	for (int scan = 0; scan < MAX_ZLIB_ALLOCS; scan++)
	{
		uint32_t *ptr = codec->m_allocptr[scan];
		if (ptr != nullptr && size == *ptr)
		{
			// set the low bit of the size so we don't match next time
			*ptr |= 1;
			return ptr + 1;
		}
	}

	// alloc a new one and put it into the list
	auto *ptr = reinterpret_cast<uint32_t *>(new uint8_t[size + sizeof(uint32_t)]);
	for (int scan = 0; scan < MAX_ZLIB_ALLOCS; scan++)
		if (codec->m_allocptr[scan] == nullptr)
		{
			codec->m_allocptr[scan] = ptr;
			break;
		}

	// set the low bit of the size so we don't match next time
	*ptr = size | 1;
	return ptr + 1;
}


//-------------------------------------------------
//  zlib_fast_free - fast free for ZLIB, which
//  allocates and frees memory frequently
//-------------------------------------------------

void chd_zlib_allocator::fast_free(voidpf opaque, voidpf address)
{
	auto *codec = reinterpret_cast<chd_zlib_allocator *>(opaque);

	// find the hunk
	uint32_t *ptr = reinterpret_cast<uint32_t *>(address) - 1;
	for (int scan = 0; scan < MAX_ZLIB_ALLOCS; scan++)
		if (ptr == codec->m_allocptr[scan])
		{
			// clear the low bit of the size to allow matches
			*ptr &= ~1;
			return;
		}
}



//**************************************************************************
//  ZLIB COMPRESSOR
//**************************************************************************

//-------------------------------------------------
//  chd_zlib_compressor - constructor
//-------------------------------------------------

chd_zlib_compressor::chd_zlib_compressor(chd_file &chd, uint32_t hunkbytes, bool lossy)
	: chd_compressor(chd, hunkbytes, lossy)
{
	// initialize the deflater
	m_deflater.next_in = (Bytef *)this; // bogus, but that's ok
	m_deflater.avail_in = 0;
	m_allocator.install(m_deflater);
	int zerr = deflateInit2(&m_deflater, Z_BEST_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY);

	// convert errors
	if (zerr == Z_MEM_ERROR)
		throw std::bad_alloc();
	else if (zerr != Z_OK)
		throw std::error_condition(chd_file::error::CODEC_ERROR);
}


//-------------------------------------------------
//  ~chd_zlib_compressor - destructor
//-------------------------------------------------

chd_zlib_compressor::~chd_zlib_compressor()
{
	deflateEnd(&m_deflater);
}


//-------------------------------------------------
//  compress - compress data using the ZLIB codec
//-------------------------------------------------

uint32_t chd_zlib_compressor::compress(const uint8_t *src, uint32_t srclen, uint8_t *dest)
{
	// reset the decompressor
	m_deflater.next_in = const_cast<Bytef *>(src);
	m_deflater.avail_in = srclen;
	m_deflater.total_in = 0;
	m_deflater.next_out = dest;
	m_deflater.avail_out = srclen;
	m_deflater.total_out = 0;
	int zerr = deflateReset(&m_deflater);
	if (zerr != Z_OK)
		throw std::error_condition(chd_file::error::COMPRESSION_ERROR);

	// do it
	zerr = deflate(&m_deflater, Z_FINISH);

	// if we ended up with more data than we started with, return an error
	if (zerr != Z_STREAM_END || m_deflater.total_out >= srclen)
		throw std::error_condition(chd_file::error::COMPRESSION_ERROR);

	// otherwise, return the length
	return m_deflater.total_out;
}



//**************************************************************************
//  ZLIB DECOMPRESSOR
//**************************************************************************

//-------------------------------------------------
//  chd_zlib_decompressor - constructor
//-------------------------------------------------

chd_zlib_decompressor::chd_zlib_decompressor(chd_file &chd, uint32_t hunkbytes, bool lossy)
	: chd_decompressor(chd, hunkbytes, lossy)
{
	// init the inflater
	m_inflater.next_in = (Bytef *)this; // bogus, but that's ok
	m_inflater.avail_in = 0;
	m_allocator.install(m_inflater);
	int zerr = inflateInit2(&m_inflater, -MAX_WBITS);

	// convert errors
	if (zerr == Z_MEM_ERROR)
		throw std::bad_alloc();
	else if (zerr != Z_OK)
		throw std::error_condition(chd_file::error::CODEC_ERROR);
}


//-------------------------------------------------
//  ~chd_zlib_decompressor - destructor
//-------------------------------------------------

chd_zlib_decompressor::~chd_zlib_decompressor()
{
	inflateEnd(&m_inflater);
}


//-------------------------------------------------
//  decompress - decompress data using the ZLIB
//  codec
//-------------------------------------------------

void chd_zlib_decompressor::decompress(const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen)
{
	// reset the decompressor
	m_inflater.next_in = const_cast<Bytef *>(src);
	m_inflater.avail_in = complen;
	m_inflater.total_in = 0;
	m_inflater.next_out = dest;
	m_inflater.avail_out = destlen;
	m_inflater.total_out = 0;
	int zerr = inflateReset(&m_inflater);
	if (zerr != Z_OK)
		throw std::error_condition(chd_file::error::DECOMPRESSION_ERROR);

	// do it
	zerr = inflate(&m_inflater, Z_FINISH);
	if (zerr != Z_STREAM_END)
		throw std::error_condition(chd_file::error::DECOMPRESSION_ERROR);
	if (m_inflater.total_out != destlen)
		throw std::error_condition(chd_file::error::DECOMPRESSION_ERROR);
}



//**************************************************************************
//  LZMA ALLOCATOR HELPER
//**************************************************************************

//-------------------------------------------------
//  chd_lzma_allocator - constructor
//-------------------------------------------------

chd_lzma_allocator::chd_lzma_allocator()
{
	// reset pointer list
	memset(m_allocptr, 0, sizeof(m_allocptr));

	// set our pointers
	Alloc = &chd_lzma_allocator::fast_alloc;
	Free = &chd_lzma_allocator::fast_free;
}


//-------------------------------------------------
//  ~chd_lzma_allocator - constructor
//-------------------------------------------------

chd_lzma_allocator::~chd_lzma_allocator()
{
	// free our memory
	for (auto & elem : m_allocptr)
		delete[] elem;
}


//-------------------------------------------------
//  lzma_fast_alloc - fast malloc for lzma, which
//  allocates and frees memory frequently
//-------------------------------------------------

void *chd_lzma_allocator::fast_alloc(void *p, size_t size)
{
	auto *codec = reinterpret_cast<chd_lzma_allocator *>(p);

	// compute the size, rounding to the nearest 1k
	size = (size + 0x3ff) & ~0x3ff;

	// reuse a hunk if we can
	for (int scan = 0; scan < MAX_LZMA_ALLOCS; scan++)
	{
		uint32_t *ptr = codec->m_allocptr[scan];
		if (ptr != nullptr && size == *ptr)
		{
			// set the low bit of the size so we don't match next time
			*ptr |= 1;
			return ptr + 1;
		}
	}

	// alloc a new one and put it into the list
	auto *ptr = reinterpret_cast<uint32_t *>(new uint8_t[size + sizeof(uint32_t)]);
	for (int scan = 0; scan < MAX_LZMA_ALLOCS; scan++)
		if (codec->m_allocptr[scan] == nullptr)
		{
			codec->m_allocptr[scan] = ptr;
			break;
		}

	// set the low bit of the size so we don't match next time
	*ptr = size | 1;
	return ptr + 1;
}


//-------------------------------------------------
//  lzma_fast_free - fast free for lzma, which
//  allocates and frees memory frequently
//-------------------------------------------------

void chd_lzma_allocator::fast_free(void *p, void *address)
{
	if (address == nullptr)
		return;

	auto *codec = reinterpret_cast<chd_lzma_allocator *>(p);

	// find the hunk
	uint32_t *ptr = reinterpret_cast<uint32_t *>(address) - 1;
	for (int scan = 0; scan < MAX_LZMA_ALLOCS; scan++)
		if (ptr == codec->m_allocptr[scan])
		{
			// clear the low bit of the size to allow matches
			*ptr &= ~1;
			return;
		}
}



//**************************************************************************
//  LZMA COMPRESSOR
//**************************************************************************

//-------------------------------------------------
//  chd_lzma_compressor - constructor
//-------------------------------------------------

chd_lzma_compressor::chd_lzma_compressor(chd_file &chd, uint32_t hunkbytes, bool lossy)
	: chd_compressor(chd, hunkbytes, lossy)
{
	// initialize the properties
	configure_properties(m_props, hunkbytes);
}


//-------------------------------------------------
//  ~chd_lzma_compressor - destructor
//-------------------------------------------------

chd_lzma_compressor::~chd_lzma_compressor()
{
}


//-------------------------------------------------
//  compress - compress data using the LZMA codec
//-------------------------------------------------

uint32_t chd_lzma_compressor::compress(const uint8_t *src, uint32_t srclen, uint8_t *dest)
{
	// allocate the encoder
	CLzmaEncHandle encoder = LzmaEnc_Create(&m_allocator);
	if (encoder == nullptr)
		throw std::error_condition(chd_file::error::COMPRESSION_ERROR);

	try
	{
		// configure the encoder
		SRes res = LzmaEnc_SetProps(encoder, &m_props);
		if (res != SZ_OK)
			throw std::error_condition(chd_file::error::COMPRESSION_ERROR);

		// run it
		SizeT complen = srclen;
		res = LzmaEnc_MemEncode(encoder, dest, &complen, src, srclen, 0, nullptr, &m_allocator, &m_allocator);
		if (res != SZ_OK)
			throw std::error_condition(chd_file::error::COMPRESSION_ERROR);

		// clean up
		LzmaEnc_Destroy(encoder, &m_allocator, &m_allocator);
		return complen;
	}
	catch (...)
	{
		// destroy before re-throwing
		LzmaEnc_Destroy(encoder, &m_allocator, &m_allocator);
		throw;
	}
}


//-------------------------------------------------
//  configure_properties - configure the LZMA
//  codec
//-------------------------------------------------

void chd_lzma_compressor::configure_properties(CLzmaEncProps &props, uint32_t hunkbytes)
{
	LzmaEncProps_Init(&props);
	props.level = 9;
	props.reduceSize = hunkbytes;
	LzmaEncProps_Normalize(&props);
}



//**************************************************************************
//  LZMA DECOMPRESSOR
//**************************************************************************

//-------------------------------------------------
//  chd_lzma_decompressor - constructor
//-------------------------------------------------

chd_lzma_decompressor::chd_lzma_decompressor(chd_file &chd, uint32_t hunkbytes, bool lossy)
	: chd_decompressor(chd, hunkbytes, lossy)
{
	// construct the decoder
	LzmaDec_Construct(&m_decoder);

	// FIXME: this code is written in a way that makes it impossible to safely upgrade the LZMA SDK
	// This code assumes that the current version of the encoder imposes the same requirements on the
	// decoder as the encoder used to produce the file.  This is not necessarily true.  The format
	// needs to be changed so the encoder properties are written to the file.

	// configure the properties like the compressor did
	CLzmaEncProps encoder_props;
	chd_lzma_compressor::configure_properties(encoder_props, hunkbytes);

	// convert to decoder properties
	CLzmaEncHandle enc = LzmaEnc_Create(&m_allocator);
	if (!enc)
		throw std::error_condition(chd_file::error::DECOMPRESSION_ERROR);
	if (LzmaEnc_SetProps(enc, &encoder_props) != SZ_OK)
	{
		LzmaEnc_Destroy(enc, &m_allocator, &m_allocator);
		throw std::error_condition(chd_file::error::DECOMPRESSION_ERROR);
	}
	Byte decoder_props[LZMA_PROPS_SIZE];
	SizeT props_size = sizeof(decoder_props);
	if (LzmaEnc_WriteProperties(enc, decoder_props, &props_size) != SZ_OK)
	{
		LzmaEnc_Destroy(enc, &m_allocator, &m_allocator);
		throw std::error_condition(chd_file::error::DECOMPRESSION_ERROR);
	}
	LzmaEnc_Destroy(enc, &m_allocator, &m_allocator);

	// do memory allocations
	if (LzmaDec_Allocate(&m_decoder, decoder_props, LZMA_PROPS_SIZE, &m_allocator) != SZ_OK)
		throw std::error_condition(chd_file::error::DECOMPRESSION_ERROR);
}


//-------------------------------------------------
//  ~chd_lzma_decompressor - destructor
//-------------------------------------------------

chd_lzma_decompressor::~chd_lzma_decompressor()
{
	// free memory
	LzmaDec_Free(&m_decoder, &m_allocator);
}


//-------------------------------------------------
//  decompress - decompress data using the LZMA
//  codec
//-------------------------------------------------

void chd_lzma_decompressor::decompress(const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen)
{
	// initialize
	LzmaDec_Init(&m_decoder);

	// decode
	SizeT consumedlen = complen;
	SizeT decodedlen = destlen;
	ELzmaStatus status;
	SRes res = LzmaDec_DecodeToBuf(&m_decoder, dest, &decodedlen, src, &consumedlen, LZMA_FINISH_END, &status);
	if ((res != SZ_OK && res != LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK) || consumedlen != complen || decodedlen != destlen)
		throw std::error_condition(chd_file::error::DECOMPRESSION_ERROR);
}



//**************************************************************************
//  HUFFMAN COMPRESSOR
//**************************************************************************

//-------------------------------------------------
//  chd_huffman_compressor - constructor
//-------------------------------------------------

chd_huffman_compressor::chd_huffman_compressor(chd_file &chd, uint32_t hunkbytes, bool lossy)
	: chd_compressor(chd, hunkbytes, lossy)
{
}


//-------------------------------------------------
//  compress - compress data using the Huffman
//  codec
//-------------------------------------------------

uint32_t chd_huffman_compressor::compress(const uint8_t *src, uint32_t srclen, uint8_t *dest)
{
	uint32_t complen;
	if (m_encoder.encode(src, srclen, dest, srclen, complen) != HUFFERR_NONE)
		throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
	return complen;
}



//**************************************************************************
//  HUFFMAN DECOMPRESSOR
//**************************************************************************

//-------------------------------------------------
//  chd_huffman_decompressor - constructor
//-------------------------------------------------

chd_huffman_decompressor::chd_huffman_decompressor(chd_file &chd, uint32_t hunkbytes, bool lossy)
	: chd_decompressor(chd, hunkbytes, lossy)
{
}


//-------------------------------------------------
//  decompress - decompress data using the Huffman
//  codec
//-------------------------------------------------

void chd_huffman_decompressor::decompress(const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen)
{
	if (m_decoder.decode(src, complen, dest, destlen) != HUFFERR_NONE)
		throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
}



//**************************************************************************
//  FLAC COMPRESSOR
//**************************************************************************

//-------------------------------------------------
//  chd_flac_compressor - constructor
//-------------------------------------------------

chd_flac_compressor::chd_flac_compressor(chd_file &chd, uint32_t hunkbytes, bool lossy)
	: chd_compressor(chd, hunkbytes, lossy)
{
	// determine whether we want native or swapped samples
	uint16_t native_endian = 0;
	*reinterpret_cast<uint8_t *>(&native_endian) = 1;
	m_big_endian = (native_endian == 0x100);

	// configure the encoder
	m_encoder.set_sample_rate(44100);
	m_encoder.set_num_channels(2);
	m_encoder.set_block_size(blocksize(hunkbytes));
	m_encoder.set_strip_metadata(true);
}


//-------------------------------------------------
//  compress - compress data using the FLAC codec
//-------------------------------------------------

uint32_t chd_flac_compressor::compress(const uint8_t *src, uint32_t srclen, uint8_t *dest)
{
	// reset and encode big-endian
	m_encoder.reset(dest + 1, hunkbytes() - 1);
	if (!m_encoder.encode_interleaved(reinterpret_cast<const int16_t *>(src), srclen / 4, !m_big_endian))
		throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
	uint32_t complen_be = m_encoder.finish();

	// reset and encode little-endian
	m_encoder.reset(dest + 1, hunkbytes() - 1);
	if (!m_encoder.encode_interleaved(reinterpret_cast<const int16_t *>(src), srclen / 4, m_big_endian))
		throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
	uint32_t complen_le = m_encoder.finish();

	// pick the best one and add a byte
	uint32_t complen = std::min(complen_le, complen_be);
	if (complen + 1 >= hunkbytes())
		throw std::error_condition(chd_file::error::COMPRESSION_ERROR);

	// if big-endian was better, re-do it
	dest[0] = 'L';
	if (complen != complen_le)
	{
		dest[0] = 'B';
		m_encoder.reset(dest + 1, hunkbytes() - 1);
		if (!m_encoder.encode_interleaved(reinterpret_cast<const int16_t *>(src), srclen / 4, !m_big_endian))
			throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
		m_encoder.finish();
	}
	return complen + 1;
}


//-------------------------------------------------
//  blocksize - return the optimal block size
//-------------------------------------------------

uint32_t chd_flac_compressor::blocksize(uint32_t bytes)
{
	// determine FLAC block size, which must be 16-65535
	// clamp to 2k since that's supposed to be the sweet spot
	uint32_t hunkbytes = bytes / 4;
	while (hunkbytes > 2048)
		hunkbytes /= 2;
	return hunkbytes;
}



//**************************************************************************
//  FLAC DECOMPRESSOR
//**************************************************************************

//-------------------------------------------------
//  chd_flac_decompressor - constructor
//-------------------------------------------------

chd_flac_decompressor::chd_flac_decompressor(chd_file &chd, uint32_t hunkbytes, bool lossy)
	: chd_decompressor(chd, hunkbytes, lossy)
{
	// determine whether we want native or swapped samples
	uint16_t native_endian = 0;
	*reinterpret_cast<uint8_t *>(&native_endian) = 1;
	m_big_endian = (native_endian == 0x100);
}


//-------------------------------------------------
//  decompress - decompress data using the FLAC
//  codec
//-------------------------------------------------

void chd_flac_decompressor::decompress(const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen)
{
	// determine the endianness
	bool swap_endian;
	if (src[0] == 'L')
		swap_endian = m_big_endian;
	else if (src[0] == 'B')
		swap_endian = !m_big_endian;
	else
		throw std::error_condition(chd_file::error::DECOMPRESSION_ERROR);

	// reset and decode
	if (!m_decoder.reset(44100, 2, chd_flac_compressor::blocksize(destlen), src + 1, complen - 1))
		throw std::error_condition(chd_file::error::DECOMPRESSION_ERROR);
	if (!m_decoder.decode_interleaved(reinterpret_cast<int16_t *>(dest), destlen / 4, swap_endian))
		throw std::error_condition(chd_file::error::DECOMPRESSION_ERROR);

	// finish up
	m_decoder.finish();
}



//**************************************************************************
//  CD FLAC COMPRESSOR
//**************************************************************************

//-------------------------------------------------
//  chd_cd_flac_compressor - constructor
//-------------------------------------------------

chd_cd_flac_compressor::chd_cd_flac_compressor(chd_file &chd, uint32_t hunkbytes, bool lossy)
	: chd_compressor(chd, hunkbytes, lossy),
		m_buffer(hunkbytes)
{
	// make sure the CHD's hunk size is an even multiple of the frame size
	if (hunkbytes % CD_FRAME_SIZE != 0)
		throw std::error_condition(chd_file::error::CODEC_ERROR);

	// determine whether we want native or swapped samples
	uint16_t native_endian = 0;
	*reinterpret_cast<uint8_t *>(&native_endian) = 1;
	m_swap_endian = (native_endian == 1);

	// configure the encoder
	m_encoder.set_sample_rate(44100);
	m_encoder.set_num_channels(2);
	m_encoder.set_block_size(blocksize((hunkbytes / CD_FRAME_SIZE) * CD_MAX_SECTOR_DATA));
	m_encoder.set_strip_metadata(true);

	// initialize the deflater
	m_deflater.next_in = (Bytef *)this; // bogus, but that's ok
	m_deflater.avail_in = 0;
	m_allocator.install(m_deflater);
	int zerr = deflateInit2(&m_deflater, Z_BEST_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY);

	// convert errors
	if (zerr == Z_MEM_ERROR)
		throw std::bad_alloc();
	else if (zerr != Z_OK)
		throw std::error_condition(chd_file::error::CODEC_ERROR);
}


//-------------------------------------------------
//  ~chd_cd_flac_compressor - destructor
//-------------------------------------------------

chd_cd_flac_compressor::~chd_cd_flac_compressor()
{
	deflateEnd(&m_deflater);
}


//-------------------------------------------------
//  compress - compress data using the FLAC codec,
//  and use zlib on the subcode data
//-------------------------------------------------

uint32_t chd_cd_flac_compressor::compress(const uint8_t *src, uint32_t srclen, uint8_t *dest)
{
	// copy audio data followed by subcode data
	uint32_t frames = hunkbytes() / CD_FRAME_SIZE;
	for (uint32_t framenum = 0; framenum < frames; framenum++)
	{
		memcpy(&m_buffer[framenum * CD_MAX_SECTOR_DATA], &src[framenum * CD_FRAME_SIZE], CD_MAX_SECTOR_DATA);
		memcpy(&m_buffer[frames * CD_MAX_SECTOR_DATA + framenum * CD_MAX_SUBCODE_DATA], &src[framenum * CD_FRAME_SIZE + CD_MAX_SECTOR_DATA], CD_MAX_SUBCODE_DATA);
	}

	// reset and encode the audio portion
	m_encoder.reset(dest, hunkbytes());
	uint8_t *buffer = &m_buffer[0];
	if (!m_encoder.encode_interleaved(reinterpret_cast<int16_t *>(buffer), frames * CD_MAX_SECTOR_DATA/4, m_swap_endian))
		throw std::error_condition(chd_file::error::COMPRESSION_ERROR);

	// finish up
	uint32_t complen = m_encoder.finish();

	// deflate the subcode data
	m_deflater.next_in = const_cast<Bytef *>(&m_buffer[frames * CD_MAX_SECTOR_DATA]);
	m_deflater.avail_in = frames * CD_MAX_SUBCODE_DATA;
	m_deflater.total_in = 0;
	m_deflater.next_out = &dest[complen];
	m_deflater.avail_out = hunkbytes() - complen;
	m_deflater.total_out = 0;
	int zerr = deflateReset(&m_deflater);
	if (zerr != Z_OK)
		throw std::error_condition(chd_file::error::COMPRESSION_ERROR);

	// do it
	zerr = deflate(&m_deflater, Z_FINISH);

	// if we ended up with more data than we started with, return an error
	complen += m_deflater.total_out;
	if (zerr != Z_STREAM_END || complen >= srclen)
		throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
	return complen;
}

/**
 * @fn  uint32_t chd_cd_flac_compressor::blocksize(uint32_t bytes)
 *
 * @brief   -------------------------------------------------
 *            blocksize - return the optimal block size
 *          -------------------------------------------------.
 *
 * @param   bytes   The bytes.
 *
 * @return  An uint32_t.
 */

uint32_t chd_cd_flac_compressor::blocksize(uint32_t bytes)
{
	// for CDs it seems that CD_MAX_SECTOR_DATA is the right target
	uint32_t blocksize = bytes / 4;
	while (blocksize > CD_MAX_SECTOR_DATA)
		blocksize /= 2;
	return blocksize;
}



//**************************************************************************
//  CD FLAC DECOMPRESSOR
//**************************************************************************

/**
 * @fn  chd_cd_flac_decompressor::chd_cd_flac_decompressor(chd_file &chd, uint32_t hunkbytes, bool lossy)
 *
 * @brief   -------------------------------------------------
 *            chd_cd_flac_decompressor - constructor
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_CODEC_ERROR  Thrown when a chderr codec error error condition occurs.
 *
 * @param [in,out]  chd The chd.
 * @param   hunkbytes   The hunkbytes.
 * @param   lossy       true to lossy.
 */

chd_cd_flac_decompressor::chd_cd_flac_decompressor(chd_file &chd, uint32_t hunkbytes, bool lossy)
	: chd_decompressor(chd, hunkbytes, lossy),
		m_buffer(hunkbytes)
{
	// make sure the CHD's hunk size is an even multiple of the frame size
	if (hunkbytes % CD_FRAME_SIZE != 0)
		throw std::error_condition(chd_file::error::CODEC_ERROR);

	// determine whether we want native or swapped samples
	uint16_t native_endian = 0;
	*reinterpret_cast<uint8_t *>(&native_endian) = 1;
	m_swap_endian = (native_endian == 1);

	// init the inflater
	m_inflater.next_in = (Bytef *)this; // bogus, but that's ok
	m_inflater.avail_in = 0;
	m_allocator.install(m_inflater);
	int zerr = inflateInit2(&m_inflater, -MAX_WBITS);

	// convert errors
	if (zerr == Z_MEM_ERROR)
		throw std::bad_alloc();
	else if (zerr != Z_OK)
		throw std::error_condition(chd_file::error::CODEC_ERROR);
}

/**
 * @fn  chd_cd_flac_decompressor::~chd_cd_flac_decompressor()
 *
 * @brief   -------------------------------------------------
 *            ~chd_cd_flac_decompressor - destructor
 *          -------------------------------------------------.
 */

chd_cd_flac_decompressor::~chd_cd_flac_decompressor()
{
	inflateEnd(&m_inflater);
}

/**
 * @fn  void chd_cd_flac_decompressor::decompress(const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen)
 *
 * @brief   -------------------------------------------------
 *            decompress - decompress data using the FLAC codec
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_DECOMPRESSION_ERROR  Thrown when a chderr decompression error error
 *                                          condition occurs.
 *
 * @param   src             Source for the.
 * @param   complen         The complen.
 * @param [in,out]  dest    If non-null, destination for the.
 * @param   destlen         The destlen.
 */

void chd_cd_flac_decompressor::decompress(const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen)
{
	// reset and decode
	uint32_t frames = destlen / CD_FRAME_SIZE;
	if (!m_decoder.reset(44100, 2, chd_cd_flac_compressor::blocksize(frames * CD_MAX_SECTOR_DATA), src, complen))
		throw std::error_condition(chd_file::error::DECOMPRESSION_ERROR);
	uint8_t *buffer = &m_buffer[0];
	if (!m_decoder.decode_interleaved(reinterpret_cast<int16_t *>(buffer), frames * CD_MAX_SECTOR_DATA/4, m_swap_endian))
		throw std::error_condition(chd_file::error::DECOMPRESSION_ERROR);

	// inflate the subcode data
	uint32_t offset = m_decoder.finish();
	m_inflater.next_in = const_cast<Bytef *>(src + offset);
	m_inflater.avail_in = complen - offset;
	m_inflater.total_in = 0;
	m_inflater.next_out = &m_buffer[frames * CD_MAX_SECTOR_DATA];
	m_inflater.avail_out = frames * CD_MAX_SUBCODE_DATA;
	m_inflater.total_out = 0;
	int zerr = inflateReset(&m_inflater);
	if (zerr != Z_OK)
		throw std::error_condition(chd_file::error::DECOMPRESSION_ERROR);

	// do it
	zerr = inflate(&m_inflater, Z_FINISH);
	if (zerr != Z_STREAM_END)
		throw std::error_condition(chd_file::error::DECOMPRESSION_ERROR);
	if (m_inflater.total_out != frames * CD_MAX_SUBCODE_DATA)
		throw std::error_condition(chd_file::error::DECOMPRESSION_ERROR);

	// reassemble the data
	for (uint32_t framenum = 0; framenum < frames; framenum++)
	{
		memcpy(&dest[framenum * CD_FRAME_SIZE], &m_buffer[framenum * CD_MAX_SECTOR_DATA], CD_MAX_SECTOR_DATA);
		memcpy(&dest[framenum * CD_FRAME_SIZE + CD_MAX_SECTOR_DATA], &m_buffer[frames * CD_MAX_SECTOR_DATA + framenum * CD_MAX_SUBCODE_DATA], CD_MAX_SUBCODE_DATA);
	}
}



//**************************************************************************
//  AVHUFF COMPRESSOR
//**************************************************************************

/**
 * @fn  chd_avhuff_compressor::chd_avhuff_compressor(chd_file &chd, uint32_t hunkbytes, bool lossy)
 *
 * @brief   -------------------------------------------------
 *            chd_avhuff_compressor - constructor
 *          -------------------------------------------------.
 *
 * @param [in,out]  chd The chd.
 * @param   hunkbytes   The hunkbytes.
 * @param   lossy       true to lossy.
 */

chd_avhuff_compressor::chd_avhuff_compressor(chd_file &chd, uint32_t hunkbytes, bool lossy)
	: chd_compressor(chd, hunkbytes, lossy)
	, m_postinit(false)
{
	try
	{
		// attempt to do a post-init now
		postinit();
	}
	catch (std::error_condition const &)
	{
		// if we're creating a new CHD, it won't work but that's ok
	}
}

/**
 * @fn  uint32_t chd_avhuff_compressor::compress(const uint8_t *src, uint32_t srclen, uint8_t *dest)
 *
 * @brief   -------------------------------------------------
 *            compress - compress data using the A/V codec
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_INVALID_DATA         Thrown when a chderr invalid data error condition
 *                                          occurs.
 * @exception   CHDERR_COMPRESSION_ERROR    Thrown when a chderr compression error error
 *                                          condition occurs.
 *
 * @param   src             Source for the.
 * @param   srclen          The srclen.
 * @param [in,out]  dest    If non-null, destination for the.
 *
 * @return  An uint32_t.
 */

uint32_t chd_avhuff_compressor::compress(const uint8_t *src, uint32_t srclen, uint8_t *dest)
{
	// if we haven't yet set up the avhuff code, do it now
	if (!m_postinit)
		postinit();

	// make sure short frames are padded with 0
	if (src != nullptr)
	{
		int size = avhuff_encoder::raw_data_size(src);
		while (size < srclen)
			if (src[size++] != 0)
				throw std::error_condition(chd_file::error::INVALID_DATA);
	}

	// encode the audio and video
	uint32_t complen;
	avhuff_error averr = m_encoder.encode_data(src, dest, complen);
	if (averr != AVHERR_NONE || complen > srclen)
		throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
	return complen;
}

/**
 * @fn  void chd_avhuff_compressor::postinit()
 *
 * @brief   -------------------------------------------------
 *            postinit - actual initialization of avhuff happens here, on the first attempt to
 *            compress or decompress data
 *          -------------------------------------------------.
 *
 * @exception   err                     Thrown when an error error condition occurs.
 * @exception   CHDERR_INVALID_METADATA Thrown when a chderr invalid metadata error condition
 *                                      occurs.
 */

void chd_avhuff_compressor::postinit()
{
	// get the metadata
	std::string metadata;
	std::error_condition err = chd().read_metadata(AV_METADATA_TAG, 0, metadata);
	if (err)
		throw err;

	// extract the info
	int fps, fpsfrac, width, height, interlaced, channels, rate;
	if (sscanf(metadata.c_str(), AV_METADATA_FORMAT, &fps, &fpsfrac, &width, &height, &interlaced, &channels, &rate) != 7)
		throw std::error_condition(chd_file::error::INVALID_METADATA);

	// compute the bytes per frame
	uint32_t fps_times_1million = fps * 1000000 + fpsfrac;
	uint32_t max_samples_per_frame = (uint64_t(rate) * 1000000 + fps_times_1million - 1) / fps_times_1million;
	uint32_t bytes_per_frame = 12 + channels * max_samples_per_frame * 2 + width * height * 2;
	if (bytes_per_frame > hunkbytes())
		throw std::error_condition(chd_file::error::INVALID_METADATA);

	// done with post-init
	m_postinit = true;
}



//**************************************************************************
//  AVHUFF DECOMPRESSOR
//**************************************************************************

/**
 * @fn  chd_avhuff_decompressor::chd_avhuff_decompressor(chd_file &chd, uint32_t hunkbytes, bool lossy)
 *
 * @brief   -------------------------------------------------
 *            chd_avhuff_decompressor - constructor
 *          -------------------------------------------------.
 *
 * @param [in,out]  chd The chd.
 * @param   hunkbytes   The hunkbytes.
 * @param   lossy       true to lossy.
 */

chd_avhuff_decompressor::chd_avhuff_decompressor(chd_file &chd, uint32_t hunkbytes, bool lossy)
	: chd_decompressor(chd, hunkbytes, lossy)
{
}

/**
 * @fn  void chd_avhuff_decompressor::decompress(const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen)
 *
 * @brief   -------------------------------------------------
 *            decompress - decompress data using the A/V codec
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_DECOMPRESSION_ERROR  Thrown when a chderr decompression error error
 *                                          condition occurs.
 *
 * @param   src             Source for the.
 * @param   complen         The complen.
 * @param [in,out]  dest    If non-null, destination for the.
 * @param   destlen         The destlen.
 */

void chd_avhuff_decompressor::decompress(const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen)
{
	// decode the audio and video
	avhuff_error averr = m_decoder.decode_data(src, complen, dest);
	if (averr != AVHERR_NONE)
		throw std::error_condition(chd_file::error::DECOMPRESSION_ERROR);

	// pad short frames with 0
	if (dest != nullptr)
	{
		int size = avhuff_encoder::raw_data_size(dest);
		if (size < destlen)
			memset(dest + size, 0, destlen - size);
	}
}

/**
 * @fn  void chd_avhuff_decompressor::configure(int param, void *config)
 *
 * @brief   -------------------------------------------------
 *            config - codec-specific configuration for the A/V codec
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_INVALID_PARAMETER    Thrown when a chderr invalid parameter error
 *                                          condition occurs.
 *
 * @param   param           The parameter.
 * @param [in,out]  config  If non-null, the configuration.
 */

void chd_avhuff_decompressor::configure(int param, void *config)
{
	// if we're getting the decompression configuration, apply it now
	if (param == AVHUFF_CODEC_DECOMPRESS_CONFIG)
		m_decoder.configure(*reinterpret_cast<avhuff_decoder::config const *>(config));

	// anything else is invalid
	else
		throw std::error_condition(std::errc::invalid_argument);
}
