/* 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 "app/app.h"
#include "app/dtmf.h"
#include "app/generic.h"
#include "app/menu.h"
#include "app/scanner.h"
#include "audio.h"
#include "driver/bk4819.h"
#include "frequencies.h"
#include "misc.h"
#include "radio.h"
#include "settings.h"
#include "ui/inputbox.h"
#include "ui/ui.h"

DCS_CodeType_t    gScanCssResultType;
uint8_t           gScanCssResultCode;
#ifdef  TEST_UNDE_CTCSS
uint16_t           gScanCssResultCode_all;
#endif
bool              gScanSingleFrequency; // scan CTCSS/DCS codes for current frequency
SCAN_SaveState_t  gScannerSaveState;
uint8_t           gScanChannel;
uint32_t          gScanFrequency;
SCAN_CssState_t   gScanCssState;
uint8_t           gScanProgressIndicator;
bool              gScanUseCssResult;

STEP_Setting_t    stepSetting;
uint8_t           scanHitCount;


static void SCANNER_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
{
	if (!bKeyHeld && bKeyPressed)
	{
		if (gScannerSaveState == SCAN_SAVE_CHAN_SEL) {
			gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;

			INPUTBOX_Append(Key);

			gRequestDisplayScreen = DISPLAY_SCANNER;

			if (gInputBoxIndex < 3) {
#ifdef ENABLE_VOICE
				gAnotherVoiceID = (VOICE_ID_t)Key;
#endif
				return;
			}

			gInputBoxIndex = 0;

			uint16_t chan = ((gInputBox[0] * 100) + (gInputBox[1] * 10) + gInputBox[2]) - 1;
			if (IS_MR_CHANNEL(chan)) {
#ifdef ENABLE_VOICE
				gAnotherVoiceID = (VOICE_ID_t)Key;
#endif
				gShowChPrefix = RADIO_CheckValidChannel(chan, false, 0);
				gScanChannel  = (uint8_t)chan;
				return;
			}
		}

		gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
	}
}

static void SCANNER_Key_EXIT(bool bKeyPressed, bool bKeyHeld)
{
	if (!bKeyHeld && bKeyPressed) { // short pressed
		gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;

		switch (gScannerSaveState) {
			case SCAN_SAVE_NO_PROMPT:
				SCANNER_Stop();
				gRequestDisplayScreen    = DISPLAY_MAIN;
				break;

			case SCAN_SAVE_CHAN_SEL:
				if (gInputBoxIndex > 0) {
					gInputBox[--gInputBoxIndex] = 10;
					gRequestDisplayScreen       = DISPLAY_SCANNER;
					break;
				}

				// Fallthrough

			case SCAN_SAVE_CHANNEL:
				gScannerSaveState     = SCAN_SAVE_NO_PROMPT;
#ifdef ENABLE_VOICE
				gAnotherVoiceID   = VOICE_ID_CANCEL;
#endif
				gRequestDisplayScreen = DISPLAY_SCANNER;
				break;
		}
	}
}

static void SCANNER_Key_MENU(bool bKeyPressed, bool bKeyHeld)
{
	if (bKeyHeld || !bKeyPressed) // ignore long press or release button events
		return;

	if (gScanCssState == SCAN_CSS_STATE_OFF && !gScanSingleFrequency) {
		gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
		return;
	}

	if (gScanCssState == SCAN_CSS_STATE_SCANNING && gScanSingleFrequency) {
		gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
		return;
	}

	if (gScanCssState == SCAN_CSS_STATE_FAILED) {
		gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
		return;
	}

	gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;

	switch (gScannerSaveState) {
		case SCAN_SAVE_NO_PROMPT:
			if (!gScanSingleFrequency)
			{
				uint32_t freq250  = FREQUENCY_RoundToStep(gScanFrequency, 250);
				uint32_t freq625  = FREQUENCY_RoundToStep(gScanFrequency, 625);

				uint32_t diff250 = gScanFrequency > freq250 ? gScanFrequency - freq250 : freq250 - gScanFrequency;
				uint32_t diff625 = gScanFrequency > freq625 ? gScanFrequency - freq625 : freq625 - gScanFrequency;

				if(diff250 > diff625) {
					stepSetting   = STEP_6_25kHz;
					gScanFrequency = freq625;
				}
				else {
					stepSetting   = STEP_2_5kHz;
					gScanFrequency = freq250;
				}
			}

			if (IS_MR_CHANNEL(gTxVfo->CHANNEL_SAVE)) {
				gScannerSaveState = SCAN_SAVE_CHAN_SEL;
				gScanChannel      = gTxVfo->CHANNEL_SAVE;
				gShowChPrefix     = RADIO_CheckValidChannel(gTxVfo->CHANNEL_SAVE, false, 0);
			}
			else {
				gScannerSaveState = SCAN_SAVE_CHANNEL;
			}

			gScanCssState         = SCAN_CSS_STATE_FOUND;
#ifdef ENABLE_VOICE
			gAnotherVoiceID   = VOICE_ID_MEMORY_CHANNEL;
#endif
			gRequestDisplayScreen = DISPLAY_SCANNER;
			
			gUpdateStatus = true;
			break;

		case SCAN_SAVE_CHAN_SEL:
			if (gInputBoxIndex == 0) {
				gBeepToPlay           = BEEP_1KHZ_60MS_OPTIONAL;
				gRequestDisplayScreen = DISPLAY_SCANNER;
				gScannerSaveState     = SCAN_SAVE_CHANNEL;
			}
			break;

		case SCAN_SAVE_CHANNEL:
			if (!gScanSingleFrequency) {
				RADIO_InitInfo(gTxVfo, gTxVfo->CHANNEL_SAVE, gScanFrequency);

				if (gScanUseCssResult) {
					gTxVfo->freq_config_RX.CodeType = gScanCssResultType;
					gTxVfo->freq_config_RX.Code     = gScanCssResultCode;
				}

				gTxVfo->freq_config_TX     = gTxVfo->freq_config_RX;
				gTxVfo->STEP_SETTING = stepSetting;
			}
			else {
				RADIO_ConfigureChannel(0, VFO_CONFIGURE_RELOAD);
				RADIO_ConfigureChannel(1, VFO_CONFIGURE_RELOAD);

				gTxVfo->freq_config_RX.CodeType = gScanCssResultType;
				gTxVfo->freq_config_RX.Code     = gScanCssResultCode;
				gTxVfo->freq_config_TX.CodeType = gScanCssResultType;
				gTxVfo->freq_config_TX.Code     = gScanCssResultCode;
			}

			uint8_t chan;
			if (IS_MR_CHANNEL(gTxVfo->CHANNEL_SAVE)) {
				chan = gScanChannel;
				gEeprom.MrChannel[gEeprom.TX_VFO] = chan;
			}
			else {
				chan = gTxVfo->Band + FREQ_CHANNEL_FIRST;
				gEeprom.FreqChannel[gEeprom.TX_VFO] = chan;
			}

			gTxVfo->CHANNEL_SAVE = chan;
			gEeprom.ScreenChannel[gEeprom.TX_VFO] = chan;
#ifdef ENABLE_VOICE	
			gAnotherVoiceID = VOICE_ID_CONFIRM;
#endif
			gRequestDisplayScreen = DISPLAY_SCANNER;
			gRequestSaveChannel = 2;
			gScannerSaveState = SCAN_SAVE_NO_PROMPT;
			break;

		default:
			gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
			break;
	}
}

static void SCANNER_Key_STAR(bool bKeyPressed, bool bKeyHeld)
{
	if (!bKeyHeld && bKeyPressed) {
		gBeepToPlay    = BEEP_1KHZ_60MS_OPTIONAL;
		SCANNER_Start(gScanSingleFrequency);
	}
	return;
}

static void SCANNER_Key_UP_DOWN(bool bKeyPressed, bool pKeyHeld, int8_t Direction)
{
	if (pKeyHeld) {
		if (!bKeyPressed)
			return;
	}
	else {
		if (!bKeyPressed)
			return;

		gInputBoxIndex = 0;
		gBeepToPlay    = BEEP_1KHZ_60MS_OPTIONAL;
	}

	if (gScannerSaveState == SCAN_SAVE_CHAN_SEL) {
		gScanChannel          = NUMBER_AddWithWraparound(gScanChannel, Direction, 0, MR_CHANNEL_LAST);
		gShowChPrefix         = RADIO_CheckValidChannel(gScanChannel, false, 0);
		gRequestDisplayScreen = DISPLAY_SCANNER;
	}
	else
		gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
}

void SCANNER_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
{
	switch (Key) {
		case KEY_0:
		case KEY_1:
		case KEY_2:
		case KEY_3:
		case KEY_4:
		case KEY_5:
		case KEY_6:
		case KEY_7:
		case KEY_8:
		case KEY_9:
			SCANNER_Key_DIGITS(Key, bKeyPressed, bKeyHeld);
			break;
		case KEY_MENU:
			SCANNER_Key_MENU(bKeyPressed, bKeyHeld);
			break;
		case KEY_UP:
			SCANNER_Key_UP_DOWN(bKeyPressed, bKeyHeld,  1);
			break;
		case KEY_DOWN:
			SCANNER_Key_UP_DOWN(bKeyPressed, bKeyHeld, -1);
			break;
		case KEY_EXIT:
			SCANNER_Key_EXIT(bKeyPressed, bKeyHeld);
			break;
		case KEY_STAR:
			SCANNER_Key_STAR(bKeyPressed, bKeyHeld);
			break;
		case KEY_PTT:
			GENERIC_Key_PTT(bKeyPressed);
			break;
		default:
			if (!bKeyHeld && bKeyPressed)
				gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
			break;
	}
}

void SCANNER_Start(bool singleFreq)
{
	gScanSingleFrequency = singleFreq;
	gMonitor = false;

#ifdef ENABLE_VOICE
	gAnotherVoiceID = VOICE_ID_SCANNING_BEGIN;
#endif

	BK4819_StopScan();
	RADIO_SelectVfos();

#ifdef ENABLE_NOAA
	if (IS_NOAA_CHANNEL(gRxVfo->CHANNEL_SAVE))
		gRxVfo->CHANNEL_SAVE = FREQ_CHANNEL_FIRST + BAND6_400MHz;
#endif

	uint8_t  backupStep      = gRxVfo->STEP_SETTING;
	uint16_t backupFrequency = gRxVfo->StepFrequency;

	RADIO_InitInfo(gRxVfo, gRxVfo->CHANNEL_SAVE, gRxVfo->pRX->Frequency);

	gRxVfo->STEP_SETTING  = backupStep;
	gRxVfo->StepFrequency = backupFrequency;

	RADIO_SetupRegisters(true);

#ifdef ENABLE_NOAA
	gIsNoaaMode = false;
#endif

	if (gScanSingleFrequency) {
		gScanCssState  = SCAN_CSS_STATE_SCANNING;
		gScanFrequency = gRxVfo->pRX->Frequency;
		stepSetting   = gRxVfo->STEP_SETTING;

		BK4819_PickRXFilterPathBasedOnFrequency(gScanFrequency);
		BK4819_SetScanFrequency(gScanFrequency);

		gUpdateStatus = true;
	}
	else {
		gScanCssState  = SCAN_CSS_STATE_OFF;
		gScanFrequency = 0xFFFFFFFF;

		BK4819_PickRXFilterPathBasedOnFrequency(gScanFrequency);
		BK4819_EnableFrequencyScan();

		gUpdateStatus = true;
	}
#ifdef ENABLE_DTMF_CALLING
	DTMF_clear_RX();
#endif
	gScanDelay_10ms        = scan_delay_10ms;
	gScanCssResultCode     = 0xFF;
#ifdef TEST_UNDE_CTCSS

    gScanCssResultCode_all=0xffff;
#endif

	gScanCssResultType     = 0xFF;
	scanHitCount          = 0;
	gScanUseCssResult      = false;
	g_CxCSS_TAIL_Found     = false;
	g_CDCSS_Lost           = false;
	gCDCSSCodeType         = 0;
	g_CTCSS_Lost           = false;
#ifdef ENABLE_VOX
	g_VOX_Lost         = false;
#endif
	g_SquelchLost          = false;
	gScannerSaveState      = SCAN_SAVE_NO_PROMPT;
	gScanProgressIndicator = 0;
}

void SCANNER_Stop(void)
{
	if(SCANNER_IsScanning()) {
		gEeprom.CROSS_BAND_RX_TX = gBackup_CROSS_BAND_RX_TX;
		gVfoConfigureMode        = VFO_CONFIGURE_RELOAD;
		gFlagResetVfos           = true;
		gUpdateStatus            = true;
		gCssBackgroundScan 			 = false;
		gScanUseCssResult = false;
#ifdef ENABLE_VOICE
		gAnotherVoiceID          = VOICE_ID_CANCEL;
#endif
		BK4819_StopScan();
	}
}

void SCANNER_TimeSlice10ms(void)
{
	if (!SCANNER_IsScanning())
		return;

	if (gScanDelay_10ms > 0) {
		gScanDelay_10ms--;
		return;
	}

	if (gScannerSaveState != SCAN_SAVE_NO_PROMPT) {
		return;
	}

	switch (gScanCssState) {
		case SCAN_CSS_STATE_OFF: {
			// must be RF frequency scanning if we're here ?
			uint32_t result;
			if (!BK4819_GetFrequencyScanResult(&result))
				break;

			int32_t delta = result - gScanFrequency;
			gScanFrequency = result;

			if (delta < 0)
				delta = -delta;
			if (delta < 100)
				scanHitCount++;
			else
				scanHitCount = 0;

			BK4819_DisableFrequencyScan();

			if (scanHitCount < 3) {
				BK4819_EnableFrequencyScan();
			}
			else {
				BK4819_SetScanFrequency(gScanFrequency);
				gScanCssResultCode     = 0xFF;
#ifdef TEST_UNDE_CTCSS

                gScanCssResultCode_all=0xffff;
#endif
				gScanCssResultType     = 0xFF;
				scanHitCount          = 0;
				gScanUseCssResult      = false;
				gScanProgressIndicator = 0;
				gScanCssState          = SCAN_CSS_STATE_SCANNING;

				if(!gCssBackgroundScan)
					GUI_SelectNextDisplay(DISPLAY_SCANNER);

				gUpdateStatus          = true;
			}

			gScanDelay_10ms = scan_delay_10ms;
			//gScanDelay_10ms = 1;   // 10ms
			break;
		}
		case SCAN_CSS_STATE_SCANNING: {
			uint32_t cdcssFreq;
			uint16_t ctcssFreq;
			BK4819_CssScanResult_t scanResult = BK4819_GetCxCSSScanResult(&cdcssFreq, &ctcssFreq);
			if (scanResult == BK4819_CSS_RESULT_NOT_FOUND)
				break;

			BK4819_Disable();

			if (scanResult == BK4819_CSS_RESULT_CDCSS) {
				const uint8_t Code = DCS_GetCdcssCode(cdcssFreq);
				if (Code != 0xFF)
				{
					gScanCssResultCode = Code;
					gScanCssResultType = CODE_TYPE_DIGITAL;
					gScanCssState      = SCAN_CSS_STATE_FOUND;
					gScanUseCssResult  = true;
					gUpdateStatus      = true;
				}
			}
			else if (scanResult == BK4819_CSS_RESULT_CTCSS) {
#ifdef TEST_UNDE_CTCSS
				const uint16_t Code = DCS_GetCtcssCode_ALL(ctcssFreq);
#else
                const uint8_t Code = DCS_GetCtcssCode(ctcssFreq);
#endif
#ifdef TEST_UNDE_CTCSS
                    if (Code != 0xFFFF) {
                        					if (Code == gScanCssResultCode_all && gScanCssResultType == CODE_TYPE_CONTINUOUS_TONE) {

#else
                if (Code != 0xFF) {
                    if (Code == gScanCssResultCode && gScanCssResultType == CODE_TYPE_CONTINUOUS_TONE) {

#endif
						if (++scanHitCount >= 2) {
							gScanCssState     = SCAN_CSS_STATE_FOUND;
							gScanUseCssResult = true;
							gUpdateStatus     = true;
						}
					}
					else
						scanHitCount = 0;

					gScanCssResultType = CODE_TYPE_CONTINUOUS_TONE;
#ifdef TEST_UNDE_CTCSS
                    gScanCssResultCode_all = Code;
#else
                    gScanCssResultCode= Code;
#endif
				}
			}
//            else if (scanResult == BK4819_CSS_RESULT_CTCSS) {
//                const uint8_t Code = DCS_GetCtcssCode(ctcssFreq);
//                if (Code != 0xFF) {
//                    if (Code == gScanCssResultCode && gScanCssResultType == CODE_TYPE_CONTINUOUS_TONE) {
//                        if (++scanHitCount >= 2) {
//                            gScanCssState     = SCAN_CSS_STATE_FOUND;
//                            gScanUseCssResult = true;
//                            gUpdateStatus     = true;
//                        }
//                    }
//                    else
//                        scanHitCount = 0;
//
//                    gScanCssResultType = CODE_TYPE_CONTINUOUS_TONE;
//                    gScanCssResultCode = Code;
//                }
//            }
			if (gScanCssState < SCAN_CSS_STATE_FOUND) { // scanning or off
				BK4819_SetScanFrequency(gScanFrequency);
				gScanDelay_10ms = scan_delay_10ms;
				break;
			}

			if(gCssBackgroundScan) {
				gCssBackgroundScan = false;
				if(gScanUseCssResult)
					MENU_CssScanFound();
			}
			else
				GUI_SelectNextDisplay(DISPLAY_SCANNER);


			break;
		}
		default:
			gCssBackgroundScan = false;
			break;
	}

}

void SCANNER_TimeSlice500ms(void)
{
	if (SCANNER_IsScanning() && gScannerSaveState == SCAN_SAVE_NO_PROMPT && gScanCssState < SCAN_CSS_STATE_FOUND) {
		gScanProgressIndicator++;
#ifdef ENABLE_NO_CODE_SCAN_TIMEOUT
		if (gScanProgressIndicator > 32) {
			if (gScanCssState == SCAN_CSS_STATE_SCANNING && !gScanSingleFrequency)
				gScanCssState = SCAN_CSS_STATE_FOUND;
			else
				gScanCssState = SCAN_CSS_STATE_FAILED;

			gUpdateStatus = true;
		}
#endif
		gUpdateDisplay = true;
	}
	else if(gCssBackgroundScan) {
		gUpdateDisplay = true;
	}
}

bool SCANNER_IsScanning(void)
{
	return gCssBackgroundScan || (gScreenToDisplay == DISPLAY_SCANNER);
}