/* Copyright 2023 Dual Tachyon
 * https://github.com/DualTachyon
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *     Unless required by applicable law or agreed to in writing, software
 *     distributed under the License is distributed on an "AS IS" BASIS,
 *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *     See the License for the specific language governing permissions and
 *     limitations under the License.
 */

#include <string.h>
#include <stdlib.h>  // abs()

#include "app/chFrScanner.h"
#include "app/dtmf.h"

#ifdef ENABLE_AM_FIX

#include "am_fix.h"

#endif

#include "bitmaps.h"
#include "board.h"
#include "driver/bk4819.h"
#include "driver/st7565.h"
#include "external/printf/printf.h"
#include "functions.h"
#include "helper/battery.h"
#include "misc.h"
#include "radio.h"
#include "settings.h"
#include "ui/helper.h"
#include "ui/inputbox.h"
#include "ui/main.h"
#include "ui/ui.h"
#include "audio.h"

#ifdef ENABLE_FEAT_F4HWN

#include "driver/system.h"
#include "app/scanner.h"

#endif

center_line_t center_line = CENTER_LINE_NONE;

#ifdef ENABLE_FEAT_F4HWN
static int8_t RxBlink;
static int8_t RxBlinkLed = 0;
static int8_t RxBlinkLedCounter;
static int8_t RxLine;
static uint32_t RxOnVfofrequency;

bool isMainOnlyInputDTMF = false;

bool isMainOnly() {
    return (gEeprom.DUAL_WATCH == DUAL_WATCH_OFF) && (gEeprom.CROSS_BAND_RX_TX == CROSS_BAND_OFF);
}

#endif

const int8_t dBmCorrTable[7] = {
        -15, // band 1
        -25, // band 2
        -20, // band 3
        -4, // band 4
        -7, // band 5
        -6, // band 6
        -1  // band 7
};

const char *VfoStateStr[] = {
        [VFO_STATE_NORMAL]="",
        [VFO_STATE_BUSY]="BUSY",
        [VFO_STATE_BAT_LOW]="BAT LOW",
        [VFO_STATE_TX_DISABLE]="TX DISABLE",
        [VFO_STATE_TIMEOUT]="TIMEOUT",
        [VFO_STATE_ALARM]="ALARM",
        [VFO_STATE_VOLTAGE_HIGH]="VOLT HIGH"
};

// ***************************************************************************

static void DrawSmallAntennaAndBars(uint8_t *p, unsigned int level) {
    level++;
    if (level > 6)
        level = 6;

    memcpy(p, BITMAP_Antenna, ARRAY_SIZE(BITMAP_Antenna));

    for (uint8_t i = 1; i <= level; i++) {
        char bar = (0xff << (6 - i)) & 0x7F;
        memset(p + 2 + i * 3, bar, 2);
    }
}

#if defined ENABLE_AUDIO_BAR || defined ENABLE_RSSI_BAR

static void DrawLevelBar(uint8_t xpos, uint8_t line, uint8_t level, uint8_t bars) {
#ifndef ENABLE_FEAT_F4HWN
    const char hollowBar[] = {
        0b01111111,
        0b01000001,
        0b01000001,
        0b01111111
    };
#endif

    uint8_t *p_line = gFrameBuffer[line];
    level = MIN(level, bars);

    for (uint8_t i = 0; i < level; i++) {
        const char hollowBar[] = {
                0b01111111,
                0b01000001,
                0b01000001,
                0b01111111
        };

        if (i < bars - 4) {
            for (uint8_t j = 0; j < 4; j++)
                p_line[xpos + i * 5 + j] = (~(0x7F >> (i + 1))) & 0x7F;
        } else {
            memcpy(p_line + (xpos + i * 5), &hollowBar, ARRAY_SIZE(hollowBar));
        }
    }
}

#endif

#ifdef ENABLE_AUDIO_BAR

// Approximation of a logarithmic scale using integer arithmetic
uint8_t log2_approx(unsigned int value) {
    uint8_t log = 0;
    while (value >>= 1) {
        log++;
    }
    return log;
}

void UI_DisplayAudioBar(void) {
    if (gSetting_mic_bar) {
        if (gLowBattery && !gLowBatteryConfirmed)
            return;

#ifdef ENABLE_FEAT_F4HWN
        RxBlinkLed = 0;
        RxBlinkLedCounter = 0;
        BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
        unsigned int line;
        if (isMainOnly()) {
            line = 5;
        } else {
            line = 3;
        }
#else
        const unsigned int line = 3;
#endif

        if (gCurrentFunction != FUNCTION_TRANSMIT ||
            gScreenToDisplay != DISPLAY_MAIN

                ) {
            return;  // screen is in use
        }

#if defined(ENABLE_ALARM) || defined(ENABLE_TX1750)
        if (gAlarmState != ALARM_STATE_OFF)
            return;
#endif
        static uint8_t barsOld = 0;
        const uint8_t thresold = 18; // arbitrary thresold
        //const uint8_t barsList[] = {0, 0, 0, 1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 25};
        const uint8_t barsList[] = {0, 0, 0, 1, 2, 3, 5, 7, 9, 12, 15, 18, 21, 25, 25, 25};
        uint8_t logLevel;
        uint8_t bars;

        unsigned int voiceLevel = BK4819_GetVoiceAmplitudeOut();  // 15:0

        voiceLevel = (voiceLevel >= thresold) ? (voiceLevel - thresold) : 0;
        logLevel = log2_approx(MIN(voiceLevel * 16, 32768u) + 1);
        bars = barsList[logLevel];
        barsOld = (barsOld - bars > 1) ? (barsOld - 1) : bars;

        uint8_t *p_line = gFrameBuffer[line];
        memset(p_line, 0, LCD_WIDTH);

        DrawLevelBar(2, line, barsOld, 25);

        if (gCurrentFunction == FUNCTION_TRANSMIT)
            ST7565_BlitFullScreen();
    }
}

#endif

void DisplayRSSIBar(const bool now) {
#if defined(ENABLE_RSSI_BAR)

    const unsigned int txt_width = 7 * 10;                 // 8 text chars
    const unsigned int bar_x = 2 + txt_width + 4;     // X coord of bar graph

#ifdef ENABLE_FEAT_F4HWN
    /*
    const char empty[] = {
        0b00000000,
        0b00000000,
        0b00000000,
        0b00000000,
        0b00000000,
        0b00000000,
        0b00000000,
    };
    */

    unsigned int line;
    if (isMainOnly()) {
        line = 5;
    } else {
        line = 3;
    }

    //char rx[4];
    //sprintf(String, "%d", RxBlink);
    //UI_PrintStringSmallBold(String, 80, 0, RxLine);

    if (RxLine >= 0 && center_line != CENTER_LINE_IN_USE) {
        if (RxBlink == 0 || RxBlink == 1) {
            UI_PrintStringSmallBold("RX", 24, 0, RxLine);
            if (RxBlink == 1) RxBlink = 2;
        } else {
            for (uint8_t i = 8; i < 24; i++) {
                gFrameBuffer[RxLine][i] = 0x00;
            }
            RxBlink = 1;
        }
        ST7565_BlitLine(RxLine);
    }
#else
    const unsigned int line = 3;
#endif
    uint8_t *p_line = gFrameBuffer[line];
    char str[16];

#ifndef ENABLE_FEAT_F4HWN
    const char plus[] = {
        0b00011000,
        0b00011000,
        0b01111110,
        0b01111110,
        0b01111110,
        0b00011000,
        0b00011000,
    };
#endif

    if ((gEeprom.KEY_LOCK && gKeypadLocked > 0) || center_line != CENTER_LINE_RSSI)
        return;     // display is in use

    if (gCurrentFunction == FUNCTION_TRANSMIT ||
        gScreenToDisplay != DISPLAY_MAIN
            )
        return;     // display is in use

    if (now)
        memset(p_line, 0, LCD_WIDTH);

#ifdef ENABLE_FEAT_F4HWN
    int16_t rssi_dBm =
            BK4819_GetRSSI_dBm()
            #ifdef ENABLE_AM_FIX
            + ((gSetting_AM_fix && gRxVfo->Modulation == MODULATION_AM) ? AM_fix_get_gain_diff() : 0)
            #endif
            + dBmCorrTable[gRxVfo->Band];

    rssi_dBm = -rssi_dBm;

    if (rssi_dBm > 141) rssi_dBm = 141;
    if (rssi_dBm < 53) rssi_dBm = 53;

    uint8_t s_level = 0;
    uint8_t overS9dBm = 0;
    uint8_t overS9Bars = 0;

    if (rssi_dBm >= 93) {
        s_level = map(rssi_dBm, 141, 93, 1, 9);
    } else {
        s_level = 9;
        overS9dBm = map(rssi_dBm, 93, 53, 0, 40);
        overS9Bars = map(overS9dBm, 0, 40, 0, 4);
    }
#else
    const int16_t s0_dBm   = -gEeprom.S0_LEVEL;                  // S0 .. base level
    const int16_t rssi_dBm =
        BK4819_GetRSSI_dBm()
#ifdef ENABLE_AM_FIX
        + ((gSetting_AM_fix && gRxVfo->Modulation == MODULATION_AM) ? AM_fix_get_gain_diff() : 0)
#endif
        + dBmCorrTable[gRxVfo->Band];

    int s0_9 = gEeprom.S0_LEVEL - gEeprom.S9_LEVEL;
    const uint8_t s_level = MIN(MAX((int32_t)(rssi_dBm - s0_dBm)*100 / (s0_9*100/9), 0), 9); // S0 - S9
    uint8_t overS9dBm = MIN(MAX(rssi_dBm + gEeprom.S9_LEVEL, 0), 99);
    uint8_t overS9Bars = MIN(overS9dBm/10, 4);
#endif

#ifdef ENABLE_FEAT_F4HWN
    char tempStr[4];

    if (overS9Bars == 0) {
        sprintf(tempStr, "S%d", s_level);
    } else {
        sprintf(tempStr, "+%02d", overS9dBm);
    }

    sprintf(str, "%4ddBm%3s", -rssi_dBm, tempStr);
    if (isMainOnly())
        UI_PrintStringSmallNormal(str, 2, 0, 4);
    else
        UI_PrintStringSmallNormal(str, 2, 0, 3);
#else
    if(overS9Bars == 0) {
        sprintf(str, "% 4d S%d", -rssi_dBm, s_level);
    }
    else {
        sprintf(str, "% 4d  %2ld", -rssi_dBm, overS9dBm);
        memcpy(p_line + 2 + 7*5, &plus, ARRAY_SIZE(plus));
    }

    UI_PrintStringSmallNormal(str, 2, 0, line);
#endif
    DrawLevelBar(bar_x, line, s_level + overS9Bars, 10);
    if (now)
        ST7565_BlitLine(line);
#else
    int16_t rssi = BK4819_GetRSSI();
    uint8_t Level;

    if (rssi >= gEEPROM_RSSI_CALIB[gRxVfo->Band][3]) {
        Level = 6;
    } else if (rssi >= gEEPROM_RSSI_CALIB[gRxVfo->Band][2]) {
        Level = 4;
    } else if (rssi >= gEEPROM_RSSI_CALIB[gRxVfo->Band][1]) {
        Level = 2;
    } else if (rssi >= gEEPROM_RSSI_CALIB[gRxVfo->Band][0]) {
        Level = 1;
    } else {
        Level = 0;
    }

    uint8_t *pLine = (gEeprom.RX_VFO == 0)? gFrameBuffer[2] : gFrameBuffer[6];
    if (now)
        memset(pLine, 0, 23);
    DrawSmallAntennaAndBars(pLine, Level);
    if (now)
        ST7565_BlitFullScreen();
#endif

}

#ifdef ENABLE_AGC_SHOW_DATA
void UI_MAIN_PrintAGC(bool now)
{
    char buf[20];
    memset(gFrameBuffer[3], 0, 128);
    union {
        struct {
            uint16_t _ : 5;
            uint16_t agcSigStrength : 7;
            int16_t gainIdx : 3;
            uint16_t agcEnab : 1;
        };
        uint16_t __raw;
    } reg7e;
    reg7e.__raw = BK4819_ReadRegister(0x7E);
    uint8_t gainAddr = reg7e.gainIdx < 0 ? 0x14 : 0x10 + reg7e.gainIdx;
    union {
        struct {
            uint16_t pga:3;
            uint16_t mixer:2;
            uint16_t lna:3;
            uint16_t lnaS:2;
        };
        uint16_t __raw;
    } agcGainReg;
    agcGainReg.__raw = BK4819_ReadRegister(gainAddr);
    int8_t lnaShortTab[] = {-28, -24, -19, 0};
    int8_t lnaTab[] = {-24, -19, -14, -9, -6, -4, -2, 0};
    int8_t mixerTab[] = {-8, -6, -3, 0};
    int8_t pgaTab[] = {-33, -27, -21, -15, -9, -6, -3, 0};
    int16_t agcGain = lnaShortTab[agcGainReg.lnaS] + lnaTab[agcGainReg.lna] + mixerTab[agcGainReg.mixer] + pgaTab[agcGainReg.pga];

    sprintf(buf, "%d%2ld %2ld %2ld %3ld", reg7e.agcEnab, reg7e.gainIdx, -agcGain, reg7e.agcSigStrength, BK4819_GetRSSI());
    UI_PrintStringSmallNormal(buf, 2, 0, 3);
    if(now)
        ST7565_BlitLine(3);
}
#endif

void UI_MAIN_TimeSlice500ms(void) {
    if (gScreenToDisplay == DISPLAY_MAIN) {
#ifdef ENABLE_AGC_SHOW_DATA
        UI_MAIN_PrintAGC(true);
        return;
#endif

        if (FUNCTION_IsRx()) {
            DisplayRSSIBar(true);
        }
    }
}

// ***************************************************************************

void UI_DisplayMain(void) {
    char String[22];

    center_line = CENTER_LINE_NONE;

    // clear the screen
    UI_DisplayClear();


    if (gLowBattery && !gLowBatteryConfirmed) {
        UI_DisplayPopup("LOW BAT");
        ST7565_BlitFullScreen();
        return;
    }

#ifndef ENABLE_FEAT_F4HWN
    if (gEeprom.KEY_LOCK && gKeypadLocked > 0)
    {   // tell user how to unlock the keyboard
        UI_PrintString("Long press #", 0, LCD_WIDTH, 1 /*, 8 */);
        UI_PrintString("to unlock",    0, LCD_WIDTH, 3 /*, 8 */);
        ST7565_BlitFullScreen();
        return;
    }
#else
    if (gEeprom.KEY_LOCK && gKeypadLocked > 0) {   // tell user how to unlock the keyboard
        uint8_t shift = 3;

        /*
        BK4819_ToggleGpioOut(BK4819_GPIO5_PIN1_RED, true);
        SYSTEM_DelayMs(50);
        BK4819_ToggleGpioOut(BK4819_GPIO5_PIN1_RED, false);
        SYSTEM_DelayMs(50);
        */

        if (isMainOnly()) {
            shift = 5;
        }
        //memcpy(gFrameBuffer[shift] + 2, gFontKeyLock, sizeof(gFontKeyLock));
        UI_PrintStringSmallBold("KEYLOCK", 12, 0, shift);
        //memcpy(gFrameBuffer[shift] + 120, gFontKeyLock, sizeof(gFontKeyLock));

        /*
        for (uint8_t i = 12; i < 116; i++)
        {
            gFrameBuffer[shift][i] ^= 0xFF;
        }
        */
    }
#endif

    unsigned int activeTxVFO = gRxVfoIsActive ? gEeprom.RX_VFO : gEeprom.TX_VFO;

    for (unsigned int vfo_num = 0; vfo_num < 2; vfo_num++) {
#ifdef ENABLE_FEAT_F4HWN
        const unsigned int line0 = 0;  // text screen line
        const unsigned int line1 = 4;
        unsigned int line;
        if (isMainOnly()) {
            line = 0;
        } else {
            line = (vfo_num == 0) ? line0 : line1;
        }
        const bool isMainVFO = (vfo_num == gEeprom.TX_VFO);
        uint8_t *p_line0 = gFrameBuffer[line + 0];
        uint8_t *p_line1 = gFrameBuffer[line + 1];
        enum Vfo_txtr_mode mode = VFO_MODE_NONE;
#else
        const unsigned int line0 = 0;  // text screen line
        const unsigned int line1 = 4;
        const unsigned int line       = (vfo_num == 0) ? line0 : line1;
        const bool         isMainVFO  = (vfo_num == gEeprom.TX_VFO);
        uint8_t           *p_line0    = gFrameBuffer[line + 0];
        uint8_t           *p_line1    = gFrameBuffer[line + 1];
        enum Vfo_txtr_mode mode       = VFO_MODE_NONE;
#endif

#ifdef ENABLE_FEAT_F4HWN
        if (isMainOnly()) {
            if (activeTxVFO != vfo_num) {
                continue;
            }
        }
#endif

#ifdef ENABLE_FEAT_F4HWN
        if (activeTxVFO != vfo_num || isMainOnly())
#else
            if (activeTxVFO != vfo_num) // this is not active TX VFO
#endif
        {
#ifdef ENABLE_SCAN_RANGES
            if (gScanRangeStart) {

#ifdef ENABLE_FEAT_F4HWN
                //if(IS_FREQ_CHANNEL(gEeprom.ScreenChannel[0]) && IS_FREQ_CHANNEL(gEeprom.ScreenChannel[1])) {
                if (IS_FREQ_CHANNEL(gEeprom.ScreenChannel[activeTxVFO])) {

                    uint8_t shift = 0;

                    if (isMainOnly()) {
                        shift = 3;
                    }

                    UI_PrintString("ScnRng", 5, 0, line + shift /*, 8 */);
                    sprintf(String, "%3lu.%05lu", gScanRangeStart / 100000, gScanRangeStart % 100000);
                    UI_PrintStringSmallNormal(String, 56, 0, line + shift);
                    sprintf(String, "%3lu.%05lu", gScanRangeStop / 100000, gScanRangeStop % 100000);
                    UI_PrintStringSmallNormal(String, 56, 0, line + shift + 1);

                    if (!isMainOnly())
                        continue;
                } else {
                    gScanRangeStart = 0;
                }
#else
                UI_PrintString("ScnRng", 5, 0, line /*, 8 */);
                sprintf(String, "%3lu.%05lu", gScanRangeStart / 100000, gScanRangeStart % 100000);
                UI_PrintStringSmallNormal(String, 56, 0, line);
                sprintf(String, "%3lu.%05lu", gScanRangeStop / 100000, gScanRangeStop % 100000);
                UI_PrintStringSmallNormal(String, 56, 0, line + 1);
                continue;
#endif
            }
#endif

            if (gDTMF_InputMode
                    ) {
                char *pPrintStr = "";
                // show DTMF stuff
                {
                    sprintf(String, ">%s", gDTMF_InputBox);
                    pPrintStr = String;
                }

#ifdef ENABLE_FEAT_F4HWN
                if (isMainOnly()) {
                    UI_PrintString(pPrintStr, 2, 0, 5 /*, 8 */);
                    isMainOnlyInputDTMF = true;
                    center_line = CENTER_LINE_IN_USE;
                } else {
                    UI_PrintString(pPrintStr, 2, 0, 0 + (vfo_num * 3) /*, 8 */);
                    isMainOnlyInputDTMF = false;
                    center_line = CENTER_LINE_IN_USE;
                    continue;
                }
#else
                UI_PrintString(pPrintStr, 2, 0, 0 + (vfo_num * 3) /*, 8 */);
                center_line = CENTER_LINE_IN_USE;
                continue;
#endif
            }

            // highlight the selected/used VFO with a marker
            if (isMainVFO)
                memcpy(p_line0 + 0, BITMAP_VFO_Default, sizeof(BITMAP_VFO_Default));
        } else // active TX VFO
        {   // highlight the selected/used VFO with a marker
            if (isMainVFO)
                memcpy(p_line0 + 0, BITMAP_VFO_Default, sizeof(BITMAP_VFO_Default));
            else
                memcpy(p_line0 + 0, BITMAP_VFO_NotDefault, sizeof(BITMAP_VFO_NotDefault));
        }

        uint32_t frequency = gEeprom.VfoInfo[vfo_num].pRX->Frequency;

        if (TX_freq_check(frequency) != 0) {
            if (isMainOnly())
                memcpy(p_line0 + 5, gFontKeyLock, sizeof(gFontKeyLock));
            else
                memcpy(p_line0 + 10, gFontKeyLock, sizeof(gFontKeyLock));
        }

        if (gCurrentFunction == FUNCTION_TRANSMIT) {   // transmitting

#ifdef ENABLE_ALARM
            if (gAlarmState == ALARM_STATE_SITE_ALARM)
                mode = VFO_MODE_RX;
            else
#endif
            {
                if (activeTxVFO == vfo_num) {   // show the TX symbol
                    mode = VFO_MODE_TX;
                    UI_PrintStringSmallBold("TX", 8, 0, line);
                }
            }
        } else {   // receiving .. show the RX symbol
            mode = VFO_MODE_RX;
            //if (FUNCTION_IsRx() && gEeprom.RX_VFO == vfo_num) {
            if (FUNCTION_IsRx() && gEeprom.RX_VFO == vfo_num && VfoState[vfo_num] == VFO_STATE_NORMAL) {
#ifdef ENABLE_FEAT_F4HWN
                RxBlinkLed = 1;
                RxBlinkLedCounter = 0;
                RxLine = line;
                RxOnVfofrequency = frequency;
                if (!isMainVFO) {
                    RxBlink = 1;
                } else {
                    RxBlink = 0;
                }
#else
                UI_PrintStringSmallBold("RX", 8, 0, line);
#endif
            }
#ifdef ENABLE_FEAT_F4HWN
            else {
                if (RxOnVfofrequency == frequency && !isMainOnly()) {
                    UI_PrintStringSmallNormal(">>", 24, 0, line);
                    //memcpy(p_line0 + 14, BITMAP_VFO_Default, sizeof(BITMAP_VFO_Default));
                }

                if (RxBlinkLed == 1)
                    RxBlinkLed = 2;
            }
#endif
        }

        if (IS_MR_CHANNEL(gEeprom.ScreenChannel[vfo_num])) {   // channel mode
            const unsigned int x = 20;
            const bool inputting = gInputBoxIndex != 0 && gEeprom.TX_VFO == vfo_num;
            if (!inputting)
                sprintf(String, "M%u", gEeprom.ScreenChannel[vfo_num] + 1);
            else
                sprintf(String, "M%.3s", INPUTBOX_GetAscii());  // show the input text
            UI_PrintStringSmallNormal(String, x, 0, line + 1);
        } else if (IS_FREQ_CHANNEL(gEeprom.ScreenChannel[vfo_num])) {   // frequency mode
            // show the frequency band number
            const unsigned int x = 0;
            char *buf = gEeprom.VfoInfo[vfo_num].pRX->Frequency < _1GHz_in_KHz ? "" : "+";
            sprintf(String, "F%u%s", 1 + gEeprom.ScreenChannel[vfo_num] - FREQ_CHANNEL_FIRST, buf);
            UI_PrintStringSmallNormal(String, x, 0, line + 1);
        }
#ifdef ENABLE_NOAA
        else
        {
            if (gInputBoxIndex == 0 || gEeprom.TX_VFO != vfo_num)
            {   // channel number
                sprintf(String, "N%u", 1 + gEeprom.ScreenChannel[vfo_num] - NOAA_CHANNEL_FIRST);
            }
            else
            {   // user entering channel number
                sprintf(String, "N%u%u", '0' + gInputBox[0], '0' + gInputBox[1]);
            }
            UI_PrintStringSmallNormal(String, 7, 0, line + 1);
        }
#endif

        // ************

        enum VfoState_t state = VfoState[vfo_num];

#ifdef ENABLE_ALARM
        if (gCurrentFunction == FUNCTION_TRANSMIT && gAlarmState == ALARM_STATE_SITE_ALARM) {
            if (activeTxVFO == vfo_num)
                state = VFO_STATE_ALARM;
        }
#endif
        if (state != VFO_STATE_NORMAL) {
            if (state < ARRAY_SIZE(VfoStateStr))
                UI_PrintString(VfoStateStr[state], 31, 0, line /*, 8 */);
        } else if (gInputBoxIndex > 0 && IS_FREQ_CHANNEL(gEeprom.ScreenChannel[vfo_num]) &&
                   gEeprom.TX_VFO == vfo_num) {   // user entering a frequency
            const char *ascii = INPUTBOX_GetAscii();
            bool isGigaF = frequency >= _1GHz_in_KHz;
            sprintf(String, "%.*s.%.3s", 3 + isGigaF, ascii, ascii + 3 + isGigaF);
            {
                // show the frequency in the main font
                UI_PrintString(String, 45, 0, line /*, 8 */);
            }

            continue;
        } else {
            if (gCurrentFunction == FUNCTION_TRANSMIT) {   // transmitting
                if (activeTxVFO == vfo_num)
                    frequency = gEeprom.VfoInfo[vfo_num].pTX->Frequency;
            }

            if (IS_MR_CHANNEL(gEeprom.ScreenChannel[vfo_num])) {   // it's a channel

#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
                if(gEeprom.MENU_LOCK == false) {
#endif
                uint8_t countList = 0;
                uint8_t shiftList = 0;

                if (gMR_ChannelExclude[gEeprom.ScreenChannel[vfo_num]] == false) {
                    // show the scan list assigment symbols
                    const ChannelAttributes_t att = gMR_ChannelAttributes[gEeprom.ScreenChannel[vfo_num]];

                    countList = att.scanlist1 + att.scanlist2 + att.scanlist3;

                    if (countList == 0) {
                        memcpy(p_line0 + 127 - (1 * 6), BITMAP_ScanList0, sizeof(BITMAP_ScanList0));
                    } else {
                        shiftList = countList;

                        if (att.scanlist1) {
                            memcpy(p_line0 + 127 - (shiftList * 6), BITMAP_ScanList1, sizeof(BITMAP_ScanList1));
                            shiftList--;
                        }
                        if (att.scanlist2) {
                            memcpy(p_line0 + 127 - (shiftList * 6), BITMAP_ScanList2, sizeof(BITMAP_ScanList2));
                            shiftList--;
                        }
                        if (att.scanlist3) {
                            memcpy(p_line0 + 127 - (shiftList * 6), BITMAP_ScanList3, sizeof(BITMAP_ScanList3));
                        }
                    }
                } else {
                    memcpy(p_line0 + 127 - (1 * 6), BITMAP_ScanListE, sizeof(BITMAP_ScanListE));
                }

#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
                {
                    }
                }
#endif

                // compander symbol
#ifndef ENABLE_BIG_FREQ
                //const ChannelAttributes_t att = gMR_ChannelAttributes[gEeprom.ScreenChannel[vfo_num]];
#else
                // TODO:  // find somewhere else to put the symbol
#endif

                switch (gEeprom.CHANNEL_DISPLAY_MODE) {
                    case MDF_FREQUENCY: // show the channel frequency
                        sprintf(String, "%3lu.%05lu", frequency / 100000, frequency % 100000);
#ifdef ENABLE_BIG_FREQ
                        if (frequency < _1GHz_in_KHz) {
                            // show the remaining 2 small frequency digits
                            UI_PrintStringSmallNormal(String + 7, 113, 0, line + 1);
                            String[7] = 0;
                            // show the main large frequency digits
                            UI_DisplayFrequency(String, 20, line, false);
                        } else
#endif
                        {
                            // show the frequency in the main font
                            UI_PrintStringSmallBold(String, 40, 0, line /*, 8 */);
                        }

                        break;

                    case MDF_CHANNEL:   // show the channel number
                        sprintf(String, "CH-%03u", gEeprom.ScreenChannel[vfo_num] + 1);
                        UI_PrintStringSmallBold(String, 40, 0, line /*, 8 */);
                        break;

                    case MDF_NAME:      // show the channel name
                    case MDF_NAME_FREQ: // show the channel name and frequency

                        SETTINGS_FetchChannelName(String, gEeprom.ScreenChannel[vfo_num]);
                        if (String[0] == 0) {   // no channel name, show the channel number instead
                            sprintf(String, "CH-%03u", gEeprom.ScreenChannel[vfo_num] + 1);
                        }

                        if (gEeprom.CHANNEL_DISPLAY_MODE == MDF_NAME) {
                            UI_PrintStringSmallBold(String, 40, 0, line /*, 8 */);
                        } else {
#ifdef ENABLE_FEAT_F4HWN
                            if (isMainOnly()) {
                                UI_PrintStringSmallBold(String, 40, 0, line /*, 8 */);
                            } else {
                                if (activeTxVFO == vfo_num) {
                                    UI_PrintStringSmallBold(String, 40 + 4, 0, line);
                                } else {
                                    UI_PrintStringSmallNormal(String, 40 + 4, 0, line);
                                }
                            }
#else
                            UI_PrintStringSmallBold(String, 20 + 4, 0, line);
#endif

#ifdef ENABLE_FEAT_F4HWN
                            if (isMainOnly()) {
                                sprintf(String, "%3lu.%05lu", frequency / 100000, frequency % 100000);
                                if (frequency < _1GHz_in_KHz) {
                                    // show the remaining 2 small frequency digits
                                    UI_PrintStringSmallNormal(String + 7, 113, 0, line + 4);
                                    String[7] = 0;
                                    // show the main large frequency digits
                                    //UI_DisplayFrequency(String, 20, line + 3, false);
                                    UI_PrintStringSmallBold(String, 20, 20, line + 3);
                                } else {
                                    // show the frequency in the main font
                                    UI_PrintString(String, 40, 0, line + 3 /*, 8 */);
                                }
                            } else {
                                sprintf(String, "%03lu.%05lu", frequency / 100000, frequency % 100000);
                                UI_PrintStringSmallBold(String, 40 + 4, 0, line + 1);
                            }
#else                           // show the channel frequency below the channel number/name
                            sprintf(String, "%03u.%05lu", frequency / 100000, frequency % 100000);
                            UI_PrintStringSmallNormal(String, 20 + 4, 0, line + 1);
#endif
                        }

                        break;
                }
            } else {   // frequency mode
                sprintf(String, "%3lu.%05lu", frequency / 100000, frequency % 100000);

#ifdef ENABLE_BIG_FREQ
                if (frequency < _1GHz_in_KHz) {
                    // show the remaining 2 small frequency digits
                    UI_PrintStringSmallNormal(String + 7, 113, 0, line + 1);
                    String[7] = 0;
                    // show the main large frequency digits
                    UI_DisplayFrequency(String, 20, line, false);
                } else
#endif
                {
                    // show the frequency in the main font
                    UI_PrintStringSmallBold(String, 40, 0, line /*, 8 */);
                }

                // show the channel symbols
                //const ChannelAttributes_t att = gMR_ChannelAttributes[gEeprom.ScreenChannel[vfo_num]];
            }
        }

        // ************

        {   // show the TX/RX level
            int8_t Level = -1;

            if (mode == VFO_MODE_TX) {   // TX power level
                /*
                switch (gRxVfo->OUTPUT_POWER)
                {
                    case OUTPUT_POWER_LOW1:     Level = 2; break;
                    case OUTPUT_POWER_LOW2:     Level = 2; break;
                    case OUTPUT_POWER_LOW3:     Level = 2; break;
                    case OUTPUT_POWER_LOW4:     Level = 2; break;
                    case OUTPUT_POWER_LOW5:     Level = 2; break;
                    case OUTPUT_POWER_MID:      Level = 4; break;
                    case OUTPUT_POWER_HIGH:     Level = 6; break;
                }

                if (gRxVfo->OUTPUT_POWER == OUTPUT_POWER_MID) {
                    Level = 4;
                } else if (gRxVfo->OUTPUT_POWER == OUTPUT_POWER_HIGH) {
                    Level = 6;
                } else {
                    Level = 2;
                }
                */
                Level = gRxVfo->OUTPUT_POWER;
            } else if (mode == VFO_MODE_RX) {   // RX signal level
#ifndef ENABLE_RSSI_BAR
                // bar graph
                if (gVFO_RSSI_bar_level[vfo_num] > 0)
                    Level = gVFO_RSSI_bar_level[vfo_num];
#endif
            }
            if (Level >= 0)
                DrawSmallAntennaAndBars(p_line1 + LCD_WIDTH, Level);
        }

        // ************

        String[0] = '\0';
        const VFO_Info_t *vfoInfo = &gEeprom.VfoInfo[vfo_num];

        // show the modulation symbol
        const char *s = "";
#ifdef ENABLE_FEAT_F4HWN
        const char *t = "";
#endif
        const ModulationMode_t mod = vfoInfo->Modulation;
        switch (mod) {
            case MODULATION_FM: {
                const FREQ_Config_t *pConfig = (mode == VFO_MODE_TX) ? vfoInfo->pTX : vfoInfo->pRX;
                const unsigned int code_type = pConfig->CodeType;
#ifdef ENABLE_FEAT_F4HWN
                const char *code_list[] = {"", "CT", "DC", "DC"};
#else
                const char *code_list[] = {"", "CT", "DCS", "DCR"};
#endif
                if (code_type < ARRAY_SIZE(code_list))
                    s = code_list[code_type];
#ifdef ENABLE_FEAT_F4HWN
                if (gCurrentFunction != FUNCTION_TRANSMIT || activeTxVFO != vfo_num)
                    t = gModulationStr[mod];
#endif
                break;
            }
            default:
                t = gModulationStr[mod];
                break;
        }

#if ENABLE_FEAT_F4HWN
        const FREQ_Config_t *pConfig = (mode == VFO_MODE_TX) ? vfoInfo->pTX : vfoInfo->pRX;
        int8_t shift = 0;
        switch ((int) pConfig->CodeType) {
            case 1:
                sprintf(String, "%u.%u", CTCSS_Options[pConfig->Code] / 10, CTCSS_Options[pConfig->Code] % 10);
                break;

            case 2:
                sprintf(String, "%03oN", DCS_Options[pConfig->Code]);
                break;

            case 3:
                sprintf(String, "%03oI", DCS_Options[pConfig->Code]);
                break;

            default:
                sprintf(String, "%d.%02uK", vfoInfo->StepFrequency / 100, vfoInfo->StepFrequency % 100);
                shift = -10;
        }



        UI_PrintStringSmallNormal(s, 50, 0, line + 2);
        UI_PrintStringSmallNormal(t, 2, 0, line + 2);

        if (shift == 0) {
            UI_PrintStringSmallNormal(String, 65, 0, line + 2);
        }

        if ((vfoInfo->StepFrequency / 100) < 100) {
            sprintf(String, "%d.%02uK", vfoInfo->StepFrequency / 100, vfoInfo->StepFrequency % 100);
        } else {
            sprintf(String, "%dK", vfoInfo->StepFrequency / 100);
        }
        UI_PrintStringSmallNormal(String, 55, 0, line + 1);
#else
        UI_PrintStringSmallNormal(s, LCD_WIDTH + 24, 0, line + 1);
#endif

        if (state == VFO_STATE_NORMAL || state == VFO_STATE_ALARM) {   // show the TX power
            const char pwr_short[][3] = {"L1", "L2", "L3", "L4", "L5", "ME",
                                         "HI"};
            UI_PrintStringSmallNormal(pwr_short[vfoInfo->OUTPUT_POWER], LCD_WIDTH + 25, 0, line + 1);
        }

        if (vfoInfo->freq_config_RX.Frequency != vfoInfo->freq_config_TX.Frequency) {   // show the TX offset symbol
            int i = vfoInfo->TX_OFFSET_FREQUENCY_DIRECTION % 3;

            const char dir_list[][2] = {"", "+", "-", "D"};

            if(gTxVfo->TX_OFFSET_FREQUENCY_DIRECTION != 0 && gTxVfo->pTX == &gTxVfo->freq_config_RX && !vfoInfo->FrequencyReverse)
            {
                i = 3;
            }

#if ENABLE_FEAT_F4HWN
            UI_PrintStringSmallNormal(dir_list[i], LCD_WIDTH + 60, 0, line + 1);
#else
            UI_PrintStringSmallNormal(dir_list[i], LCD_WIDTH + 54, 0, line + 1);
#endif
        }

        // show the TX/RX reverse symbol
        if (vfoInfo->FrequencyReverse)
#if ENABLE_FEAT_F4HWN
        {
            UI_PrintStringSmallNormal("R", LCD_WIDTH + 110, 0, line + 1);
        }
#else
        UI_PrintStringSmallNormal("R", LCD_WIDTH + 110, 0, line + 1);
#endif

#if ENABLE_FEAT_F4HWN

        const char *bandWidthNames[] = {"WID", "NAR", "NR+", "FSK"};
        UI_PrintStringSmallNormal(bandWidthNames[vfoInfo->CHANNEL_BANDWIDTH], LCD_WIDTH + 30, 0,
                                  line);

#else
        if (vfoInfo->CHANNEL_BANDWIDTH == BANDWIDTH_NARROW)
            UI_PrintStringSmallNormal("N", LCD_WIDTH + 70, 0, line + 1);
#endif


        // show the audio scramble symbol
        if (vfoInfo->SCRAMBLING_TYPE > 0) {
            sprintf(String, "S%d", inverse_scale_freq(vfoInfo->SCRAMBLING_TYPE));
            UI_PrintStringSmallNormal(String, 90, 0, line + 2);
        }

#ifdef ENABLE_FEAT_F4HWN
        if (isMainVFO && gMonitor) {
            strcpy(String, "MONI");

        } else {
            sprintf(String, "SQL%d", gEeprom.SQUELCH_LEVEL);
        }

        UI_PrintStringSmallNormal(String, LCD_WIDTH + 98, 0, line);

#endif
    }

#ifdef ENABLE_AGC_SHOW_DATA
    center_line = CENTER_LINE_IN_USE;
    UI_MAIN_PrintAGC(false);
#endif

    if (center_line == CENTER_LINE_NONE) {   // we're free to use the middle line

        const bool rx = FUNCTION_IsRx();

#ifdef ENABLE_AUDIO_BAR
        if (gSetting_mic_bar && gCurrentFunction == FUNCTION_TRANSMIT) {
            center_line = CENTER_LINE_AUDIO_BAR;
            UI_DisplayAudioBar();
        } else
#endif

#if defined(ENABLE_AM_FIX) && defined(ENABLE_AM_FIX_SHOW_DATA)
            if (rx && gEeprom.VfoInfo[gEeprom.RX_VFO].Modulation == MODULATION_AM && gSetting_AM_fix)
            {
                if (gScreenToDisplay != DISPLAY_MAIN
                    )
                    return;

                center_line = CENTER_LINE_AM_FIX_DATA;
                AM_fix_print_data(gEeprom.RX_VFO, String);
                UI_PrintStringSmallNormal(String, 2, 0, 3);
            }
            else
#endif

#ifdef ENABLE_RSSI_BAR
        if (rx) {
            center_line = CENTER_LINE_RSSI;
            DisplayRSSIBar(false);
        } else
#endif
        if (rx || gCurrentFunction == FUNCTION_FOREGROUND || gCurrentFunction == FUNCTION_POWER_SAVE) {
#if 1
            if (gSetting_live_DTMF_decoder && gDTMF_RX_live[0] != 0) {   // show live DTMF decode
                const unsigned int len = strlen(gDTMF_RX_live);
                const unsigned int idx = (len > (17 - 5)) ? len - (17 - 5) : 0;  // limit to last 'n' chars

                if (gScreenToDisplay != DISPLAY_MAIN
                        )
                    return;

                center_line = CENTER_LINE_DTMF_DEC;

                sprintf(String, "DTMF %s", gDTMF_RX_live + idx);
#ifdef ENABLE_FEAT_F4HWN
                if (isMainOnly()) {
                    UI_PrintStringSmallNormal(String, 2, 0, 5);
                } else {
                    UI_PrintStringSmallNormal(String, 2, 0, 3);
                }
#else
                UI_PrintStringSmallNormal(String, 2, 0, 3);

#endif
            }
#else
            if (gSetting_live_DTMF_decoder && gDTMF_RX_index > 0)
            {   // show live DTMF decode
                const unsigned int len = gDTMF_RX_index;
                const unsigned int idx = (len > (17 - 5)) ? len - (17 - 5) : 0;  // limit to last 'n' chars

                if (gScreenToDisplay != DISPLAY_MAIN ||
                    gDTMF_CallState != DTMF_CALL_STATE_NONE)
                    return;

                center_line = CENTER_LINE_DTMF_DEC;

                sprintf(String, "DTMF %s", gDTMF_RX_live + idx);
                UI_PrintStringSmallNormal(String, 2, 0, 3);
            }
#endif

#ifdef ENABLE_SHOW_CHARGE_LEVEL
            else if (gChargingWithTypeC)
            {   // charging .. show the battery state
                if (gScreenToDisplay != DISPLAY_MAIN
                    )
                    return;

                center_line = CENTER_LINE_CHARGE_DATA;

                sprintf(String, "Charge %u.%02uV %u%%",
                    gBatteryVoltageAverage / 100, gBatteryVoltageAverage % 100,
                    BATTERY_VoltsToPercent(gBatteryVoltageAverage));
                UI_PrintStringSmallNormal(String, 2, 0, 3);
            }
#endif
        }
    }

#ifdef ENABLE_FEAT_F4HWN
    //#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
    //if(gEeprom.MENU_LOCK == false)
    //{
    //#endif
    if (isMainOnly() && !gDTMF_InputMode) {
        sprintf(String, "VFO %s", activeTxVFO ? "B" : "A");
        UI_PrintStringSmallBold(String, 92, 0, 6);
        for (uint8_t i = 92; i < 128; i++) {
            gFrameBuffer[6][i] ^= 0x7F;
        }
    }
    //#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
    //}
    //#endif
#endif

    ST7565_BlitFullScreen();
}

// ***************************************************************************