/*
    libmaus2
    Copyright (C) 2020 German Tischler-Höhle

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#if ! defined(LIBMAUS2_GAMMA_GAMMAENCODERGENERIC_HPP)
#define LIBMAUS2_GAMMA_GAMMAENCODERGENERIC_HPP

#include <libmaus2/gamma/GammaGenericBase.hpp>
#include <libmaus2/math/lowbits.hpp>

namespace libmaus2
{
	namespace gamma
	{

		template<typename _stream_type, typename _stream_data_type, typename _value_type>
		struct GammaGenericEncoder : public GammaGenericBase<sizeof(_value_type)>
		{
			typedef _stream_type stream_type;
			typedef _stream_data_type stream_data_type;
			typedef _value_type value_type;
			typedef GammaGenericBase<sizeof(value_type)> base_type;
			typedef GammaGenericEncoder<stream_type,stream_data_type,value_type> this_type;
			typedef std::unique_ptr<this_type> unique_ptr_type;

			static unsigned int const w = CHAR_BIT * sizeof(stream_data_type);

			stream_type & stream;
			stream_data_type v;
			unsigned int bav;

			GammaGenericEncoder(stream_type & rstream)
			: stream(rstream), v(0), bav(w)
			{

			}

			inline void checkFlush()
			{
				// if no more bits available in current word
				if ( ! bav )
				{
					stream.put(v);
					v = 0;
					bav = w;
				}
			}

			// encode sequence of codelen zero bits
			inline void encodeZero(unsigned int const codelen)
			{
				unsigned int oleft = codelen;

				while ( oleft )
				{
					// number of code bits to put
					unsigned int ored = std::min(oleft,bav);

					// make space for code bits
					v <<= ored;
					// reduce number of bits left to put
					oleft -= ored;
					// reduce number of bits available in current word
					bav -= ored;

					checkFlush();
				}
			}

			// encode word using codelen bits
			inline void encodeWord(value_type const code, unsigned int const codelen)
			{
				unsigned int oleft = codelen;

				while ( oleft )
				{
					// number of code bits to put
					unsigned int ored = std::min(oleft,bav);

					// code bits to put
					value_type const put =
						(code >> (oleft-ored)) & libmaus2::math::lowbits(ored);

					// make space for code bits
					v <<= ored;
					// put code bits
					v |= put;
					// reduce number of bits left to put
					oleft -= ored;
					// reduce number of bits available in current word
					bav -= ored;

					checkFlush();
				}
			}

			void encode(value_type const q)
			{
				// word we encode (cannot encode zero, so shift by 1)
				value_type const code = q+static_cast<value_type>(1);
				// get length of code
				unsigned int codelen = base_type::getLength(code);

				encodeZero(              codelen-1);
				encodeWord(code         ,codelen-0);
			}

			void flush()
			{
				if ( bav != w )
				{
					v <<= bav;
					stream.put(v);
					v = 0;
					bav = w;
				}
			}

			uint64_t getOffset() const
			{
				return w * stream.getWrittenWords() + (w-bav);
			}
		};
	}
}
#endif
