/* -*- c++ -*- */
/*
 * Copyright 2020 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 "correctiq_auto_impl.h"
#include <gnuradio/io_signature.h>
#include <volk/volk.h>
#include <boost/format.hpp>

namespace gr {
namespace blocks {

namespace {
constexpr int default_const_buffer = 8192;
}

correctiq_auto::sptr
correctiq_auto::make(double samp_rate, double freq, float gain, float sync_window)
{
    return gnuradio::make_block_sptr<correctiq_auto_impl>(
        samp_rate, freq, gain, sync_window);
}

/*
 * The private constructor
 */
correctiq_auto_impl::correctiq_auto_impl(double samp_rate,
                                         double freq,
                                         float gain,
                                         float sync_window)
    : gr::sync_block("correctiq_auto",
                     gr::io_signature::make(1, 1, sizeof(gr_complex)),
                     gr::io_signature::make(1, 1, sizeof(gr_complex))),
      d_samp_rate(samp_rate),
      d_freq(freq),
      d_gain(gain),
      d_sync_window(sync_window),
      d_max_sync_samples((long)(d_samp_rate * (double)d_sync_window)),
      d_volk_const_buffer(default_const_buffer)
{
    set_const_buffer(default_const_buffer);

    message_port_register_in(pmt::mp("rsync"));
    set_msg_handler(pmt::mp("rsync"),
                    [this](pmt::pmt_t msg) { this->handle_resync(msg); });

    message_port_register_out(pmt::mp("sync_start"));
    message_port_register_out(pmt::mp("offsets"));

    GR_LOG_INFO(d_logger, "Block start.  Auto Synchronizing...");
}

void correctiq_auto_impl::trigger_resync(std::string reason)
{
    gr::thread::scoped_lock guard(d_setlock);

    std::string d_resync_msg = reason + ".  Auto Synchronizing...";

    GR_LOG_INFO(d_logger, d_resync_msg);
    d_synchronized = false;
    d_sync_counter = 0;

    // reset counters on an unsync
    d_avg_real = 0.0;
    d_avg_img = 0.0;
    d_k = gr_complex(d_avg_real, d_avg_img);

    send_syncing();
}

void correctiq_auto_impl::send_sync_values()
{
    pmt::pmt_t meta = pmt::make_dict();

    meta = pmt::dict_add(meta, pmt::mp("real"), pmt::mp(d_avg_real));
    meta = pmt::dict_add(meta, pmt::mp("imag"), pmt::mp(d_avg_img));

    pmt::pmt_t pdu = pmt::cons(meta, pmt::PMT_NIL);
    message_port_pub(pmt::mp("offsets"), pdu);
}

void correctiq_auto_impl::send_syncing()
{
    pmt::pmt_t pdu = pmt::cons(pmt::intern("syncing"), pmt::from_bool(true));

    message_port_pub(pmt::mp("sync_start"), pdu);
}

void correctiq_auto_impl::handle_resync(pmt::pmt_t msg)
{
    trigger_resync("Resync message received.");
}

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

void correctiq_auto_impl::set_const_buffer(int new_size)
{
    d_volk_const_buffer.resize(new_size);
    fill_const_buffer();
}

void correctiq_auto_impl::fill_const_buffer()
{
    std::fill(std::begin(d_volk_const_buffer), std::end(d_volk_const_buffer), d_k);
}

double correctiq_auto_impl::get_freq() const { return d_freq; }

float correctiq_auto_impl::get_gain() const { return d_gain; }

void correctiq_auto_impl::set_freq(double new_value)
{
    d_freq = new_value;
    trigger_resync("Frequency change detected");
}

void correctiq_auto_impl::set_gain(float new_value)
{
    d_gain = new_value;
    trigger_resync("Gain change detected");
}

int correctiq_auto_impl::work(int noutput_items,
                              gr_vector_const_void_star& input_items,
                              gr_vector_void_star& output_items)
{
    gr::thread::scoped_lock guard(d_setlock);

    if (!d_synchronized) {
        const gr_complex* in = (const gr_complex*)input_items[0];
        gr_complex* out = (gr_complex*)output_items[0];

        for (int i = 0; i < noutput_items; i++) {
            // Synchronizing.  Behave just like normal correctiq.
            d_avg_real = d_ratio * (in[i].real() - d_avg_real) + d_avg_real;
            d_avg_img = d_ratio * (in[i].imag() - d_avg_img) + d_avg_img;
            d_sync_counter++;

            out[i].real(in[i].real() - d_avg_real);
            out[i].imag(in[i].imag() - d_avg_img);
        }
    } else {
        if (noutput_items > static_cast<int>(d_volk_const_buffer.size()))
            set_const_buffer(noutput_items);

        // Inputs are complex but we're casting as floats to leverage volk
        const float* in = (const float*)input_items[0];
        float* out = (float*)output_items[0];

        volk_32f_x2_subtract_32f(out,
                                 in,
                                 reinterpret_cast<float*>(d_volk_const_buffer.data()),
                                 2 * noutput_items);
    }

    if (!d_synchronized && (d_sync_counter >= d_max_sync_samples)) {
        d_synchronized = true;
        d_k = gr_complex(d_avg_real, d_avg_img);
        fill_const_buffer();

        GR_LOG_INFO(d_logger, "Auto offset now synchronized.");
        GR_LOG_INFO(
            d_logger,
            boost::format{ "Applying these offsets (real-real_offset, "
                           "imag-imag_offset)... real_offset: %.6f, imag_offset: %.6f" } %
                d_avg_real % d_avg_img);

        send_sync_values();
    }

    return noutput_items;
}

} /* namespace blocks */
} /* namespace gr */
