/* -*- c++ -*- */
/*
 * Copyright 2015 Free Software Foundation, Inc.
 *
 * This 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, or (at your option)
 * any later version.
 *
 * This software 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 software; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

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

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

namespace gr {
namespace dtv {

const int dvbt_energy_descramble_impl::d_nblocks = 8;
const int dvbt_energy_descramble_impl::d_bsize = 188;
const int dvbt_energy_descramble_impl::d_SYNC = 0x47;
const int dvbt_energy_descramble_impl::d_NSYNC = 0xB8;
const int dvbt_energy_descramble_impl::d_MUX_PKT = 8;

void dvbt_energy_descramble_impl::init_prbs() { d_reg = 0xa9; }

int dvbt_energy_descramble_impl::clock_prbs(int clocks)
{
    int res = 0;
    int feedback = 0;

    for (int i = 0; i < clocks; i++) {
        feedback = ((d_reg >> (14 - 1)) ^ (d_reg >> (15 - 1))) & 0x1;
        d_reg = ((d_reg << 1) | feedback) & 0x7fff;

        res = (res << 1) | feedback;
    }

    return res;
}

dvbt_energy_descramble::sptr dvbt_energy_descramble::make(int nblocks)
{
    return gnuradio::get_initial_sptr(new dvbt_energy_descramble_impl(nblocks));
}

/*
 * The private constructor
 */
dvbt_energy_descramble_impl::dvbt_energy_descramble_impl(int nblocks)
    : block("dvbt_energy_descramble",
            io_signature::make(1, 1, sizeof(unsigned char) * d_nblocks * d_bsize),
            io_signature::make(1, 1, sizeof(unsigned char))),
      d_reg(0xa9),
      d_index(0),
      d_search(0)
{
    set_relative_rate((uint64_t)(d_nblocks * d_bsize), 1);

    // Set output multiple of (2 search + 1 data = 3) at least (4 for now)
    set_output_multiple(4 * d_nblocks * d_bsize);

    // Search interval for NSYNC is 2 * 8 * MUX size
    d_search = 2 * (d_nblocks * d_bsize);
}

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

void dvbt_energy_descramble_impl::forecast(int noutput_items,
                                           gr_vector_int& ninput_items_required)
{
    ninput_items_required[0] = 4 * (noutput_items / (d_nblocks * d_bsize));
}

int dvbt_energy_descramble_impl::general_work(int noutput_items,
                                              gr_vector_int& ninput_items,
                                              gr_vector_const_void_star& input_items,
                                              gr_vector_void_star& output_items)
{
    const unsigned char* in = (const unsigned char*)input_items[0];
    unsigned char* out = (unsigned char*)output_items[0];

    int to_consume, to_out;

    // Search for a NSYNC
    while ((in[d_index] != d_NSYNC) && (d_index < d_search)) {
        d_index += d_bsize;
    }

    /*
     * If we did not find a NSYNC then just consume
     * and return 0.
     */
    if (d_index >= d_search) {
        d_index = 0;
        to_consume = 2;
        to_out = 0;
    } else {
        // We found a NSYNC, descramble the data

        to_consume = (noutput_items / (d_nblocks * d_bsize)) - 2;
        to_out = noutput_items - 2 * (d_nblocks * d_bsize);

        for (int count = 0, i = 0; i < to_consume; i++) {
            init_prbs();

            for (int mux_pkt = 0; mux_pkt < d_MUX_PKT; mux_pkt++) {
                out[count++] = d_SYNC;
                // PRBS clocking starts right after NSYNC

                for (int k = 1; k < d_bsize; k++) {
                    out[count] = in[d_index + count] ^ clock_prbs(d_nblocks);
                    count++;
                }

                // For subsequent blocks PRBS is clocked also on SYNC
                // but its output is not used
                clock_prbs(d_nblocks);
            }
        }
    }

    // Tell runtime how many input items we consumed
    consume_each(to_consume);

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

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