/* -*- c++ -*- */
/* Copyright 2012,2014 Free Software Foundation, Inc.
 *
 * This file is part of GNU Radio
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 */

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

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

namespace gr {
namespace digital {

ofdm_serializer_vcc::sptr
ofdm_serializer_vcc::make(int fft_len,
                          const std::vector<std::vector<int>>& occupied_carriers,
                          const std::string& len_tag_key,
                          const std::string& packet_len_tag_key,
                          int symbols_skipped,
                          const std::string& carr_offset_key,
                          bool input_is_shifted)
{
    return gnuradio::make_block_sptr<ofdm_serializer_vcc_impl>(fft_len,
                                                               occupied_carriers,
                                                               len_tag_key,
                                                               packet_len_tag_key,
                                                               symbols_skipped,
                                                               carr_offset_key,
                                                               input_is_shifted);
}

ofdm_serializer_vcc::sptr
ofdm_serializer_vcc::make(const gr::digital::ofdm_carrier_allocator_cvc::sptr& allocator,
                          const std::string& packet_len_tag_key,
                          int symbols_skipped,
                          const std::string& carr_offset_key,
                          bool input_is_shifted)
{
    return gnuradio::make_block_sptr<ofdm_serializer_vcc_impl>(
        allocator->fft_len(),
        allocator->occupied_carriers(),
        allocator->len_tag_key(),
        packet_len_tag_key,
        symbols_skipped,
        carr_offset_key,
        !input_is_shifted);
}

ofdm_serializer_vcc_impl::ofdm_serializer_vcc_impl(
    int fft_len,
    const std::vector<std::vector<int>>& occupied_carriers,
    const std::string& len_tag_key,
    const std::string& packet_len_tag_key,
    int symbols_skipped,
    const std::string& carr_offset_key,
    bool input_is_shifted)
    : tagged_stream_block("ofdm_serializer_vcc",
                          io_signature::make(1, 1, sizeof(gr_complex) * fft_len),
                          io_signature::make(1, 1, sizeof(gr_complex)),
                          len_tag_key),
      d_fft_len(fft_len),
      d_occupied_carriers(occupied_carriers),
      d_packet_len_tag_key(pmt::string_to_symbol(packet_len_tag_key)),
      d_out_len_tag_key(pmt::string_to_symbol(
          (packet_len_tag_key.empty() ? len_tag_key : packet_len_tag_key))),
      d_symbols_skipped(symbols_skipped % occupied_carriers.size()),
      d_carr_offset_key(pmt::string_to_symbol(carr_offset_key)),
      d_curr_set(symbols_skipped % occupied_carriers.size()),
      d_symbols_per_set(0)
{
    for (unsigned i = 0; i < d_occupied_carriers.size(); i++) {
        for (unsigned k = 0; k < d_occupied_carriers[i].size(); k++) {
            if (input_is_shifted) {
                d_occupied_carriers[i][k] += fft_len / 2;
                if (d_occupied_carriers[i][k] > fft_len) {
                    d_occupied_carriers[i][k] -= fft_len;
                }
            } else {
                if (d_occupied_carriers[i][k] < 0) {
                    d_occupied_carriers[i][k] += fft_len;
                }
            }
            if (d_occupied_carriers[i][k] >= fft_len || d_occupied_carriers[i][k] < 0) {
                throw std::invalid_argument("ofdm_serializer_vcc: trying to occupy a "
                                            "carrier outside the fft length.");
            }
        }
    }

    for (unsigned i = 0; i < d_occupied_carriers.size(); i++) {
        d_symbols_per_set += d_occupied_carriers[i].size();
    }
    set_relative_rate((uint64_t)d_symbols_per_set, (uint64_t)d_occupied_carriers.size());
    set_tag_propagation_policy(TPP_DONT);
}

ofdm_serializer_vcc_impl::~ofdm_serializer_vcc_impl() {}

int ofdm_serializer_vcc_impl::calculate_output_stream_length(
    const gr_vector_int& ninput_items)
{
    int nout = (ninput_items[0] / d_occupied_carriers.size()) * d_symbols_per_set;
    for (unsigned i = 0; i < ninput_items[0] % d_occupied_carriers.size(); i++) {
        nout += d_occupied_carriers[(i + d_curr_set) % d_occupied_carriers.size()].size();
    }
    return nout;
}

void ofdm_serializer_vcc_impl::update_length_tags(int n_produced, int n_ports)
{
    add_item_tag(0, nitems_written(0), d_out_len_tag_key, pmt::from_long(n_produced));
}

int ofdm_serializer_vcc_impl::work(int noutput_items,
                                   gr_vector_int& ninput_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];
    long frame_length = ninput_items[0]; // Input frame
    long packet_length = 0;              // Output frame
    int carr_offset = 0;

    std::vector<tag_t> tags;
    // Packet mode
    if (!d_length_tag_key_str.empty()) {
        get_tags_in_range(tags, 0, nitems_read(0), nitems_read(0) + 1);
        for (unsigned i = 0; i < tags.size(); i++) {
            if (tags[i].key == d_carr_offset_key) {
                carr_offset = pmt::to_long(tags[i].value);
            }
            if (tags[i].key == d_packet_len_tag_key) {
                packet_length = pmt::to_long(tags[i].value);
            }
        }
    } else {
        // recalc frame length from noutput_items
        frame_length = 0;
        int sym_per_frame = 0;
        while ((sym_per_frame +
                d_occupied_carriers[(frame_length + 1) % d_occupied_carriers.size()]
                    .size()) < (size_t)noutput_items) {
            frame_length++;
            sym_per_frame +=
                d_occupied_carriers[(frame_length + 1) % d_occupied_carriers.size()]
                    .size();
        }
    }

    // Copy symbols
    int n_out_symbols = 0;
    for (int i = 0; i < frame_length; i++) {
        // Copy all tags associated with this input OFDM symbol onto the first output
        // symbol
        get_tags_in_range(tags, 0, nitems_read(0) + i, nitems_read(0) + i + 1);
        for (size_t t = 0; t < tags.size(); t++) {
            // The packet length tag is not propagated
            if (tags[t].key != d_packet_len_tag_key) {
                add_item_tag(
                    0, nitems_written(0) + n_out_symbols, tags[t].key, tags[t].value);
            }
        }
        for (unsigned k = 0; k < d_occupied_carriers[d_curr_set].size(); k++) {
            out[n_out_symbols++] =
                in[i * d_fft_len + d_occupied_carriers[d_curr_set][k] + carr_offset];
        }
        if (packet_length && n_out_symbols > packet_length) {
            n_out_symbols = packet_length;
            break;
        }
        d_curr_set = (d_curr_set + 1) % d_occupied_carriers.size();
    }

    // Housekeeping
    if (d_length_tag_key_str.empty()) {
        consume_each(frame_length);
    } else {
        d_curr_set = d_symbols_skipped;
    }

    return n_out_symbols;
}

} /* namespace digital */
} /* namespace gr */