#pragma once

#include "core/module.h"
#include <complex>
#include "common/codings/viterbi/viterbi_1_2.h"
#include "common/codings/deframing/bpsk_ccsds_deframer.h"
#include <fstream>
#include "common/dsp/demod/constellation.h"
#include "common/codings/reedsolomon/reedsolomon.h"
#include "common/dsp/utils/random.h"

namespace ccsds
{
    /*
    This decoder is meant to decode simple PSK signals
    using or akin to the CCSDS standard.

    Frame length, differential encoding or not are kept
    configurable, alongside the ASM as this same decoder
    can cover a lot of other specifications.

    For QPSK non-differentially encoded signals, 2 BPSK
    deframers are used : one at 0 degs in phase, and another
    at 90 degs out of phase.
    This ensure one or the other will end up with the right
    "BPSK-like" branch with the ASM potentially flipped.
    That's fine to do it that way as the 2 deframers should
    never lock at the same time.
    */
    class CCSDSSimplePSKDecoderModule : public ProcessingModule
    {
    protected:
        const bool is_ccsds; // Just to know if we should output .cadu or .frm

        const std::string d_constellation_str;     // Constellation type string
        dsp::constellation_type_t d_constellation; // Constellation type

        const int d_cadu_size;   // CADU Size in bits, including ASM
        const int d_cadu_bytes;  // CADU Size in bytes, including ASM
        const int d_buffer_size; // Processing buffer size, default half of a frame (= d_cadu_size)

        const bool d_qpsk_swapiq;                      // If IQ should be swapped before being demodulated (to support different DQPSK modes)
        const bool d_qpsk_swapdiff;                    // If bits should be swapped after the output of the QPSK diff decoder
        const bool d_oqpsk_delay;                      // For some OQPSK satellites, the Q branch is delayed by 1 symbol
        const bool d_oqpsk_method_2, d_oqpsk_method_3; // Weird satellites

        const bool d_diff_decode; // If NRZ-M Decoding is required or not

        const bool d_derand;          // Perform derandomizion or not
        const bool d_derand_after_rs; // Derandomization after RS
        const int d_derand_from;      // Byte to start derand on

        const int d_rs_interleaving_depth; // RS Interleaving depth. If = 0, then RS is disabled
        const int d_rs_fill_bytes;         // RS Frame size, if -1, no puncturing
        const bool d_rs_dualbasis;         // RS Representation. Dual basis or none?
        const std::string d_rs_type;       // RS Type identifier
        const bool d_rs_usecheck;          // RS Used as frame check?

        uint8_t *bits_out;
        int8_t *soft_buffer, *soft_buffer2;
        uint8_t *qpsk_diff_buffer;
        uint8_t *frame_buffer;

        std::ifstream data_in;
        std::ofstream data_out;

        std::atomic<uint64_t> filesize;
        std::atomic<uint64_t> progress;

        std::shared_ptr<deframing::BPSK_CCSDS_Deframer> deframer;
        std::shared_ptr<deframing::BPSK_CCSDS_Deframer> deframer_qpsk;
        std::shared_ptr<reedsolomon::ReedSolomon> reed_solomon;

        int errors[10];

        // UI Stuff
        dsp::Random rng;

    public:
        CCSDSSimplePSKDecoderModule(std::string input_file, std::string output_file_hint, nlohmann::json parameters);
        ~CCSDSSimplePSKDecoderModule();
        void process();
        void drawUI(bool window);
        std::vector<ModuleDataType> getInputTypes();
        std::vector<ModuleDataType> getOutputTypes();

    public:
        static std::string getID();
        virtual std::string getIDM() { return getID(); };
        static std::vector<std::string> getParameters();
        static std::shared_ptr<ProcessingModule> getInstance(std::string input_file, std::string output_file_hint, nlohmann::json parameters);
    };
} // namespace npp