/*
								+----------------------------------+
								|                                  |
								|  ***   Base85 coder class   ***  |
								|                                  |
								|   Copyright © -tHE SWINe- 2014   |
								|                                  |
								|             Base85.h             |
								|                                  |
								+----------------------------------+
*/

#pragma once
#ifndef __BASE85_CODING_INCLUDED
#define __BASE85_CODING_INCLUDED

/**
 *	@file Base85.h
 *	@author -tHE SWINe-
 *	@date 2014
 *	@brief Base-85 encoding implementation
 */

#include "../UberLame_src/Integer.h"
#include "../UberLame_src/Buffer.h"

/**
 *	@brief simple Modified Base-85 encoding class
 *
 *	@note This only performs encoding/decoding on buffers (not on streams)
 *		and decoding can only be performed on buffers, containing no
 *		whitespace / other characters, not belonging to Base-85 character set.
 *	@note By default "0-9A-Za-z!#$%&()*+-;<=>?@^_`{|}~" characters are used
 *		(as in RFC 1924) and no padding characters are inserted / removed.
 */
class CModifiedBase85 {
public:
	/**
	 *	@brief simple inverse character-mapping table
	 */
	class CInverseTable {
	protected:
		int m_p_decode[256]; /**< @brief inverse table data */

	public:
		/**
		 *	Default constructor; creates table of 256 indices,
		 *	containing positions of characters in p_s_encode_chars, or -1 in
		 *	case character with given ordinal value doesn't occur in p_s_encode_chars.
		 *
		 *	@param[in] p_s_encode_chars is array of 64 different characters, which
		 *		will be used to encode Base-85 (referred to as "alphabet" in RFC).
		 */
		CInverseTable(const char *p_s_encode_table = CModifiedBase85::p_s_Default_EncodeTable());

		/**
		 *	@return Returns table with 256 elements, when indexed by character codes,
		 *		yields zero-based index of a given character in the string, given to
		 *		constructor or -1 in case the string didn't contain such character.
		 *
		 *	@note Use of type int here is justified by fact that on most of today's
		 *		architectures it's faster to work with data on 4B boundaries.
		 */
		inline const int *p_Table() const;
	};

protected:
	static const CInverseTable m_default_invtable; /**< @brief inverse coding table */

public:
	/**
	 *	@brief gets default Base-85 alphabet
	 *
	 *	@return Returns default encoding character table
	 *
	 *	@note The returned string contains 85 different (us-ascii)
	 *		characters specified in RFC 1924.
	 */
	static inline const char *p_s_Default_EncodeTable();

	/**
	 *	@brief gets default Base-85 alphabet inverse
	 *
	 *	@return Returns default decoding character index table.
	 *
	 *	@note The returned table contains 256 entries, containing
	 *		zero-based character position of each of characters in
	 *		p_s_DefaultEncodeChars(), indexed by character codes.
	 *		table contains -1 for characters not being present in
	 *		p_s_DefaultEncodeChars().
	 *	@note Use of type int here is justified by fact that on most of today's
	 *		architectures it's faster to work with data on 4B boundaries.
	 */
	static inline const int *p_Default_DecodeTable();

	/**
	 *	@brief returns maximal size of data to be encoded
	 *
	 *	@return Returns maximal size of data to be encoded
	 *		(ratio of original and base64 encoded data is 6:8,
	 *		therefore for 4GB buffer limit, this returns 3.2GB,
	 *		which is largest possible amount of input data that
	 *		can be encoded to base64 without crossing 4BG limit).
	 */
	static inline size_t n_Max_EncodeSize();

	/**
	 *	@brief calculates size, needed for encoding output
	 *
	 *	@param[in] n_input is size of input data in bytes
	 *
	 *	@return Returns size of buffer (in bytes) to contain encoded data.
	 *
	 *	@note n_input must be less or equal to n_Max_EncodeSize().
	 *	@note This returns an upper bound of encoded size, if explicit
	 *		null encoding is used, the encoded size may be lower.
	 */
	static inline size_t n_EncodedSize_UpperBound(size_t n_input);

	/**
	 *	@brief calculates size, needed for decoding output
	 *	@param[in] n_input is size of input (encoded) data in bytes
	 *	@return Returns size of buffer (in bytes) to contain decoded data.
	 *	@note This returns a lower bound of decoded size, if explicit
	 *		null encoding is used, the decoded size may be much higher.
	 */
	static inline size_t n_DecodedSize(size_t n_input);

	/**
	 *	@brief calculates size, needed for decoding output with explicit null coding
	 *
	 *	@param[in] p_s_base85 is input Base-85 string
	 *	@param[in] n_length is length of the input, in characters
	 *		(if -1, the input is assumed null-terminated)
	 *	@param[in] p_decode_table is pointer to the Base-85 decoding table
	 *
	 *	@return Returns size of buffer (in bytes) to contain decoded data,
	 *		returns -1 in case the decoded output size does not fit in size_t.
	 *
	 *	@note This ignores errors in the input stream.
	 */
	static size_t n_DecodedSize_ExplicitNull(const char *p_s_base85,
		size_t n_length = size_t(-1), const int *p_decode_table = p_Default_DecodeTable());

	/**
	 *	@brief data encoding routine
	 *
	 *	Encodes contents of p_src_data buffer and stores it in r_s_dest string
	 *	(rewrites original contents, does not concatenate).
	 *
	 *	@param[in] n_dest_size is size of the destination buffer, in bytes
	 *	@param[out] p_dest is destination buffer
	 *	@param[in] n_size is input data size (must be less or equal to n_Max_EncodeSize() bytes)
	 *	@param[in] p_data is buffer, containing data to be encoded
	 *	@param[in] b_allow_explicit_null is explicitly encoded null flag
	 *	@param[in] p_s_encode_table must be string containing 85 different characters,
	 *		preferably representable using some basic encoding such as iso-8859-1
	 *		so they can be printed / viewed by pretty much any kind of system
	 *
	 *	@return Returns number of bytes written to dest buffer on success, zero on failure.
	 *		(watch out for empty input buffer!; can't return negative number,
	 *		size_t may be unsigned)
	 *
	 *	@note The inverse mapping table of p_s_encode_chars is required for decoding.
	 *	@note Encoding increases data size in 5:4 ratio, so there's n_Max_EncodeSize() limit
	 *		(for x86 application it's 3.2GB, so the result wouldn't exceed 4GB).
	 */
	static size_t n_Encode(size_t n_dest_size, void *p_dest, size_t n_size,
		const void *p_data, bool b_allow_explicit_null = false,
		const char *p_s_encode_table = p_s_Default_EncodeTable());

	/**
	 *	@brief data encoding routine
	 *
	 *	Encodes contents of p_src_data buffer and stores it in r_s_dest string
	 *	(rewrites original contents, does not concatenate).
	 *
	 *	@param[out] r_s_output is destination string
	 *	@param[in] n_size is input data size (must be less or equal to n_Max_EncodeSize() bytes)
	 *	@param[in] p_data is buffer, containing data to be encoded
	 *	@param[in] b_allow_explicit_null is explicitly encoded null flag
	 *	@param[in] p_s_encode_table must be string containing 85 different characters,
	 *		preferably representable using some basic encoding such as iso-8859-1
	 *		so they can be printed / viewed by pretty much any kind of system
	 *
	 *	@return Returns true on success, false on failure (not enough memory).
	 *
	 *	@note The inverse mapping table of p_s_encode_chars is required for decoding.
	 *	@note Encoding increases data size in 5:4 ratio, so there's n_Max_EncodeSize() limit
	 *		(for x86 application it's 3.2GB, so the result wouldn't exceed 4GB).
	 */
	static inline bool Encode(std::string &r_s_output, size_t n_size,
		const void *p_data, bool b_allow_explicit_null = false,
		const char *p_s_encode_table = p_s_Default_EncodeTable());

	/**
	 *	@brief data decoding routine
	 *
	 *	Decodes contents of p_s_base85 buffer and stores it in p_dest buffer
	 *	(rewrites original contents, does not concatenate).
	 *
	 *	@param[out] n_dest_size is size of the destination buffer, in bytes
	 *	@param[out] p_dest is destination buffer
	 *	@param[in] p_s_base85 is pointer to Base-85 encoded data
	 *	@param[in] n_length is length of the input, in characters
	 *		(if -1, the input is assumed null-terminated)
	 *	@param[in] b_allow_explicit_null is explicitly encoded null flag
	 *	@param[in] p_decode_table is pointer to the Base-85 decoding table
	 *
	 *	@return Returns true on success, false on failure (error in sequence, bad buffer sizes).
	 */
	static bool Decode(size_t n_dest_size, void *p_dest, const char *p_s_base85,
		size_t n_length = size_t(-1), bool b_allow_explicit_null = false,
		const int *p_decode_table = p_Default_DecodeTable());

	/**
	 *	@brief data decoding routine
	 *
	 *	Decodes contents of p_s_base85 buffer and stores it in p_dest buffer
	 *	(rewrites original contents, does not concatenate).
	 *
	 *	@param[out] r_t_dest is reference to the destination buffer
	 *	@param[in] p_s_base85 is pointer to Base-85 encoded data
	 *	@param[in] n_length is length of the input, in characters
	 *		(if -1, the input is assumed null-terminated)
	 *	@param[in] b_allow_explicit_null is explicitly encoded null flag
	 *	@param[in] p_decode_table is pointer to the Base-85 decoding table
	 *
	 *	@return Returns true on success, false on failure (error in sequence, not enough memory).
	 */
	static inline bool Decode(TBuffer &r_t_dest, const char *p_s_base85,
		size_t n_length = size_t(-1), bool b_allow_explicit_null = false,
		const int *p_decode_table = p_Default_DecodeTable());
};

// inlines below

/*
 *								=== CModifiedBase85::CInverseTable ===
 */

inline const int *CModifiedBase85::CInverseTable::p_Table() const
{
	return m_p_decode;
}

/*
 *								=== ~CModifiedBase85::CInverseTable ===
 */

/*
 *								=== CModifiedBase85 ===
 */

inline const char *CModifiedBase85::p_s_Default_EncodeTable()
{
	return "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
		"abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";
}

inline const int *CModifiedBase85::p_Default_DecodeTable()
{
	return m_default_invtable.p_Table();
}

inline size_t CModifiedBase85::n_Max_EncodeSize()
{
	return n_DecodedSize(TBuffer::n_Max_Size());
}

inline size_t CModifiedBase85::n_EncodedSize_UpperBound(size_t n_input)
{
	return (n_input / 4 * 5 + ((n_input % 4)? n_input % 4 + 1 : 0)) * sizeof(char);
}

inline size_t CModifiedBase85::n_DecodedSize(size_t n_input)
{
	n_input /= sizeof(char);
	return (n_input / 5) * 4 + ((n_input % 5 > 1)? n_input % 5 : 1) - 1;
}

inline bool CModifiedBase85::Encode(std::string &r_s_output, size_t n_size,
	const void *p_data, bool b_allow_explicit_null /*= false*/,
	const char *p_s_encode_table /*= p_s_Default_EncodeTable()*/)
{
	_ASSERTE(p_data);
	if(n_size > n_Max_EncodeSize())
		return false;

	try {
		r_s_output.clear();
		r_s_output.resize(n_EncodedSize_UpperBound(n_size));
		// alloc output

		size_t n_dest_size = n_Encode(r_s_output.size() * sizeof(char), &r_s_output[0],
			n_size, p_data, b_allow_explicit_null, p_s_encode_table);
		if(b_allow_explicit_null)
			r_s_output.erase(n_dest_size / sizeof(char));
		_ASSERTE(r_s_output.size() * sizeof(char) == n_dest_size);
		// encode and shorten
	} catch(std::bad_alloc&) {
		return false;
	}

	return true;
}

inline bool CModifiedBase85::Decode(TBuffer &r_t_dest, const char *p_s_base85,
	size_t n_length /*= size_t(-1)*/, bool b_allow_explicit_null /*= false*/,
	const int *p_decode_table /*= p_Default_DecodeTable()*/)
{
	_ASSERTE(p_s_base85);
	if(n_length == size_t(-1))
		n_length = strlen(p_s_base85);
	// calculate length

	if(r_t_dest.Resize((b_allow_explicit_null)? n_DecodedSize_ExplicitNull(p_s_base85,
	   n_length, p_decode_table) : n_DecodedSize(n_length * sizeof(char)), false))
		return false;
	// alloc the output buffer

	return Decode(r_t_dest.n_Size(), r_t_dest.p_Data(), p_s_base85,
		n_length, b_allow_explicit_null, p_decode_table);
	// decode
}

/*
 *								=== ~CModifiedBase85 ===
 */

#endif // __BASE85_CODING_INCLUDED
