/*!
 * \file glonass_l2_ca_telemetry_decoder_gs.cc
 * \brief Implementation of a GLONASS L2 C/A NAV data decoder block
 * \author Damian Miralles, 2018. dmiralles2009(at)gmail.com
 *
 * -----------------------------------------------------------------------------
 *
 * GNSS-SDR is a Global Navigation Satellite System software-defined receiver.
 * This file is part of GNSS-SDR.
 *
 * Copyright (C) 2010-2020  (see AUTHORS file for a list of contributors)
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 * -----------------------------------------------------------------------------
 */


#include "glonass_l2_ca_telemetry_decoder_gs.h"
#include "display.h"
#include "glonass_gnav_almanac.h"
#include "glonass_gnav_ephemeris.h"
#include "glonass_gnav_utc_model.h"
#include "gnss_sdr_make_unique.h"  // for std::make_unique in C++11
#include "tlm_crc_stats.h"
#include "tlm_utils.h"
#include "tow_to_trk.h"
#include <gnuradio/io_signature.h>
#include <pmt/pmt.h>        // for make_any
#include <pmt/pmt_sugar.h>  // for mp
#include <cmath>            // for floor, round
#include <cstddef>          // for size_t
#include <cstdlib>          // for abs
#include <exception>        // for exception
#include <iomanip>          // for std::setprecision
#include <iostream>         // for cout

#if USE_GLOG_AND_GFLAGS
#include <glog/logging.h>
#else
#include <absl/log/log.h>
#endif

#define CRC_ERROR_LIMIT 6


glonass_l2_ca_telemetry_decoder_gs_sptr
glonass_l2_ca_make_telemetry_decoder_gs(const Gnss_Satellite &satellite, const Tlm_Conf &conf)
{
    return glonass_l2_ca_telemetry_decoder_gs_sptr(new glonass_l2_ca_telemetry_decoder_gs(satellite, conf));
}


glonass_l2_ca_telemetry_decoder_gs::glonass_l2_ca_telemetry_decoder_gs(
    const Gnss_Satellite &satellite,
    const Tlm_Conf &conf) : telemetry_impl_interface("glonass_l2_ca_telemetry_decoder_gs",
                                gr::io_signature::make(1, 1, sizeof(Gnss_Synchro)),
                                gr::io_signature::make(1, 1, sizeof(Gnss_Synchro))),
                            d_dump_filename(conf.dump_filename),
                            d_preamble_time_samples(0),
                            d_TOW_at_current_symbol(0),
                            d_sample_counter(0ULL),
                            d_preamble_index(0ULL),
                            d_stat(0),
                            d_CRC_error_counter(0),
                            d_channel(0),
                            d_flag_frame_sync(false),
                            d_flag_preamble(false),
                            d_dump(conf.dump),
                            d_dump_mat(conf.dump_mat),
                            d_remove_dat(conf.remove_dat),
                            d_enable_navdata_monitor(conf.enable_navdata_monitor),
                            d_dump_crc_stats(conf.dump_crc_stats),
                            d_tow_to_trk(conf.tow_to_trk)
{
    configure_basic_outputs();

    this->message_port_register_out(pmt::mp("preamble_timestamp_samples"));

    if (d_enable_navdata_monitor)
        {
            // register nav message monitor out
            this->message_port_register_out(pmt::mp("Nav_msg_from_TLM"));
            d_nav_msg_packet.system = std::string("R");
            d_nav_msg_packet.signal = std::string("2G");
        }

    d_satellite = Gnss_Satellite(satellite.get_system(), satellite.get_PRN());
    LOG(INFO) << "Initializing GLONASS L2 CA TELEMETRY DECODING";

    d_symbol_history.set_capacity(GLONASS_GNAV_STRING_BIBINARY_WITH_PREABLE);

    if (d_dump_crc_stats)
        {
            // initialize the telemetry CRC statistics class
            d_Tlm_CRC_Stats = std::make_unique<Tlm_CRC_Stats>();
            d_Tlm_CRC_Stats->initialize(conf.dump_crc_stats_filename);
        }
    else
        {
            d_Tlm_CRC_Stats = nullptr;
        }
}


glonass_l2_ca_telemetry_decoder_gs::~glonass_l2_ca_telemetry_decoder_gs()
{
    DLOG(INFO) << "Glonass L2 Telemetry decoder block (channel " << d_channel << ") destructor called.";
    size_t pos = 0;
    if (d_dump_file.is_open() == true)
        {
            pos = d_dump_file.tellp();
            try
                {
                    d_dump_file.close();
                }
            catch (const std::exception &ex)
                {
                    LOG(WARNING) << "Exception in destructor closing the dump file " << ex.what();
                }
            if (pos == 0)
                {
                    if (!tlm_remove_file(d_dump_filename))
                        {
                            LOG(WARNING) << "Error deleting temporary file";
                        }
                }
        }
    if (d_dump && (pos != 0) && d_dump_mat)
        {
            save_tlm_matfile(d_dump_filename);
            if (d_remove_dat)
                {
                    if (!tlm_remove_file(d_dump_filename))
                        {
                            LOG(WARNING) << "Error deleting temporary file";
                        }
                }
        }
}


void glonass_l2_ca_telemetry_decoder_gs::decode_string(const double *frame_symbols, double cn0)
{
    // 1. Transform from symbols to bits
    std::string relative_code;
    relative_code.reserve(GLONASS_GNAV_STRING_BITS);
    std::string data_bits;
    data_bits.reserve(GLONASS_GNAV_STRING_BITS + 1);
    // Convert from bi-binary code to relative code
    for (int32_t i = 0; i < (GLONASS_GNAV_STRING_BITS); i++)
        {
            if (frame_symbols[2 * i] > 0 && frame_symbols[2 * i + 1] <= 0)
                {
                    relative_code.push_back('1');
                }
            else
                {
                    relative_code.push_back('0');
                }
        }
    // Convert from relative code to data bits
    data_bits.push_back('0');
    for (int32_t i = 1; i < (GLONASS_GNAV_STRING_BITS); i++)
        {
            data_bits.push_back(((relative_code[i - 1] - '0') ^ (relative_code[i] - '0')) + '0');
        }

    if (d_enable_navdata_monitor)
        {
            d_nav_msg_packet.nav_message = data_bits;
        }

    // 2. Call the GLONASS GNAV string decoder
    d_nav.string_decoder(data_bits);

    // 3. Check operation executed correctly
    bool crc_ok = d_nav.get_flag_CRC_test();
    if (crc_ok)
        {
            LOG(INFO) << "GLONASS GNAV CRC correct in channel " << d_channel << " from satellite " << d_satellite;
        }
    else
        {
            LOG(INFO) << "GLONASS GNAV CRC error in channel " << d_channel << " from satellite " << d_satellite;
        }
    if (d_dump_crc_stats)
        {
            // update CRC statistics
            d_Tlm_CRC_Stats->update_CRC_stats(crc_ok);
        }

    // 4. Push the new navigation data to the queues
    if (d_nav.have_new_ephemeris() == true)
        {
            // get object for this SV (mandatory)
            d_nav.set_rf_link(d_satellite.get_rf_link());
            const std::shared_ptr<Glonass_Gnav_Ephemeris> tmp_obj = std::make_shared<Glonass_Gnav_Ephemeris>(d_nav.get_ephemeris());
            this->message_port_pub(pmt::mp("telemetry"), pmt::make_any(tmp_obj));
            LOG(INFO) << "GLONASS GNAV Ephemeris have been received in channel" << d_channel << " from satellite " << d_satellite;
#if __cplusplus == 201103L
            const int default_precision = std::cout.precision();
#else
            const auto default_precision{std::cout.precision()};
#endif
            std::cout << TEXT_CYAN << "New GLONASS L2 GNAV message received in channel " << d_channel
                      << ": ephemeris from satellite " << d_satellite
                      << " with CN0=" << std::setprecision(2) << cn0 << std::setprecision(default_precision)
                      << " dB-Hz" << TEXT_RESET << std::endl;
        }
    if (d_nav.have_new_utc_model() == true)
        {
            // get object for this SV (mandatory)
            const std::shared_ptr<Glonass_Gnav_Utc_Model> tmp_obj = std::make_shared<Glonass_Gnav_Utc_Model>(d_nav.get_utc_model());
            this->message_port_pub(pmt::mp("telemetry"), pmt::make_any(tmp_obj));
            LOG(INFO) << "GLONASS GNAV UTC Model data have been received in channel" << d_channel << " from satellite " << d_satellite;
#if __cplusplus == 201103L
            const int default_precision = std::cout.precision();
#else
            const auto default_precision{std::cout.precision()};
#endif
            std::cout << TEXT_CYAN << "New GLONASS L2 GNAV message received in channel " << d_channel
                      << ": UTC model parameters from satellite " << d_satellite
                      << " with CN0=" << std::setprecision(2) << cn0 << std::setprecision(default_precision)
                      << " dB-Hz" << TEXT_RESET << std::endl;
        }
    if (d_nav.have_new_almanac() == true)
        {
            const uint32_t slot_nbr = d_nav.get_alm_satellite_slot_number();
            const std::shared_ptr<Glonass_Gnav_Almanac>
                tmp_obj = std::make_shared<Glonass_Gnav_Almanac>(d_nav.get_almanac(slot_nbr));
            this->message_port_pub(pmt::mp("telemetry"), pmt::make_any(tmp_obj));
            LOG(INFO) << "GLONASS GNAV Almanac data have been received in channel" << d_channel << " in slot number " << slot_nbr;
#if __cplusplus == 201103L
            const int default_precision = std::cout.precision();
#else
            const auto default_precision{std::cout.precision()};
#endif
            std::cout << TEXT_CYAN << "New GLONASS L2 GNAV almanac received in channel " << d_channel
                      << " from satellite " << d_satellite
                      << " with CN0=" << std::setprecision(2) << cn0 << std::setprecision(default_precision)
                      << " dB-Hz" << TEXT_RESET << std::endl;
        }
    // 5. Update satellite information on system
    if (d_nav.get_flag_update_slot_number() == true)
        {
            LOG(INFO) << "GLONASS GNAV Slot Number Identified in channel " << d_channel;
            d_satellite.update_PRN(d_nav.get_ephemeris().d_n);
            d_satellite.what_block(d_satellite.get_system(), d_nav.get_ephemeris().d_n);
            d_nav.set_flag_update_slot_number(false);
        }
}


void glonass_l2_ca_telemetry_decoder_gs::set_satellite(const Gnss_Satellite &satellite)
{
    d_satellite = Gnss_Satellite(satellite.get_system(), satellite.get_PRN());
    DLOG(INFO) << "Setting decoder Finite State Machine to satellite " << d_satellite;
    DLOG(INFO) << "Navigation Satellite set to " << d_satellite;
}


void glonass_l2_ca_telemetry_decoder_gs::set_channel(int32_t channel)
{
    d_channel = channel;
    LOG(INFO) << "Navigation channel set to " << channel;

    configure_dump_file(d_channel, d_dump, d_dump_filename, d_dump_file);
    configure_crc_stats_channel(d_channel, d_dump_crc_stats, d_Tlm_CRC_Stats);
}


int glonass_l2_ca_telemetry_decoder_gs::general_work(int noutput_items __attribute__((unused)), gr_vector_int &ninput_items __attribute__((unused)),
    gr_vector_const_void_star &input_items, gr_vector_void_star &output_items)
{
    int32_t corr_value = 0;
    int32_t preamble_diff = 0;

    auto **out = reinterpret_cast<Gnss_Synchro **>(&output_items[0]);            // Get the output buffer pointer
    const auto **in = reinterpret_cast<const Gnss_Synchro **>(&input_items[0]);  // Get the input buffer pointer

    Gnss_Synchro current_symbol{};  // structure to save the synchronization information and send the output object to the next block

    // 1. Copy the current tracking output
    current_symbol = in[0][0];
    d_symbol_history.push_back(current_symbol);  // add new symbol to the symbol queue
    d_sample_counter++;                          // count for the processed samples
    consume_each(1);

    d_flag_preamble = false;

    if (static_cast<int32_t>(d_symbol_history.size()) >= GLONASS_GNAV_PREAMBLE_LENGTH_BITS)
        {
            // ******* preamble correlation ********
            for (int32_t i = 0; i < GLONASS_GNAV_PREAMBLE_LENGTH_BITS; i++)
                {
                    if (d_symbol_history[i].Prompt_I < 0.0)  // symbols clipping
                        {
                            corr_value -= d_preambles_bits[i];
                        }
                    else
                        {
                            corr_value += d_preambles_bits[i];
                        }
                }
        }

    // ******* frame sync ******************
    if (d_stat == 0)  // no preamble information
        {
            if (abs(corr_value) >= GLONASS_GNAV_PREAMBLE_LENGTH_BITS)
                {
                    // Record the preamble sample stamp
                    d_preamble_index = d_sample_counter;
                    LOG(INFO) << "Preamble detection for GLONASS L2 C/A SAT " << this->d_satellite;
                    // Enter into frame pre-detection status
                    d_stat = 1;
                    d_preamble_time_samples = d_symbol_history[0].Tracking_sample_counter;  // record the preamble sample stamp
                }
        }
    else if (d_stat == 1)  // possible preamble lock
        {
            if (abs(corr_value) >= GLONASS_GNAV_PREAMBLE_LENGTH_BITS)
                {
                    // check preamble separation
                    preamble_diff = static_cast<int32_t>(d_sample_counter - d_preamble_index);
                    // Record the PRN start sample index associated to the preamble
                    d_preamble_time_samples = static_cast<double>(d_symbol_history[0].Tracking_sample_counter);
                    if (abs(preamble_diff - GLONASS_GNAV_PREAMBLE_PERIOD_BITS) == 0)
                        {
                            // try to decode frame
                            LOG(INFO) << "Starting string decoder for GLONASS L2 C/A SAT " << this->d_satellite;
                            d_preamble_index = d_sample_counter;  // record the preamble sample stamp
                            this->message_port_pub(pmt::mp("preamble_timestamp_samples"), pmt::mp(d_preamble_time_samples));
                            d_stat = 2;
                        }
                    else
                        {
                            if (preamble_diff > GLONASS_GNAV_PREAMBLE_PERIOD_BITS)
                                {
                                    d_stat = 0;  // start again
                                }
                            DLOG(INFO) << "Failed string decoder for GLONASS L2 C/A SAT " << this->d_satellite;
                        }
                }
        }
    else if (d_stat == 2)
        {
            // FIXME: The preamble index marks the first symbol of the string count. Here I just wait for another full string to be received before processing
            if (d_sample_counter == d_preamble_index + static_cast<uint64_t>(GLONASS_GNAV_STRING_BIBINARY_WITH_PREABLE))
                {
                    // NEW GLONASS string received
                    // 0. fetch the symbols into an array
                    const int32_t string_length = GLONASS_GNAV_STRING_BIBINARY_WITH_PREABLE - GLONASS_GNAV_PREAMBLE_LENGTH_BITS;
                    std::array<double, GLONASS_GNAV_STRING_BIBINARY_WITH_PREABLE - GLONASS_GNAV_PREAMBLE_LENGTH_BITS> string_symbols{};

                    // ******* SYMBOL TO BIT *******
                    for (int32_t i = 0; i < string_length; i++)
                        {
                            if (corr_value > 0)
                                {
                                    string_symbols[i] = d_symbol_history[i + GLONASS_GNAV_PREAMBLE_LENGTH_BITS].Prompt_I;  // because last symbol of the preamble is just received now!
                                }
                            else
                                {
                                    string_symbols[i] = -d_symbol_history[i + GLONASS_GNAV_PREAMBLE_LENGTH_BITS].Prompt_I;  // because last symbol of the preamble is just received now!
                                }
                        }

                    // call the decoder
                    decode_string(string_symbols.data(), current_symbol.CN0_dB_hz);
                    bool crc_ok = d_nav.get_flag_CRC_test();
                    if (crc_ok == true)
                        {
                            d_CRC_error_counter = 0;
                            d_flag_preamble = true;               // valid preamble indicator (initialized to false every work())
                            d_preamble_index = d_sample_counter;  // record the preamble sample stamp (t_P)
                            if (!d_flag_frame_sync)
                                {
                                    d_flag_frame_sync = true;
                                    LOG(INFO) << "Successful frame synchronization in channel " << d_channel << " for satellite " << this->d_satellite
                                              << " at sample_counter=" << d_symbol_history[0].Tracking_sample_counter;
                                }
                        }
                    else
                        {
                            d_CRC_error_counter++;
                            d_preamble_index = d_sample_counter;  // record the preamble sample stamp
                            if (d_CRC_error_counter > CRC_ERROR_LIMIT)
                                {
                                    LOG(INFO) << "Lost of frame sync SAT " << this->d_satellite;
                                    d_flag_frame_sync = false;
                                    d_stat = 0;
                                }
                        }
                }
        }

    // UPDATE GNSS SYNCHRO DATA
    // 2. Add the telemetry decoder information
    if (this->d_flag_preamble == true && d_nav.get_flag_TOW_new() == true)
        // update TOW at the preamble instant
        {
            d_TOW_at_current_symbol = floor((d_nav.get_ephemeris().d_TOW - GLONASS_GNAV_PREAMBLE_DURATION_S) * 1000) / 1000;
            d_nav.set_flag_TOW_new(false);
        }
    else  // if there is not a new preamble, we define the TOW of the current symbol
        {
            d_TOW_at_current_symbol = d_TOW_at_current_symbol + GLONASS_L2_CA_SYMBOL_PERIOD_S;
        }

    // if (d_flag_frame_sync == true && d_nav.flag_TOW_set==true && d_nav.get_flag_CRC_test() == true)

    // if(d_nav.flag_GGTO_1 == true  &&  d_nav.flag_GGTO_2 == true &&  d_nav.flag_GGTO_3 == true &&  d_nav.flag_GGTO_4 == true) // all GGTO parameters arrived
    //     {
    //         delta_t = d_nav.A_0G + d_nav.A_1G * (d_TOW_at_current_symbol - d_nav.t_0G + 604800.0 * (fmod((d_nav.WN_0 - d_nav.WN_0G), 64)));
    //     }

    current_symbol.PRN = this->d_satellite.get_PRN();
    current_symbol.TOW_at_current_symbol_ms = round(d_TOW_at_current_symbol * 1000.0);

    if (d_flag_frame_sync == true && d_nav.is_flag_TOW_set() == true)
        {
            current_symbol.Flag_valid_word = true;
            if (d_enable_navdata_monitor && !d_nav_msg_packet.nav_message.empty())
                {
                    d_nav_msg_packet.prn = static_cast<int32_t>(current_symbol.PRN);
                    d_nav_msg_packet.tow_at_current_symbol_ms = static_cast<int32_t>(current_symbol.TOW_at_current_symbol_ms);
                    const std::shared_ptr<Nav_Message_Packet> tmp_obj = std::make_shared<Nav_Message_Packet>(d_nav_msg_packet);
                    this->message_port_pub(pmt::mp("Nav_msg_from_TLM"), pmt::make_any(tmp_obj));
                    d_nav_msg_packet.nav_message = "";
                }

            // SEND TOW TO THE TRACKING BLOCK
            if (d_tow_to_trk)
                {
                    const std::shared_ptr<TOW_to_trk> tmp_tow_obj = std::make_shared<TOW_to_trk>(TOW_to_trk(
                        std::string(current_symbol.Signal),
                        d_channel,
                        current_symbol.TOW_at_current_symbol_ms,
                        current_symbol.Tracking_sample_counter,
                        d_nav.get_ephemeris().d_WN, d_satellite.get_PRN()));
                    this->message_port_pub(pmt::mp("telemetry_to_trk"), pmt::make_any(tmp_tow_obj));
                }
        }
    else
        {
            current_symbol.Flag_valid_word = false;
        }

    // todo: glonass time to gps time should be done in observables block
    // current_symbol.TOW_at_current_symbol_ms -= static_cast<uint32_t>(delta_t) * 1000;

    if (d_dump == true)
        {
            // MULTIPLEXED FILE RECORDING - Record results to file
            try
                {
                    double tmp_double;
                    uint64_t tmp_ulong_int;
                    int32_t tmp_int;
                    tmp_double = d_TOW_at_current_symbol;
                    d_dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
                    tmp_ulong_int = current_symbol.Tracking_sample_counter;
                    d_dump_file.write(reinterpret_cast<char *>(&tmp_ulong_int), sizeof(uint64_t));
                    tmp_double = 0;
                    d_dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
                    tmp_int = (current_symbol.Prompt_I > 0.0 ? 1 : -1);
                    d_dump_file.write(reinterpret_cast<char *>(&tmp_int), sizeof(int32_t));
                    tmp_int = static_cast<int32_t>(current_symbol.PRN);
                    d_dump_file.write(reinterpret_cast<char *>(&tmp_int), sizeof(int32_t));
                }
            catch (const std::ofstream::failure &e)
                {
                    LOG(WARNING) << "Exception writing observables dump file " << e.what();
                }
        }

    // 3. Make the output (move the object contents to the GNURadio reserved memory)
    *out[0] = std::move(current_symbol);

    return 1;
}
