// SPDX-License-Identifier: GPL-2.0-only
/*
 * Digital Voice Modem - FNE System View
 * GPLv2 Open Source. Use is subject to license terms.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 *  Copyright (C) 2024 Bryan Biedenkapp, N2PLL
 *
 */
/**
 * @file TransmitWndBase.h
 * @ingroup fneSysView
 */
#if !defined(__TRANSMIT_WND_BASE_H__)
#define __TRANSMIT_WND_BASE_H__

#include "common/lookups/AffiliationLookup.h"
#include "common/dmr/DMRDefines.h"
#include "common/dmr/lc/CSBK.h"
#include "common/dmr/lc/csbk/CSBKFactory.h"
#include "common/dmr/SlotType.h"
#include "common/dmr/Sync.h"
#include "common/p25/P25Defines.h"
#include "common/p25/lc/LC.h"
#include "common/p25/lc/TSBK.h"
#include "common/p25/lc/tsbk/TSBKFactory.h"
#include "common/p25/Sync.h"
#include "host/modem/Modem.h"
#include "SysViewMain.h"

#include "FDblDialog.h"

#include <final/final.h>
using namespace finalcut;

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

/**
 * @brief This class implements the line edit control for RIDs.
 * @ingroup fneSysView
 */
class HOST_SW_API RIDLineEdit final : public FLineEdit {
public:
    /**
     * @brief Initializes a new instance of the RIDLineEdit class.
     * @param widget 
     */
    explicit RIDLineEdit(FWidget* widget = nullptr) : FLineEdit{widget}
    {
        setInputFilter("[[:digit:]]");
    }

    /*
    ** Event Handlers
    */

    /**
     * @brief Event that occurs on keyboard key press.
     * @param e Keyboard Event.
     */
    void onKeyPress(finalcut::FKeyEvent* e) override
    {
        const auto key = e->key();
        if (key == FKey::Up) {
            emitCallback("up-pressed");
            e->accept();
            return;
        } else if (key == FKey::Down) {
            emitCallback("down-pressed");
            e->accept();
            return;
        }

        FLineEdit::onKeyPress(e);
    }
};

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

/**
 * @brief This class implements the base class for transmit windows.
 * @ingroup fneSysView
 */
class HOST_SW_API TransmitWndBase : public FDblDialog {
public:
    /**
     * @brief Initializes a new instance of the TransmitWndBase class.
     * @param widget 
     */
    explicit TransmitWndBase(FWidget* widget = nullptr) : FDblDialog{widget}
    {
        /* stub */
    }

protected:
    uint8_t m_mode = modem::STATE_DMR;

    /**
     * @brief Initializes the window layout.
     */
    void initLayout() override
    {
        FDialog::setMinimizable(true);
        FDialog::setShadow();

        std::size_t maxWidth, maxHeight;
        const auto& rootWidget = getRootWidget();

        if (rootWidget) {
            maxWidth = rootWidget->getClientWidth();
            maxHeight = rootWidget->getClientHeight();
        }
        else {
            // fallback to xterm default size
            maxWidth = 80;
            maxHeight = 24;
        }

        const int x = 1 + int((maxWidth - getWidth()) / 2);
        const int y = 1 + int((maxHeight - getHeight()) / 3);
        FWindow::setPos(FPoint{x, y}, false);
        FDialog::adjustSize();

        FDialog::setModal();

        initControls();

        FDialog::initLayout();

        rootWidget->redraw(); // bryanb: wtf?
        redraw();
    }

    /**
     * @brief Initializes window controls.
     */
    virtual void initControls()
    {
        resizeControls();

        m_dmrSlotLabel.setGeometry(FPoint(2, 4), FSize(10, 1));
        m_dmrSlot.setGeometry(FPoint(18, 4), FSize(5, 1));
        m_dmrSlot.setRange(1, 2);
        m_dmrSlot.setValue(1);
        m_dmrSlot.setShadow(false);
    

        m_digModeGroup.setGeometry(FPoint(2, 1), FSize(56, 2));
        m_modeDMR.setPos(FPoint(1, 1));
        m_modeDMR.addCallback("toggled", [&]() {
            if (m_modeDMR.isChecked()) {
                m_mode = modem::STATE_DMR;
                m_dmrSlot.setEnable(true);
                redraw();
            }
        });

        m_modeP25.setPos(FPoint(13, 1));
        m_modeP25.addCallback("toggled", [&]() {
            if (m_modeP25.isChecked()) {
                m_mode = modem::STATE_P25;
                m_dmrSlot.setEnable(false);
                redraw();
            }
        });
/*
        m_modeNXDN.setPos(FPoint(22, 1));
        m_modeNXDN.addCallback("toggled", [&]() {
            if (m_modeNXDN.isChecked()) {
                m_mode = modem::STATE_NXDN;
                m_dmrSlot.setEnable(false);
                redraw();
            }
        });
*/

        focusFirstChild();
    }

    /**
     * @brief
     */
    void resizeControls()
    {
        // transmit button and close button logic
        m_txButton.setGeometry(FPoint(3, int(getHeight()) - 6), FSize(10, 3));
        m_txButton.addCallback("clicked", [&]() { setTransmit(); });

        m_closeButton.setGeometry(FPoint(17, int(getHeight()) - 6), FSize(9, 3));
        m_closeButton.addCallback("clicked", [&]() { hide(); });
    }

    /**
     * @brief Adjusts window size.
     */
    void adjustSize() override
    {
        FDialog::adjustSize();
    }

    /*
    ** Event Handlers
    */

    /**
     * @brief Event that occurs on keyboard key press.
     * @param e Keyboard Event.
     */
    void onKeyPress(finalcut::FKeyEvent* e) override
    {
        const auto key = e->key();
        if (key == FKey::Escape) {
            this->close();
        }
        else if (key == FKey::F12) {
            setTransmit();
        }
    }

    /**
     * @brief Event that occurs when the window is closed.
     * @param e Close Event
     */
    void onClose(FCloseEvent* e) override
    {
        hide();
    }

protected:
    /**
     * @brief Helper to write a DMR extended function packet.
     * @param slot DMR slot number.
     * @param func 
     * @param arg 
     * @param dstId 
     */
    void writeDMR_Ext_Func(uint8_t slot, uint32_t func, uint32_t arg, uint32_t dstId)
    {
        using namespace dmr;
        using namespace dmr::defines;
        using namespace dmr::lc::csbk;

        std::unique_ptr<CSBK_EXT_FNCT> csbk = std::make_unique<CSBK_EXT_FNCT>();
        csbk->setGI(false);
        csbk->setExtendedFunction(func);
        csbk->setSrcId(arg);
        csbk->setDstId(dstId);

        LogInfoEx(LOG_RF, "DMR Slot %u, CSBK, %s, op = $%02X, arg = %u, tgt = %u",
            slot, csbk->toString().c_str(), func, arg, dstId);

        write_CSBK(slot, csbk.get());
    }

    /**
     * @brief Helper to write a network CSBK.
     * @param slot DMR slot number.
     * @param csbk Instance of dmr::lc::CSBK.
     */
    void write_CSBK(uint8_t slot, dmr::lc::CSBK* csbk)
    {
        using namespace dmr;
        using namespace dmr::defines;

        uint8_t data[DMR_FRAME_LENGTH_BYTES + 2U];
        ::memset(data + 2U, 0x00U, DMR_FRAME_LENGTH_BYTES);

        SlotType slotType;
        slotType.setColorCode(0U);
        slotType.setDataType(DataType::CSBK);

        // Regenerate the CSBK data
        csbk->encode(data + 2U);

        // Regenerate the Slot Type
        slotType.encode(data + 2U);

        // Convert the Data Sync to be from the BS or MS as needed
        Sync::addDMRDataSync(data + 2U, true);

        data::NetData dmrData;
        dmrData.setSlotNo(slot);
        dmrData.setDataType(DataType::CSBK);
        dmrData.setSrcId(csbk->getSrcId());
        dmrData.setDstId(csbk->getDstId());
        dmrData.setFLCO(csbk->getGI() ? FLCO::GROUP : FLCO::PRIVATE);
        dmrData.setN(0U);
        dmrData.setSeqNo(0U);
        dmrData.setBER(0U);
        dmrData.setRSSI(0U);

        dmrData.setData(data + 2U);

        getNetwork()->writeDMR(dmrData);
    }

    /**
     * @brief Helper to write a P25 extended function packet.
     * @param func 
     * @param arg 
     * @param dstId 
     */
    void writeP25_Ext_Func(uint32_t func, uint32_t arg, uint32_t dstId)
    {
        using namespace p25;
        using namespace p25::defines;
        using namespace p25::lc::tsbk;

        std::unique_ptr<IOSP_EXT_FNCT> iosp = std::make_unique<IOSP_EXT_FNCT>();
        iosp->setExtendedFunction(func);
        iosp->setSrcId(arg);
        iosp->setDstId(dstId);

        // class $02 is Motorola -- set the MFID properly
        if ((func >> 8) == 0x02U) {
            iosp->setMFId(MFG_MOT);
        }

        LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, mfId = $%02X, op = $%02X, arg = %u, tgt = %u",
            iosp->toString().c_str(), iosp->getMFId(), iosp->getExtendedFunction(), iosp->getSrcId(), iosp->getDstId());

        write_TSDU(iosp.get());
    }

    /**
     * @brief Helper to write a network TSDU.
     * @param tsbk Instance of p25::lc::TSBK.
     */
    void write_TSDU(p25::lc::TSBK* tsbk)
    {
        using namespace p25;
        using namespace p25::defines;

        uint8_t data[P25_TSDU_FRAME_LENGTH_BYTES];
        ::memset(data, 0x00U, P25_TSDU_FRAME_LENGTH_BYTES);

        // generate Sync
        Sync::addP25Sync(data);

        // network bursts have no NID

        // generate TSBK block
        tsbk->setLastBlock(true); // always set last block -- this a Single Block TSDU
        tsbk->encode(data);

        // add status bits
        P25Utils::addStatusBits(data, P25_TSDU_FRAME_LENGTH_BYTES, false, true);
        P25Utils::setStatusBitsStartIdle(data);

        if (g_debug) {
            LogDebug(LOG_RF, P25_TSDU_STR ", lco = $%02X, mfId = $%02X, lastBlock = %u, AIV = %u, EX = %u, srcId = %u, dstId = %u, sysId = $%03X, netId = $%05X",
                tsbk->getLCO(), tsbk->getMFId(), tsbk->getLastBlock(), tsbk->getAIV(), tsbk->getEX(), tsbk->getSrcId(), tsbk->getDstId(),
                tsbk->getSysId(), tsbk->getNetId());

            Utils::dump(1U, "!!! *TSDU (SBF) TSBK Block Data", data + P25_PREAMBLE_LENGTH_BYTES, P25_TSBK_FEC_LENGTH_BYTES);
        }

        lc::LC lc = lc::LC();
        lc.setLCO(tsbk->getLCO());
        lc.setMFId(tsbk->getMFId());
        lc.setSrcId(tsbk->getSrcId());
        lc.setDstId(tsbk->getDstId());

        getNetwork()->writeP25TSDU(lc, data);
    }

    /**
     * @brief Helper to transmit.
     */
    virtual void setTransmit()
    {
        /* stub */
    }

    FButton m_txButton{"Transmit", this};
    FButton m_closeButton{"Close", this};

    FButtonGroup m_digModeGroup{"Digital Mode", this};
    FRadioButton m_modeDMR{"DMR", &m_digModeGroup};
    FRadioButton m_modeP25{"P25", &m_digModeGroup};
//    FRadioButton m_modeNXDN{"NXDN", &m_digModeGroup};

    FLabel m_dmrSlotLabel{"DMR Slot: ", this};
    FSpinBox m_dmrSlot{this};
};

#endif // __TRANSMIT_WND_BASE_H__