/* -*- 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 "dvbt2_cellinterleaver_cc_impl.h"
#include <gnuradio/io_signature.h>

namespace gr {
namespace dtv {

dvbt2_cellinterleaver_cc::sptr
dvbt2_cellinterleaver_cc::make(dvb_framesize_t framesize,
                               dvb_constellation_t constellation,
                               int fecblocks,
                               int tiblocks)
{
    return gnuradio::make_block_sptr<dvbt2_cellinterleaver_cc_impl>(
        framesize, constellation, fecblocks, tiblocks);
}

/*
 * The private constructor
 */
dvbt2_cellinterleaver_cc_impl::dvbt2_cellinterleaver_cc_impl(
    dvb_framesize_t framesize,
    dvb_constellation_t constellation,
    int fecblocks,
    int tiblocks)
    : gr::sync_block("dvbt2_cellinterleaver_cc",
                     gr::io_signature::make(1, 1, sizeof(gr_complex)),
                     gr::io_signature::make(1, 1, sizeof(gr_complex)))
{
    int max_states, xor_size, pn_mask, result, q = 0;
    int lfsr = 0;
    int logic11[2] = { 0, 3 };
    int logic12[2] = { 0, 2 };
    int logic13[4] = { 0, 1, 4, 6 };
    int logic14[6] = { 0, 1, 4, 5, 9, 11 };
    int logic15[4] = { 0, 1, 2, 12 };
    int* logic;
    if (framesize == FECFRAME_NORMAL) {
        switch (constellation) {
        case MOD_QPSK:
            cell_size = 32400;
            pn_degree = 15;
            pn_mask = 0x3fff;
            max_states = 32768;
            logic = &logic15[0];
            xor_size = 4;
            break;
        case MOD_16QAM:
            cell_size = 16200;
            pn_degree = 14;
            pn_mask = 0x1fff;
            max_states = 16384;
            logic = &logic14[0];
            xor_size = 6;
            break;
        case MOD_64QAM:
            cell_size = 10800;
            pn_degree = 14;
            pn_mask = 0x1fff;
            max_states = 16384;
            logic = &logic14[0];
            xor_size = 6;
            break;
        case MOD_256QAM:
            cell_size = 8100;
            pn_degree = 13;
            pn_mask = 0xfff;
            max_states = 8192;
            logic = &logic13[0];
            xor_size = 4;
            break;
        default:
            cell_size = 32400;
            pn_degree = 15;
            pn_mask = 0x3fff;
            max_states = 32768;
            logic = &logic15[0];
            xor_size = 4;
            break;
        }
    } else {
        switch (constellation) {
        case MOD_QPSK:
            cell_size = 8100;
            pn_degree = 13;
            pn_mask = 0xfff;
            max_states = 8192;
            logic = &logic13[0];
            xor_size = 4;
            break;
        case MOD_16QAM:
            cell_size = 4050;
            pn_degree = 12;
            pn_mask = 0x7ff;
            max_states = 4096;
            logic = &logic12[0];
            xor_size = 2;
            break;
        case MOD_64QAM:
            cell_size = 2700;
            pn_degree = 12;
            pn_mask = 0x7ff;
            max_states = 4096;
            logic = &logic12[0];
            xor_size = 2;
            break;
        case MOD_256QAM:
            cell_size = 2025;
            pn_degree = 11;
            pn_mask = 0x3ff;
            max_states = 2048;
            logic = &logic11[0];
            xor_size = 2;
            break;
        default:
            cell_size = 8100;
            pn_degree = 13;
            pn_mask = 0xfff;
            max_states = 8192;
            logic = &logic13[0];
            xor_size = 4;
            break;
        }
    }
    for (int i = 0; i < max_states; i++) {
        if (i == 0 || i == 1) {
            lfsr = 0;
        } else if (i == 2) {
            lfsr = 1;
        } else {
            result = 0;
            for (int k = 0; k < xor_size; k++) {
                result ^= (lfsr >> logic[k]) & 1;
            }
            lfsr &= pn_mask;
            lfsr >>= 1;
            lfsr |= result << (pn_degree - 2);
        }
        lfsr |= (i % 2) << (pn_degree - 1);
        if (lfsr < cell_size) {
            permutations[q++] = lfsr;
        }
    }
    if (tiblocks == 0) {
        FECBlocksPerSmallTIBlock = 1;
        FECBlocksPerBigTIBlock = 1;
        numBigTIBlocks = 0;
        numSmallTIBlocks = fecblocks;
    } else {
        FECBlocksPerSmallTIBlock = floor(((float)fecblocks) / ((float)tiblocks));
        FECBlocksPerBigTIBlock = ceil(((float)fecblocks) / ((float)tiblocks));
        numBigTIBlocks = fecblocks % tiblocks;
        numSmallTIBlocks = tiblocks - numBigTIBlocks;
    }
    time_interleave = (gr_complex*)malloc(sizeof(gr_complex) * cell_size * fecblocks);
    if (time_interleave == NULL) {
        GR_LOG_FATAL(
            d_logger,
            "Cell/Time Interleaver, cannot allocate memory for time_interleave.");
        throw std::bad_alloc();
    }
    cols = (gr_complex**)malloc(sizeof(gr_complex*) * FECBlocksPerBigTIBlock * 5);
    if (cols == NULL) {
        free(time_interleave);
        GR_LOG_FATAL(d_logger, "Cell/Time Interleaver, cannot allocate memory for cols.");
        throw std::bad_alloc();
    }
    ti_blocks = tiblocks;
    fec_blocks = fecblocks;
    set_output_multiple(cell_size * fecblocks);
    interleaved_items = cell_size * fecblocks;
}

/*
 * Our virtual destructor.
 */
dvbt2_cellinterleaver_cc_impl::~dvbt2_cellinterleaver_cc_impl()
{
    free(cols);
    free(time_interleave);
}

int dvbt2_cellinterleaver_cc_impl::work(int noutput_items,
                                        gr_vector_const_void_star& input_items,
                                        gr_vector_void_star& output_items)
{
    const gr_complex* in = (const gr_complex*)input_items[0];
    gr_complex* out = (gr_complex*)output_items[0];
    int FECBlocksPerTIBlock, n, shift, temp, index, rows, numCols, ti_index;

    for (int i = 0; i < noutput_items; i += interleaved_items) {
        index = 0;
        for (int s = 0; s < numSmallTIBlocks + numBigTIBlocks; s++) {
            n = 0;
            if (s < numSmallTIBlocks) {
                FECBlocksPerTIBlock = FECBlocksPerSmallTIBlock;
            } else {
                FECBlocksPerTIBlock = FECBlocksPerBigTIBlock;
            }
            for (int r = 0; r < FECBlocksPerTIBlock; r++) {
                shift = cell_size;
                while (shift >= cell_size) {
                    temp = n;
                    shift = 0;
                    for (int p = 0; p < pn_degree; p++) {
                        shift |= temp & 1;
                        shift <<= 1;
                        temp >>= 1;
                    }
                    n++;
                }
                for (int w = 0; w < cell_size; w++) {
                    time_interleave[((permutations[w] + shift) % cell_size) + index] =
                        *in++;
                }
                index += cell_size;
            }
        }
        if (ti_blocks != 0) {
            ti_index = 0;
            for (int s = 0; s < numSmallTIBlocks + numBigTIBlocks; s++) {
                if (s < numSmallTIBlocks) {
                    FECBlocksPerTIBlock = FECBlocksPerSmallTIBlock;
                } else {
                    FECBlocksPerTIBlock = FECBlocksPerBigTIBlock;
                }
                numCols = 5 * FECBlocksPerTIBlock;
                rows = cell_size / 5;
                for (int j = 0; j < numCols; j++) {
                    cols[j] = &time_interleave[(rows * j) + ti_index];
                }
                index = 0;
                for (int k = 0; k < rows; k++) {
                    for (int w = 0; w < numCols; w++) {
                        *out++ = *(cols[w] + index);
                    }
                    index++;
                }
                ti_index += rows * numCols;
            }
        } else {
            index = 0;
            for (int w = 0; w < fec_blocks * cell_size; w++) {
                *out++ = time_interleave[index++];
            }
        }
    }

    // Tell runtime system how many output items we produced.
    return noutput_items;
}

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