// SPDX-License-Identifier: GPL-2.0-only
/*
 * Digital Voice Modem - Modem Host Software
 * GPLv2 Open Source. Use is subject to license terms.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 *  Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
 *
 */
/**
 * @file ModemV24.h
 * @ingroup modem
 * @file ModemV24.cpp
 * @ingroup modem
 */
#if !defined(__MODEM_V24_H__)
#define __MODEM_V24_H__

#include "Defines.h"
#include "common/edac/RS634717.h"
#include "common/p25/data/DataHeader.h"
#include "common/p25/data/DataBlock.h"
#include "common/p25/lc/LC.h"
#include "common/p25/Audio.h"
#include "common/p25/NID.h"
#include "modem/Modem.h"

namespace modem
{
    // ---------------------------------------------------------------------------
    //  Constants
    // ---------------------------------------------------------------------------

    /**
     * @addtogroup modem
     * @{
     */

    /**
     * @brief DFSI serial tx flags used to determine proper jitter handling of data in ringbuffer.
     * @ingroup modem
     */
    enum SERIAL_TX_TYPE {
        STT_NO_DATA,                        //!< No Data
        STT_START_STOP,                     //!< Start/Stop Signalling Frame
        STT_START_STOP_NO_JITTER,           //!< Start/Stop Signalling Frame with Jitter Disabled
        STT_DATA,                           //!< Paced Data/Signalling Frame or IMBE Voice Frame
        STT_DATA_FAST                       //!< Fast Paced Data/Signalling Frame
    };

    /** @} */

    // ---------------------------------------------------------------------------
    //  Class Declaration
    // ---------------------------------------------------------------------------

    /**
     * @brief Represents DFSI call data.
     * @ingroup modem
     */
    class HOST_SW_API DFSICallData {
    public:
        auto operator=(DFSICallData&) -> DFSICallData& = delete;
        auto operator=(DFSICallData&&) -> DFSICallData& = delete;
        DFSICallData(DFSICallData&) = delete;

        /**
         * @brief Initializes a new instance of the DFSICallData class.
         */
        DFSICallData() :
            srcId(0U),
            dstId(0U),
            lco(0U),
            mfId(P25DEF::MFG_STANDARD),
            serviceOptions(0U),
            lsd1(0U),
            lsd2(0U),
            MI(nullptr),
            algoId(P25DEF::ALGO_UNENCRYPT),
            kId(0U),
            VHDR1(nullptr),
            VHDR2(nullptr),
            LDULC(nullptr),
            seqNo(0U),
            n(0U),
            netLDU1(nullptr),
            netLDU2(nullptr),
            pduUserData(nullptr),
            dataHeader(nullptr),
            dataCall(false),
            pduUserDataOffset(0U),
            pduTotalBlocks(0U),
            errors(0U)
        {
            MI = new uint8_t[P25DEF::MI_LENGTH_BYTES];
            VHDR1 = new uint8_t[P25DFSIDEF::DFSI_MOT_VHDR_1_LEN];
            VHDR2 = new uint8_t[P25DFSIDEF::DFSI_MOT_VHDR_2_LEN];
            LDULC = new uint8_t[P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES];

            netLDU1 = new uint8_t[9U * 25U];
            netLDU2 = new uint8_t[9U * 25U];

            ::memset(netLDU1, 0x00U, 9U * 25U);
            ::memset(netLDU2, 0x00U, 9U * 25U);

            pduUserData = new uint8_t[P25DEF::P25_MAX_PDU_BLOCKS * P25DEF::P25_PDU_CONFIRMED_LENGTH_BYTES + 2U];
            ::memset(pduUserData, 0x00U, P25DEF::P25_MAX_PDU_BLOCKS * P25DEF::P25_PDU_CONFIRMED_LENGTH_BYTES + 2U);

            resetCallData();
        }
        /**
         * @brief Finalizes a instance of the DFSICallData class.
         */
        ~DFSICallData()
        {
            if (MI != nullptr) {
                delete[] MI;
                MI = nullptr;
            }
            if (VHDR1 != nullptr) {
                delete[] VHDR1;
                VHDR1 = nullptr;
            }
            if (VHDR2 != nullptr) {
                delete[] VHDR2;
                VHDR2 = nullptr;
            }
            if (LDULC != nullptr) {
                delete[] LDULC;
                LDULC = nullptr;
            }
            if (netLDU1 != nullptr) {
                delete[] netLDU1;
                netLDU1 = nullptr;
            }
            if (netLDU2 != nullptr) {
                delete[] netLDU2;
                netLDU2 = nullptr;
            }
            if (pduUserData != nullptr) {
                delete[] pduUserData;
                pduUserData = nullptr;
            }
            if (dataHeader != nullptr) {
                delete dataHeader;
                dataHeader = nullptr;
            }
        }

        /**
         * @brief Helper to reset the call data associated with this connection.
         */
        void resetCallData()
        {
            srcId = 0U;
            dstId = 0U;

            lco = 0U;
            mfId = P25DEF::MFG_STANDARD;
            serviceOptions = 0U;

            lsd1 = 0U;
            lsd2 = 0U;

            if (MI != nullptr)
                ::memset(MI, 0x00U, P25DEF::MI_LENGTH_BYTES);
            algoId = P25DEF::ALGO_UNENCRYPT;
            kId = 0U;

            if (VHDR1 != nullptr)
                ::memset(VHDR1, 0x00U, P25DFSIDEF::DFSI_MOT_VHDR_1_LEN);
            if (VHDR2 != nullptr)
                ::memset(VHDR2, 0x00U, P25DFSIDEF::DFSI_MOT_VHDR_2_LEN);

            if (LDULC != nullptr)
                ::memset(LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES);

            if (netLDU1 != nullptr)
                ::memset(netLDU1, 0x00U, 9U * 25U);
            if (netLDU2 != nullptr)
                ::memset(netLDU2, 0x00U, 9U * 25U);

            n = 0U;
            seqNo = 0U;

            if (pduUserData != nullptr)
                ::memset(pduUserData, 0x00U, P25DEF::P25_MAX_PDU_BLOCKS * P25DEF::P25_PDU_CONFIRMED_LENGTH_BYTES + 2U);
            if (dataHeader != nullptr)
                dataHeader->reset();

            dataCall = false;
            pduUserDataOffset = 0U;
            pduTotalBlocks = 0U;

            errors = 0U;
        }

    public:
        /** @name Call Data */
        /** 
         * @brief Source Radio ID.
         */
        uint32_t srcId;
        /** 
         * @brief Destination ID.
         */
        uint32_t dstId;

        /**
         * @brief Link Control Opcode.
         */
        uint8_t lco;
        /**
         * @brief Manufacturer ID.
         */
        uint8_t mfId;
        /**
         * @brief Call Service Options.
         */
        uint8_t serviceOptions;

        /**
         * @brief Low Speed Data 1.
         */
        uint8_t lsd1;
        /**
         * @brief Low Speed Data 2.
         */
        uint8_t lsd2;

        /**
         * @brief Encryption Message Indicator.
         */
        uint8_t* MI;
        /**
         * @brief Encryption Algorithm ID.
         */
        uint8_t algoId;
        /**
         * @brief Encryption Key ID.
         */
        uint32_t kId;

        /**
         * @brief Voice Header 1.
         */
        uint8_t* VHDR1;
        /**
         * @brief Voice Header 2.
         */
        uint8_t* VHDR2;

        /**
         * @brief LDU LC.
         */
        uint8_t* LDULC;

        /**
         * @brief Sequence Number.
         */
        uint32_t seqNo;
        /**
         * @brief 
         */
        uint8_t n;

        /**
         * @brief LDU1 Buffer.
         */
        uint8_t* netLDU1;
        /**
         * @brief LDU2 Buffer.
         */
        uint8_t* netLDU2;

        /**
         * @brief User data associated with this call.
         */
        uint8_t* pduUserData;
        /**
         * @brief Data call header.
         */
        p25::data::DataHeader* dataHeader;

        /**
         * @brief Flag indicating the current call is a data call.
         */
        bool dataCall;
        /**
         * @brief Offset index when populating the user data buffer.
         */
        uint32_t pduUserDataOffset;
        /**
         * @brief Total count of PDU blocks.
         */
        uint32_t pduTotalBlocks;

        /**
         * @brief Total errors for a given call sequence.
         */
        uint32_t errors;
        /** @} */
    };

    // ---------------------------------------------------------------------------
    //  Class Declaration
    // ---------------------------------------------------------------------------

    /**
     * @brief Implements the core interface to the V.24 modem hardware.
     * \code{.unparsed}
     * This is the format of the Motorola V.24 DFSI Voice Headers:
     * 
     * Voice Header 1 (VHDR1):
     * Bit  7 6 5 4 3 2 1 0
     *     +-+-+-+-+-+-+-+-+
     *     |       FT      | 0
     *     +-+-+-+-+-+-+-+-+
     *     | Start of Strm | 1 - 9
     *     +-+-+-+-+-+-+-+-+
     *     |S0 |  G0       | 10
     *     +-+-+-+-+-+-+-+-+
     *     |S1 |  G1       | 11
     *     +-+-+-+-+-+-+-+-+
     *     |S2 |  G2       | 12
     *     +-+-+-+-+-+-+-+-+
     *     |S3 |  G3       | 13
     *     +-+-+-+-+-+-+-+-+
     *     |S4 |  G4       | 14
     *     +-+-+-+-+-+-+-+-+
     *     |S5 |  G5       | 15
     *     +-+-+-+-+-+-+-+-+
     *     |S6 |  G6       | 16
     *     +-+-+-+-+-+-+-+-+
     *     |S7 |  G7       | 17
     *     +-+-+-+-+-+-+-+-+
     *     |  S7...S0      | 18
     *     +-+-+-+-+-+-+-+-+
     *     |S8 |  G8       | 19
     *     +-+-+-+-+-+-+-+-+
     *     |S9 |  G9       | 20
     *     +-+-+-+-+-+-+-+-+
     *     |S10|  G10      | 21
     *     +-+-+-+-+-+-+-+-+
     *     |S11|  G11      | 22
     *     +-+-+-+-+-+-+-+-+
     *     |S12|  G12      | 23
     *     +-+-+-+-+-+-+-+-+
     *     |S13|  G13      | 24
     *     +-+-+-+-+-+-+-+-+
     *     |S14|  G14      | 25
     *     +-+-+-+-+-+-+-+-+
     *     |S15|  G15      | 26
     *     +-+-+-+-+-+-+-+-+
     *     |  S15...S8     | 27
     *     +-+-+-+-+-+-+-+-+
     *     |S16|  G16      | 26
     *     +-+-+-+-+-+-+-+-+
     *     |S17|  G17      | 28
     *     +-+-+-+-+-+-+-+-+
     *     |7|6| Rsvd  |Bsy| 29
     *     +-+-+-+-+-+-+-+-+
     *     |  Rsvd         | 30
     *     +-+-+-+-+-+-+-+-+
     *
     * Voice Header 2 (VHDR2):
     * Bit  7 6 5 4 3 2 1 0
     *     +-+-+-+-+-+-+-+-+
     *     |       FT      | 0
     *     +-+-+-+-+-+-+-+-+
     *     |S18|  G18      | 1
     *     +-+-+-+-+-+-+-+-+
     *     |S19|  G19      | 2
     *     +-+-+-+-+-+-+-+-+
     *     |S20|  G20      | 3
     *     +-+-+-+-+-+-+-+-+
     *     |S21|  G21      | 4
     *     +-+-+-+-+-+-+-+-+
     *     |S22|  G22      | 5
     *     +-+-+-+-+-+-+-+-+
     *     |S23|  G23      | 6
     *     +-+-+-+-+-+-+-+-+
     *     |S24|  G24      | 7
     *     +-+-+-+-+-+-+-+-+
     *     |S25|  G25      | 8
     *     +-+-+-+-+-+-+-+-+
     *     |  S25...S18    | 9
     *     +-+-+-+-+-+-+-+-+
     *     |S26|  G26      | 10
     *     +-+-+-+-+-+-+-+-+
     *     |S27|  G27      | 11
     *     +-+-+-+-+-+-+-+-+
     *     |S28|  G28      | 12
     *     +-+-+-+-+-+-+-+-+
     *     |S29|  G29      | 13
     *     +-+-+-+-+-+-+-+-+
     *     |S30|  G30      | 14
     *     +-+-+-+-+-+-+-+-+
     *     |S31|  G31      | 15
     *     +-+-+-+-+-+-+-+-+
     *     |S32|  G32      | 16
     *     +-+-+-+-+-+-+-+-+
     *     |S33|  G33      | 17
     *     +-+-+-+-+-+-+-+-+
     *     |  S33...S26    | 18
     *     +-+-+-+-+-+-+-+-+
     *     |S34|  G34      | 19
     *     +-+-+-+-+-+-+-+-+
     *     |S35|  G35      | 20
     *     +-+-+-+-+-+-+-+-+
     *     |5|4| Rsvd  |Bsy| 21
     *     +-+-+-+-+-+-+-+-+
     *
     * This is the format of the TIA-102.BAHA DFSI Voice Headers:
     * 
     * TIA-102.BAHA Voice Header 1 (VHDR1):
     * Bit  7 6 5 4 3 2 1 0
     *     +-+-+-+-+-+-+-+-+
     *     |       FT      | 0
     *     +-+-+-+-+-+-+-+-+
     *     |S0 |  G0       | 1
     *     +-+-+-+-+-+-+-+-+
     *     |S1 |  G1       | 2
     *     +-+-+-+-+-+-+-+-+
     *     |S2 |  G2       | 3
     *     +-+-+-+-+-+-+-+-+
     *     |S3 |  G3       | 4
     *     +-+-+-+-+-+-+-+-+
     *     |S4 |  G4       | 5
     *     +-+-+-+-+-+-+-+-+
     *     |S5 |  G5       | 6
     *     +-+-+-+-+-+-+-+-+
     *     |S6 |  G6       | 7
     *     +-+-+-+-+-+-+-+-+
     *     |S7 |  G7       | 8
     *     +-+-+-+-+-+-+-+-+
     *     |S8 |  G8       | 9
     *     +-+-+-+-+-+-+-+-+
     *     |S9 |  G9       | 10
     *     +-+-+-+-+-+-+-+-+
     *     |S10|  G10      | 11
     *     +-+-+-+-+-+-+-+-+
     *     |S11|  G11      | 12
     *     +-+-+-+-+-+-+-+-+
     *     |S12|  G12      | 13
     *     +-+-+-+-+-+-+-+-+
     *     |S13|  G13      | 14
     *     +-+-+-+-+-+-+-+-+
     *     |S14|  G14      | 15
     *     +-+-+-+-+-+-+-+-+
     *     |S15|  G15      | 16
     *     +-+-+-+-+-+-+-+-+
     *     |S16|  G16      | 17
     *     +-+-+-+-+-+-+-+-+
     *     |S17|  G17      | 18
     *     +-+-+-+-+-+-+-+-+
     *     | Rsvd      |7|6| 19
     *     +-+-+-+-+-+-+-+-+
     *     |  S15...S0     | 20
     *     +-+-+-+-+-+-+-+-+
     *     |  S15...S0     | 21
     *     +-+-+-+-+-+-+-+-+
     *
     * TIA-102.BAHA Voice Header 2 (VHDR2):
     * Bit  7 6 5 4 3 2 1 0
     *     +-+-+-+-+-+-+-+-+
     *     |       FT      | 0
     *     +-+-+-+-+-+-+-+-+
     *     |S18|  G18      | 1
     *     +-+-+-+-+-+-+-+-+
     *     |S19|  G19      | 2 
     *     +-+-+-+-+-+-+-+-+
     *     |S20|  G20      | 3
     *     +-+-+-+-+-+-+-+-+
     *     |S21|  G21      | 4
     *     +-+-+-+-+-+-+-+-+
     *     |S22|  G22      | 5
     *     +-+-+-+-+-+-+-+-+
     *     |S23|  G23      | 6
     *     +-+-+-+-+-+-+-+-+
     *     |S24|  G24      | 7
     *     +-+-+-+-+-+-+-+-+
     *     |S25|  G25      | 8
     *     +-+-+-+-+-+-+-+-+
     *     |S26|  G26      | 9
     *     +-+-+-+-+-+-+-+-+
     *     |S27|  G27      | 10
     *     +-+-+-+-+-+-+-+-+
     *     |S28|  G28      | 11
     *     +-+-+-+-+-+-+-+-+
     *     |S29|  G29      | 12
     *     +-+-+-+-+-+-+-+-+
     *     |S30|  G30      | 13
     *     +-+-+-+-+-+-+-+-+
     *     |S31|  G31      | 14
     *     +-+-+-+-+-+-+-+-+ 
     *     |S32|  G32      | 15
     *     +-+-+-+-+-+-+-+-+
     *     |S33|  G33      | 16
     *     +-+-+-+-+-+-+-+-+
     *     |S34|  G34      | 17
     *     +-+-+-+-+-+-+-+-+
     *     |S35|  G35      | 18
     *     +-+-+-+-+-+-+-+-+
     *     | Rsvd      |5|4| 19
     *     +-+-+-+-+-+-+-+-+
     *     |  S33...S18    | 20
     *     +-+-+-+-+-+-+-+-+
     *     |  S33...S18    | 21
     *     +-+-+-+-+-+-+-+-+
     * \endcode
     * @ingroup modem
     */
    class HOST_SW_API ModemV24 : public Modem {
    public:
        /**
         * @brief Initializes a new instance of the ModemV24 class.
         * @param port Port the air interface modem is connected to.
         * @param duplex Flag indicating the modem is operating in duplex mode.
         * @param p25QueueSize Modem P25 Rx frame buffer queue size (bytes).
         * @param p25TxQueueSize Modem P25 Tx frame buffer queue size (bytes).
         * @param rtrt Flag indicating whether or not RT/RT is enabled.
         * @param diu Flag indicating whether or not V.24 communications are to a DIU.
         * @param jitter 
         * @param dumpModemStatus Flag indicating whether the modem status is dumped to the log.
         * @param displayDebugMessages Flag indicating whether or not modem debug messages are displayed in the log.
         * @param trace Flag indicating whether air interface modem trace is enabled.
         * @param debug Flag indicating whether air interface modem debug is enabled.
         */
        ModemV24(port::IModemPort* port, bool duplex, uint32_t p25QueueSize, uint32_t p25TxQueueSize,
            bool rtrt, uint16_t jitter, bool dumpModemStatus, bool displayDebugMessages, bool trace, bool debug);
        /**
         * @brief Finalizes a instance of the ModemV24 class.
         */
        ~ModemV24();

        /**
         * @brief Sets the call timeout.
         * @param timeout Timeout.
         */
        void setCallTimeout(uint16_t timeout);
        /**
         * @brief Sets the P25 NAC.
         * @param nac NAC.
         */
        void setP25NAC(uint32_t nac) override;

        /**
         * @brief Helper to set the TIA-102 format DFSI frame flag.
         * @param set 
         */
        void setTIAFormat(bool set);

        /**
         * @brief Opens connection to the air interface modem.
         * @returns bool True, if connection to modem is made, otherwise false.
         */
        bool open() override;

        /**
         * @brief Updates the modem by the passed number of milliseconds.
         * @param ms Number of milliseconds.
         */
        void clock(uint32_t ms) override;

        /**
         * @brief Closes connection to the air interface modem.
         */
        void close() override;

        /**
         * @brief Helper to test if the P25 ring buffer has free space.
         * @returns bool True, if the P25 ring buffer has free space, otherwise false.
         */
        bool hasP25Space(uint32_t length) const override;

        /**
         * @brief Writes raw data to the air interface modem.
         * @param data Data to write to modem.
         * @param length Length of data to write.
         * @param imm Flag indicating whether the frame is immediate.
         * @returns int Actual length of data written.
         */
        int write(const uint8_t* data, uint32_t length, bool imm = false) override;

    private:
        bool m_rtrt;

        uint8_t m_superFrameCnt;

        p25::Audio m_audio;

        p25::NID* m_nid;

        RingBuffer<uint8_t> m_txP25Queue;
        RingBuffer<uint8_t> m_txImmP25Queue;

        DFSICallData* m_txCall;
        DFSICallData* m_rxCall;
        bool m_txCallInProgress;
        bool m_rxCallInProgress;
        uint64_t m_txLastFrameTime;
        uint64_t m_rxLastFrameTime;
        
        uint16_t m_callTimeout;

        uint16_t m_jitter;
        uint64_t m_lastP25Tx;

        edac::RS634717 m_rs;

        bool m_useTIAFormat;

        std::mutex m_txP25QueueLock;

        /**
         * @brief Helper to write data from the P25 Tx queue to the serial interface.
         * @param[in] queue Pointer to the ring buffer containing data to write.
         * @return int Actual number of bytes written to the serial interface.
         */
        int writeSerial(RingBuffer<uint8_t>* queue);

        /**
         * @brief Helper to store converted Rx frames.
         * @param buffer Buffer containing converted Rx frame.
         * @param length Length of buffer.
         */
        void storeConvertedRx(const uint8_t* buffer, uint32_t length);
        /**
         * @brief Internal helper to store converted PDU Rx frames.
         * @param dataHeader Instance of a PDU data header.
         * @param pduUserData Buffer containing user data to transmit.
         */
        void storeConvertedRxPDU(p25::data::DataHeader* dataHeader, uint8_t* pduUserData);
        /**
         * @brief Helper to generate a P25 TDU packet.
         * @param buffer Buffer to create TDU.
         */
        void create_TDU(uint8_t* buffer);

        /**
         * @brief Internal helper to convert from V.24/DFSI to TIA-102 air interface.
         * @param data Buffer containing data to convert.
         * @param length Length of buffer.
         */
        void convertToAirV24(const uint8_t *data, uint32_t length);
        /**
         * @brief Internal helper to convert from TIA-102 DFSI to TIA-102 air interface.
         * @param data Buffer containing data to convert.
         * @param length Length of buffer.
         */
        void convertToAirTIA(const uint8_t *data, uint32_t length);

        /**
         * @brief Helper to add a V.24 data frame to the P25 Tx queue with the proper timestamp and formatting.
         * @param data Buffer containing V.24 data frame to send.
         * @param len Length of buffer.
         * @param msgType Type of message to send (used for proper jitter clocking).
         * @param imm Flag indicating whether the frame is immediate.
         * @returns bool True, if data is queued, otherwise false.
         */
        bool queueP25Frame(uint8_t* data, uint16_t length, SERIAL_TX_TYPE msgType, bool imm = false);

        /**
         * @brief Send a start of stream sequence (HDU, etc) to the connected serial V24 device.
         * @param[in] control Instance of p25::lc::LC containing link control data.
         */
        void startOfStreamV24(const p25::lc::LC& control);
        /**
         * @brief Send an end of stream sequence (TDU, etc) to the connected serial V24 device.
         */
        void endOfStreamV24();

        /**
         * @brief Helper to generate the NID value.
         * @param duid P25 DUID.
         * @returns uint16_t P25 NID.
         */
        uint16_t generateNID(P25DEF::DUID::E duid = P25DEF::DUID::LDU1);

        /**
         * @brief Send a start of stream sequence (HDU, etc) to the connected UDP TIA-102 device.
         * @param[in] control Instance of p25::lc::LC containing link control data.
         */
        void startOfStreamTIA(const p25::lc::LC& control);
        /**
         * @brief Send an end of stream sequence (TDU, etc) to the connected UDP TIA-102 device.
         */
        void endOfStreamTIA();
        /**
         * @brief Send a start of stream ACK.
         */
        void ackStartOfStreamTIA();

        /**
         * @brief Internal helper to convert from TIA-102 air interface to V.24/DFSI.
         * @param data Buffer containing data to convert.
         * @param length Length of buffer.
         * @param imm Flag indicating whether the frame is immediate.
         */
        void convertFromAirV24(uint8_t* data, uint32_t length, bool imm);
        /**
         * @brief Internal helper to convert from TIA-102 air interface to TIA-102 DFSI.
         * @param data Buffer containing data to convert.
         * @param length Length of buffer.
         * @param imm Flag indicating whether the frame is immediate.
         */
        void convertFromAirTIA(uint8_t* data, uint32_t length, bool imm);
    };
} // namespace modem

#endif // __MODEM_V24_H__
