/* -*- c++ -*- */
/*
 * Copyright 2015,2016 Free Software Foundation, Inc.
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "dvbt_viterbi_decoder_impl.h"
#include <gnuradio/io_signature.h>

namespace gr {
namespace dtv {

const unsigned char dvbt_viterbi_decoder_impl::d_puncture_1_2[2] = { 1, 1 };
const unsigned char dvbt_viterbi_decoder_impl::d_puncture_2_3[4] = { 1, 1, 0, 1 };
const unsigned char dvbt_viterbi_decoder_impl::d_puncture_3_4[6] = { 1, 1, 0, 1, 1, 0 };
const unsigned char dvbt_viterbi_decoder_impl::d_puncture_5_6[10] = { 1, 1, 0, 1, 1,
                                                                      0, 0, 1, 1, 0 };
const unsigned char dvbt_viterbi_decoder_impl::d_puncture_7_8[14] = {
    1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0
};
/* 8-bit parity lookup table, generated by partab.c */
const unsigned char dvbt_viterbi_decoder_impl::d_Partab[] = {
    0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1,
    0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0,
    0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0,
    1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1,
    0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0,
    1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1,
    1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0,
    1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
    0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
};

#ifdef DTV_SSE2
__GR_ATTR_ALIGNED(16) __m128i dvbt_viterbi_decoder_impl::d_metric0[4];
__GR_ATTR_ALIGNED(16) __m128i dvbt_viterbi_decoder_impl::d_metric1[4];
__GR_ATTR_ALIGNED(16) __m128i dvbt_viterbi_decoder_impl::d_path0[4];
__GR_ATTR_ALIGNED(16) __m128i dvbt_viterbi_decoder_impl::d_path1[4];
#else
__GR_ATTR_ALIGNED(16) unsigned char dvbt_viterbi_decoder_impl::d_metric0_generic[64];
__GR_ATTR_ALIGNED(16) unsigned char dvbt_viterbi_decoder_impl::d_metric1_generic[64];
__GR_ATTR_ALIGNED(16) unsigned char dvbt_viterbi_decoder_impl::d_path0_generic[64];
__GR_ATTR_ALIGNED(16) unsigned char dvbt_viterbi_decoder_impl::d_path1_generic[64];
#endif

#ifdef DTV_SSE2
__GR_ATTR_ALIGNED(16) branchtab27 dvbt_viterbi_decoder_impl::Branchtab27_sse2[2];
#else
__GR_ATTR_ALIGNED(16) branchtab27 dvbt_viterbi_decoder_impl::Branchtab27_generic[2];
#endif

__GR_ATTR_ALIGNED(16) unsigned char dvbt_viterbi_decoder_impl::mmresult[64];
__GR_ATTR_ALIGNED(16)
unsigned char dvbt_viterbi_decoder_impl::ppresult[TRACEBACK_MAX][64];

#ifdef DTV_SSE2
void dvbt_viterbi_decoder_impl::dvbt_viterbi_chunks_init_sse2(__m128i* mm0, __m128i* pp0)
{
#else
void dvbt_viterbi_decoder_impl::dvbt_viterbi_chunks_init_generic(unsigned char* mm0,
                                                                 unsigned char* pp0)
{
#endif
    // Initialize starting metrics to prefer 0 state
    int i, j;

#ifdef DTV_SSE2
    for (i = 0; i < 4; i++) {
        mm0[i] = _mm_setzero_si128();
        pp0[i] = _mm_setzero_si128();
    }

    int polys[2] = { POLYA, POLYB };
    for (i = 0; i < 32; i++) {
        Branchtab27_sse2[0].c[i] =
            (polys[0] < 0) ^ d_Partab[(2 * i) & abs(polys[0])] ? 1 : 0;
        Branchtab27_sse2[1].c[i] =
            (polys[1] < 0) ^ d_Partab[(2 * i) & abs(polys[1])] ? 1 : 0;
    }
#else
    for (i = 0; i < 64; i++) {
        mm0[i] = 0;
        pp0[i] = 0;
    }

    int polys[2] = { POLYA, POLYB };
    for (i = 0; i < 32; i++) {
        Branchtab27_generic[0].c[i] =
            (polys[0] < 0) ^ d_Partab[(2 * i) & abs(polys[0])] ? 1 : 0;
        Branchtab27_generic[1].c[i] =
            (polys[1] < 0) ^ d_Partab[(2 * i) & abs(polys[1])] ? 1 : 0;
    }
#endif

    for (i = 0; i < 64; i++) {
        mmresult[i] = 0;
        for (j = 0; j < TRACEBACK_MAX; j++) {
            ppresult[j][i] = 0;
        }
    }
}

#ifdef DTV_SSE2
void dvbt_viterbi_decoder_impl::dvbt_viterbi_butterfly2_sse2(
    unsigned char* symbols, __m128i* mm0, __m128i* mm1, __m128i* pp0, __m128i* pp1)
{
    int i;

    __m128i *metric0, *metric1;
    __m128i *path0, *path1;

    metric0 = mm0;
    path0 = pp0;
    metric1 = mm1;
    path1 = pp1;

    // Operate on 4 symbols (2 bits) at a time

    __m128i m0, m1, m2, m3, decision0, decision1, survivor0, survivor1;
    __m128i metsv, metsvm;
    __m128i shift0, shift1;
    __m128i tmp0, tmp1;
    __m128i sym0v, sym1v;

    sym0v = _mm_set1_epi8(symbols[0]);
    sym1v = _mm_set1_epi8(symbols[1]);

    for (i = 0; i < 2; i++) {
        if (symbols[0] == 2) {
            metsvm = _mm_xor_si128(Branchtab27_sse2[1].v[i], sym1v);
            metsv = _mm_sub_epi8(_mm_set1_epi8(1), metsvm);
        } else if (symbols[1] == 2) {
            metsvm = _mm_xor_si128(Branchtab27_sse2[0].v[i], sym0v);
            metsv = _mm_sub_epi8(_mm_set1_epi8(1), metsvm);
        } else {
            metsvm = _mm_add_epi8(_mm_xor_si128(Branchtab27_sse2[0].v[i], sym0v),
                                  _mm_xor_si128(Branchtab27_sse2[1].v[i], sym1v));
            metsv = _mm_sub_epi8(_mm_set1_epi8(2), metsvm);
        }

        m0 = _mm_add_epi8(metric0[i], metsv);
        m1 = _mm_add_epi8(metric0[i + 2], metsvm);
        m2 = _mm_add_epi8(metric0[i], metsvm);
        m3 = _mm_add_epi8(metric0[i + 2], metsv);

        decision0 = _mm_cmpgt_epi8(_mm_sub_epi8(m0, m1), _mm_setzero_si128());
        decision1 = _mm_cmpgt_epi8(_mm_sub_epi8(m2, m3), _mm_setzero_si128());
        survivor0 =
            _mm_or_si128(_mm_and_si128(decision0, m0), _mm_andnot_si128(decision0, m1));
        survivor1 =
            _mm_or_si128(_mm_and_si128(decision1, m2), _mm_andnot_si128(decision1, m3));

        shift0 = _mm_slli_epi16(path0[i], 1);
        shift1 = _mm_slli_epi16(path0[2 + i], 1);
        shift1 = _mm_add_epi8(shift1, _mm_set1_epi8(1));

        metric1[2 * i] = _mm_unpacklo_epi8(survivor0, survivor1);
        tmp0 = _mm_or_si128(_mm_and_si128(decision0, shift0),
                            _mm_andnot_si128(decision0, shift1));

        metric1[2 * i + 1] = _mm_unpackhi_epi8(survivor0, survivor1);
        tmp1 = _mm_or_si128(_mm_and_si128(decision1, shift0),
                            _mm_andnot_si128(decision1, shift1));

        path1[2 * i] = _mm_unpacklo_epi8(tmp0, tmp1);
        path1[2 * i + 1] = _mm_unpackhi_epi8(tmp0, tmp1);
    }

    metric0 = mm1;
    path0 = pp1;
    metric1 = mm0;
    path1 = pp0;

    sym0v = _mm_set1_epi8(symbols[2]);
    sym1v = _mm_set1_epi8(symbols[3]);

    for (i = 0; i < 2; i++) {
        if (symbols[2] == 2) {
            metsvm = _mm_xor_si128(Branchtab27_sse2[1].v[i], sym1v);
            metsv = _mm_sub_epi8(_mm_set1_epi8(1), metsvm);
        } else if (symbols[3] == 2) {
            metsvm = _mm_xor_si128(Branchtab27_sse2[0].v[i], sym0v);
            metsv = _mm_sub_epi8(_mm_set1_epi8(1), metsvm);
        } else {
            metsvm = _mm_add_epi8(_mm_xor_si128(Branchtab27_sse2[0].v[i], sym0v),
                                  _mm_xor_si128(Branchtab27_sse2[1].v[i], sym1v));
            metsv = _mm_sub_epi8(_mm_set1_epi8(2), metsvm);
        }

        m0 = _mm_add_epi8(metric0[i], metsv);
        m1 = _mm_add_epi8(metric0[i + 2], metsvm);
        m2 = _mm_add_epi8(metric0[i], metsvm);
        m3 = _mm_add_epi8(metric0[i + 2], metsv);

        decision0 = _mm_cmpgt_epi8(_mm_sub_epi8(m0, m1), _mm_setzero_si128());
        decision1 = _mm_cmpgt_epi8(_mm_sub_epi8(m2, m3), _mm_setzero_si128());
        survivor0 =
            _mm_or_si128(_mm_and_si128(decision0, m0), _mm_andnot_si128(decision0, m1));
        survivor1 =
            _mm_or_si128(_mm_and_si128(decision1, m2), _mm_andnot_si128(decision1, m3));

        shift0 = _mm_slli_epi16(path0[i], 1);
        shift1 = _mm_slli_epi16(path0[2 + i], 1);
        shift1 = _mm_add_epi8(shift1, _mm_set1_epi8(1));

        metric1[2 * i] = _mm_unpacklo_epi8(survivor0, survivor1);
        tmp0 = _mm_or_si128(_mm_and_si128(decision0, shift0),
                            _mm_andnot_si128(decision0, shift1));

        metric1[2 * i + 1] = _mm_unpackhi_epi8(survivor0, survivor1);
        tmp1 = _mm_or_si128(_mm_and_si128(decision1, shift0),
                            _mm_andnot_si128(decision1, shift1));

        path1[2 * i] = _mm_unpacklo_epi8(tmp0, tmp1);
        path1[2 * i + 1] = _mm_unpackhi_epi8(tmp0, tmp1);
    }
}
#else
void dvbt_viterbi_decoder_impl::dvbt_viterbi_butterfly2_generic(unsigned char* symbols,
                                                                unsigned char* mm0,
                                                                unsigned char* mm1,
                                                                unsigned char* pp0,
                                                                unsigned char* pp1)
{
    int i, j, k;

    unsigned char *metric0, *metric1;
    unsigned char *path0, *path1;

    metric0 = mm0;
    path0 = pp0;
    metric1 = mm1;
    path1 = pp1;

    // Operate on 4 symbols (2 bits) at a time

    unsigned char m0[16], m1[16], m2[16], m3[16], decision0[16], decision1[16],
        survivor0[16], survivor1[16];
    unsigned char metsv[16], metsvm[16];
    unsigned char shift0[16], shift1[16];
    unsigned char tmp0[16], tmp1[16];
    unsigned char sym0v[16], sym1v[16];
    unsigned short simd_epi16;

    for (j = 0; j < 16; j++) {
        sym0v[j] = symbols[0];
        sym1v[j] = symbols[1];
    }

    for (i = 0; i < 2; i++) {
        if (symbols[0] == 2) {
            for (j = 0; j < 16; j++) {
                metsvm[j] = Branchtab27_generic[1].c[(i * 16) + j] ^ sym1v[j];
                metsv[j] = 1 - metsvm[j];
            }
        } else if (symbols[1] == 2) {
            for (j = 0; j < 16; j++) {
                metsvm[j] = Branchtab27_generic[0].c[(i * 16) + j] ^ sym0v[j];
                metsv[j] = 1 - metsvm[j];
            }
        } else {
            for (j = 0; j < 16; j++) {
                metsvm[j] = (Branchtab27_generic[0].c[(i * 16) + j] ^ sym0v[j]) +
                            (Branchtab27_generic[1].c[(i * 16) + j] ^ sym1v[j]);
                metsv[j] = 2 - metsvm[j];
            }
        }

        for (j = 0; j < 16; j++) {
            m0[j] = metric0[(i * 16) + j] + metsv[j];
            m1[j] = metric0[((i + 2) * 16) + j] + metsvm[j];
            m2[j] = metric0[(i * 16) + j] + metsvm[j];
            m3[j] = metric0[((i + 2) * 16) + j] + metsv[j];
        }

        for (j = 0; j < 16; j++) {
            decision0[j] = ((m0[j] - m1[j]) > 0) ? 0xff : 0x0;
            decision1[j] = ((m2[j] - m3[j]) > 0) ? 0xff : 0x0;
            survivor0[j] = (decision0[j] & m0[j]) | ((~decision0[j]) & m1[j]);
            survivor1[j] = (decision1[j] & m2[j]) | ((~decision1[j]) & m3[j]);
        }

        for (j = 0; j < 16; j += 2) {
            simd_epi16 = path0[(i * 16) + j];
            simd_epi16 |= path0[(i * 16) + (j + 1)] << 8;
            simd_epi16 <<= 1;
            shift0[j] = simd_epi16;
            shift0[j + 1] = simd_epi16 >> 8;

            simd_epi16 = path0[((i + 2) * 16) + j];
            simd_epi16 |= path0[((i + 2) * 16) + (j + 1)] << 8;
            simd_epi16 <<= 1;
            shift1[j] = simd_epi16;
            shift1[j + 1] = simd_epi16 >> 8;
        }
        for (j = 0; j < 16; j++) {
            shift1[j] = shift1[j] + 1;
        }

        for (j = 0, k = 0; j < 16; j += 2, k++) {
            metric1[(2 * i * 16) + j] = survivor0[k];
            metric1[(2 * i * 16) + (j + 1)] = survivor1[k];
        }
        for (j = 0; j < 16; j++) {
            tmp0[j] = (decision0[j] & shift0[j]) | ((~decision0[j]) & shift1[j]);
        }

        for (j = 0, k = 8; j < 16; j += 2, k++) {
            metric1[((2 * i + 1) * 16) + j] = survivor0[k];
            metric1[((2 * i + 1) * 16) + (j + 1)] = survivor1[k];
        }
        for (j = 0; j < 16; j++) {
            tmp1[j] = (decision1[j] & shift0[j]) | ((~decision1[j]) & shift1[j]);
        }

        for (j = 0, k = 0; j < 16; j += 2, k++) {
            path1[(2 * i * 16) + j] = tmp0[k];
            path1[(2 * i * 16) + (j + 1)] = tmp1[k];
        }
        for (j = 0, k = 8; j < 16; j += 2, k++) {
            path1[((2 * i + 1) * 16) + j] = tmp0[k];
            path1[((2 * i + 1) * 16) + (j + 1)] = tmp1[k];
        }
    }

    metric0 = mm1;
    path0 = pp1;
    metric1 = mm0;
    path1 = pp0;

    for (j = 0; j < 16; j++) {
        sym0v[j] = symbols[2];
        sym1v[j] = symbols[3];
    }

    for (i = 0; i < 2; i++) {
        if (symbols[2] == 2) {
            for (j = 0; j < 16; j++) {
                metsvm[j] = Branchtab27_generic[1].c[(i * 16) + j] ^ sym1v[j];
                metsv[j] = 1 - metsvm[j];
            }
        } else if (symbols[3] == 2) {
            for (j = 0; j < 16; j++) {
                metsvm[j] = Branchtab27_generic[0].c[(i * 16) + j] ^ sym0v[j];
                metsv[j] = 1 - metsvm[j];
            }
        } else {
            for (j = 0; j < 16; j++) {
                metsvm[j] = (Branchtab27_generic[0].c[(i * 16) + j] ^ sym0v[j]) +
                            (Branchtab27_generic[1].c[(i * 16) + j] ^ sym1v[j]);
                metsv[j] = 2 - metsvm[j];
            }
        }

        for (j = 0; j < 16; j++) {
            m0[j] = metric0[(i * 16) + j] + metsv[j];
            m1[j] = metric0[((i + 2) * 16) + j] + metsvm[j];
            m2[j] = metric0[(i * 16) + j] + metsvm[j];
            m3[j] = metric0[((i + 2) * 16) + j] + metsv[j];
        }

        for (j = 0; j < 16; j++) {
            decision0[j] = ((m0[j] - m1[j]) > 0) ? 0xff : 0x0;
            decision1[j] = ((m2[j] - m3[j]) > 0) ? 0xff : 0x0;
            survivor0[j] = (decision0[j] & m0[j]) | ((~decision0[j]) & m1[j]);
            survivor1[j] = (decision1[j] & m2[j]) | ((~decision1[j]) & m3[j]);
        }

        for (j = 0; j < 16; j += 2) {
            simd_epi16 = path0[(i * 16) + j];
            simd_epi16 |= path0[(i * 16) + (j + 1)] << 8;
            simd_epi16 <<= 1;
            shift0[j] = simd_epi16;
            shift0[j + 1] = simd_epi16 >> 8;

            simd_epi16 = path0[((i + 2) * 16) + j];
            simd_epi16 |= path0[((i + 2) * 16) + (j + 1)] << 8;
            simd_epi16 <<= 1;
            shift1[j] = simd_epi16;
            shift1[j + 1] = simd_epi16 >> 8;
        }
        for (j = 0; j < 16; j++) {
            shift1[j] = shift1[j] + 1;
        }

        for (j = 0, k = 0; j < 16; j += 2, k++) {
            metric1[(2 * i * 16) + j] = survivor0[k];
            metric1[(2 * i * 16) + (j + 1)] = survivor1[k];
        }
        for (j = 0; j < 16; j++) {
            tmp0[j] = (decision0[j] & shift0[j]) | ((~decision0[j]) & shift1[j]);
        }

        for (j = 0, k = 8; j < 16; j += 2, k++) {
            metric1[((2 * i + 1) * 16) + j] = survivor0[k];
            metric1[((2 * i + 1) * 16) + (j + 1)] = survivor1[k];
        }
        for (j = 0; j < 16; j++) {
            tmp1[j] = (decision1[j] & shift0[j]) | ((~decision1[j]) & shift1[j]);
        }

        for (j = 0, k = 0; j < 16; j += 2, k++) {
            path1[(2 * i * 16) + j] = tmp0[k];
            path1[(2 * i * 16) + (j + 1)] = tmp1[k];
        }
        for (j = 0, k = 8; j < 16; j += 2, k++) {
            path1[((2 * i + 1) * 16) + j] = tmp0[k];
            path1[((2 * i + 1) * 16) + (j + 1)] = tmp1[k];
        }
    }
}
#endif

#ifdef DTV_SSE2
unsigned char dvbt_viterbi_decoder_impl::dvbt_viterbi_get_output_sse2(
    __m128i* mm0, __m128i* pp0, int ntraceback, unsigned char* outbuf)
{
#else
unsigned char dvbt_viterbi_decoder_impl::dvbt_viterbi_get_output_generic(
    unsigned char* mm0, unsigned char* pp0, int ntraceback, unsigned char* outbuf)
{
#endif
    //  Find current best path
    int i;
    int bestmetric, minmetric;
    int beststate = 0;
    int pos = 0;
#ifndef DTV_SSE2
    int j;
#endif

    // Implement a circular buffer with the last ntraceback paths
    store_pos = (store_pos + 1) % ntraceback;

#ifdef DTV_SSE2
    // TODO - find another way to extract the value
    for (i = 0; i < 4; i++) {
        _mm_store_si128((__m128i*)&mmresult[i * 16], mm0[i]);
        _mm_store_si128((__m128i*)&ppresult[store_pos][i * 16], pp0[i]);
    }
#else
    for (i = 0; i < 4; i++) {
        for (j = 0; j < 16; j++) {
            mmresult[(i * 16) + j] = mm0[(i * 16) + j];
            ppresult[store_pos][(i * 16) + j] = pp0[(i * 16) + j];
        }
    }
#endif

    // Find out the best final state
    bestmetric = mmresult[beststate];
    minmetric = mmresult[beststate];

    for (i = 1; i < 64; i++) {
        if (mmresult[i] > bestmetric) {
            bestmetric = mmresult[i];
            beststate = i;
        }
        if (mmresult[i] < minmetric) {
            minmetric = mmresult[i];
        }
    }

    // Trace back
    for (i = 0, pos = store_pos; i < (ntraceback - 1); i++) {
        // Obtain the state from the output bits
        // by clocking in the output bits in reverse order.
        // The state has only 6 bits
        beststate = ppresult[pos][beststate] >> 2;
        pos = (pos - 1 + ntraceback) % ntraceback;
    }

    // Store output byte
    *outbuf = ppresult[pos][beststate];

#ifdef DTV_SSE2
    // Zero out the path variable
    // and prevent metric overflow
    for (i = 0; i < 4; i++) {
        pp0[i] = _mm_setzero_si128();
        mm0[i] = _mm_sub_epi8(mm0[i], _mm_set1_epi8(minmetric));
    }
#else
    for (i = 0; i < 4; i++) {
        for (j = 0; j < 16; j++) {
            pp0[(i * 16) + j] = 0;
            mm0[(i * 16) + j] = mm0[(i * 16) + j] - minmetric;
        }
    }
#endif

    return bestmetric;
}

dvbt_viterbi_decoder::sptr dvbt_viterbi_decoder::make(dvb_constellation_t constellation,
                                                      dvbt_hierarchy_t hierarchy,
                                                      dvb_code_rate_t coderate,
                                                      int bsize)
{
    return gnuradio::make_block_sptr<dvbt_viterbi_decoder_impl>(
        constellation, hierarchy, coderate, bsize);
}

/*
 * The private constructor
 */
dvbt_viterbi_decoder_impl::dvbt_viterbi_decoder_impl(dvb_constellation_t constellation,
                                                     dvbt_hierarchy_t hierarchy,
                                                     dvb_code_rate_t coderate,
                                                     int bsize)
    : block("dvbt_viterbi_decoder",
            io_signature::make(1, 1, sizeof(unsigned char)),
            io_signature::make(1, 1, sizeof(unsigned char))),
      config(constellation, hierarchy, coderate, coderate),
      d_k(config.d_cr_k),
      d_n(config.d_cr_n),
      d_m(config.d_m),
      d_bsize(bsize),
      d_nsymbols(d_bsize * d_n / d_m),
      d_nbits(2 * d_k * d_bsize),
      d_inbits(d_nbits)
{
    if (config.d_code_rate_HP == C1_2) {
        d_puncture = d_puncture_1_2;
        d_ntraceback = 5;
    } else if (config.d_code_rate_HP == C2_3) {
        d_puncture = d_puncture_2_3;
        d_ntraceback = 9;
    } else if (config.d_code_rate_HP == C3_4) {
        d_puncture = d_puncture_3_4;
        d_ntraceback = 10;
    } else if (config.d_code_rate_HP == C5_6) {
        d_puncture = d_puncture_5_6;
        d_ntraceback = 15;
    } else if (config.d_code_rate_HP == C7_8) {
        d_puncture = d_puncture_7_8;
        d_ntraceback = 24;
    } else {
        d_puncture = d_puncture_1_2;
        d_ntraceback = 5;
    }

    /*
     * We input n bytes, each carrying m bits => nm bits
     * The result after decoding is km bits, therefore km/8 bytes.
     *
     * out/in rate is therefore km/8n in bytes
     */

    assert((d_bsize * d_n) % d_m == 0);
    set_output_multiple(d_bsize * d_k / 8);

    mettab[0][0] = 1;
    mettab[0][1] = 0;
    mettab[1][0] = 0;
    mettab[1][1] = 1;

#ifdef DTV_SSE2
    dvbt_viterbi_chunks_init_sse2(d_metric0, d_path0);
#else
    dvbt_viterbi_chunks_init_generic(d_metric0_generic, d_path0_generic);
#endif
}

/*
 * Our virtual destructor.
 */
dvbt_viterbi_decoder_impl::~dvbt_viterbi_decoder_impl() {}

void dvbt_viterbi_decoder_impl::forecast(int noutput_items,
                                         gr_vector_int& ninput_items_required)
{
    int input_required = noutput_items * 8 * d_n / (d_k * d_m);

    unsigned ninputs = ninput_items_required.size();
    for (unsigned int i = 0; i < ninputs; i++) {
        ninput_items_required[i] = input_required;
    }
}

int dvbt_viterbi_decoder_impl::general_work(int noutput_items,
                                            gr_vector_int& ninput_items,
                                            gr_vector_const_void_star& input_items,
                                            gr_vector_void_star& output_items)
{
    int nstreams = input_items.size();
    int nblocks = 8 * noutput_items / (d_bsize * d_k);
    int out_count = 0;

    for (int m = 0; m < nstreams; m++) {
        const unsigned char* in = (const unsigned char*)input_items[m];
        unsigned char* out = (unsigned char*)output_items[m];

        /*
         * Look for a tag that signals superframe_start and consume all input items
         * that are in input buffer so far.
         * This will actually reset the viterbi decoder.
         */
        std::vector<tag_t> tags;
        const uint64_t nread = this->nitems_read(0); // number of items read on port 0
        this->get_tags_in_range(tags,
                                0,
                                nread,
                                nread + (nblocks * d_nsymbols),
                                pmt::string_to_symbol("superframe_start"));

        if (!tags.empty()) {
            d_init = 0;

#ifdef DTV_SSE2
            dvbt_viterbi_chunks_init_sse2(d_metric0, d_path0);
#else
            dvbt_viterbi_chunks_init_generic(d_metric0_generic, d_path0_generic);
#endif

            if (tags[0].offset - nread) {
                consume_each(tags[0].offset - nread);
                return (0);
            }
        }

        // This is actually the Viterbi decoder
        for (int n = 0; n < nblocks; n++) {
            /*
             * Depuncture and unpack a block.
             * We receive the symbol (d_m bits/byte) in one byte (e.g. for QAM16
             * 00001111). Create a buffer of bytes containing just one bit/byte. Also
             * depuncture according to the puncture vector.
             * TODO - reduce the number of branches while depuncturing.
             */
            for (int count = 0, i = 0; i < d_nsymbols; i++) {
                for (int j = (d_m - 1); j >= 0; j--) {
                    // Depuncture
                    while (d_puncture[count % (2 * d_k)] == 0) {
                        d_inbits[count++] = 2;
                    }

                    // Insert received bits
                    d_inbits[count++] = (in[(n * d_nsymbols) + i] >> j) & 1;

                    // Depuncture
                    while (d_puncture[count % (2 * d_k)] == 0) {
                        d_inbits[count++] = 2;
                    }
                }
            }

            /*
             * Decode a block.
             */
            for (int in_count = 0; in_count < d_nbits; in_count++) {
                if ((in_count % 4) == 0) { // 0 or 3

#ifdef DTV_SSE2
                    dvbt_viterbi_butterfly2_sse2(&d_inbits[in_count & 0xfffffffc],
                                                 d_metric0,
                                                 d_metric1,
                                                 d_path0,
                                                 d_path1);
#else
                    dvbt_viterbi_butterfly2_generic(&d_inbits[in_count & 0xfffffffc],
                                                    d_metric0_generic,
                                                    d_metric1_generic,
                                                    d_path0_generic,
                                                    d_path1_generic);
#endif

                    if ((in_count > 0) && (in_count % 16) == 8) { // 8 or 11
                        unsigned char c;

#ifdef DTV_SSE2
                        dvbt_viterbi_get_output_sse2(
                            d_metric0, d_path0, d_ntraceback, &c);
#else
                        dvbt_viterbi_get_output_generic(
                            d_metric0_generic, d_path0_generic, d_ntraceback, &c);
#endif

                        if (d_init == 0) {
                            if (out_count >= d_ntraceback) {
                                out[out_count - d_ntraceback] = c;
                            }
                        } else {
                            out[out_count] = c;
                        }
                        out_count++;
                    }
                }
            }
        }
    }

    int to_out = noutput_items;

    if (d_init == 0) {
        /*
         * Send superframe_start to signal this situation
         * downstream
         */
        const uint64_t offset = this->nitems_written(0);
        pmt::pmt_t key = pmt::string_to_symbol("superframe_start");
        pmt::pmt_t value = pmt::from_long(1);
        this->add_item_tag(0, offset, key, value);

        // Take in consideration the traceback length
        to_out = to_out - d_ntraceback;
        d_init = 1;
    }

    // Tell runtime system how many input items we consumed on
    // each input stream.
    consume_each(nblocks * d_nsymbols);

    // Tell runtime system how many output items we produced.
    return (to_out);
}

} /* namespace dtv */
} /* namespace gr */
