/*
 * Copyright 2011-2019, European Organisation for the Exploitation of Meteorological Satellites (EUMETSAT)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef CACModel_included
#define CACModel_included

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

TYPE:
CACModel : Concrete class.

PURPOSE:
These classes handles the adaptative symbols probabilities to be used with entropy
coding.
  
FUNCTION:
Starting from an equi-probable distribution, the model adapts itself each time a
symbol is encoded by managing symbols frequency counts, cumulative frequency and
frequency-based symbol sorting.
	
INTERFACES:	
See 'INTERFACES' in the module declaration below
      
RESOURCES:	
Heap Memory (>2K).
		
REFERENCE:	
"Arithmetic Coding for Data Compression"; Witten, Neal & Cleary;
Commun. ACM, vol. 30, pp 520-540, June 1987.
		  
PROCESSING:	
The symbols probabilities are initially equal, frequency counts are set to 1, 
cumulative frequencies are computed according to initial symbol ordering.
Each time a symbol is encoded a model update is performed, the symbol frequency count 
is incremented, a move-to-front technique is used to keep the symbols sorted
by frequency and the cumulative frequencies are computed appropriatelly.
When the the sum of the frequency counts exceeds a given threshold, the symbols 
frequencies are scaled down by half to give more weight to more recents events.
			
DATA:
See 'DATA :' in the class header below.			

LOGIC:

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

#include <memory>

#include "RMAErrorHandling.h"
#include "Bitlevel.h"
#include "WTConst.h"


namespace COMP 
{

	
class CACModel
{

private:

// DATA:

	const unsigned __int32	c_MaxFrequency;		// upper limit for cumulative threshold
	unsigned __int32		m_MaxFreq;			// cumulative frequency threshold
	unsigned int			m_NbSymbols;		// number of symbol handled by the model
	unsigned __int32		m_Freq[33];			// frequency counts array
	unsigned __int32		m_CumFreq[33];		// cumulative frequency counts array
	unsigned int			m_SymbolToIndex[33];// symbol to sorting index array
	unsigned int			m_IndexToSymbol[33];// sorting index to symbol array

// INTERFACES:

	// Description: Performs the symbol frequencies rescaling.
	// Returns:		Nothing.
	void Rescale();

public:

// INTERFACES:

	// Description:	Default constructor.
	// Returns:		Nothing.
	CACModel()
	: c_MaxFrequency((1UL << (c_ACNbBits - 2)) - 1)
	, m_NbSymbols(0)
	{
	}

	// Description:	Get the number of symbols handled by the model [0, 32].
	// Returns:		The number of symbols.
	unsigned int GetNbSymbols() const
	{
		return m_NbSymbols;
	}

	// Description: Get the frequency of the symbol given by its sorting index.
	//				1 is the most probable symbol, GetNbSymbols() is the least
	//				probable one.
	// Returns:		The symbol frequency count.
	unsigned __int32 GetFreq
	(
		const unsigned int i_Index	// sorting index of the symbol
	)
	const
	{
		COMP_TRYTHIS_SPEED
#ifdef _DEBUG 
			Assert (i_Index > 0 && i_Index <= m_NbSymbols, Util::CParamException());
#endif
			return m_Freq[i_Index];
		COMP_CATCHTHIS_SPEED
	}

	// Description: Get the cumulative frequency at a given sorting index.
	//				1 is the most probable symbol, GetNbSymbols() is the least
	//				probable one,
	//				GetCumFreq(i) is equal to the sum of GetFreq(k) with
	//				k=[i+1, GetNbSymbols()]
	// Returns:		The cumulative frequency.
	unsigned __int32 GetCumFreq
	(
		const unsigned int i_Index	// sorting index
	)
	const
	{
		COMP_TRYTHIS_SPEED
#ifdef _DEBUG 
			Assert (i_Index <= m_NbSymbols, Util::CParamException());
#endif
			return m_CumFreq[i_Index];
		COMP_CATCHTHIS_SPEED
	}

	// Description:	Get the sorting index of a given symbol.
	// Returns:		The sorting index.
	unsigned int GetIndex
	(
		const unsigned int i_Symbol	// the symbol [0, GetNbSymbols()[
	)
	const
	{
		COMP_TRYTHIS_SPEED
#ifdef _DEBUG 
			Assert (i_Symbol < m_NbSymbols, Util::CParamException());
#endif
			return m_SymbolToIndex[i_Symbol];
		COMP_CATCHTHIS_SPEED
	}

	// Description:	Get the symbol associated with a given sorting index.
	// Returns:		The associated symbol.
	unsigned int GetSymbol
	(
		const unsigned int i_Index	// the sorting index [1, GetNbSymbols()]
	)
	const
	{
		COMP_TRYTHIS_SPEED
#ifdef _DEBUG 
			Assert (i_Index > 0 && i_Index <= m_NbSymbols, Util::CParamException());
#endif
			return m_IndexToSymbol[i_Index];
		COMP_CATCHTHIS_SPEED
	}
	
	// Description:	Check if the model has been initialised.
	// Returns:		true if the model was initialized, false otherwise.
	bool IsInitialized() const
	{
		COMP_TRYTHIS_SPEED
			return m_NbSymbols ? true : false;
		COMP_CATCHTHIS_SPEED
	}

	// Description:	Reset the symbol frequencies to the initial state (equi-probable).
	// Returns:		Nothing.
	void Start();

	// Description:	Update the model after the encoding of the MPS.
	// Returns:		Nothing.
	void UpdateMps()
	{
		COMP_TRYTHIS_SPEED
			if (m_CumFreq[0] >= m_MaxFreq) Rescale ();
			m_Freq[1]++;
			m_CumFreq[0]++;
		COMP_CATCHTHIS_SPEED
	}

	// Description:	Update the model after the encoding of
	//				one of the LPS given by its sorting index.
	// Returns:		Nothing.
	void UpdateLps
	(
		const unsigned int i_Index	// the sorting index
	);

	// Description:	Initialise the model to handle a given number of symbols.
	// Returns:		Nothing.
	void Initialize
	(
		unsigned int i_NbS	// the number of symbols [0, 32]
	)
	{
		COMP_TRYTHIS
			Assert (i_NbS <= 32, Util::CParamException());
			m_NbSymbols = i_NbS;
			if (i_NbS)
			{
				unsigned __int32 temp = (unsigned __int32)m_NbSymbols << 5;
				m_MaxFreq = (temp < c_MaxFrequency) ? temp : c_MaxFrequency;
				Start ();
			}
		COMP_CATCHTHIS
	}

};


} // end namespace


#endif
