// q65.c
// q65 modes encoding/decoding functions
// 
// (c) 2020 - Nico Palermo, IV3NWV - Microtelecom Srl, Italy
// ------------------------------------------------------------------------------
// This file is part of the qracodes project, a Forward Error Control
// encoding/decoding package based on Q-ary RA (Repeat and Accumulate) LDPC codes.
//
//    qracodes 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.
//    qracodes 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 qracodes source distribution.  
//    If not, see .
#include 
#include 
#include 
#include "q65.h"
#include "pdmath.h"	
// Minimum codeword loglikelihood for decoding
#define Q65_LLH_THRESHOLD -260.0f 
// This value produce the same WER performance in decode_fullaplist
// #define Q65_LLH_THRESHOLD -262.0f 
static int	_q65_crc6(int *x, int sz);
static void _q65_crc12(int *y, int *x, int sz);
float q65_llh;
int q65_init(q65_codec_ds *pCodec, 	const qracode *pqracode)
{
	// Eb/No value for which we optimize the decoder metric (AWGN/Rayleigh cases)
	const float EbNodBMetric = 2.8f;	
	const float EbNoMetric   = (float)pow(10,EbNodBMetric/10);
	float	R;		// code effective rate (after puncturing)
	int		nm;		// bits per symbol
	if (!pCodec)
		return -1;		// why do you called me?
	if (!pqracode)
		return -2;		// invalid qra code
	if (pqracode->M!=64)
		return -3;		// q65 supports only codes over GF(64)
	pCodec->pQraCode = pqracode;
	// allocate buffers used by encoding/decoding functions
	pCodec->x			= (int*)malloc(pqracode->K*sizeof(int));
	pCodec->y			= (int*)malloc(pqracode->N*sizeof(int));
	pCodec->qra_v2cmsg	= (float*)malloc(pqracode->NMSG*pqracode->M*sizeof(float));
	pCodec->qra_c2vmsg	= (float*)malloc(pqracode->NMSG*pqracode->M*sizeof(float));
	pCodec->ix			= (float*)malloc(pqracode->N*pqracode->M*sizeof(float));
	pCodec->ex			= (float*)malloc(pqracode->N*pqracode->M*sizeof(float));
	if (pCodec->x== NULL			||
		pCodec->y== NULL			||
		pCodec->qra_v2cmsg== NULL	||
		pCodec->qra_c2vmsg== NULL	||
		pCodec->ix== NULL			||
		pCodec->ex== NULL) {
			q65_free(pCodec);
			return -4; // out of memory
		}
	// compute and store the AWGN/Rayleigh Es/No ratio for which we optimize
	// the decoder metric
	nm = _q65_get_bits_per_symbol(pqracode);
	R  = _q65_get_code_rate(pqracode);
	pCodec->decoderEsNoMetric   = 1.0f*nm*R*EbNoMetric;
	return 1;
}
void q65_free(q65_codec_ds *pCodec)
{
	if (!pCodec)
		return;
	// free internal buffers
	if (pCodec->x!=NULL)
		free(pCodec->x);
	if (pCodec->y!=NULL)
		free(pCodec->y);
	if (pCodec->qra_v2cmsg!=NULL)
		free(pCodec->qra_v2cmsg);
	if (pCodec->qra_c2vmsg!=NULL)
		free(pCodec->qra_c2vmsg);
	if (pCodec->ix!=NULL)
		free(pCodec->ix);
	if (pCodec->ex!=NULL)
		free(pCodec->ex);
	pCodec->pQraCode	= NULL;
	pCodec->x			= NULL;
	pCodec->y			= NULL;
	pCodec->qra_v2cmsg	= NULL;
	pCodec->qra_c2vmsg	= NULL;
	pCodec->qra_v2cmsg	= NULL;
	pCodec->ix			= NULL;
	pCodec->ex			= NULL;
	return;
}
int q65_encode(const q65_codec_ds *pCodec, int *pOutputCodeword, const int *pInputMsg)
{
	const qracode *pQraCode;
	int *px;
	int *py;
	int nK;
	int nN;
	if (!pCodec)
		return -1;	// which codec?
	pQraCode = pCodec->pQraCode;
	px = pCodec->x;
	py = pCodec->y;
	nK = _q65_get_message_length(pQraCode);
	nN = _q65_get_codeword_length(pQraCode);
	// copy the information symbols into the internal buffer
	memcpy(px,pInputMsg,nK*sizeof(int));
	// compute and append the appropriate CRC if required
	switch (pQraCode->type) {
		case QRATYPE_NORMAL:
			break;
		case QRATYPE_CRC:
		case QRATYPE_CRCPUNCTURED:
			px[nK] = _q65_crc6(px,nK);
			break;
		case QRATYPE_CRCPUNCTURED2:
			_q65_crc12(px+nK,px,nK);
			break;
		default:
			return -2;	// code type not supported
	}
	// encode with the given qra code
	qra_encode(pQraCode,py,px);
	// puncture the CRC symbols as required
	// and copy the result to the destination buffer
	switch (pQraCode->type) {
		case QRATYPE_NORMAL:
		case QRATYPE_CRC:
			// no puncturing
			memcpy(pOutputCodeword,py,nN*sizeof(int));
			break;
		case QRATYPE_CRCPUNCTURED:
			// strip the single CRC symbol from the encoded codeword
			memcpy(pOutputCodeword,py,nK*sizeof(int));				// copy the systematic symbols 
			memcpy(pOutputCodeword+nK,py+nK+1,(nN-nK)*sizeof(int));	// copy the check symbols skipping the CRC symbol
			break;
		case QRATYPE_CRCPUNCTURED2:
			// strip the 2 CRC symbols from the encoded codeword
			memcpy(pOutputCodeword,py,nK*sizeof(int));				// copy the systematic symbols
			memcpy(pOutputCodeword+nK,py+nK+2,(nN-nK)*sizeof(int)); // copy the check symbols skipping the two CRC symbols 
			break;
		default:
			return -2;	// code type unsupported
	}
	return 1; // ok
}
int q65_intrinsics(q65_codec_ds *pCodec, float *pIntrinsics, const float *pInputEnergies)
{
	// compute observations intrinsics probabilities
	// for the AWGN/Rayleigh channels
	// NOTE:
	// A true Rayleigh channel metric would require that the channel gains were known
	// for each symbol in the codeword. Such gains cannot be estimated reliably when
	// the Es/No ratio is small. Therefore we compute intrinsic probabilities assuming
	// that, on average, these channel gains are unitary.
	// In general it is even difficult to estimate the Es/No ratio for the AWGN channel
	// Therefore we always compute the intrinsic probabilities assuming that the Es/No
	// ratio is known and equal to the constant decoderEsNoMetric. This assumption will
	// generate the true intrinsic probabilities only when the actual Eb/No ratio is
	// equal to this constant. As in all the other cases the probabilities are evaluated
	// with a wrong scaling constant we can expect that the decoder performance at different
	// Es/No will be worse. Anyway, since the EsNoMetric constant has been chosen so that the 
	// decoder error rate is about 50%, we obtain almost optimal error rates down to
	// any useful Es/No ratio.
	const qracode *pQraCode;
	int	nN, nBits;
	float EsNoMetric;
	if (pCodec==NULL)
		return -1;		// which codec?
	pQraCode = pCodec->pQraCode;
	nN		 = _q65_get_codeword_length(pQraCode);
	nBits	 = pQraCode->m;
	EsNoMetric = pCodec->decoderEsNoMetric;
	qra_mfskbesselmetric(pIntrinsics,pInputEnergies,nBits,nN,EsNoMetric);
	return 1;	// success
}
int q65_esnodb(const q65_codec_ds *pCodec, float *pEsNodB, const int *ydec, const float *pInputEnergies)
{
	// compute average Es/No for the AWGN/Rayleigh channel cases
	int k,j;
	float sigplusnoise=0;
	float noise=0;
	int nN, nM;
	const float *pIn = pInputEnergies;
	const int *py = ydec;
	float EsNodB;
	nN = q65_get_codeword_length(pCodec);
	nM = q65_get_alphabet_size(pCodec);
	for (k=0;k4)
		return Q65_DECODE_INVPARAMS;	// invalid submode
	// As the symbol duration in q65 is different than in QRA64,
	// the fading tables continue to be valid if the B90Ts parameter 
	// is properly scaled to the QRA64 symbol interval
	// Compute index to most appropriate weighting function coefficients
	B90 = B90Ts/TS_QRA64;
    hidx = (int)(logf(B90)/logf(1.09f) - 0.499f);
	// Unlike in QRA64 we accept any B90, anyway limiting it to
	// the extreme cases (0.9 to 210 Hz approx.)
	if (hidx<0)
		hidx = 0;
	else
	  if (hidx > 63)   //Changed by K1JT: previously max was 64.
	    hidx=63;       //Changed by K1JT: previously max was 64.
	// select the appropriate weighting fading coefficients array
	if (fadingModel==0) {	 // gaussian fading model
		// point to gaussian energy weighting taps
		hlen = glen_tab_gauss[hidx];	 // hlen = (L+1)/2 (where L=(odd) number of taps of w fun)
		hptr = gptr_tab_gauss[hidx];     // pointer to the first (L+1)/2 coefficients of w fun
		}
	else if (fadingModel==1) {
		// point to lorentzian energy weighting taps
		hlen = glen_tab_lorentz[hidx];	 // hlen = (L+1)/2 (where L=(odd) number of taps of w fun)
		hptr = gptr_tab_lorentz[hidx];   // pointer to the first (L+1)/2 coefficients of w fun
		}
	else 
		return Q65_DECODE_INVPARAMS;	 // invalid fading model
	// compute (euristically) the optimal decoder metric accordingly the given spread amount
	// We assume that the decoder 50% decoding threshold is:
	// Es/No(dB) = Es/No(AWGN)(dB) + 8*log(B90)/log(240)(dB)
	// that's to say, at the maximum Doppler spread bandwidth (240 Hz for QRA64) 
	// there's a ~8 dB Es/No degradation over the AWGN case
	fTemp = 8.0f*logf(B90)/logf(240.0f); // assumed Es/No degradation for the given fading bandwidth
	EsNoMetric = pCodec->decoderEsNoMetric*powf(10.0f,fTemp/10.0f);
	nM = q65_get_alphabet_size(pCodec);
	nN = q65_get_codeword_length(pCodec);
	nBinsPerTone   = 1<ffNoiseVar		= fNoiseVar;
	pCodec->ffEsNoMetric	= EsNoMetric;
	pCodec->nBinsPerTone    = nBinsPerTone;
	pCodec->nBinsPerSymbol  = nBinsPerSymbol;
	pCodec->nWeights        = hlen;
	weight					= pCodec->ffWeight;
	// compute the fast fading weights accordingly to the Es/No ratio
	// for which we compute the exact intrinsics probabilities
	for (k=0;kmaxlogp)		// keep track of the max 
				maxlogp = fTemp;
			pCurIx[k]=fTemp;
			pCurBin += nBinsPerTone;	// next tone
			}
		// exponentiate and accumulate the normalization constant
		sumix = 0.0f;
		for (k=0;k  85.0) x= 85.0;
		  fTemp = expf(x);
		  pCurIx[k]=fTemp;
		  sumix  +=fTemp;
		}
		// scale to a probability distribution
		sumix = 1.0f/sumix;
		for (k=0;knBinsPerTone;
	nBinsPerSymbol = pCodec->nBinsPerSymbol;
	nWeights       = pCodec->nWeights;
	ffNoiseVar	   = pCodec->ffNoiseVar;
	ffEsNoMetric   = pCodec->ffEsNoMetric;
	nTotWeights    = 2*nWeights-1;
	// compute symbols energy (noise included) summing the 
	// energies pertaining to the decoded symbols in the codeword
	EsPlusWNo = 0.0f;
	pCurSym = pInputEnergies + nM;	// point to first central bin of first symbol tone
	for (n=0;npQraCode;
	ix			= pCodec->ix;
	ex			= pCodec->ex;
	nK			= _q65_get_message_length(pQraCode);
	nN			= _q65_get_codeword_length(pQraCode);
	nM			= pQraCode->M;
	nBits		= pQraCode->m;
	px			= pCodec->x;
	py			= pCodec->y;
	// Depuncture intrinsics observations as required by the code type
	switch (pQraCode->type) {
		case QRATYPE_CRCPUNCTURED:
			memcpy(ix,pIntrinsics,nK*nM*sizeof(float));							// information symbols
			pd_init(PD_ROWADDR(ix,nM,nK),pd_uniform(nBits),nM);					// crc
			memcpy(ix+(nK+1)*nM,pIntrinsics+nK*nM,(nN-nK)*nM*sizeof(float));	// parity checks
			break;
		case QRATYPE_CRCPUNCTURED2:
			memcpy(ix,pIntrinsics,nK*nM*sizeof(float));							// information symbols
			pd_init(PD_ROWADDR(ix,nM,nK),pd_uniform(nBits),nM);					// crc
			pd_init(PD_ROWADDR(ix,nM,nK+1),pd_uniform(nBits),nM);				// crc
			memcpy(ix+(nK+2)*nM,pIntrinsics+nK*nM,(nN-nK)*nM*sizeof(float));	// parity checks
			break;
		case QRATYPE_NORMAL:
		case QRATYPE_CRC:
		default:
			// no puncturing
			memcpy(ix,pIntrinsics,nN*nM*sizeof(float));							// as they are
		}
	// mask the intrinsics with the available a priori knowledge
	if (pAPMask!=NULL)
		_q65_mask(pQraCode,ix,pAPMask,pAPSymbols);
	// Compute the extrinsic symbols probabilities with the message-passing algorithm
	// Stop if the extrinsics information does not converges to unity
	// within the given number of iterations
	rc = qra_extrinsic( pQraCode,
						ex,
						ix,
						maxiters,
						pCodec->qra_v2cmsg,
						pCodec->qra_c2vmsg);
	if (rc<0) 
		// failed to converge to a solution
		return Q65_DECODE_FAILED;
	// decode the information symbols (punctured information symbols included)
	qra_mapdecode(pQraCode,px,ex,ix);
	// verify CRC match 
	switch (pQraCode->type) {
		case QRATYPE_CRC:
		case QRATYPE_CRCPUNCTURED:
			crc6=_q65_crc6(px,nK);			 // compute crc-6
			if (crc6!=px[nK]) 				
				return Q65_DECODE_CRCMISMATCH; // crc doesn't match
			break;
		case QRATYPE_CRCPUNCTURED2:
			_q65_crc12(crc12, px,nK);			 // compute crc-12
			if (crc12[0]!=px[nK] || 
				crc12[1]!=px[nK+1]) 
				return Q65_DECODE_CRCMISMATCH; // crc doesn't match
			break;
		case QRATYPE_NORMAL:
		default:
			// nothing to check
			break;
		}
	// copy the decoded msg to the user buffer (excluding punctured symbols)
	if (pDecodedMsg)
		memcpy(pDecodedMsg,px,nK*sizeof(int));
#ifndef Q65_CHECKLLH
	if (pDecodedCodeword==NULL)		// user is not interested in the decoded codeword
		return rc;					// return the number of iterations required to decode
#else
	if (pDecodedCodeword==NULL)			// we must have a buffer
		return Q65_DECODE_INVPARAMS;	// return error
#endif
	// crc matches therefore we can reconstruct the transmitted codeword
	//  reencoding the information available in px...
	qra_encode(pQraCode, py, px);
	// ...and strip the punctured symbols from the codeword
	switch (pQraCode->type) {
		case QRATYPE_CRCPUNCTURED:
				memcpy(pDecodedCodeword,py,nK*sizeof(int));
				memcpy(pDecodedCodeword+nK,py+nK+1,(nN-nK)*sizeof(int));	// puncture crc-6 symbol
			break;
		case QRATYPE_CRCPUNCTURED2:
				memcpy(pDecodedCodeword,py,nK*sizeof(int));
				memcpy(pDecodedCodeword+nK,py+nK+2,(nN-nK)*sizeof(int));	// puncture crc-12 symbols
			break;
		case QRATYPE_CRC:
		case QRATYPE_NORMAL:
		default:
			memcpy(pDecodedCodeword,py,nN*sizeof(int));		// no puncturing
		}
#ifdef Q65_CHECKLLH
	if (q65_check_llh(NULL,pDecodedCodeword, nN, nM, pIntrinsics)==0) // llh less than threshold
		return Q65_DECODE_LLHLOW;	
#endif
	return rc;	// return the number of iterations required to decode
}
// Compute and verify the loglikelihood of the decoded codeword
int q65_check_llh(float *llh, const int* ydec, const int nN, const int nM, const float *pIntrin)
{
	int k;
	float t = 0;
	for (k=0;k=Q65_LLH_THRESHOLD);
}
// Full AP decoding from a list of codewords
int q65_decode_fullaplist(q65_codec_ds *codec,
						   int *ydec,
						   int *xdec, 
						   const float *pIntrinsics, 
						   const int *pCodewords, 
						   const int nCodewords)
{
	int			k;
	int			nK, nN, nM;
	float llh, maxllh, llh_threshold; 
	int   maxcw = -1;					// index of the most likely codeword
	const int  *pCw;
	if (nCodewords<1 || nCodewords>Q65_FULLAPLIST_SIZE)
		return Q65_DECODE_INVPARAMS;	// invalid list length
	nK  = q65_get_message_length(codec);
	nN	= q65_get_codeword_length(codec);
	nM	= q65_get_alphabet_size(codec);
	// we adjust the llh threshold in order to mantain the
	// same false decode rate independently from the size
	// of the list
	llh_threshold = Q65_LLH_THRESHOLD + logf(1.0f*nCodewords/3);
	maxllh = llh_threshold; // at least one llh should be larger than the threshold
	// compute codewords log likelihoods and find max
	pCw = pCodewords;	// start from the first codeword
	for (k=0;kmaxllh) {
				maxllh = llh;
				maxcw    = k;
			}
		//		printf("BBB  %d  %f\n",k,llh);
		// point to next codeword
		pCw+=nN;
	}
	q65_llh=maxllh;		// save for Joe's use
	if (maxcw<0) // no llh larger than threshold found
		return Q65_DECODE_FAILED;	  
	pCw = pCodewords+nN*maxcw;
	memcpy(ydec,pCw,nN*sizeof(int));
	memcpy(xdec,pCw,nK*sizeof(int));
	return maxcw;	// index to the decoded message (>=0)
}
// helper functions -------------------------------------------------------------
int _q65_get_message_length(const qracode *pCode)
{
	// return the actual information message length (in symbols)
	// excluding any punctured symbol
	int nMsgLength;
	switch (pCode->type) {
		case QRATYPE_NORMAL:
			nMsgLength = pCode->K;
			break;
		case QRATYPE_CRC:
		case QRATYPE_CRCPUNCTURED:
			// one information symbol of the underlying qra code is reserved for CRC
			nMsgLength = pCode->K-1;
			break;
		case QRATYPE_CRCPUNCTURED2:
			// two code information symbols are reserved for CRC
			nMsgLength = pCode->K-2;
			break;
		default:
			nMsgLength = -1;
	}
	return nMsgLength;
}
int _q65_get_codeword_length(const qracode *pCode)
{
	// return the actual codeword length (in symbols)
	// excluding any punctured symbol
	
	int nCwLength;
	switch (pCode->type) {
		case QRATYPE_NORMAL:
		case QRATYPE_CRC:
			// no puncturing
			nCwLength = pCode->N;
			break;
		case QRATYPE_CRCPUNCTURED:
			// the CRC symbol is punctured
			nCwLength = pCode->N-1;
			break;
		case QRATYPE_CRCPUNCTURED2:
			// the two CRC symbols are punctured
			nCwLength = pCode->N-2;
			break;
		default:
			nCwLength = -1;
	}
	return nCwLength;
}
float _q65_get_code_rate(const qracode *pCode)
{
	return 1.0f*_q65_get_message_length(pCode)/_q65_get_codeword_length(pCode);
}
int _q65_get_alphabet_size(const qracode *pCode)
{
	return pCode->M;
}
int _q65_get_bits_per_symbol(const qracode *pCode)
{
	return pCode->m;
}
static void _q65_mask(const qracode *pcode, float *ix, const int *mask, const int *x)
{
	// mask intrinsic information ix with available a priori knowledge
	
	int k,kk, smask;
	const int nM=pcode->M;	
	const int nm=pcode->m;
	int nK;
	// Exclude from masking the symbols which have been punctured.
	// nK is the length of the mask and x arrays, which do 
	// not include any punctured symbol 
	nK = _q65_get_message_length(pcode);
	// for each symbol set to zero the probability
	// of the values which are not allowed by
	// the a priori information
	for (k=0;k>1) ^ CRC6_GEN_POL;
			else
				sr = (sr>>1);
			t>>=1;
			}
		}
	return sr;
}
static void _q65_crc12(int *y, int *x, int sz)
{
	int k,j,t,sr = 0;
	for (k=0;k>1) ^ CRC12_GEN_POL;
			else
				sr = (sr>>1);
			t>>=1;
			}
		}
	y[0] = sr&0x3F; 
	y[1] = (sr>>6);
}