/*
 * Copyright (C) 2019-2025 Roger Clark, VK3KYY / G4KYF
 *                         Daniel Caujolle-Bert, F1RMB
 *
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
 *    in the documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * 4. Use of this source code or binary releases for commercial purposes is strictly forbidden. This includes, without limitation,
 *    incorporation in a commercial product or incorporation into a product or project which allows commercial use.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
#include "user_interface/uiGlobals.h"
#include "user_interface/menuSystem.h"
#include "user_interface/uiUtilities.h"
#include "user_interface/uiLocalisation.h"


#if defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MDUV380) || defined(PLATFORM_MD2017)
#if defined(PLATFORM_VARIANT_DM1701)
static const int DISPLAYED_LINES_MAX = 6;
#else
static const int DISPLAYED_LINES_MAX = 7;
#endif
#else
static const int DISPLAYED_LINES_MAX = 3;
#endif

static bool displayLHDetails = false;
static menuStatus_t menuLastHeardExitCode;
static LinkItem_t *selectedItem;
static int lastHeardCount;
static int firstDisplayed;

static void displayTalkerAlias(uint8_t y, char *text, uint32_t time, uint32_t now, uint32_t TGorPC, size_t maxLen, bool displayDetails, bool itemIsSelected, bool isFirstRun, LinkItem_t * item);
static void promptsInit(bool isFirstRun);

menuStatus_t menuLastHeard(uiEvent_t *ev, bool isFirstRun)
{
	static uint32_t m = 0;

	if (isFirstRun)
	{
		menuLastHeardInit();
		menuLastHeardUpdateScreen(true, displayLHDetails, true);
		m = ev->time;

		return (menuLastHeardExitCode | (uiDataGlobal.lastHeardCount ? MENU_STATUS_LIST_TYPE : 0));
	}
	else
	{
		bool headHasChanged = (menuDataGlobal.startIndex != LinkHead->id);

		menuLastHeardExitCode = MENU_STATUS_SUCCESS;

		// do live update by checking if the item at the top of the list has changed
		if (headHasChanged || (uiDataGlobal.displayQSOState == QSO_DISPLAY_CALLER_DATA))
		{
			bool backlightOn = false;

			// the LH list has grown
			if (lastHeardCount != uiDataGlobal.lastHeardCount)
			{
				backlightOn = true;
				lastHeardCount = uiDataGlobal.lastHeardCount;
			}
			else
			{
				// Do not turn the backlight ON on caller data updates
				if (headHasChanged)
				{
					backlightOn = true;
				}
			}

			menuDataGlobal.startIndex = LinkHead->id;

			if (backlightOn)
			{
				displayLightTrigger(false);
			}

			menuLastHeardUpdateScreen(true, displayLHDetails, false);
		}

		if (ev->hasEvent)
		{
			m = ev->time;
			menuLastHeardHandleEvent(ev);
		}
		else
		{
			// Just refresh the list while displaying details, it's all about elapsed time
			if (displayLHDetails && ((ev->time - m) > (1000U * 60U)))
			{
				m = ev->time;
				menuLastHeardUpdateScreen(true, displayLHDetails, false);
			}
		}

	}

	return menuLastHeardExitCode;
}

void menuLastHeardUpdateScreen(bool showTitleOrHeader, bool displayDetails, bool isFirstRun)
{
	int numDisplayed = 0;
	LinkItem_t *item = LinkHead;
	uint32_t now = ticksGetMillis();
	bool invertColour;
	bool displayTA;

	// Jumping here from <SK2> + 3, with an empty heard list won't announce "Last Heard", handle this here
	if (isFirstRun && (uiDataGlobal.lastHeardCount == 0) && (nonVolatileSettings.audioPromptMode >= AUDIO_PROMPT_MODE_VOICE_THRESHOLD))
	{
		promptsInit(isFirstRun);
	}

	displayClearBuf();
	if (showTitleOrHeader)
	{
		menuDisplayTitle(currentLanguage->last_heard);
	}
	else
	{
		uiUtilityRenderHeader(false, false, false);
	}

	displayThemeApply(THEME_ITEM_FG_CHANNEL_CONTACT_INFO, THEME_ITEM_BG);

	// skip over the first menuDataGlobal.currentItemIndex in the listing
	for(int i = 0; i < firstDisplayed; i++)
	{
		item = item->next;
	}

	if (uiDataGlobal.lastHeardCount > 0)
	{
		while((item != NULL) && (item->id != 0) && (numDisplayed < DISPLAYED_LINES_MAX))
		{
			displayTA = false;

			if (menuDataGlobal.currentItemIndex == (firstDisplayed + numDisplayed))
			{
				invertColour = true;
				displayThemeApply(THEME_ITEM_FG_CHANNEL_CONTACT_INFO, THEME_ITEM_BG_MENU_ITEM_SELECTED);
				displayFillRect(0, 16 + (numDisplayed * MENU_ENTRY_HEIGHT), DISPLAY_SIZE_X, MENU_ENTRY_HEIGHT,
#if defined(HAS_COLOURS)
						true
#else
						false
#endif
						);
				displayThemeApply(THEME_ITEM_FG_CHANNEL_CONTACT_INFO, THEME_ITEM_BG);
				selectedItem = item;
			}
			else
			{
				invertColour = false;
			}

			switch (nonVolatileSettings.contactDisplayPriority)
			{
				case CONTACT_DISPLAY_PRIO_CC_DB_TA:
				case CONTACT_DISPLAY_PRIO_DB_CC_TA:
					// No contact found in codeplug and DMRIDs, use TA as fallback, if any.
					if ((strncmp(item->contact, "ID:", 3) == 0) && (item->talkerAlias[0] != 0x00))
					{
						displayTA = true;
					}
					break;
				case CONTACT_DISPLAY_PRIO_TA_CC_DB:
				case CONTACT_DISPLAY_PRIO_TA_DB_CC:
					if (item->talkerAlias[0] != 0x00)
					{
						displayTA = true;
					}
					break;
			}

			if (displayTA)
			{
				displayTalkerAlias(16 + (numDisplayed * MENU_ENTRY_HEIGHT) + LH_ENTRY_V_OFFSET, item->talkerAlias, item->time, now, item->talkGroupOrPcId, 32, displayDetails, invertColour, isFirstRun, item);
			}
			else
			{
				displayTalkerAlias(16 + (numDisplayed * MENU_ENTRY_HEIGHT) + LH_ENTRY_V_OFFSET, item->contact, item->time, now, item->talkGroupOrPcId, MAX_DMR_ID_CONTACT_TEXT_LENGTH, displayDetails, invertColour, isFirstRun, item);
			}

			numDisplayed++;

			item = item->next;
		}
	}
	else
	{
		promptsPlayNotAfterTx();
	}

	displayThemeResetToDefault();

	displayRender();
	uiDataGlobal.displayQSOState = QSO_DISPLAY_IDLE;
}

void menuLastHeardHandleEvent(uiEvent_t *ev)
{
	bool isDirty = false;
	int currentMenu = menuSystemGetCurrentMenuNumber();

	if (currentMenu == MENU_LAST_HEARD)
	{
		if (ev->events & BUTTON_EVENT)
		{
			if (repeatVoicePromptOnSK1(ev))
			{
				return;
			}
		}

		if ((ev->events & FUNCTION_EVENT) && (ev->function == FUNC_REDRAW))
		{
			menuLastHeardUpdateScreen(true, displayLHDetails, false);
			return;
		}
	}

	if (uiDataGlobal.lastHeardCount > 0)
	{
		if (KEYCHECK_PRESS(ev->keys, KEY_DOWN) && (BUTTONCHECK_DOWN(ev, BUTTON_SK2) == 0))
		{
			if (menuDataGlobal.currentItemIndex < (uiDataGlobal.lastHeardCount - 1))
			{
				isDirty = true;
				menuDataGlobal.currentItemIndex++;

				if (menuDataGlobal.currentItemIndex >= DISPLAYED_LINES_MAX)
				{
					if (((menuDataGlobal.currentItemIndex - DISPLAYED_LINES_MAX) == firstDisplayed) &&
							(menuDataGlobal.currentItemIndex <= (uiDataGlobal.lastHeardCount - 1)))
					{
						firstDisplayed++;
					}
				}
			}

			if (menuDataGlobal.currentItemIndex == 0)
			{
				menuLastHeardExitCode |= MENU_STATUS_LIST_TYPE;
			}
		}
		else if (KEYCHECK_PRESS(ev->keys, KEY_DOWN) && BUTTONCHECK_DOWN(ev, BUTTON_SK2))
		{
			isDirty = true;
			menuDataGlobal.currentItemIndex = (uiDataGlobal.lastHeardCount - 1);
			firstDisplayed = SAFE_MAX((menuDataGlobal.currentItemIndex - (DISPLAYED_LINES_MAX - 1)), 0);

			if (menuDataGlobal.currentItemIndex == 0)
			{
				menuLastHeardExitCode |= MENU_STATUS_LIST_TYPE;
			}
		}
		else if (KEYCHECK_PRESS(ev->keys, KEY_UP) && (BUTTONCHECK_DOWN(ev, BUTTON_SK2) == 0))
		{
			if (menuDataGlobal.currentItemIndex > 0)
			{
				isDirty = true;
				menuDataGlobal.currentItemIndex--;

				if (firstDisplayed && (menuDataGlobal.currentItemIndex < firstDisplayed))
				{
					firstDisplayed--;
				}
			}

			if (menuDataGlobal.currentItemIndex == 0)
			{
				menuLastHeardExitCode |= MENU_STATUS_LIST_TYPE;
			}
		}
		else if (KEYCHECK_PRESS(ev->keys, KEY_UP) && BUTTONCHECK_DOWN(ev, BUTTON_SK2))
		{
			menuDataGlobal.currentItemIndex = 0;
			isDirty = true;
			firstDisplayed = 0;
			menuLastHeardExitCode |= MENU_STATUS_LIST_TYPE;
		}
		else if ((currentMenu == MENU_LAST_HEARD) && KEYCHECK_SHORTUP(ev->keys, KEY_GREEN))
		{
			int timeslot = selectedItem->receivedTS;
			uint32_t tgOrPC = ((BUTTONCHECK_DOWN(ev, BUTTON_SK2) == 0) ? selectedItem->talkGroupOrPcId : ((PC_CALL_FLAG << 24) | selectedItem->id));

			setOverrideTGorPC(tgOrPC, ((tgOrPC >> 24) == PC_CALL_FLAG));

			if ((timeslot != -1) && (timeslot != trxGetDMRTimeSlot()))
			{
				trxSetDMRTimeSlot(timeslot, true);
				tsSetManualOverride(((menuSystemGetRootMenuNumber() == UI_CHANNEL_MODE) ? CHANNEL_CHANNEL : (CHANNEL_VFO_A + nonVolatileSettings.currentVFONumber)), (timeslot + 1));
			}

			announceItem(PROMPT_SEQUENCE_CONTACT_TG_OR_PC, PROMPT_THRESHOLD_3);
			menuSystemPopAllAndDisplayRootMenu();
			return;
		}
		else if (KEYCHECK_LONGDOWN(ev->keys, KEY_HASH) && (BUTTONCHECK_DOWN(ev, (BUTTON_SK1 | BUTTON_SK2)) == 0)) // Clear LH list
		{
			if (currentMenu == MENU_LAST_HEARD) // Only allowed within LH menu
			{
				lastHeardInitList();
				lastHeardClearLastID();
				menuLastHeardInit();
				promptsInit(true); // Stack "Empty List" VP.
				isDirty = true;
			}
		}
	}

	if (currentMenu == MENU_LAST_HEARD)
	{
		if (KEYCHECK_SHORTUP(ev->keys, KEY_RED))
		{
			menuSystemPopPreviousMenu();
			return;
		}
		else if (KEYCHECK_SHORTUP_NUMBER(ev->keys) && (BUTTONCHECK_DOWN(ev, BUTTON_SK2)))
		{
			saveQuickkeyMenuIndex(ev->keys.key, menuSystemGetCurrentMenuNumber(), 0, 0);
			return;
		}

		// Toggles LH simple/detailed views on SK2 press
		if (!displayLHDetails && BUTTONCHECK_DOWN(ev, BUTTON_SK2))
		{
			isDirty = true;
			displayLHDetails = true;
		}
		else if (displayLHDetails && (BUTTONCHECK_DOWN(ev, BUTTON_SK2) == 0))
		{
			isDirty = true;
			displayLHDetails = false;
		}

		if (isDirty)
		{
			menuLastHeardUpdateScreen(true, displayLHDetails, false);// This will also setup the voice prompt

			if (nonVolatileSettings.audioPromptMode >= AUDIO_PROMPT_MODE_VOICE_THRESHOLD)
			{
				voicePromptsPlay();
			}
		}
	}
	else
	{
		if (isDirty)
		{
			menuLastHeardUpdateScreen(false, false, false);
		}
	}
}

void menuLastHeardInit(void)
{
	menuDataGlobal.startIndex = LinkHead->id;// reuse this global to store the ID of the first item in the list
	menuDataGlobal.currentItemIndex = 0;
	menuDataGlobal.numItems = uiDataGlobal.lastHeardCount;
	selectedItem = NULL;
	firstDisplayed = 0;
	displayLHDetails = false;
	lastHeardCount = uiDataGlobal.lastHeardCount;
	menuLastHeardExitCode = MENU_STATUS_SUCCESS;
}

static void promptsInit(bool isFirstRun)
{
	voicePromptsInit();
	if (isFirstRun)
	{
		voicePromptsAppendPrompt(PROMPT_SILENCE);
		voicePromptsAppendLanguageString(currentLanguage->last_heard);
		voicePromptsAppendLanguageString(currentLanguage->menu);

		if (uiDataGlobal.lastHeardCount == 0)
		{
			voicePromptsAppendPrompt(PROMPT_SILENCE);
			voicePromptsAppendLanguageString(currentLanguage->list_empty);
		}
	}
}

static void displayTalkerAlias(uint8_t y, char *text, uint32_t time, uint32_t now, uint32_t TGorPC, size_t maxLen, bool displayDetails, bool itemIsSelected, bool isFirstRun, LinkItem_t * item)
{
	char timeBuffer[SCREEN_LINE_BUFFER_SIZE];
	uint32_t tg = (TGorPC & 0xFFFFFF);
	bool isPC = ((TGorPC >> 24) == PC_CALL_FLAG);
	bool inHours = false;
	uint32_t heardSince = (((now - time) / 1000U) / 60U);

	// If last heard time is greater than 720 minutes (12h), switch to hours view to avoid mixup between TG and time.
	if (heardSince > 720U)
	{
		heardSince /= 60U;
		inHours = true;

		if (heardSince > 999)
		{
			heardSince = 999;
		}
	}

	snprintf(timeBuffer, 6, "%u", heardSince); // Time, without unit

	if (itemIsSelected && (nonVolatileSettings.audioPromptMode >= AUDIO_PROMPT_MODE_VOICE_THRESHOLD))
	{
		promptsInit(isFirstRun);
	}

	if (!displayDetails) // search for callsign + first name
	{
		char buffer[37]; // Max: TA 27 (in 7bit format) + ' [' + 6 (Maidenhead)  + ']' + NULL

		if (strlen(text) >= 5)
		{
			int32_t  cpos;

			if ((cpos = getFirstSpacePos(text)) != -1) // Callsign found
			{
				// Check if second part is 'DMR ID:'
				// In this case, don't go further
				if (strncmp((text + cpos + 1), "DMR ID:", 7) == 0)
				{
					if (cpos > 15)
					{
						cpos = 16;
					}

					memcpy(buffer, text, cpos);
					buffer[cpos] = 0;

					displayPrintCore(0, y, chomp(buffer), FONT_SIZE_3, TEXT_ALIGN_CENTER, itemIsSelected);
				}
				else // Nope, look for first name
				{
					uint32_t npos;
					char nameBuf[SCREEN_LINE_BUFFER_SIZE];
					char outputBuf[SCREEN_LINE_BUFFER_SIZE];

					memset(nameBuf, 0, sizeof(nameBuf));

					// Look for the second part, aka first name
					if ((npos = getFirstSpacePos(text + cpos + 1)) != -1)
					{
						// Store callsign
						memcpy(buffer, text, cpos);
						buffer[cpos] = 0;

						// Store 'first name'
						memcpy(nameBuf, (text + cpos + 1), npos);
						nameBuf[npos] = 0;

						snprintf(outputBuf, SCREEN_LINE_BUFFER_SIZE, "%s %s", chomp(buffer), chomp(nameBuf));

						displayPrintCore(0, y, chomp(outputBuf), FONT_SIZE_3, TEXT_ALIGN_CENTER, itemIsSelected);
					}
					else
					{
						// Store callsign
						memcpy(buffer, text, cpos);
						buffer[cpos] = 0;

						memcpy(nameBuf, (text + cpos + 1), strlen(text) - cpos - 1);
						nameBuf[(SCREEN_LINE_BUFFER_SIZE - 1)] = 0;

						snprintf(outputBuf, SCREEN_LINE_BUFFER_SIZE, "%s %s", chomp(buffer), chomp(nameBuf));

						displayPrintCore(0, y, chomp(outputBuf), FONT_SIZE_3, TEXT_ALIGN_CENTER, itemIsSelected);
					}
				}
			}
			else
			{
				// No space found, use a chainsaw
				memcpy(buffer, text, (SCREEN_LINE_BUFFER_SIZE - 1));
				buffer[(SCREEN_LINE_BUFFER_SIZE - 1)] = 0;

				displayPrintCore(0, y, chomp(buffer), FONT_SIZE_3, TEXT_ALIGN_CENTER, itemIsSelected);
			}
		}
		else // short callsign
		{
			memcpy(buffer, text, strlen(text));
			buffer[strlen(text)] = 0;

			displayPrintCore(0, y, chomp(buffer), FONT_SIZE_3, TEXT_ALIGN_CENTER, itemIsSelected);
		}

		if (itemIsSelected && (nonVolatileSettings.audioPromptMode >= AUDIO_PROMPT_MODE_VOICE_THRESHOLD))
		{
			voicePromptsAppendString(chomp(buffer));
			voicePromptsAppendString("  ");

			snprintf(buffer, 37, "%u ", tg);
			if (isPC)
			{
				voicePromptsAppendLanguageString(currentLanguage->private_call);
				if (tg != trxDMRID)
				{
					voicePromptsAppendString(buffer);
				}
			}
			else
			{
				voicePromptsAppendPrompt(PROMPT_TALKGROUP);
				voicePromptsAppendString(buffer);
			}

			voicePromptsAppendString(timeBuffer);
			if (inHours)
			{
				voicePromptsAppendLanguageString(currentLanguage->hours);
			}
			else
			{
				voicePromptsAppendPrompt(PROMPT_MINUTES);
			}
			voicePromptsAppendString("   ");// Add some blank sound at the end of the callsign, to allow time for follow-on scrolling
		}
	}
	else
	{
		char buffer[SCREEN_LINE_BUFFER_SIZE];
		bool displayTS = (item->dmrMode == DMR_MODE_RMO);
		int16_t yTimeUnit =
#if defined(PLATFORM_RD5R)
				y;
#else
				(y + 6);
#endif

		snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%u", tg);

		displayPrintCore(0, y, (isPC ? currentLanguage->pc : currentLanguage->tg), FONT_SIZE_3, TEXT_ALIGN_LEFT, itemIsSelected);
		displayPrintCore((2 * 8) + 4, y, buffer, FONT_SIZE_3, TEXT_ALIGN_LEFT, itemIsSelected);

		// Display TS (if in RMO), in a reverse video box.
		if (displayTS)
		{
			int16_t yTS =
#if defined(PLATFORM_RD5R)
					y;
#else
					(y + 5);
#endif

			snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%d", (item->receivedTS + 1));

			displayFillRect((DISPLAY_SIZE_X - (1 * 6) - (4 * 8) - 3), yTS - 1, (6 + 1), 9, itemIsSelected);
			displayPrintCore((DISPLAY_SIZE_X - (1 * 6) - (4 * 8) - 2), yTS, buffer, FONT_SIZE_1, TEXT_ALIGN_LEFT, !itemIsSelected);

		}

		displayPrintCore((DISPLAY_SIZE_X - (strlen(timeBuffer) * 8) - (1 * 6) - 1), y, timeBuffer, FONT_SIZE_3, TEXT_ALIGN_LEFT, itemIsSelected);

		// Time unit is displayed using the smallest font
		// 'm' for minutes is not SI, but using 3 characters "min" won't fit the screen
		displayPrintCore((DISPLAY_SIZE_X - (1 * 6)), yTimeUnit, (inHours ? "h" : "m"), FONT_SIZE_1, TEXT_ALIGN_LEFT, itemIsSelected);
	}

	if (isFirstRun)
	{
		promptsPlayNotAfterTx();
	}
}
