/*
 * Copyright (C) 2019      Kai Ludwig, DG4KLU
 * 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 <stdint.h>
#include <stdio.h>
#include "functions/codeplug.h"
#include "hardware/EEPROM.h"
#include "hardware/SPI_Flash.h"
#include "functions/trx.h"
#include "usb/usb_com.h"
#include "user_interface/uiLocalisation.h"
#include "user_interface/uiGlobals.h"
#include "interfaces/settingsStorage.h"


const int CODEPLUG_ADDR_EX_ZONE_BASIC = 0x8000;
const int CODEPLUG_ADDR_EX_ZONE_INUSE_PACKED_DATA = 0x8010;
#define CODEPLUG_EX_ZONE_INUSE_PACKED_DATA_SIZE  32
const int CODEPLUG_ADDR_EX_ZONE_LIST = 0x8030;

const int CODEPLUG_ZONE_MAX_COUNT = 250;
const int CODEPLUG_ADDR_CHANNEL_EEPROM = 0x3790;
const int CODEPLUG_ADDR_CHANNEL_HEADER_EEPROM = 0x3780; // CODEPLUG_ADDR_CHANNEL_EEPROM - 16
const int CODEPLUG_ADDR_CHANNEL_FLASH = 0x7B1C0;
const int CODEPLUG_ADDR_CHANNEL_HEADER_FLASH = 0x7B1B0; // CODEPLUG_ADDR_CHANNEL_FLASH - 16

const int CODEPLUG_ADDR_SIGNALLING_DTMF = 0x1400;
const int CODEPLUG_ADDR_SIGNALLING_DTMF_DURATIONS = (CODEPLUG_ADDR_SIGNALLING_DTMF + 0x72); // offset to grab the DTMF durations
const int CODEPLUG_SIGNALLING_DTMF_DURATIONS_SIZE = 4;

const int CODEPLUG_ADDR_RX_GROUP_LEN = 0x8D620;  // 76 TG lists
const int CODEPLUG_ADDR_RX_GROUP = 0x8D6A0;//

const int CODEPLUG_ADDR_CONTACTS = 0x87620;

const int CODEPLUG_ADDR_DTMF_CONTACTS = 0x02f88;

const int CODEPLUG_ADDR_USER_DMRID = 0x00E8;
const int CODEPLUG_ADDR_USER_CALLSIGN = 0x00E0;

const int CODEPLUG_ADDR_GENERAL_SETTINGS = 0x00E0;

const int CODEPLUG_ADDR_BOOT_INTRO_SCREEN = 0x7518;// 0x01 = Chars 0x00 = Picture
const int CODEPLUG_ADDR_BOOT_PASSWORD_ENABLE= 0x7519;// 0x00 = password disabled 0x01 = password enable
const int CODEPLUG_ADDR_BOOT_PASSWORD_AREA = 0x751C;// Seems to be 3 bytes coded as BCD e.f. 0x12 0x34 0x56
const int CODEPLUG_BOOT_PASSWORD_LEN = 3;
const int CODEPLUG_ADDR_BOOT_LINE1 = 0x7540;
const int CODEPLUG_ADDR_BOOT_LINE2 = 0x7550;
const int CODEPLUG_ADDR_VFO_A_CHANNEL = 0x7590;
const int CPS_CODEPLUG_SECTION_2_START = 0x7500;

const int CODEPLUG_ADDR_APRS_CONFIGS = 0x1588;// Address of start of APRS configs (formerly Emergency Systems)

int codeplugChannelsPerZone = 16;

const int VFO_FREQ_STEP_TABLE[8] = {250,500,625,1000,1250,2500,3000,5000};
const int VFO_SWEEP_SCAN_RANGE_SAMPLE_STEP_TABLE[7] = {78,157,313,625, 1250,2500,5000};


const int CODEPLUG_MAX_VARIABLE_SQUELCH = 21U;
const int CODEPLUG_MIN_VARIABLE_SQUELCH = 1U;

const int CODEPLUG_MIN_PER_CHANNEL_POWER  = 1;

const int CODEPLUG_ADDR_DEVICE_INFO = 0x80;
const int CODEPLUG_ADDR_DEVICE_INFO_READ_SIZE = 96;// (sizeof struct_codeplugDeviceInfo_t)

const int CODEPLUG_ADDR_BOOT_PASSWORD_PIN = 0x7518;

const uint16_t APRS_MAGIC_VERSION = 0x4153;


static uint16_t allChannelsTotalNumOfChannels = 0;
static uint16_t allChannelsHighestChannelIndex = 0;

typedef struct
{
	uint32_t tgOrPCNum;
	uint16_t index;
} codeplugContactCache_t;


typedef struct
{
	uint8_t index;
} codeplugDTMFContactCache_t;

typedef struct
{
	int numTGContacts;
	int numPCContacts;
	int numALLContacts;
	int numDTMFContacts;
	codeplugContactCache_t contactsLookupCache[CODEPLUG_CONTACTS_MAX];
	codeplugDTMFContactCache_t contactsDTMFLookupCache[CODEPLUG_DTMF_CONTACTS_MAX];
} codeplugContactsCache_t;

typedef struct
{
	int 	numOfConfigs;
} codeplugAPRSConfigsCache_t;

typedef struct
{
	int dataType;
	int dataLength;
} codeplugCustomDataBlockHeader_t;

__attribute__((section(".data.$RAM2"))) codeplugContactsCache_t codeplugContactsCache;

__attribute__((section(".data.$RAM2"))) uint8_t codeplugRXGroupCache[CODEPLUG_RX_GROUPLIST_MAX];
__attribute__((section(".data.$RAM2"))) uint8_t codeplugAllChannelsCache[128];
__attribute__((section(".data.$RAM2"))) uint8_t codeplugZonesInUseCache[CODEPLUG_EX_ZONE_INUSE_PACKED_DATA_SIZE];
__attribute__((section(".data.$RAM2"))) uint16_t quickKeysCache[CODEPLUG_QUICKKEYS_SIZE];

__attribute__((section(".data.$RAM2"))) uint8_t lastUsedChannelInZoneData[CODEPLUG_ALL_ZONES_MAX + 1]; // All zones (0..79) + AllChannel 0..1023 (hence one extra byte to store this value)
static bool lastUsedChannelInZoneHasChanged = false;

__attribute__((section(".data.$RAM2"))) codeplugAPRSConfigsCache_t codeplugAPRSCache;

static bool codeplugContactGetReserve1ByteForIndex(int index, CodeplugContact_t *contact);

static uint32_t byteSwap32(uint32_t n)
{
    return ((((n) & 0x000000FFU) << 24U) | (((n) & 0x0000FF00U) << 8U) | (((n) & 0x00FF0000U) >> 8U) | (((n) & 0xFF000000U) >> 24U));// from usb_misc.h
}

#if 0
uint16_t byteSwap16(uint16_t n)
{
    return (((n) & 0x00FFU) << 8U) | (((n) & 0xFF00U) >> 8U);
}
#endif

// BCD encoding to integer conversion
uint32_t bcd2int(uint32_t i)
{
    uint32_t result = 0;
    int multiplier = 1;
    while (i)
    {
        result += (i & 0x0f) * multiplier;
        multiplier *= 10;
        i = i >> 4;
    }
    return result;
}

// Needed to convert the legacy DMR ID data which uses BCD encoding for the DMR ID numbers
uint32_t int2bcd(uint32_t i)
{
	uint32_t result = 0;
    int shift = 0;

    while (i)
    {
        result +=  (i % 10) << shift;
        i = i / 10;
        shift += 4;
    }
    return result;
}

uint16_t bcd2uint16(uint16_t i)
{
    int result = 0;
    int multiplier = 1;
    while (i)
    {
        result += (i & 0x0f) * multiplier;
        multiplier *= 10;
        i = i >> 4;
    }
    return result;
}

CodeplugCSSTypes_t codeplugGetCSSType(uint16_t tone)
{
	if ((tone == CODEPLUG_CSS_TONE_NONE) || (tone == 0x0))
	{
		return CSS_TYPE_NONE;
	}
	else if((tone != CODEPLUG_CSS_TONE_NONE) && (tone != 0x0))
	{
		if ((tone & CSS_TYPE_DCS_MASK) == 0)
		{
			return CSS_TYPE_CTCSS;
		}
		else if (tone & CSS_TYPE_DCS_INVERTED)
		{
			return (CSS_TYPE_DCS | CSS_TYPE_DCS_INVERTED);
		}

		return CSS_TYPE_DCS;
	}

	return CSS_TYPE_NONE;
}

// Converts a codeplug coded-squelch system value to int (with flags if DCS)
uint16_t codeplugCSSToInt(uint16_t tone)
{
	CodeplugCSSTypes_t type = codeplugGetCSSType(tone);

	// Only the CTCSS needs convertion
	if (type == CSS_TYPE_CTCSS)
	{
		return bcd2int(tone);
	}

	return ((tone == 0x0) ? CODEPLUG_CSS_TONE_NONE : tone);
}

// Converts an int (with flags if DCS) to codeplug coded-squelch system value
uint16_t codeplugIntToCSS(uint16_t tone)
{
	CodeplugCSSTypes_t type = codeplugGetCSSType(tone);

	// Only the CTCSS needs convertion
	if (type == CSS_TYPE_CTCSS)
	{
		return int2bcd(tone);
	}

	return ((tone == 0x0) ? CODEPLUG_CSS_TONE_NONE : tone);
}

void codeplugUtilConvertBufToString(char *codeplugBuf, char *outBuf, int len)
{
	for(int i = 0; i < len; i++)
	{
		if (codeplugBuf[i] == 0xff)
		{
			codeplugBuf[i] = 0;
		}
		outBuf[i] = codeplugBuf[i];
	}
	outBuf[len] = 0;
}

void codeplugUtilConvertStringToBuf(char *inBuf, char *outBuf, int len)
{
	memset(outBuf,0xff,len);
	for (int i = 0; i < len; i++)
	{
		if (inBuf[i] == 0x00)
		{
			break;
		}
		outBuf[i] = inBuf[i];
	}
}

void codeplugZonesInitCache(void)
{
	EEPROM_Read(CODEPLUG_ADDR_EX_ZONE_INUSE_PACKED_DATA, (uint8_t *)&codeplugZonesInUseCache, CODEPLUG_EX_ZONE_INUSE_PACKED_DATA_SIZE);
}

int codeplugZonesGetCount(void)
{
	int numZones = 1;// Add one extra zone to allow for the special 'All Channels' Zone

	for(int i = 0; i < CODEPLUG_EX_ZONE_INUSE_PACKED_DATA_SIZE; i++)
	{
		numZones += __builtin_popcount(codeplugZonesInUseCache[i]);
	}

	return numZones;
}

bool codeplugZoneGetDataForNumber(int zoneNum, CodeplugZone_t *returnBuf)
{
	if (zoneNum == (codeplugZonesGetCount() - 1)) //special case: return a special Zone called 'All Channels'
	{
		int nameLen = SAFE_MIN(((int)sizeof(returnBuf->name)), ((int)strlen(currentLanguage->all_channels)));

		// Codeplug name is 0xff filled, codeplugUtilConvertBufToString() handles the conversion
		memset(returnBuf->name, 0xff, sizeof(returnBuf->name));
		memcpy(returnBuf->name, currentLanguage->all_channels, nameLen);

		// set all channels to zero, All Channels is handled separately
		memset(returnBuf->channels, 0, codeplugChannelsPerZone);

		returnBuf->NOT_IN_CODEPLUGDATA_numChannelsInZone = allChannelsTotalNumOfChannels;
		returnBuf->NOT_IN_CODEPLUGDATA_highestIndex = allChannelsHighestChannelIndex;
		returnBuf->NOT_IN_CODEPLUGDATA_indexNumber = -1;// Set as -1 as this is not a real zone. Its the "All Channels" zone
		return true;
	}
	else
	{
		// Need to find the index into the Zones data for the specific Zone number.
		// Because the Zones data is not guaranteed to be packed by the CPS (though we should attempt to make the CPS always pack the Zones)
		int count = -1;// Need to start counting at -1 because the Zone number is zero indexed
		int foundIndex = -1;

		// Go though each byte in the In Use table
		for(int i = 0; i < CODEPLUG_EX_ZONE_INUSE_PACKED_DATA_SIZE; i++)
		{
			// Go though each binary bit, counting them one by one
			for(int j = 0; j < 8; j++)
			{
				if (((codeplugZonesInUseCache[i] >> j) & 0x01) == 0x01)
				{
					count++;

					if (count == zoneNum)
					{
						// found it. So save the index before we exit the "for" loops
						foundIndex = (i * 8) + j;
						break;// Will break out of this loop, but the outer loop breaks because it also checks for foundIndex
					}
				}
			}
		}

		if (foundIndex != -1)
		{
			// Save this in case we need to add channels to a zone and hence need the index number so it can be saved back to the codeplug memory
			returnBuf->NOT_IN_CODEPLUGDATA_indexNumber = foundIndex;

			// IMPORTANT. Write size is different from the size of the data, because it the zone struct contains properties not in the codeplug data
			EEPROM_Read(CODEPLUG_ADDR_EX_ZONE_LIST + (foundIndex * (16 + (sizeof(uint16_t) * codeplugChannelsPerZone))),
					(uint8_t *)returnBuf, ((codeplugChannelsPerZone == 16) ? CODEPLUG_ZONE_DATA_ORIGINAL_STRUCT_SIZE : CODEPLUG_ZONE_DATA_OPENGD77_STRUCT_SIZE));


			for(int i = 0; i < codeplugChannelsPerZone; i++)
			{
				// Empty channels seem to be filled with zeros, and zone could be full of channels.
				if ((returnBuf->channels[i] == 0) || (i == (codeplugChannelsPerZone - 1)))
				{
					returnBuf->NOT_IN_CODEPLUGDATA_highestIndex = returnBuf->NOT_IN_CODEPLUGDATA_numChannelsInZone = (i + ((returnBuf->channels[i] == 0) ? 0 : 1));
					return true;
				}
			}
		}

		memset(returnBuf->channels, 0, codeplugChannelsPerZone);
		returnBuf->NOT_IN_CODEPLUGDATA_highestIndex = returnBuf->NOT_IN_CODEPLUGDATA_numChannelsInZone = 0;
		returnBuf->NOT_IN_CODEPLUGDATA_indexNumber = -2; // we could not use '-1' on error, as -1 is All Channel zone
	}

	return false;
}

bool codeplugZoneAddChannelToZoneAndSave(int channelIndex, CodeplugZone_t *zoneBuf)
{
	if ((zoneBuf->NOT_IN_CODEPLUGDATA_numChannelsInZone < codeplugChannelsPerZone) && (zoneBuf->NOT_IN_CODEPLUGDATA_indexNumber != -1))
	{
		zoneBuf->channels[zoneBuf->NOT_IN_CODEPLUGDATA_numChannelsInZone++] = channelIndex;// add channel to zone, and increment numb channels in zone
		zoneBuf->NOT_IN_CODEPLUGDATA_highestIndex = zoneBuf->NOT_IN_CODEPLUGDATA_numChannelsInZone;

		// IMPORTANT. Write size is different from the size of the data, because it the zone struct contains properties not in the codeplug data
		return EEPROM_Write(CODEPLUG_ADDR_EX_ZONE_LIST + (zoneBuf->NOT_IN_CODEPLUGDATA_indexNumber * (16 + (sizeof(uint16_t) * codeplugChannelsPerZone))),
				(uint8_t *)zoneBuf, ((codeplugChannelsPerZone == 16) ? CODEPLUG_ZONE_DATA_ORIGINAL_STRUCT_SIZE : CODEPLUG_ZONE_DATA_OPENGD77_STRUCT_SIZE));
	}

	return false;
}

bool codeplugZoneRemoveChannelFromZoneAndSave(int channelIndex, CodeplugZone_t *zoneBuf)
{
	// Not valid for the virtual "All Channels" zone (indexNumber -1): its membership
	// is the channel in-use bitmap, not a stored channels[] list.
	if (zoneBuf->NOT_IN_CODEPLUGDATA_indexNumber < 0)
	{
		return false;
	}

	// Find the channel in the zone's list.
	int found = -1;
	for (int i = 0; i < zoneBuf->NOT_IN_CODEPLUGDATA_numChannelsInZone; i++)
	{
		if (zoneBuf->channels[i] == channelIndex)
		{
			found = i;
			break;
		}
	}

	if (found == -1)
	{
		return false; // channel is not a member of this zone
	}

	// Shift the remaining channels down to close the gap, then zero the freed tail slot
	// (a zero also terminates the list for codeplugZoneGetDataForNumber()).
	for (int i = found; i < (zoneBuf->NOT_IN_CODEPLUGDATA_numChannelsInZone - 1); i++)
	{
		zoneBuf->channels[i] = zoneBuf->channels[i + 1];
	}
	zoneBuf->channels[zoneBuf->NOT_IN_CODEPLUGDATA_numChannelsInZone - 1] = 0;
	zoneBuf->NOT_IN_CODEPLUGDATA_numChannelsInZone--;
	zoneBuf->NOT_IN_CODEPLUGDATA_highestIndex = zoneBuf->NOT_IN_CODEPLUGDATA_numChannelsInZone;

	// IMPORTANT. Write size is different from the size of the data (see the add function).
	return EEPROM_Write(CODEPLUG_ADDR_EX_ZONE_LIST + (zoneBuf->NOT_IN_CODEPLUGDATA_indexNumber * (16 + (sizeof(uint16_t) * codeplugChannelsPerZone))),
			(uint8_t *)zoneBuf, ((codeplugChannelsPerZone == 16) ? CODEPLUG_ZONE_DATA_ORIGINAL_STRUCT_SIZE : CODEPLUG_ZONE_DATA_OPENGD77_STRUCT_SIZE));
}

static uint16_t codeplugAllChannelsGetCount(void)
{
	uint16_t c = 0;

	//                         v-- This is a bit verbose, but it does make it clear
	for (uint16_t i = 0; i < ((CODEPLUG_CHANNELS_BANKS_MAX * CODEPLUG_CHANNELS_PER_BANK) / 8); i++)
	{
		c += __builtin_popcount(codeplugAllChannelsCache[i]);
	}

	return c;
}

static bool codeplugAllChannelsReadHeaderBank(int channelBank, uint8_t *bitArray)
{
	if(channelBank == 0)
	{
		return EEPROM_Read(CODEPLUG_ADDR_CHANNEL_HEADER_EEPROM, bitArray, 16);
	}

	return SPI_Flash_read(FLASH_ADDRESS_OFFSET + CODEPLUG_ADDR_CHANNEL_HEADER_FLASH + ((channelBank - 1) *
			(CODEPLUG_CHANNELS_PER_BANK * CODEPLUG_CHANNEL_DATA_STRUCT_SIZE + 16)), bitArray, 16);
}

bool codeplugAllChannelsIndexIsInUse(int index)
{
	if ((index >= CODEPLUG_CHANNELS_MIN) && (index <= CODEPLUG_CHANNELS_MAX))
	{
		index--;
		return (((codeplugAllChannelsCache[index / 8] >> (index % 8)) & 0x01) != 0);
	}

	return false;
}

void codeplugAllChannelsIndexSetUsed(int index)
{
	if ((index >= CODEPLUG_CHANNELS_MIN) && (index <= CODEPLUG_CHANNELS_MAX))
	{
		index--;
		int channelBank = (index / CODEPLUG_CHANNELS_PER_BANK);
		int byteno = (index % CODEPLUG_CHANNELS_PER_BANK) / 8;
		int cacheOffset = index / 8;

		codeplugAllChannelsCache[cacheOffset] |= (1 << (index % 8));

		if(channelBank == 0)
		{
			EEPROM_Write(CODEPLUG_ADDR_CHANNEL_HEADER_EEPROM + byteno, &codeplugAllChannelsCache[cacheOffset], 1);
		}
		else
		{
			SPI_Flash_write(FLASH_ADDRESS_OFFSET + (CODEPLUG_ADDR_CHANNEL_HEADER_FLASH + ((channelBank - 1) *
					(CODEPLUG_CHANNELS_PER_BANK * CODEPLUG_CHANNEL_DATA_STRUCT_SIZE + 16))) + byteno, &codeplugAllChannelsCache[cacheOffset], 1);
		}

		allChannelsTotalNumOfChannels++;
		if ((index + 1) > allChannelsHighestChannelIndex)
		{
			allChannelsHighestChannelIndex = (index + 1);
		}
	}
}

void codeplugAllChannelsIndexSetUnused(int index)
{
	if ((index >= CODEPLUG_CHANNELS_MIN) && (index <= CODEPLUG_CHANNELS_MAX))
	{
		index--;
		int channelBank = (index / CODEPLUG_CHANNELS_PER_BANK);
		int byteno = (index % CODEPLUG_CHANNELS_PER_BANK) / 8;
		int cacheOffset = index / 8;

		codeplugAllChannelsCache[cacheOffset] &= ~(1 << (index % 8));

		if(channelBank == 0)
		{
			EEPROM_Write(CODEPLUG_ADDR_CHANNEL_HEADER_EEPROM + byteno, &codeplugAllChannelsCache[cacheOffset], 1);
		}
		else
		{
			SPI_Flash_write(FLASH_ADDRESS_OFFSET + (CODEPLUG_ADDR_CHANNEL_HEADER_FLASH + ((channelBank - 1) *
					(CODEPLUG_CHANNELS_PER_BANK * CODEPLUG_CHANNEL_DATA_STRUCT_SIZE + 16))) + byteno, &codeplugAllChannelsCache[cacheOffset], 1);
		}

		if (allChannelsTotalNumOfChannels > 0)
		{
			allChannelsTotalNumOfChannels--;
		}

		// If we just freed the highest-numbered channel, rescan for the new highest.
		if ((index + 1) >= allChannelsHighestChannelIndex)
		{
			allChannelsHighestChannelIndex = 0;
			for (int i = CODEPLUG_CHANNELS_MAX; i >= CODEPLUG_CHANNELS_MIN; i--)
			{
				if (codeplugAllChannelsIndexIsInUse(i))
				{
					allChannelsHighestChannelIndex = i;
					break;
				}
			}
		}
	}
}

void codeplugAllChannelsInitCache(void)
{
	// There are 8 banks
	for (uint16_t bank = 0; bank < CODEPLUG_CHANNELS_BANKS_MAX; bank++)
	{
		// Of 128 channels
		codeplugAllChannelsReadHeaderBank(bank, &codeplugAllChannelsCache[bank * 16]);
	}

	allChannelsHighestChannelIndex = 0;
	for (uint16_t index = CODEPLUG_CHANNELS_MAX; index >= CODEPLUG_CHANNELS_MIN ; index--)
	{
		if (codeplugAllChannelsIndexIsInUse(index))
		{
			allChannelsHighestChannelIndex = index;
			break;
		}
	}

	allChannelsTotalNumOfChannels = codeplugAllChannelsGetCount();
}

uint32_t codeplugChannelGetOptionalDMRID(CodeplugChannel_t *channelBuf)
{
	uint32_t retVal = 0;

	if (codeplugChannelGetFlag(channelBuf, CHANNEL_FLAG_OPTIONAL_DMRID) != 0)
	{
		retVal = ((channelBuf->rxSignaling << 16) | (channelBuf->artsInterval << 8) | channelBuf->encrypt);
	}

	return retVal;
}

void codeplugChannelSetOptionalDMRID(CodeplugChannel_t *channelBuf, uint32_t dmrID)
{
	uint32_t tmpID = 0x00001600;
	bool dmrIDIsValid = ((dmrID >= MIN_TG_OR_PC_VALUE) && (dmrID <= MAX_TG_OR_PC_VALUE));

	//  Default values are:
	//
	//	rxSignaling = 0x00  e.g. SignalingSystemE.Off
	//  artsInterval = 22 = 0x16
	//  encrypt = 0x00;
	//

	// Set DMRId and flag, if valid.
	if (dmrIDIsValid)
	{
		tmpID = dmrID;
	}

	codeplugChannelSetFlag(channelBuf, CHANNEL_FLAG_OPTIONAL_DMRID, (dmrIDIsValid ? 1 : 0));

	channelBuf->rxSignaling = (tmpID >> 16) & 0xFF;
	channelBuf->artsInterval = (tmpID >> 8) & 0xFF;
	channelBuf->encrypt = tmpID & 0xFF;
}

// -----------------------------------
// -- Codeplug channel flag helpers --
// -----------------------------------
typedef uint8_t *(*flagGetter_t)(CodeplugChannel_t *chan);
static uint8_t *_getLDMRFlag1(CodeplugChannel_t *chan)
{
	return &chan->LibreDMR_flag1;
}
static uint8_t *_getFlag2(CodeplugChannel_t *chan)
{
	return &chan->flag2;
}
static uint8_t *_getFlag3(CodeplugChannel_t *chan)
{
	return &chan->flag3;
}
static uint8_t *_getFlag4(CodeplugChannel_t *chan)
{
	return &chan->flag4;
}

struct
{
		flagGetter_t getter;
		uint8_t      mask;
		uint8_t      shift;
} flagAccessor[] = // The order of this array HAS to follow ChannelFlag_t enum
{
		// LibreDMR_flag1
		{ _getLDMRFlag1, CODEPLUG_CHANNEL_LIBREDMR_FLAG1_OPTIONAL_DMRID,      7 }, // CHANNEL_FLAG_OPTIONAL_DMRID
		{ _getLDMRFlag1, CODEPLUG_CHANNEL_LIBREDMR_FLAG1_NO_BEEP,             6 }, // CHANNEL_FLAG_NO_BEEP,
		{ _getLDMRFlag1, CODEPLUG_CHANNEL_LIBREDMR_FLAG1_NO_ECO,              5 }, // CHANNEL_FLAG_NO_ECO,
		{ _getLDMRFlag1, CODEPLUG_CHANNEL_LIBREDMR_FLAG1_OUT_OF_BAND,         4 }, // CHANNEL_FLAG_OUT_OF_BAND,
		{ _getLDMRFlag1, CODEPLUG_CHANNEL_LIBREDMR_FLAG1_USE_LOCATION,        3 }, // CHANNEL_FLAG_USE_LOCATION
		{ _getLDMRFlag1, CODEPLUG_CHANNEL_LIBREDMR_FLAG1_FORCE_DMO,           2 }, // CHANNEL_FLAG_FORCE_DMO
		{ _getLDMRFlag1, CODEPLUG_CHANNEL_LIBREDMR_FLAG1_ROAMING,             0 }, // CHANNEL_FLAG_ROAMING
		// flag2
		{ _getFlag2,     CODEPLUG_CHANNEL_FLAG2_TIMESLOT_TWO,                 6 }, // CHANNEL_FLAG_TIMESLOT_TWO,
		// flag3
		{ _getFlag3,     CODEPLUG_CHANNEL_FLAG3_STE,                          6 }, // CHANNEL_FLAG_STE,
		{ _getFlag3,     CODEPLUG_CHANNEL_FLAG3_NON_STE,                      5 }, // CHANNEL_FLAG_NON_STE,
		// flag4
		{ _getFlag4,     CODEPLUG_CHANNEL_FLAG4_POWER,                        7 }, // CHANNEL_FLAG_POWER,
		{ _getFlag4,     CODEPLUG_CHANNEL_FLAG4_VOX,                          6 }, // CHANNEL_FLAG_VOX,
		{ _getFlag4,     CODEPLUG_CHANNEL_FLAG4_ZONE_SKIP,                    5 }, // CHANNEL_FLAG_ZONE_SKIP,
		{ _getFlag4,     CODEPLUG_CHANNEL_FLAG4_ALL_SKIP,                     4 }, // CHANNEL_FLAG_ALL_SKIP,
		// Talkaround is unused (bit #3)
		{ _getFlag4,     CODEPLUG_CHANNEL_FLAG4_RX_ONLY,                      2 }, // CHANNEL_FLAG_RX_ONLY,
		{ _getFlag4,     CODEPLUG_CHANNEL_FLAG4_BW_25K,                       1 }, // CHANNEL_FLAG_BW_25K,
		{ _getFlag4,     CODEPLUG_CHANNEL_FLAG4_SQUELCH,                      0 }  // CHANNEL_FLAG_SQUELCH,
};

//
// Returns 0, 1 or the value (e.g. CODEPLUG_CHANNEL_FLAG3_STE), right shifted
//
uint8_t codeplugChannelGetFlag(CodeplugChannel_t *channelBuf, ChannelFlag_t flag)
{
	return ((*flagAccessor[flag].getter(channelBuf) & flagAccessor[flag].mask) >> flagAccessor[flag].shift);
}

//
// Returns unshifted value
//
uint8_t codeplugChannelSetFlag(CodeplugChannel_t *channelBuf, ChannelFlag_t flag, uint8_t value)
{
	uint8_t *data = flagAccessor[flag].getter(channelBuf);

	*data &= ~flagAccessor[flag].mask;

	if (value)
	{
		*data |= (value << flagAccessor[flag].shift);
	}

	return value;
}
// ----------------------------------------
// -- END: Codeplug channel flag helpers --
// ----------------------------------------

void codeplugChannelGetDataWithOffsetAndLengthForIndex(int index, CodeplugChannel_t *channelBuf, uint8_t offset, int length)
{
	// lower 128 channels are in EEPROM. Remaining channels are in Flash ! (What a mess...)
	index--; // I think the channel index numbers start from 1 not zero.
	if (index < 128)
	{
		EEPROM_Read((CODEPLUG_ADDR_CHANNEL_EEPROM + index * CODEPLUG_CHANNEL_DATA_STRUCT_SIZE) + offset, ((uint8_t *)channelBuf) + offset, length);
	}
	else
	{
		int flashReadPos = CODEPLUG_ADDR_CHANNEL_FLASH;

		index -= 128;// First 128 channels are in the EEPROM, so subtract 128 from the number when looking in the Flash

		// Every 128 bytes there seem to be 16 bytes gaps. I don't know why,bits since 16*8 = 128 bits, its likely they are flag bytes to indicate which channel in the next block are in use
		flashReadPos += 16 * (index / 128);// we just need to skip over that these flag bits when calculating the position of the channel data in memory

		SPI_Flash_read(FLASH_ADDRESS_OFFSET + (flashReadPos + index * CODEPLUG_CHANNEL_DATA_STRUCT_SIZE) + offset, ((uint8_t *)channelBuf) + offset, length);
	}
}

void codeplugChannelGetDataForIndex(int index, CodeplugChannel_t *channelBuf)
{
	// Read the whole channel
	codeplugChannelGetDataWithOffsetAndLengthForIndex(index, channelBuf, 0, CODEPLUG_CHANNEL_DATA_STRUCT_SIZE);

	channelBuf->chMode = (channelBuf->chMode == 0) ? RADIO_MODE_ANALOG : RADIO_MODE_DIGITAL;
	// Convert legacy codeplug tx and rx freq values into normal integers
	channelBuf->txFreq = bcd2int(channelBuf->txFreq);
	channelBuf->rxFreq = bcd2int(channelBuf->rxFreq);
	channelBuf->txTone = codeplugCSSToInt(channelBuf->txTone);
	channelBuf->rxTone = codeplugCSSToInt(channelBuf->rxTone);

	channelBuf->NOT_IN_CODEPLUG_flag = 0x00;
	channelBuf->NOT_IN_CODEPLUG_CALCULATED_DISTANCE_X10 = -1;

	// Sanity check the sql value, because its not used by the official firmware and may contain random value e.g. 255
	if (channelBuf->sql > 21)
	{
		channelBuf->sql = 10;
	}
	/* 2020.10.27 vk3kyy - I don't think this is necessary as the function which loads the contact treats index = 0 as a special case and always loads TG 9
	 *
	// Sanity check the digital contact and set it to 1 is its not been assigned, even for FM channels, as the user could switch to DMR on this channel
	if (channelBuf->contact == 0)
	{
		channelBuf->contact = 1;
	}*/
}

bool codeplugChannelSaveDataForIndex(int index, CodeplugChannel_t *channelBuf)
{
	bool retVal = true;
#if defined(PLATFORM_MD9600)
	bool outOfBandFlag = ((channelBuf->LibreDMR_flag1 & CODEPLUG_CHANNEL_LIBREDMR_FLAG1_OUT_OF_BAND) != 0);

	// Clear the out of band flag, blindly.
	channelBuf->LibreDMR_flag1 &= ~CODEPLUG_CHANNEL_LIBREDMR_FLAG1_OUT_OF_BAND;
#endif

	channelBuf->chMode = (channelBuf->chMode == RADIO_MODE_ANALOG) ? 0 : 1;
	// Convert normal integers into legacy codeplug tx and rx freq values
	channelBuf->txFreq = int2bcd(channelBuf->txFreq);
	channelBuf->rxFreq = int2bcd(channelBuf->rxFreq);
	channelBuf->txTone = codeplugIntToCSS(channelBuf->txTone);
	channelBuf->rxTone = codeplugIntToCSS(channelBuf->rxTone);

	// lower 128 channels are in EEPROM. Remaining channels are in Flash ! (What a mess...)
	index--; // I think the channel index numbers start from 1 not zero.
	if (index < 128)
	{
		retVal = EEPROM_Write(CODEPLUG_ADDR_CHANNEL_EEPROM + index * CODEPLUG_CHANNEL_DATA_STRUCT_SIZE, (uint8_t *)channelBuf, CODEPLUG_CHANNEL_DATA_STRUCT_SIZE);
	}
	else
	{
		int flashWritePos = CODEPLUG_ADDR_CHANNEL_FLASH;
		int flashSector;
		int flashEndSector;
		int bytesToWriteInCurrentSector = CODEPLUG_CHANNEL_DATA_STRUCT_SIZE;

		index -= 128;// First 128 channels are in the EEPOM, so subtract 128 from the number when looking in the Flash

		// Every 128 bytes there seem to be 16 bytes gaps. I don't know why,bits since 16*8 = 128 bits, its likely they are flag bytes to indicate which channel in the next block are in use
		flashWritePos += 16 * (index / 128);// we just need to skip over that these flag bits when calculating the position of the channel data in memory
		flashWritePos += index * CODEPLUG_CHANNEL_DATA_STRUCT_SIZE;// go to the position of the specific index

		flashSector 	= flashWritePos / 4096;
		flashEndSector 	= (flashWritePos + CODEPLUG_CHANNEL_DATA_STRUCT_SIZE) / 4096;

		if (flashSector != flashEndSector)
		{
			bytesToWriteInCurrentSector = (flashEndSector * 4096) - flashWritePos;
		}

		SPI_Flash_read(FLASH_ADDRESS_OFFSET + (flashSector * 4096), SPI_Flash_sectorbuffer, 4096);
		uint8_t *writePos = SPI_Flash_sectorbuffer + flashWritePos - (flashSector * 4096);
		memcpy(writePos, channelBuf, bytesToWriteInCurrentSector);

		retVal = SPI_Flash_eraseSector(FLASH_ADDRESS_OFFSET + (flashSector * 4096));
		if (!retVal)
		{
			goto errorExit;
		}

		for (int i = 0; i < 16; i++)
		{
			retVal = SPI_Flash_writePage(FLASH_ADDRESS_OFFSET + (flashSector * 4096) + (i * 256), SPI_Flash_sectorbuffer + (i * 256));
			if (!retVal)
			{
				goto errorExit;
			}
		}

		if (flashSector != flashEndSector)
		{
			uint8_t *channelBufPusOffset = (uint8_t *)channelBuf + bytesToWriteInCurrentSector;
			bytesToWriteInCurrentSector = CODEPLUG_CHANNEL_DATA_STRUCT_SIZE - bytesToWriteInCurrentSector;

			SPI_Flash_read(FLASH_ADDRESS_OFFSET + (flashEndSector * 4096), SPI_Flash_sectorbuffer, 4096);
			memcpy(SPI_Flash_sectorbuffer, (uint8_t *)channelBufPusOffset, bytesToWriteInCurrentSector);

			retVal = SPI_Flash_eraseSector(FLASH_ADDRESS_OFFSET + (flashEndSector * 4096));

			if (!retVal)
			{
				goto errorExit;
			}
			for (int i = 0; i < 16; i++)
			{
				retVal = SPI_Flash_writePage(FLASH_ADDRESS_OFFSET + (flashEndSector * 4096) + (i * 256), SPI_Flash_sectorbuffer + (i * 256));

				if (!retVal)
				{
					goto errorExit;
				}
			}

		}
	}

errorExit:
#if defined(PLATFORM_MD9600)
	if (outOfBandFlag)
	{
		channelBuf->LibreDMR_flag1 |= CODEPLUG_CHANNEL_LIBREDMR_FLAG1_OUT_OF_BAND;
	}
#endif

	// Need to restore the values back to what we need for the operation of the firmware rather than the BCD values the codeplug uses
	channelBuf->chMode = (channelBuf->chMode == 0) ? RADIO_MODE_ANALOG : RADIO_MODE_DIGITAL;
	// Convert the the legacy codeplug tx and rx freq values into normal integers
	channelBuf->txFreq = bcd2int(channelBuf->txFreq);
	channelBuf->rxFreq = bcd2int(channelBuf->rxFreq);
	channelBuf->txTone = codeplugCSSToInt(channelBuf->txTone);
	channelBuf->rxTone = codeplugCSSToInt(channelBuf->rxTone);

	return retVal;
}

static void codeplugRxGroupInitCache(void)
{
	SPI_Flash_read(FLASH_ADDRESS_OFFSET + CODEPLUG_ADDR_RX_GROUP_LEN, (uint8_t*) &codeplugRXGroupCache[0], CODEPLUG_RX_GROUPLIST_MAX);
}

bool codeplugRxGroupGetDataForIndex(int index, CodeplugRxGroup_t *rxGroupBuf)
{
	int i = 0;
	CodeplugContact_t contactData;

	if ((index >= 1) && (index <= CODEPLUG_RX_GROUPLIST_MAX))
	{
		index--; // Index numbers start from 1 not zero

		if (codeplugRXGroupCache[index] > 0)
		{
			// Not our struct contains an extra property to hold the number of TGs in the group
			SPI_Flash_read(FLASH_ADDRESS_OFFSET + CODEPLUG_ADDR_RX_GROUP + (index * CODEPLUG_RXGROUP_DATA_STRUCT_SIZE), (uint8_t *) rxGroupBuf, CODEPLUG_RXGROUP_DATA_STRUCT_SIZE);

			for (i = 0; i < 32; i++)
			{
				codeplugContactGetDataForIndex(rxGroupBuf->contacts[i], &contactData);
				rxGroupBuf->NOT_IN_CODEPLUG_contactsTG[i] = contactData.tgNumber;
				// Empty groups seem to be filled with zeros
				if (rxGroupBuf->contacts[i] == 0)
				{
					break;
				}
			}

			rxGroupBuf->NOT_IN_CODEPLUG_numTGsInGroup = i;
			return true;
		}
	}

	rxGroupBuf->name[0] = 0;
	rxGroupBuf->NOT_IN_CODEPLUG_numTGsInGroup = 0;
	return false;
}

int codeplugDTMFContactsGetCount(void)
{
	return codeplugContactsCache.numDTMFContacts;
}

int codeplugContactsGetCount(uint32_t callType) // 0:TG 1:PC 2:ALL
{
	switch (callType)
	{
		case CONTACT_CALLTYPE_TG:
			return codeplugContactsCache.numTGContacts;
			break;
		case CONTACT_CALLTYPE_PC:
			return codeplugContactsCache.numPCContacts;
			break;
		case CONTACT_CALLTYPE_ALL:
			return codeplugContactsCache.numALLContacts;
			break;
	}

	return 0; // Should not happen
}

// Returns contact's index, or 0 on failure.
int codeplugDTMFContactGetDataForNumber(int number, CodeplugDTMFContact_t *contact)
{
	if ((number >= CODEPLUG_DTMF_CONTACTS_MIN) && (number <= CODEPLUG_DTMF_CONTACTS_MAX))
	{
		if (codeplugDTMFContactGetDataForIndex(codeplugContactsCache.contactsDTMFLookupCache[number - 1].index, contact))
		{
			return codeplugContactsCache.contactsDTMFLookupCache[number - 1].index;
		}
	}

	return 0;
}

// Returns contact's index, or 0 on failure.
int codeplugContactGetDataForNumberInType(int number, uint32_t callType, CodeplugContact_t *contact)
{
	int numContacts = codeplugContactsCache.numTGContacts + codeplugContactsCache.numALLContacts + codeplugContactsCache.numPCContacts;

	for (int i = 0; i < numContacts; i++)
	{
		if ((codeplugContactsCache.contactsLookupCache[i].tgOrPCNum >> 24) == callType)
		{
			number--;
		}

		if (number == 0)
		{
			if (codeplugContactGetDataForIndex(codeplugContactsCache.contactsLookupCache[i].index, contact))
			{
				return codeplugContactsCache.contactsLookupCache[i].index;
			}
		}
	}

	return 0;
}

// optionalTS: 0 = no TS checking, 1..2 = TS
int codeplugContactIndexByTGorPCFromNumber(int number, uint32_t tgorpc, ContactCalltype_t callType, CodeplugContact_t *contact, uint8_t optionalTS)
{
	int numContacts = codeplugContactsCache.numTGContacts + codeplugContactsCache.numALLContacts + codeplugContactsCache.numPCContacts;
	int firstMatch = -1;

	for (int i = number; i < numContacts; i++)
	{
		if (((codeplugContactsCache.contactsLookupCache[i].tgOrPCNum & 0xFFFFFF) == tgorpc) &&
				/* All Call, hence ignore callType */
				((tgorpc == ALL_CALL_VALUE) || ((codeplugContactsCache.contactsLookupCache[i].tgOrPCNum >> 24) == callType)))
		{
			// Check for the contact TS override
			if (optionalTS > 0)
			{
				// Just read the reserve1 byte for now
				codeplugContactGetReserve1ByteForIndex(codeplugContactsCache.contactsLookupCache[i].index, contact);

				if (((contact->reserve1 & CODEPLUG_CONTACT_FLAG_NO_TS_OVERRIDE) == 0x00) && (((contact->reserve1 & CODEPLUG_CONTACT_FLAG_TS_OVERRIDE_TIMESLOT_MASK) >> 1) == (optionalTS - 1)))
				{
					codeplugContactGetDataForIndex(codeplugContactsCache.contactsLookupCache[i].index, contact);
					return i;
				}
				else
				{
					if (firstMatch < 0)
					{
						firstMatch = i;
					}
				}
			}
			else
			{
				codeplugContactGetDataForIndex(codeplugContactsCache.contactsLookupCache[i].index, contact);
				return i;
			}
		}
	}

	if (firstMatch >= 0)
	{
		codeplugContactGetDataForIndex(codeplugContactsCache.contactsLookupCache[firstMatch].index, contact);
		return firstMatch;
	}

	return -1;
}

// optionalTS: 0 = no TS checking, 1..2 = TS
int codeplugContactIndexByTGorPC(uint32_t tgorpc, ContactCalltype_t callType, CodeplugContact_t *contact, uint8_t optionalTS)
{
	return codeplugContactIndexByTGorPCFromNumber(0, tgorpc, callType, contact, optionalTS);
}

bool codeplugContactsContainsPC(uint32_t pc)
{
	int numContacts =  codeplugContactsCache.numTGContacts + codeplugContactsCache.numALLContacts + codeplugContactsCache.numPCContacts;
	pc = pc & 0x00FFFFFF;
	pc = pc | (CONTACT_CALLTYPE_PC << 24);

	for (int i = 0; i < numContacts; i++)
	{
		if (codeplugContactsCache.contactsLookupCache[i].tgOrPCNum == pc)
		{
			return true;
		}
	}
	return false;
}

static void codeplugInitContactsCache(void)
{
	CodeplugContact_t contact;
	uint8_t                  c;
	int                      codeplugNumContacts = 0;

	codeplugContactsCache.numTGContacts = 0;
	codeplugContactsCache.numPCContacts = 0;
	codeplugContactsCache.numALLContacts = 0;
	codeplugContactsCache.numDTMFContacts = 0;

	for(int i = 0; i < CODEPLUG_CONTACTS_MAX; i++)
	{
		if (SPI_Flash_read(FLASH_ADDRESS_OFFSET + (CODEPLUG_ADDR_CONTACTS + (i * CODEPLUG_CONTACT_DATA_SIZE)), (uint8_t *)&contact, 16 + 4 + 1))// Name + TG/ID + Call type
		{
			if (contact.name[0] != 0xFF)
			{
				codeplugContactsCache.contactsLookupCache[codeplugNumContacts].tgOrPCNum = bcd2int(byteSwap32(contact.tgNumber));
				codeplugContactsCache.contactsLookupCache[codeplugNumContacts].index = i + 1;// Contacts are numbered from 1 to 1024
				codeplugContactsCache.contactsLookupCache[codeplugNumContacts].tgOrPCNum |= (contact.callType << 24);// Store the call type in the upper byte
				if (contact.callType == CONTACT_CALLTYPE_PC)
				{
					codeplugContactsCache.numPCContacts++;
				}
				else if (contact.callType == CONTACT_CALLTYPE_TG)
				{
					codeplugContactsCache.numTGContacts++;
				}
				else if (contact.callType == CONTACT_CALLTYPE_ALL)
				{
					codeplugContactsCache.numALLContacts++;
				}

				codeplugNumContacts++;
			}
		}
	}

	for (int i = 0; i < CODEPLUG_DTMF_CONTACTS_MAX; i++)
	{
		if (EEPROM_Read(CODEPLUG_ADDR_DTMF_CONTACTS + (i * CODEPLUG_DTMF_CONTACT_DATA_STRUCT_SIZE), (uint8_t *)&c, 1))
		{
			// Empty DTMF contacts normally begin with 0xFF, but when expanding to use 64 DTMF contacts, the old Zone
			// basic data is in the last contact and this contains 0x00 in the first byte, until the codeplug is updated
			if ((c != 0xFF) && (c != 0x00))
			{
				codeplugContactsCache.contactsDTMFLookupCache[codeplugContactsCache.numDTMFContacts++].index = i + 1; // Contacts are numbered from 1 to 32
			}
		}
	}
}

void codeplugContactsCacheUpdateOrInsertContactAt(int index, CodeplugContact_t *contact)
{
	int numContacts =  codeplugContactsCache.numTGContacts + codeplugContactsCache.numALLContacts + codeplugContactsCache.numPCContacts;
	int numContactsMinus1 = numContacts - 1;

	for(int i = 0; i < numContacts; i++)
	{
		// Check if the contact is already in the cache, and is being modified
		if (codeplugContactsCache.contactsLookupCache[i].index == index)
		{
			uint8_t callType = codeplugContactsCache.contactsLookupCache[i].tgOrPCNum >> 24;// get call type from cache

			if (callType != contact->callType)
			{
				switch (callType)
				{
					case CONTACT_CALLTYPE_TG:
						codeplugContactsCache.numTGContacts--;
						break;
					case CONTACT_CALLTYPE_PC:
						codeplugContactsCache.numPCContacts--;
						break;
					case CONTACT_CALLTYPE_ALL:
						codeplugContactsCache.numALLContacts--;
						break;
				}

				switch (contact->callType)
				{
					case CONTACT_CALLTYPE_TG:
						codeplugContactsCache.numTGContacts++;
						break;
					case CONTACT_CALLTYPE_PC:
						codeplugContactsCache.numPCContacts++;
						break;
					case CONTACT_CALLTYPE_ALL:
						codeplugContactsCache.numALLContacts++;
						break;
				}
			}
			//update the
			codeplugContactsCache.contactsLookupCache[i].tgOrPCNum = bcd2int(byteSwap32(contact->tgNumber));
			codeplugContactsCache.contactsLookupCache[i].tgOrPCNum |= (contact->callType << 24);// Store the call type in the upper byte

			return;
		}
		else
		{
			if((i < numContactsMinus1) && (codeplugContactsCache.contactsLookupCache[i].index < index) && (codeplugContactsCache.contactsLookupCache[i + 1].index > index))
			{
				if (contact->callType == CONTACT_CALLTYPE_PC)
				{
					codeplugContactsCache.numPCContacts++;
				}
				else if (contact->callType == CONTACT_CALLTYPE_TG)
				{
					codeplugContactsCache.numTGContacts++;
				}
				else if (contact->callType == CONTACT_CALLTYPE_ALL)
				{
					codeplugContactsCache.numALLContacts++;
				}

				numContacts++;// Total contacts increases by 1

				// Note . Need to use memmove as the source and destination overlap.
				memmove(&codeplugContactsCache.contactsLookupCache[i + 2], &codeplugContactsCache.contactsLookupCache[i + 1], (numContacts - 2 - i) * sizeof(codeplugContactCache_t));

				codeplugContactsCache.contactsLookupCache[i + 1].tgOrPCNum = bcd2int(byteSwap32(contact->tgNumber));
				codeplugContactsCache.contactsLookupCache[i + 1].index = index;// Contacts are numbered from 1 to 1024
				codeplugContactsCache.contactsLookupCache[i + 1].tgOrPCNum |= (contact->callType << 24);// Store the call type in the upper byte
				return;
			}
		}
	}

	// Did not find the index in the cache or a gap between 2 existing indexes. So the new contact needs to be added to the end of the cache

	if (contact->callType == CONTACT_CALLTYPE_PC)
	{
		codeplugContactsCache.numPCContacts++;
	}
	else if (contact->callType == CONTACT_CALLTYPE_TG)
	{
		codeplugContactsCache.numTGContacts++;
	}
	else if (contact->callType == CONTACT_CALLTYPE_ALL)
	{
		codeplugContactsCache.numALLContacts++;
	}

	// Note. We can use numContacts as the the index as the array is zero indexed but the number of contacts is starts from 1
	// Hence is already in some ways pre incremented in terms of being an array index
	codeplugContactsCache.contactsLookupCache[numContacts].tgOrPCNum = bcd2int(byteSwap32(contact->tgNumber));
	codeplugContactsCache.contactsLookupCache[numContacts].index = index;// Contacts are numbered from 1 to 1024
	codeplugContactsCache.contactsLookupCache[numContacts].tgOrPCNum |= (contact->callType << 24);// Store the call type in the upper byte
}

void codeplugContactsCacheRemoveContactAt(int index)
{
	int numContacts = codeplugContactsCache.numTGContacts + codeplugContactsCache.numALLContacts + codeplugContactsCache.numPCContacts;
	for(int i = 0; i < numContacts; i++)
	{
		if(codeplugContactsCache.contactsLookupCache[i].index == index)
		{
			uint8_t callType = codeplugContactsCache.contactsLookupCache[i].tgOrPCNum >> 24;

			if (callType == CONTACT_CALLTYPE_PC)
			{
				codeplugContactsCache.numPCContacts--;
			}
			else if (callType == CONTACT_CALLTYPE_TG)
			{
				codeplugContactsCache.numTGContacts--;
			}
			else if (callType == CONTACT_CALLTYPE_ALL)
			{
				codeplugContactsCache.numALLContacts--;
			}
			// Note memcpy should work here, because memcpy normally copys from the lowest memory location upwards
			memcpy(&codeplugContactsCache.contactsLookupCache[i], &codeplugContactsCache.contactsLookupCache[i + 1], (numContacts - 1 - i) * sizeof(codeplugContactCache_t));
			return;
		}
	}
}

uint32_t codeplugContactGetPackedId(CodeplugContact_t *contact)
{
	return ((contact->callType == CONTACT_CALLTYPE_PC) ? (contact->tgNumber | (PC_CALL_FLAG << 24)) : contact->tgNumber);
}

int codeplugContactGetFreeIndex(void)
{
	int numContacts = codeplugContactsCache.numTGContacts + codeplugContactsCache.numALLContacts + codeplugContactsCache.numPCContacts;
	int lastIndex = 0;
	int i;

	for (i = 0; i < numContacts; i++)
	{
		if (codeplugContactsCache.contactsLookupCache[i].index != lastIndex + 1)
		{
			return lastIndex + 1;
		}
		lastIndex = codeplugContactsCache.contactsLookupCache[i].index;
	}

	if (i < CODEPLUG_CONTACTS_MAX)
	{
		return codeplugContactsCache.contactsLookupCache[i - 1].index + 1;
	}

	return 0;
}

bool codeplugDTMFContactGetDataForIndex(int index, CodeplugDTMFContact_t *contact)
{
	if ((codeplugContactsCache.numDTMFContacts > 0) &&  (index >= CODEPLUG_DTMF_CONTACTS_MIN) && (index <= CODEPLUG_DTMF_CONTACTS_MAX))
	{
		index--;
		if(EEPROM_Read(CODEPLUG_ADDR_DTMF_CONTACTS + (index * CODEPLUG_DTMF_CONTACT_DATA_STRUCT_SIZE), (uint8_t *)contact, CODEPLUG_DTMF_CONTACT_DATA_STRUCT_SIZE))
		{
			return true;
		}
	}

	memset(contact, 0xff, CODEPLUG_DTMF_CONTACT_DATA_STRUCT_SIZE);
	return false;
}

static bool codeplugContactGetReserve1ByteForIndex(int index, CodeplugContact_t *contact)
{
	if (((codeplugContactsCache.numTGContacts > 0) || (codeplugContactsCache.numPCContacts > 0) || (codeplugContactsCache.numALLContacts > 0)) &&
			(index >= CODEPLUG_CONTACTS_MIN) && (index <= CODEPLUG_CONTACTS_MAX))
	{
		index--;
		SPI_Flash_read(FLASH_ADDRESS_OFFSET + CODEPLUG_ADDR_CONTACTS + (index * CODEPLUG_CONTACT_DATA_SIZE) + 23 , (uint8_t *)contact + 23, 1);
		return true;
	}
	return false;
}

bool codeplugContactGetDataForIndex(int index, CodeplugContact_t *contact)
{
	char buf[SCREEN_LINE_BUFFER_SIZE];

	if (((codeplugContactsCache.numTGContacts > 0) || (codeplugContactsCache.numPCContacts > 0) || (codeplugContactsCache.numALLContacts > 0)) &&
			(index >= CODEPLUG_CONTACTS_MIN) && (index <= CODEPLUG_CONTACTS_MAX))
	{
		index--;
		SPI_Flash_read(FLASH_ADDRESS_OFFSET + CODEPLUG_ADDR_CONTACTS + index * CODEPLUG_CONTACT_DATA_SIZE, (uint8_t *)contact, CODEPLUG_CONTACT_DATA_SIZE);
		contact->NOT_IN_CODEPLUGDATA_indexNumber = index + 1;
		contact->tgNumber = bcd2int(byteSwap32(contact->tgNumber));
		return true;
	}

	// If an invalid contact number has been requested, return a TG 9 contact
	contact->tgNumber = 9;
	contact->callType = CONTACT_CALLTYPE_TG;
	contact->reserve1 = 0xff;
	contact->NOT_IN_CODEPLUGDATA_indexNumber = -1;
	snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "%s 9", currentLanguage->tg);
	codeplugUtilConvertStringToBuf(buf, contact->name, 16);
	return false;
}

int codeplugContactSaveDataForIndex(int index, CodeplugContact_t *contact)
{
	int retVal;
	int flashWritePos = CODEPLUG_ADDR_CONTACTS;
	int flashSector;
	int flashEndSector;
	int bytesToWriteInCurrentSector = CODEPLUG_CONTACT_DATA_SIZE;
	uint32_t unconvertedTgNumber = contact->tgNumber;

	index--;
	contact->tgNumber = byteSwap32(int2bcd(contact->tgNumber));

	flashWritePos += index * CODEPLUG_CONTACT_DATA_SIZE;// go to the position of the specific index

	flashSector 	= flashWritePos / 4096;
	flashEndSector 	= (flashWritePos + CODEPLUG_CONTACT_DATA_SIZE) / 4096;

	if (flashSector != flashEndSector)
	{
		bytesToWriteInCurrentSector = (flashEndSector * 4096) - flashWritePos;
	}

	SPI_Flash_read(FLASH_ADDRESS_OFFSET + (flashSector * 4096), SPI_Flash_sectorbuffer, 4096);
	uint8_t *writePos = SPI_Flash_sectorbuffer + flashWritePos - (flashSector * 4096);
	memcpy(writePos, contact, bytesToWriteInCurrentSector);

	retVal = SPI_Flash_eraseSector(FLASH_ADDRESS_OFFSET + (flashSector * 4096));
	if (!retVal)
	{
		goto hasFailed;
	}

	for (int i = 0; i < 16; i++)
	{
		retVal = SPI_Flash_writePage(FLASH_ADDRESS_OFFSET + (flashSector * 4096) + (i * 256), SPI_Flash_sectorbuffer + (i * 256));
		if (!retVal)
		{
			goto hasFailed;
		}
	}

	if (flashSector != flashEndSector)
	{
		uint8_t *channelBufPusOffset = (uint8_t *)contact + bytesToWriteInCurrentSector;
		bytesToWriteInCurrentSector = CODEPLUG_CONTACT_DATA_SIZE - bytesToWriteInCurrentSector;

		SPI_Flash_read(FLASH_ADDRESS_OFFSET + (flashEndSector * 4096), SPI_Flash_sectorbuffer, 4096);
		memcpy(SPI_Flash_sectorbuffer, (uint8_t *)channelBufPusOffset, bytesToWriteInCurrentSector);

		retVal = SPI_Flash_eraseSector(FLASH_ADDRESS_OFFSET + (flashEndSector * 4096));

		if (!retVal)
		{
			goto hasFailed;
		}

		for (int i = 0; i < 16; i++)
		{
			retVal = SPI_Flash_writePage(FLASH_ADDRESS_OFFSET + (flashEndSector * 4096) + (i * 256), SPI_Flash_sectorbuffer + (i * 256));

			if (!retVal)
			{
				goto hasFailed;
			}
		}

	}
	if ((contact->name[0] == 0xff) || (contact->callType == 0xFF))
	{
		codeplugContactsCacheRemoveContactAt(index + 1);// index was decremented at the start of the function
	}
	else
	{
		codeplugContactsCacheUpdateOrInsertContactAt(index + 1, contact);
		//initCodeplugContactsCache();// Update the cache
	}


	hasFailed:

	// Restore contact's TG number
	contact->tgNumber = unconvertedTgNumber;

	return retVal;
}

bool codeplugContactGetRXGroup(int index)
{
	CodeplugRxGroup_t rxGroupBuf;
	int i;

	for (i = 1; i <= CODEPLUG_RX_GROUPLIST_MAX; i++)
	{
		if (codeplugRxGroupGetDataForIndex(i, &rxGroupBuf))
		{
			for (int j = 0; j < 32; j++)
			{
				if (rxGroupBuf.contacts[j] == index)
				{
					return true;
				}
			}
		}
	}
	return false;
}

uint32_t codeplugGetUserDMRID(void)
{
	uint32_t dmrId;
	EEPROM_Read(CODEPLUG_ADDR_USER_DMRID, (uint8_t *)&dmrId, 4);
	return bcd2int(byteSwap32(dmrId));
}

void codeplugSetUserDMRID(uint32_t dmrId)
{
	dmrId = byteSwap32(int2bcd(dmrId));
	EEPROM_Write(CODEPLUG_ADDR_USER_DMRID, (uint8_t *)&dmrId, 4);
}

// Max length the user can enter is 8. Hence buf must be 16 chars to allow for the termination
void codeplugGetRadioName(char *buf)
{
	memset(buf, 0, 9);
	EEPROM_Read(CODEPLUG_ADDR_USER_CALLSIGN, (uint8_t *)buf, 8);
	codeplugUtilConvertBufToString(buf, buf, 8);
}

// Max length the user can enter is 16. Hence buf must be 17 chars to allow for the termination
void codeplugGetBootScreenData(char *line1, char *line2, uint8_t *displayType)
{
	memset(line1, 0, SCREEN_LINE_BUFFER_SIZE);
	memset(line2, 0, SCREEN_LINE_BUFFER_SIZE);

	EEPROM_Read(CODEPLUG_ADDR_BOOT_LINE1, (uint8_t *)line1, (SCREEN_LINE_BUFFER_SIZE - 1));
	codeplugUtilConvertBufToString(line1, line1, (SCREEN_LINE_BUFFER_SIZE - 1));
	EEPROM_Read(CODEPLUG_ADDR_BOOT_LINE2, (uint8_t *)line2, (SCREEN_LINE_BUFFER_SIZE - 1));
	codeplugUtilConvertBufToString(line2, line2, (SCREEN_LINE_BUFFER_SIZE - 1));

	EEPROM_Read(CODEPLUG_ADDR_BOOT_INTRO_SCREEN, displayType, 1);// read the display type
}

void codeplugGetVFO_ChannelData(CodeplugChannel_t *vfoBuf, Channel_t VFONumber)
{
	EEPROM_Read(CODEPLUG_ADDR_VFO_A_CHANNEL + (CODEPLUG_CHANNEL_DATA_STRUCT_SIZE * (int)VFONumber), (uint8_t *)vfoBuf, CODEPLUG_CHANNEL_DATA_STRUCT_SIZE);

	// Convert the the legacy codeplug tx and rx freq values into normal integers
	vfoBuf->chMode = (vfoBuf->chMode == 0) ? RADIO_MODE_ANALOG : RADIO_MODE_DIGITAL;
	vfoBuf->txFreq = bcd2int(vfoBuf->txFreq);
	vfoBuf->rxFreq = bcd2int(vfoBuf->rxFreq);
	vfoBuf->txTone = codeplugCSSToInt(vfoBuf->txTone);
	vfoBuf->rxTone = codeplugCSSToInt(vfoBuf->rxTone);
	vfoBuf->NOT_IN_CODEPLUG_flag = ((VFONumber == CHANNEL_VFO_A) ? 0x01 : ((VFONumber == CHANNEL_VFO_B) ? 0x03 : 0x00));
	vfoBuf->NOT_IN_CODEPLUG_CALCULATED_DISTANCE_X10 = -1;

	if (vfoBuf->sql > 21U)
	{
		vfoBuf->sql = 10U;
	}
}

void codeplugSetVFO_ChannelData(CodeplugChannel_t *vfoBuf, Channel_t VFONumber)
{
	CodeplugChannel_t tmpChannel;

	memcpy(&tmpChannel, vfoBuf, CODEPLUG_CHANNEL_DATA_STRUCT_SIZE);// save current VFO data as we need to modify

	codeplugConvertChannelInternalToCodeplug(&tmpChannel, vfoBuf);

	EEPROM_Write(CODEPLUG_ADDR_VFO_A_CHANNEL + (CODEPLUG_CHANNEL_DATA_STRUCT_SIZE * (int)VFONumber), (uint8_t *)&tmpChannel, CODEPLUG_CHANNEL_DATA_STRUCT_SIZE);
}

void codeplugConvertChannelInternalToCodeplug(CodeplugChannel_t *codeplugChannel, CodeplugChannel_t *internalChannel)
{
	codeplugChannel->chMode = (internalChannel->chMode == RADIO_MODE_ANALOG) ? 0 : 1;
	codeplugChannel->txFreq = int2bcd(internalChannel->txFreq);
	codeplugChannel->rxFreq = int2bcd(internalChannel->rxFreq);
	codeplugChannel->txTone = codeplugIntToCSS(internalChannel->txTone);
	codeplugChannel->rxTone = codeplugIntToCSS(internalChannel->rxTone);
}

void codeplugInitChannelsPerZone(void)
{
	uint8_t buf[16];
	// 0x806F is the last byte of the name of the second Zone if 16 channels per zone
	// And because the CPS terminates the zone name with 0xFF, this will be 0xFF if its a 16 channel per zone schema
	// If the zone has 80 channels per zone this address will be the upper byte of the channel number and because the max channels is 1024
	// this value can never me 0xff if its a channel number

	// Note. I tried to read just 1 byte but it crashed. So I am now reading 16 bytes and checking the last one
	EEPROM_Read(0x8060, (uint8_t *)buf, 16);
	if ((buf[15] >= 0x00) && (buf[15] <= 0x04))
	{
		codeplugChannelsPerZone = 80;// Must be the new 80 channel per zone format
	}
}

static uint32_t codeplugGetOpenGD77CustomDataStartAddressForType(CodeplugCustomDataType_t dataType, codeplugCustomDataBlockHeader_t *blockHeader)
{
	const uint32_t MAX_BLOCK_ADDRESS = 0x10000;
	uint32_t dataHeaderAddress = 12;
	uint8_t tmpBuf[12];

	SPI_Flash_read(FLASH_ADDRESS_OFFSET + 0, tmpBuf, 12);

	if (memcmp("OpenGD77", tmpBuf, 8) == 0)
	{
		do
		{
			SPI_Flash_read(FLASH_ADDRESS_OFFSET + dataHeaderAddress, (uint8_t *)blockHeader, sizeof(codeplugCustomDataBlockHeader_t));

			if (blockHeader->dataType == dataType)
			{
				return dataHeaderAddress;
			}

			if ((blockHeader->dataLength == 0) || (blockHeader->dataLength == 0xFFFFFFFF))
			{
				return 0;
			}

			dataHeaderAddress += sizeof(codeplugCustomDataBlockHeader_t) + blockHeader->dataLength;

		} while (dataHeaderAddress < MAX_BLOCK_ADDRESS);
	}

	return 0;
}

static uint32_t codeplugGetOpenGD77CustomDataFirstEmptySlot(int len)
{
	const uint32_t MAX_BLOCK_ADDRESS = 0x10000;
	codeplugCustomDataBlockHeader_t blockHeader;
	uint32_t dataHeaderAddress;

	if ((dataHeaderAddress = codeplugGetOpenGD77CustomDataStartAddressForType(CODEPLUG_CUSTOM_DATA_TYPE_EMPTY, &blockHeader)) > 0)
	{
		if ((blockHeader.dataLength == 0xFFFFFFFF) &&
				((dataHeaderAddress + sizeof(codeplugCustomDataBlockHeader_t) + len) < MAX_BLOCK_ADDRESS))
		{
			return dataHeaderAddress;
		}
	}

	return 0;
}

bool codeplugGetOpenGD77CustomData(CodeplugCustomDataType_t dataType, uint8_t *dataBuf)
{
	codeplugCustomDataBlockHeader_t blockHeader;
	uint32_t dataHeaderAddress;

	if ((dataHeaderAddress = codeplugGetOpenGD77CustomDataStartAddressForType(dataType, &blockHeader)) > 0)
	{
#if defined(PLATFORM_RD5R)
		// On RD5R, the display buffer is smaller than the other platforms
		if ((dataType == CODEPLUG_CUSTOM_DATA_TYPE_IMAGE) &&
				(blockHeader.dataLength > ((DISPLAY_SIZE_X * DISPLAY_SIZE_Y) >> 3)))
		{
			blockHeader.dataLength = ((DISPLAY_SIZE_X * DISPLAY_SIZE_Y) >> 3);
		}
#endif

		SPI_Flash_read(FLASH_ADDRESS_OFFSET + dataHeaderAddress + sizeof(codeplugCustomDataBlockHeader_t), dataBuf, blockHeader.dataLength);
		return true;
	}

	return false;
}

bool codeplugSetOpenGD77CustomData(CodeplugCustomDataType_t dataType, uint8_t *dataBuf, int len)
{
	codeplugCustomDataBlockHeader_t blockHeader;
	uint32_t dataHeaderAddress;

	if ((dataHeaderAddress = codeplugGetOpenGD77CustomDataStartAddressForType(dataType, &blockHeader)) == 0)
	{
		if ((dataHeaderAddress = codeplugGetOpenGD77CustomDataFirstEmptySlot(len)) == 0)
		{
			return false;
		}

		// We got a location for this new storage, validate the header.
		blockHeader.dataType = dataType;
		blockHeader.dataLength = len;
	}

	if (blockHeader.dataLength == len) // it's not permitted to change the block size, it has to be equal.
	{
		SPI_Flash_write(FLASH_ADDRESS_OFFSET + dataHeaderAddress, (uint8_t *)&blockHeader, sizeof(codeplugCustomDataBlockHeader_t));
		SPI_Flash_write(FLASH_ADDRESS_OFFSET + dataHeaderAddress + sizeof(codeplugCustomDataBlockHeader_t), dataBuf, len);
		return true;
	}

	return false;
}

bool codeplugGetGeneralSettings(CodeplugGeneralSettings_t *generalSettingsBuffer)
{
	return EEPROM_Read(CODEPLUG_ADDR_GENERAL_SETTINGS, (uint8_t *)generalSettingsBuffer, CODEPLUG_GENERAL_SETTINGS_DATA_STRUCT_SIZE);
}

bool codeplugGetSignallingDTMF(CodeplugSignallingDTMF_t *signallingDTMFBuffer)
{
	return EEPROM_Read(CODEPLUG_ADDR_SIGNALLING_DTMF, (uint8_t *)signallingDTMFBuffer, CODEPLUG_SIGNALLING_DTMF_DATA_STRUCT_SIZE);
}

bool codeplugGetSignallingDTMFDurations(CodeplugSignallingDTMFDurations_t *signallingDTMFDurationsBuffer)
{
	if (EEPROM_Read(CODEPLUG_ADDR_SIGNALLING_DTMF_DURATIONS, (uint8_t *)signallingDTMFDurationsBuffer, SIGNALLING_DTMF_DURATIONS_DATA_STRUCT_SIZE))
	{
		// Default codeplug value was 128(0x80). Override any value above 1000ms
		if (signallingDTMFDurationsBuffer->libreDMR_Tail > 10)
		{
			signallingDTMFDurationsBuffer->libreDMR_Tail = 5;
		}

		return true;
	}
	else
	{
		// Avoid division-by-zero
		signallingDTMFDurationsBuffer->rate = 2; // 250ms/250ms
	}

	return false;
}


bool codeplugGetDeviceInfo(CodeplugDeviceInfo_t *deviceInfoBuffer)
{
	bool readOK = EEPROM_Read(CODEPLUG_ADDR_DEVICE_INFO, (uint8_t *)deviceInfoBuffer, sizeof(CodeplugDeviceInfo_t));//CODEPLUG_ADDR_DEVICE_INFO_READ_SIZE);
	if (readOK)
	{
		deviceInfoBuffer->minUHFFreq = bcd2uint16(deviceInfoBuffer->minUHFFreq);
		deviceInfoBuffer->maxUHFFreq = bcd2uint16(deviceInfoBuffer->maxUHFFreq);

		deviceInfoBuffer->minVHFFreq = bcd2uint16(deviceInfoBuffer->minVHFFreq);
		deviceInfoBuffer->maxVHFFreq = bcd2uint16(deviceInfoBuffer->maxVHFFreq);

		return true;
	}
	return false;
}

static void codeplugQuickKeyInitCache(void)
{
	EEPROM_Read(CODEPLUG_ADDR_QUICKKEYS, (uint8_t *)&quickKeysCache, (CODEPLUG_QUICKKEYS_SIZE * sizeof(uint16_t)));
}

uint16_t codeplugGetQuickkeyFunctionID(char key)
{
	uint16_t functionId = 0;

	if ((key >= '0') && (key <= '9'))
	{
		key = key - '0';
		functionId = quickKeysCache[(int) key];
	}

	return functionId;
}

static bool quickkeyIsEmpty(char key, uint16_t functionId)
{
	// Wants to clear a slot, so consider it empty
	if (functionId == 0x8000)
	{
		return true;
	}

	uint16_t data = codeplugGetQuickkeyFunctionID(key);

	if ((data == 0x8000) ||
			(((data & 0x8000) == 0) && ((data < CODEPLUG_CONTACTS_MIN) || (data > CODEPLUG_CONTACTS_MAX))))
	{
		return true;
	}

	return false;
}

bool codeplugSetQuickkeyFunctionID(char key, uint16_t functionId)
{
	// Only permit to store QuickKey in empty slots
	if (quickkeyIsEmpty(key, functionId) && (key >= '0' && key <= '9'))
	{
		key = key - '0';
		EEPROM_Write(CODEPLUG_ADDR_QUICKKEYS + (sizeof(uint16_t) * (int)key), (uint8_t *)&functionId, sizeof(uint16_t));
		quickKeysCache[(int) key] = functionId;

		return true;
	}

	return false;
}

int codeplugGetRepeaterWakeAttempts(void)
{
	return 4;// Hard coded. In the future we may read this from the codeplug.
}

static void codeplugAPRSInitCache(void)
{
	CodeplugAPRSConfig_t aprsBuff;
	int aprsIdx = 1;

	do
	{
		memset(&aprsBuff, 0x00, CODEPLUG_APRS_CONFIG_DATA_STRUCT_SIZE);

		codeplugAPRSCache.numOfConfigs = aprsIdx; // Cheating the read function

		if (codeplugAPRSGetDataForIndex(aprsIdx, &aprsBuff) == false)
		{
			break;
		}

		aprsIdx++;
		codeplugAPRSCache.numOfConfigs++;

	} while(aprsIdx <= 8);

	codeplugAPRSCache.numOfConfigs = (aprsIdx - 1);
}

void codeplugInitCaches(void)
{
	codeplugInitContactsCache();

	codeplugAllChannelsInitCache();

	codeplugZonesInitCache();
	codeplugRxGroupInitCache();
	codeplugQuickKeyInitCache();

	codeplugInitLastUsedChannelInZone();

	codeplugAPRSInitCache();
}

// Returns pin length or 0 if no pin. Pin code is passed as pointer to int32_t
int codeplugGetPasswordPin(int32_t *pinCode)
{
	int pinLength = 0;
	uint8_t buf[6];

	if (EEPROM_Read(CODEPLUG_ADDR_BOOT_PASSWORD_PIN + 1, (uint8_t *)buf, 6))
	{
		if ((buf[0] & 0x01) == 0)
		{
			return pinLength;
		}

		uint8_t nibble;
		*pinCode = 0;
		for(int i = 0 ; i < 6 ; i++)
		{
			nibble = buf[(i/2)+3] >> ((1-(i % 2)) * 4) & 0x0F;
			if (nibble != 0x0f)
			{
				*pinCode = (*pinCode * 10) + nibble;
				pinLength++;
			}
			else
			{
				break;
			}
		}
	}
	return pinLength;
}

void codeplugSetTATxForTS(CodeplugChannel_t *channelBuf, uint8_t ts, CodeplugTATxDestination_t taTx)
{
	bool isTS1 = (ts == 0);
	channelBuf->flag1 &= (isTS1 ? 0xFC : 0xF3);
	channelBuf->flag1 |= (taTx << (isTS1 ? 0 : 2));
}

CodeplugTATxDestination_t codeplugGetTATxForTS(CodeplugChannel_t *channelBuf, uint8_t ts)
{
	return (channelBuf->flag1 >> ((ts == 0) ? 0 : 2)) & 0x03;
}

void codeplugInitLastUsedChannelInZone(void)
{
#if ! defined(PLATFORM_RD5R)
	uint8_t tmpBuf[4];

	EEPROM_Read(CODEPLUG_ADDR_LUCZ, tmpBuf, 4);

	if (memcmp("LUCZ", tmpBuf, 4) == 0) // Check for the header tag
	{
		EEPROM_Read((CODEPLUG_ADDR_LUCZ + 4), (uint8_t *)&lastUsedChannelInZoneData, sizeof(lastUsedChannelInZoneData));
	}
	else
#endif
	{
		// There is no CustomData available, set all last channels to 0
		memset(&lastUsedChannelInZoneData, 0, sizeof(lastUsedChannelInZoneData));

#if defined(PLATFORM_RD5R)
		codeplugSetLastUsedChannelInZone(-1, nonVolatileSettings.currentChannelIndexInAllZone);
		codeplugSetLastUsedChannelInZone(nonVolatileSettings.currentZone, nonVolatileSettings.currentChannelIndexInZone);
#endif
	}
}

int16_t codeplugGetLastUsedChannelInZone(int zoneNum)
{
	int offset = SAFE_MIN(zoneNum, (CODEPLUG_ALL_ZONES_MAX - 1));
	uint8_t *p = (uint8_t *)&lastUsedChannelInZoneData[offset];

	return ((zoneNum >= 0) ? ((int16_t)*p) : (*((int16_t *)p) + 1));
}

int16_t codeplugGetLastUsedChannelInCurrentZone(void)
{
	return (codeplugGetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber));
}

int16_t codeplugGetLastUsedChannelNumberInCurrentZone(void)
{
	int16_t index = codeplugGetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber);

	return ((currentZone.NOT_IN_CODEPLUGDATA_indexNumber >= 0) ? currentZone.channels[index] : index);
}

int16_t codeplugSetLastUsedChannelInZone(int zoneNum, int16_t channelNum)
{
	int offset = SAFE_MIN(zoneNum, (CODEPLUG_ALL_ZONES_MAX - 1));
	uint8_t *p = (uint8_t *)&lastUsedChannelInZoneData[offset];

	if (zoneNum >= 0) // Channels 0..79
	{
		*p = (uint8_t)channelNum;
#if defined(PLATFORM_RD5R)
		settingsSet(nonVolatileSettings.currentChannelIndexInZone, (int16_t) channelNum);
#endif
	}
	else // AllChannels (channels 1..1024)
	{
		*((int16_t *)p) = (channelNum - 1);
#if defined(PLATFORM_RD5R)
		settingsSet(nonVolatileSettings.currentChannelIndexInAllZone, (int16_t) channelNum);
#endif
	}

	lastUsedChannelInZoneHasChanged = true;

	return channelNum;
}

int16_t codeplugSetLastUsedChannelInCurrentZone(int16_t channelNum)
{
	return (codeplugSetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber, channelNum));
}

bool codeplugSaveLastUsedChannelInZone(void)
{
#if ! defined(PLATFORM_RD5R)
	if (lastUsedChannelInZoneHasChanged)
	{
		uint8_t header[4] = { 'L', 'U', 'C', 'Z' };

		lastUsedChannelInZoneHasChanged = ! (EEPROM_Write(CODEPLUG_ADDR_LUCZ, (uint8_t *)&header, 4) &&
				EEPROM_Write(CODEPLUG_ADDR_LUCZ + 4,
						(uint8_t *)&lastUsedChannelInZoneData, sizeof(lastUsedChannelInZoneData)));

		return !lastUsedChannelInZoneHasChanged;
	}
#endif

	return true;
}

int codeplugAPRSConfigGetCount(void)
{
	return codeplugAPRSCache.numOfConfigs;
}

bool codeplugAPRSGetDataForIndex(int index, CodeplugAPRSConfig_t *APRS_Buf)
{
	if ((index > 0) && (index <= codeplugAPRSCache.numOfConfigs))
	{
		index--;
		EEPROM_Read(CODEPLUG_ADDR_APRS_CONFIGS + (index * CODEPLUG_APRS_CONFIG_DATA_STRUCT_SIZE), (uint8_t *)APRS_Buf, CODEPLUG_APRS_CONFIG_DATA_STRUCT_SIZE);
	}
	else
	{
		memset(APRS_Buf, 0x00, CODEPLUG_APRS_CONFIG_DATA_STRUCT_SIZE);
	}

	if (APRS_Buf->magicVer == 0x4152) // Ambiguity move left by 1 bit
	{
		APRS_Buf->flags = (((APRS_Buf->flags & 0xF0) << 1) | (APRS_Buf->flags & 0x0F));
		APRS_Buf->magicVer = APRS_MAGIC_VERSION;
	}

	return !((APRS_Buf->name[0] == 0xFF) || (APRS_Buf->magicVer != APRS_MAGIC_VERSION));
}

int codeplugAPRSGetIndexOfName(char *configName)
{
	uint8_t APRSConfigName[8];
	char convertedConfigName[8];

	codeplugUtilConvertStringToBuf(configName, convertedConfigName, 8);

	for(int i = 0; i < codeplugAPRSCache.numOfConfigs; i++)
	{
		if (!EEPROM_Read(CODEPLUG_ADDR_APRS_CONFIGS + (i * CODEPLUG_APRS_CONFIG_DATA_STRUCT_SIZE), (uint8_t *) &APRSConfigName, sizeof(APRSConfigName)))
		{
			return 0;
		}

		if (memcmp(APRSConfigName, convertedConfigName, 8) == 0)
		{
			return (i + 1);
		}
	}

	return 0;
}
