#include "bladerf_dev.h"
#include "common/dsp/complex.h"
#include "dsp/block.h"
#include "nlohmann/json.hpp"
#include <bladeRF2.h>
#include <libbladeRF.h>
#include <logger.h>
#include <string>

namespace satdump
{
    namespace ndsp
    {
        BladeRFDevBlock::BladeRFDevBlock() : DeviceBlock("bladerf_dev_cc", {}, {{"RX", DSP_SAMPLE_TYPE_CF32}})
        {
            outputs[0].fifo = std::make_shared<DSPStream>(16); // TODOREWORK
        }

        BladeRFDevBlock::~BladeRFDevBlock()
        {
            stop(); // TODOREWORK (make sure to close device)
        }

        void BladeRFDevBlock::set_frequency()
        {
            if (is_open)
            {
                if (rx_ch_number > 0)
                {
                    bladerf_frequency actual;
                    bladerf_set_frequency(bladerf_dev_obj, BLADERF_CHANNEL_RX(0), rx_frequency);
                    bladerf_get_frequency(bladerf_dev_obj, BLADERF_CHANNEL_RX(0), &actual);
                    logger->debug("Set BladeRF RX frequency to %f, actual %f", rx_frequency, (double)actual);
                }

                if (tx_ch_number > 0)
                {
                    bladerf_frequency actual;
                    bladerf_set_frequency(bladerf_dev_obj, BLADERF_CHANNEL_TX(0), tx_frequency);
                    bladerf_get_frequency(bladerf_dev_obj, BLADERF_CHANNEL_TX(0), &actual);
                    logger->debug("Set BladeRF TX frequency to %f, actual %f", tx_frequency, (double)actual);
                }
            }
        }

        void BladeRFDevBlock::set_gains()
        {
            if (is_open)
            {
                for (int i = 0; i < 2; i++)
                {
                    if (rx_ch_number > 0)
                    {
                        if (rx_ch_number == 2 || rx_ch_id == i)
                        {
                            bladerf_set_gain_mode(bladerf_dev_obj, BLADERF_CHANNEL_RX(i), BLADERF_GAIN_MANUAL);
                            bladerf_set_gain(bladerf_dev_obj, BLADERF_CHANNEL_RX(i), rx_gain[i]);
                            logger->debug("Set BladeRF RX%d gain to %d", i + 1, rx_gain[i]);
                        }
                    }

                    if (tx_ch_number > 0)
                    {
                        if (tx_ch_number == 2 || tx_ch_id == i)
                        {
                            bladerf_set_gain(bladerf_dev_obj, BLADERF_CHANNEL_TX(i), tx_gain[i]);
                            logger->debug("Set BladeRF TX%d gain to %d", i + 1, tx_gain[i]);
                        }
                    }
                }
            }
        }

        void BladeRFDevBlock::set_others()
        {
            if (is_open)
            {
                // if (bladerf_model == 2) // TODOREWORK?
                {
                    bladerf_set_pll_enable(bladerf_dev_obj, extclock);
                    logger->debug("Set BladeRF External Clock to %d", (int)extclock);
                }

                if (rx_ch_number > 0)
                {
                    bladerf_set_bias_tee(bladerf_dev_obj, BLADERF_CHANNEL_RX(0), rx_bias);
                    logger->debug("Set BladeRF RX bias to %d", rx_bias);
                }
                if (tx_ch_number > 0)
                {
                    bladerf_set_bias_tee(bladerf_dev_obj, BLADERF_CHANNEL_TX(0), tx_bias);
                    logger->debug("Set BladeRF TX bias to %d", rx_bias);
                }
            }
        }

        void BladeRFDevBlock::init()
        {
            set_frequency();
            set_gains();
            set_others();
        }

        void BladeRFDevBlock::start()
        {
            struct bladerf_devinfo info;
            bladerf_init_devinfo(&info);
            if (dev_serial.size() >= BLADERF_SERIAL_LENGTH - 1)
            {
                strncpy(info.serial, dev_serial.c_str(), BLADERF_SERIAL_LENGTH - 1);
                info.serial[BLADERF_SERIAL_LENGTH - 1] = '\0';
            }
            info.serial[BLADERF_SERIAL_LENGTH - 1] = '\0';

            int r = 0;
            if (r = bladerf_open_with_devinfo(&bladerf_dev_obj, &info))
                throw satdump_exception("Could not open BladeRF '" + std::string(info.serial) + "' device! " + std::string(bladerf_strerror(r)));
            is_open = true;

            // Get the board version. TODOREWORK MAKE THIS COMMON!!!!
            int bladerf_model;
            const char *bladerf_name = bladerf_get_board_name(bladerf_dev_obj);
            if (std::string(bladerf_name) == "bladerf1")
                bladerf_model = 1;
            else if (std::string(bladerf_name) == "bladerf2")
                bladerf_model = 2;
            else
                bladerf_model = 0;

#ifdef BLADERF_HAS_WIDEBAND
            if (samplerate > 61.44e6)
            {
                is_8bit = true;
                if (bladerf_enable_feature(bladerf_dev_obj, BLADERF_FEATURE_OVERSAMPLE, true) != 0)
                    logger->error("Could not set Oversample mode");
                logger->debug("Using BladeRF Wideband mode");
            }
            else
            {
                is_8bit = false;
                if (bladerf_enable_feature(bladerf_dev_obj, BLADERF_FEATURE_DEFAULT, true) != 0)
                    logger->error("Could not set Default mode");
                logger->debug("Using BladeRF Default mode");
            }
#endif

#ifdef BLADERF_HAS_WIDEBAND
            if (bladerf_model == 2 && samplerate <= 61.44e6)
            {
#endif
                bladerf_set_sample_rate(bladerf_dev_obj, BLADERF_CHANNEL_RX(0), samplerate, NULL);
                logger->info("Actual samplerate %f (precise)", samplerate);
#ifdef BLADERF_HAS_WIDEBAND
            }
            else
            {
                struct bladerf_rational_rate rational_rate, actual;
                rational_rate.integer = static_cast<uint32_t>(samplerate);
                rational_rate.den = 10000;
                rational_rate.num = (samplerate - rational_rate.integer) * rational_rate.den;
                bladerf_set_rational_sample_rate(bladerf_dev_obj, BLADERF_CHANNEL_RX(0), &rational_rate, &actual);
                uint64_t actuals = actual.integer + (actual.num / static_cast<double>(actual.den));
                logger->info("Actual samplerate %d (rational)", actuals);
            }
#endif

            bladerf_set_bandwidth(bladerf_dev_obj, BLADERF_CHANNEL_RX(0), samplerate, NULL);

            sample_buffer_size = std::min<int>(samplerate / 250, 1e6);
            if (is_8bit && bladerf_model == 2)
                sample_buffer_size = (sample_buffer_size / 2048) * 2048;
            else
                sample_buffer_size = (sample_buffer_size / 1024) * 1024;

            if (sample_buffer_size < 1024)
                sample_buffer_size = 1024;
            if (sample_buffer_size > 1048576)
                sample_buffer_size = 1048576;
            logger->trace("BladeRF Buffer size %d", sample_buffer_size);

            init();

            if (rx_ch_number > 0)
            {
#ifdef BLADERF_HAS_WIDEBAND
                bladerf_sync_config(bladerf_dev_obj, rx_ch_number == 2 ? BLADERF_RX_X2 : BLADERF_RX_X1, is_8bit ? BLADERF_FORMAT_SC8_Q7 : BLADERF_FORMAT_SC16_Q11, 16, sample_buffer_size, 8, 4000);
#else
                bladerf_sync_config(bladerf_dev_obj, rx_ch_number == 2 ? BLADERF_RX_X2 : BLADERF_RX_X1, BLADERF_FORMAT_SC16_Q11, 16, sample_buffer_size, 8, 4000);
#endif
                if (rx_ch_number == 2 || rx_ch_id == 0)
                    bladerf_enable_module(bladerf_dev_obj, BLADERF_CHANNEL_RX(0), true);
                if (rx_ch_number == 2 || rx_ch_id == 1)
                    bladerf_enable_module(bladerf_dev_obj, BLADERF_CHANNEL_RX(1), true);
            }
            if (tx_ch_number > 0)
            {
#ifdef BLADERF_HAS_WIDEBAND
                bladerf_sync_config(bladerf_dev_obj, tx_ch_number == 2 ? BLADERF_TX_X2 : BLADERF_TX_X1, is_8bit ? BLADERF_FORMAT_SC8_Q7 : BLADERF_FORMAT_SC16_Q11, 16, sample_buffer_size, 8, 4000);
#else
                bladerf_sync_config(bladerf_dev_obj, tx_ch_number == 2 ? BLADERF_TX_X2 : BLADERF_TX_X1, BLADERF_FORMAT_SC16_Q11, 16, sample_buffer_size, 8, 4000);
#endif
                if (tx_ch_number == 2 || tx_ch_id == 0)
                    bladerf_enable_module(bladerf_dev_obj, BLADERF_CHANNEL_TX(0), true);
                if (tx_ch_number == 2 || tx_ch_id == 1)
                    bladerf_enable_module(bladerf_dev_obj, BLADERF_CHANNEL_TX(1), true);
            }

            if (rx_ch_number > 0)
            {
                thread_should_run_rx = true;
                work_thread_rx = std::thread(&BladeRFDevBlock::mainThread_rx, this);
            }
            if (tx_ch_number > 0)
            {
                work_thread_tx = std::thread(&BladeRFDevBlock::mainThread_tx, this);
            }
            is_started = true;
        }

        void BladeRFDevBlock::stop(bool stop_now, bool force)
        {
            if (is_started) // TODOREWORK Split wait & stop?
            {
                if (rx_ch_number > 0)
                {
                    if (stop_now)
                        thread_should_run_rx = false;
                    logger->info("Waiting for the RX thread...");
                    if (work_thread_rx.joinable())
                        work_thread_rx.join();
                    logger->info("RX Thread stopped");
                    for (int i = 0; i < rx_ch_number; i++)
                        outputs[i].fifo->wait_enqueue(outputs[i].fifo->newBufferTerminator());
                }

                if (tx_ch_number > 0)
                {
                    logger->info("Waiting for the TX thread...");
                    if (work_thread_tx.joinable())
                        work_thread_tx.join();
                    logger->info("TX Thread stopped");
                }

                bladerf_set_bias_tee(bladerf_dev_obj, BLADERF_CHANNEL_RX(0), false);
                bladerf_set_bias_tee(bladerf_dev_obj, BLADERF_CHANNEL_TX(0), false);

                if (rx_ch_number > 0)
                {
                    if (rx_ch_number == 2 || rx_ch_id == 0)
                        bladerf_enable_module(bladerf_dev_obj, BLADERF_CHANNEL_RX(0), false);
                    if (rx_ch_number == 2 || rx_ch_id == 1)
                        bladerf_enable_module(bladerf_dev_obj, BLADERF_CHANNEL_RX(1), false);
                }
                if (tx_ch_number > 0)
                {
                    if (tx_ch_number == 2 || tx_ch_id == 0)
                        bladerf_enable_module(bladerf_dev_obj, BLADERF_CHANNEL_TX(0), false);
                    if (tx_ch_number == 2 || tx_ch_id == 1)
                        bladerf_enable_module(bladerf_dev_obj, BLADERF_CHANNEL_TX(1), false);
                }

                bladerf_close(bladerf_dev_obj);

                is_started = false;
                is_open = false;
            }
        }

        std::vector<DeviceInfo> BladeRFDevBlock::listDevs()
        {
            std::vector<DeviceInfo> r;

            bladerf_devinfo *devs_list = nullptr;
            int devs_cnt = bladerf_get_device_list(&devs_list);

            bladerf *bladerf_dev_obj;
            for (int i = 0; i < devs_cnt; i++)
            {
                nlohmann::ordered_json c;

                // Open device
                if (bladerf_open_with_devinfo(&bladerf_dev_obj, &devs_list[i]) != 0)
                {
                    logger->warn("Could not open BladeRF for probing!");
                    continue;
                }

                // Get channel count
                int rx_channel_cnt = bladerf_get_channel_count(bladerf_dev_obj, BLADERF_RX);
                int tx_channel_cnt = bladerf_get_channel_count(bladerf_dev_obj, BLADERF_TX);

                // Get the board version
                int bladerf_model;
                const char *bladerf_name = bladerf_get_board_name(bladerf_dev_obj);
                if (std::string(bladerf_name) == "bladerf1")
                    bladerf_model = 1;
                else if (std::string(bladerf_name) == "bladerf2")
                    bladerf_model = 2;
                else
                    bladerf_model = 0;

                // Get all ranges, per channel
                for (int tx_rx = 0; tx_rx < 2; tx_rx++)
                {
                    const bladerf_range *bladerf_range_samplerate;
                    const bladerf_range *bladerf_range_bandwidth;
                    const bladerf_range *bladerf_range_gain_rx;
                    const bladerf_range *bladerf_range_gain_tx;

                    bladerf_get_sample_rate_range(bladerf_dev_obj, BLADERF_CHANNEL_RX(0), &bladerf_range_samplerate);
                    bladerf_get_bandwidth_range(bladerf_dev_obj, BLADERF_CHANNEL_RX(0), &bladerf_range_bandwidth);
                    bladerf_get_gain_range(bladerf_dev_obj, BLADERF_CHANNEL_RX(0), &bladerf_range_gain_rx);
                    bladerf_get_gain_range(bladerf_dev_obj, BLADERF_CHANNEL_TX(0), &bladerf_range_gain_tx);

                    // Get available samplerates
                    std::vector<double> available_samplerates;
                    available_samplerates.push_back(bladerf_range_samplerate->min);
                    for (int i = 1e6; i < bladerf_range_samplerate->max; i += 1e6)
                        available_samplerates.push_back(i);
                    auto old_max = bladerf_range_samplerate->max;
                    available_samplerates.push_back(bladerf_range_samplerate->max);

#ifdef BLADERF_HAS_WIDEBAND
                    if (bladerf_model == 2)
                    {
                        if (bladerf_enable_feature(bladerf_dev_obj, BLADERF_FEATURE_OVERSAMPLE, true) != 0)
                            logger->warn("Couldn't set oversample mode!");

                        // Get all ranges
                        bladerf_get_sample_rate_range(bladerf_dev_obj, BLADERF_CHANNEL_RX(0), &bladerf_range_samplerate);

                        for (int i = 1e6; i < bladerf_range_samplerate->max; i += 1e6)
                        {
                            if (i > old_max)
                                available_samplerates.push_back(i);
                        }

                        available_samplerates.push_back(bladerf_range_samplerate->max);

                        if (bladerf_enable_feature(bladerf_dev_obj, BLADERF_FEATURE_DEFAULT, true) != 0)
                            logger->warn("Couldn't unset oversample mode!");
                    }
#endif

                    //                    add_param_list(c, "samplerate", "uint", available_samplerates, "Samplerate");
                    //                    add_param_range(c, "samplerate", "uint", bladerf_range_samplerate->min, bladerf_range_samplerate->max, bladerf_range_samplerate->step, "Samplerate");
                    //                    c["samplerate"]["range_noslider"] = true;
                    c["samplerate"]["type"] = "samplerate";
                    c["samplerate"]["name"] = "Samplerate";
                    c["samplerate"]["list"] = available_samplerates;
                    c["samplerate"]["allow_manual"] = true;

                    add_param_range(c, "rx1_gain", "int", bladerf_range_gain_rx->min, bladerf_range_gain_rx->max, bladerf_range_gain_rx->step, "RX1 Gain");
                    if (bladerf_model == 2)
                        add_param_range(c, "rx2_gain", "int", bladerf_range_gain_rx->min, bladerf_range_gain_rx->max, bladerf_range_gain_rx->step, "RX2 Gain");
                    add_param_range(c, "tx1_gain", "float", bladerf_range_gain_tx->min * bladerf_range_gain_tx->scale, bladerf_range_gain_tx->max * bladerf_range_gain_tx->scale,
                                    bladerf_range_gain_tx->step * bladerf_range_gain_tx->scale, "TX1 Gain");
                    if (bladerf_model == 2)
                        add_param_range(c, "tx2_gain", "float", bladerf_range_gain_tx->min * bladerf_range_gain_tx->scale, bladerf_range_gain_tx->max * bladerf_range_gain_tx->scale,
                                        bladerf_range_gain_tx->step * bladerf_range_gain_tx->scale, "TX2 Gain");
                }

                std::string name = "BladeRF";
                if (bladerf_model == 1)
                    name += "1";
                else if (bladerf_model == 2)
                    name += "2";
                name += " " + std::string(devs_list[i].serial);

                nlohmann::json p;
                p["serial"] = std::string(devs_list[i].serial);
                r.push_back({"bladerf", name, p, c});

                bladerf_close(bladerf_dev_obj);
            }

            if (devs_list != nullptr && devs_cnt > 0)
                bladerf_free_device_list(devs_list);

            return r;
        }
    } // namespace ndsp
} // namespace satdump