/*
 * 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 <hardware/HX8353E.h>
#include <stdlib.h>
//vk3kyy #include "io/display.h"
#if defined(LANGUAGE_BUILD_JAPANESE)
#include "hardware/HX8353E_charset_JA.h"
#else
#include <hardware/HX8353E_charset.h>
#endif
#include "functions/settings.h"
#include "user_interface/uiLocalisation.h"
#include "user_interface/menuSystem.h"
#include "utils.h"
#include "stm32f4xx_hal.h"

// number representing the maximum angle (e.g. if 100, then if you pass in start=0 and end=50, you get a half circle)
// this can be changed with setArcParams function at runtime
#define DEFAULT_ARC_ANGLE_MAX 360.0f
// rotational offset in degrees defining position of value 0 (-90 will put it at the top of circle)
// this can be changed with setAngleOffset function at runtime
#define DEFAULT_ANGLE_OFFSET -90.0f
static float _arcAngleMax = DEFAULT_ARC_ANGLE_MAX;
static float _angleOffset = DEFAULT_ANGLE_OFFSET;
#define DEG_TO_RAD  0.017453292519943295769236907684886f
#define RAD_TO_DEG 57.295779513082320876798154814105f

extern bool headerRowIsDirty;

static bool inverted = false;
#if defined(HAS_COLOURS)
static themeItem_t foregroundThemeItem = THEME_ITEM_FG_DEFAULT;
static uint16_t foregroundColour = PLATFORM_COLOUR_FORMAT_SWAP_BYTES(RGB888_TO_PLATFORM_COLOUR_FORMAT(0x000000));
static themeItem_t backgroundThemeItem = THEME_ITEM_BG;
static uint16_t backgroundColour = PLATFORM_COLOUR_FORMAT_SWAP_BYTES(RGB888_TO_PLATFORM_COLOUR_FORMAT(0xFFFFFFU));
#else
static uint16_t foregroundColour = 0x0000U;
static uint16_t backgroundColour = 0xFFFFU;
#endif

static uint16_t screenBufData[DISPLAY_SIZE_X * DISPLAY_SIZE_Y];
uint16_t *screenBuf = screenBufData;

#if defined(HAS_COLOURS)
// When the Pride theme is active the background canvas is painted as six
// horizontal rainbow stripes instead of a single flat colour.
static bool prideBackgroundActive = false;
static uint16_t prideStripes[6];

// Pride stripe colour for a linear screen-buffer index (row = index / width).
static inline uint16_t pridePixelColour(int index)
{
	int band = ((index / DISPLAY_SIZE_X) * 6) / DISPLAY_SIZE_Y;

	return prideStripes[(band < 0) ? 0 : ((band > 5) ? 5 : band)];
}
#endif
//#define DISPLAY_CHECK_BOUNDS

#ifdef DISPLAY_CHECK_BOUNDS
static const uint8_t *screenBufEnd = screenBuf + sizeof(screenBuf);
#endif

#if defined(HAS_COLOURS)
DayTime_t themeDaytime = DAY;
uint16_t themeItems[NIGHT + 1][THEME_ITEM_MAX]; // Theme storage
#endif

int16_t displaySetPixel(int16_t x, int16_t y, bool isInverted)
{
	int16_t i = (y * DISPLAY_SIZE_X) + x;

	if ((i < 0) || (i >= (DISPLAY_SIZE_X * DISPLAY_SIZE_Y)))
	{
		return -1;// off the screen
	}

	screenBuf[i] = isInverted ? foregroundColour : backgroundColour;

	return 0;
}

void displayRenderWithoutNotification(void)
{
	displayRenderRows(0, DISPLAY_NUMBER_OF_ROWS);
	headerRowIsDirty = false;
}

void displayRender(void)
{
	if (uiNotificationIsVisible())
	{
		uiNotificationRefresh();
	}
	else
	{
		displayRenderRows(0, DISPLAY_NUMBER_OF_ROWS);
	}
	headerRowIsDirty = false;
}

//#define DISPLAY_CHECK_BOUNDS
#ifdef DISPLAY_CHECK_BOUNDS
static inline bool checkWritePos(uint8_t * writePos)
{
	if (writePos < screenBuf || writePos > screenBufEnd)
	{
#if defined(USING_EXTERNAL_DEBUGGER)
		SEGGER_RTT_printf(0,"Display buffer error\n");
#endif
		return false;
	}
	return true;
}
#endif

#if ! defined(PLATFORM_GD77S)
static uint8_t *getUncompressedChar(uint8_t *dest, uint8_t *currentFont, uint8_t charOffset)
{
	if (charOffset < CHARS_PER_FONT)
	{
		uint8_t count = 0;
		uint8_t *p = currentFont + 8; // skip the header
		uint8_t numOfPairs = *p;

		do
		{
			if (count == charOffset)
			{
				uint8_t *pSrc = p + 1;
				uint8_t *pDest = dest;

				// decode RLE8
				while (numOfPairs > 0)
				{
					uint8_t l = (*(pSrc + 1) + 1);

					memset(pDest, *pSrc, l);

					pDest += l;
					pSrc += 2;
					numOfPairs--;
				}

				break;
			}

			p += ((numOfPairs * 2) + 1);
			numOfPairs = *p;

			count++;

		} while (count < CHARS_PER_FONT);
	}

	return dest;
}
#endif

int displayPrintCore(int16_t xPos, int16_t yPos, const char *szMsg, ucFont_t fontSize, ucTextAlign_t alignment, bool isInverted)
{
	return displayPrintCoreDoubleHeight(xPos, yPos, szMsg, fontSize, alignment, isInverted, false);
}

int displayPrintCoreDoubleHeight(int16_t xPos, int16_t yPos, const char *szMsg, ucFont_t fontSize, ucTextAlign_t alignment, bool isInverted, bool doubleHeight)
{
#if ! defined(PLATFORM_GD77S)
	int16_t sLen;
	uint8_t *currentCharData;
	int16_t charWidthPixels;
	int16_t charHeightPixels;
	int16_t bytesPerChar;
	int16_t startCode;
	int16_t endCode;
	uint8_t *currentFont;
	bool fontIsCompressed = false;
	uint8_t uncompressChar[64];

	sLen = strlen(szMsg);

	switch(fontSize)
	{
#if defined(PLATFORM_RD5R)
		case FONT_SIZE_1:
			currentFont = (uint8_t *) font_6x8;
			break;
		case FONT_SIZE_1_BOLD:
			currentFont = (uint8_t *) font_6x8_bold;
			break;
		case FONT_SIZE_2:
			currentFont = (uint8_t *) font_8x8;
			break;
		case FONT_SIZE_3:
			currentFont = (uint8_t *) font_8x8; // font_8x16;
			break;
		case FONT_SIZE_4:
			currentFont = (uint8_t *) font_8x16; // font_16x32;
			break;
#else
		case FONT_SIZE_1:
			currentFont = (uint8_t *) font_6x8;
			break;
		case FONT_SIZE_1_BOLD:
			currentFont = (uint8_t *) font_6x8_bold;
			break;
		case FONT_SIZE_2:
			currentFont = (uint8_t *) font_8x8;
			break;
		case FONT_SIZE_3:
			currentFont = (uint8_t *) font_8x16;
			break;
		case FONT_SIZE_4:
			currentFont = (uint8_t *) font_16x32_compressed;
			break;
#endif
		default:
			return -2;// Invalid font selected
			break;
	}

	fontIsCompressed	= (currentFont[0] & 0x01); // does the current font is compressed ?
	startCode   		= currentFont[2];  // get first defined character
	endCode 	  		= currentFont[3];  // get last defined character
	charWidthPixels   	= currentFont[4];  // width in pixel of one char
	charHeightPixels  	= currentFont[5];  // page count per char
	bytesPerChar 		= currentFont[7];  // bytes per char

	if ((charWidthPixels * sLen) + xPos > DISPLAY_SIZE_X)
	{
		sLen = (DISPLAY_SIZE_X - xPos) / charWidthPixels;
	}

	if (sLen < 0)
	{
		return -1;
	}

	switch(alignment)
	{
		case TEXT_ALIGN_LEFT:
			// left aligned, do nothing.
			break;
		case TEXT_ALIGN_CENTER:
			xPos = (DISPLAY_SIZE_X - (charWidthPixels * sLen)) >> 1;
			break;
		case TEXT_ALIGN_RIGHT:
			xPos = DISPLAY_SIZE_X - (charWidthPixels * sLen);
			break;
	}

	for (int16_t i = 0; i < sLen; i++)
	{
		// Skip space character as it's empty (and no more part of the fonts).
		if (szMsg[i] == ' ')
		{
			continue;
		}

		uint32_t charOffset = (szMsg[i] - startCode);

		// End boundary checking.
		if (charOffset > endCode)
		{
			charOffset = ('?' - startCode); // Substitute unsupported ASCII code by a question mark
		}

		if (fontIsCompressed)
		{
			currentCharData = getUncompressedChar(&uncompressChar[0], currentFont, charOffset);
		}
		else
		{
			currentCharData = (uint8_t *)&currentFont[8 + (charOffset * bytesPerChar)];
		}

		uint32_t charPixelOffset = (i * charWidthPixels);

		for (int16_t x = 0; x < charWidthPixels; x++)
		{
			uint32_t yDblHeightOffset = 0;
			uint32_t xp = x + xPos;

			for (int16_t y = 0; y < charHeightPixels; y++)
			{
				uint8_t rowData = currentCharData[x + ((y / 8) * charWidthPixels)];
				uint32_t bOffset = xp + yDblHeightOffset + ((yPos + y) * DISPLAY_SIZE_X) + charPixelOffset;
				uint16_t colour = (isInverted ? backgroundColour : foregroundColour);

				if ((rowData >> (y % 8) & 0x01) && (bOffset < (DISPLAY_SIZE_X * DISPLAY_SIZE_Y)))
				{
					screenBuf[bOffset] = colour;

					if (doubleHeight)
					{
						bOffset += DISPLAY_SIZE_X;

						if (bOffset < (DISPLAY_SIZE_X * DISPLAY_SIZE_Y))
						{
							screenBuf[bOffset] = colour;
						}
					}
				}

				if (doubleHeight)
				{
					yDblHeightOffset += DISPLAY_SIZE_X;
				}
			}
		}
	}
#endif // ! PLATFORM_GD77S
	return 0;
}

void displayClearBuf(void)
{
#if defined(HAS_COLOURS)
	if (prideBackgroundActive)
	{
		for (int i = 0; i < DISPLAY_SIZE_X * DISPLAY_SIZE_Y; i++)
		{
			screenBuf[i] = pridePixelColour(i);
		}
		return;
	}
#endif
	// may be able to do this using DMA
	for(int i = 0; i < DISPLAY_SIZE_X * DISPLAY_SIZE_Y; i++)
	{
		screenBuf[i] = backgroundColour;
	}
}

void displayClearRows(int16_t startRow, int16_t endRow, bool isInverted)
{
	// Boundaries
	if (((startRow < 0) || (endRow < 0)) || ((startRow > DISPLAY_NUMBER_OF_ROWS) || (endRow > DISPLAY_NUMBER_OF_ROWS)) || (startRow == endRow))
	{
		return;
	}

	if (endRow < startRow)
	{
		SAFE_SWAP(startRow, endRow);
	}

	startRow *= (8 * DISPLAY_SIZE_X);
	endRow *= (8 * DISPLAY_SIZE_X);

	uint16_t fillColour = (isInverted ? foregroundColour : backgroundColour);

	for(int i = startRow; i < endRow; i++)
	{
#if defined(HAS_COLOURS)
		screenBuf[i] = ((prideBackgroundActive && (isInverted == false)) ? pridePixelColour(i) : fillColour);
#else
		screenBuf[i] = fillColour;
#endif
	}

}

void displayPrintCenteredDoubleHeight(uint16_t y, const char *text, ucFont_t fontSize, bool doubleHeight)
{
	displayPrintCoreDoubleHeight(0, y, text, fontSize, TEXT_ALIGN_CENTER, false, doubleHeight);
}

void displayPrintCentered(uint16_t y, const char *text, ucFont_t fontSize)
{
	displayPrintCoreDoubleHeight(0, y, text, fontSize, TEXT_ALIGN_CENTER, false, false);
}

void displayPrintAt(uint16_t x, uint16_t y, const char *text, ucFont_t fontSize)
{
	displayPrintCoreDoubleHeight(x, y, text, fontSize, TEXT_ALIGN_LEFT, false, false);
}

void displayPrintAtDoubleHeight(uint16_t x, uint16_t y, const char *text, ucFont_t fontSize, bool doubleHeight)
{
	displayPrintCoreDoubleHeight(x, y, text, fontSize, TEXT_ALIGN_LEFT, false, doubleHeight);
}

// Bresenham's algorithm - thx wikpedia
void displayDrawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, bool isInverted)
{
	bool steep = abs(y1 - y0) > abs(x1 - x0);

	if (steep)
	{
		SAFE_SWAP(x0, y0);
		SAFE_SWAP(x1, y1);
	}

	if (x0 > x1)
	{
		SAFE_SWAP(x0, x1);
		SAFE_SWAP(y0, y1);
	}

	int16_t dx, dy;
	dx = x1 - x0;
	dy = abs(y1 - y0);

	int16_t err = dx >> 1;
	int16_t ystep;

	ystep = ((y0 < y1) ? 1 : -1);

	for (; x0 <= x1; x0++)
	{
		if (steep)
		{
			displaySetPixel(y0, x0, isInverted);
		}
		else
		{
			displaySetPixel(x0, y0, isInverted);
		}

		err -= dy;
		if (err < 0)
		{
			y0 += ystep;
			err += dx;
		}
	}
}

void displayDrawFastVLine(int16_t x, int16_t y, int16_t h, bool isInverted)
{
	displayFillRect(x, y, 1, h, !isInverted);
}

void displayDrawFastHLine(int16_t x, int16_t y, int16_t w, bool isInverted)
{
	displayFillRect(x, y, w, 1, !isInverted);
}

// Draw a circle outline
void displayDrawCircle(int16_t x0, int16_t y0, int16_t r, bool isInverted)
{
	int16_t f     = 1 - r;
	int16_t ddF_x = 1;
	int16_t ddF_y = -2 * r;
	int16_t x     = 0;
	int16_t y     = r;

	displaySetPixel(x0    , y0 + r, isInverted);
	displaySetPixel(x0    , y0 - r, isInverted);
	displaySetPixel(x0 + r, y0    , isInverted);
	displaySetPixel(x0 - r, y0    , isInverted);

	while (x < y)
	{
		if (f >= 0)
		{
			y--;
			ddF_y += 2;
			f += ddF_y;
		}

		x++;
		ddF_x += 2;
		f += ddF_x;

		displaySetPixel(x0 + x, y0 + y, isInverted);
		displaySetPixel(x0 - x, y0 + y, isInverted);
		displaySetPixel(x0 + x, y0 - y, isInverted);
		displaySetPixel(x0 - x, y0 - y, isInverted);
		displaySetPixel(x0 + y, y0 + x, isInverted);
		displaySetPixel(x0 - y, y0 + x, isInverted);
		displaySetPixel(x0 + y, y0 - x, isInverted);
		displaySetPixel(x0 - y, y0 - x, isInverted);
	}
}

void displayDrawCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, bool isInverted)
{
	int16_t f     = 1 - r;
	int16_t ddF_x = 1;
	int16_t ddF_y = -2 * r;
	int16_t x     = 0;
	int16_t y     = r;

	while (x < y)
	{
		if (f >= 0)
		{
			y--;
			ddF_y += 2;
			f     += ddF_y;
		}

		x++;
		ddF_x += 2;
		f     += ddF_x;

		if (cornername & 0x4)
		{
			displaySetPixel(x0 + x, y0 + y, isInverted);
			displaySetPixel(x0 + y, y0 + x, isInverted);
		}

		if (cornername & 0x2)
		{
			displaySetPixel(x0 + x, y0 - y, isInverted);
			displaySetPixel(x0 + y, y0 - x, isInverted);
		}

		if (cornername & 0x8)
		{
			displaySetPixel(x0 - y, y0 + x, isInverted);
			displaySetPixel(x0 - x, y0 + y, isInverted);
		}

		if (cornername & 0x1)
		{
			displaySetPixel(x0 - y, y0 - x, isInverted);
			displaySetPixel(x0 - x, y0 - y, isInverted);
		}
	}
}

/*
 * Used to do circles and roundrects
 */
void displayFillCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, int16_t delta, bool isInverted)
{
	int16_t f     = 1 - r;
	int16_t ddF_x = 1;
	int16_t ddF_y = -2 * r;
	int16_t x     = 0;
	int16_t y     = r;

	while (x < y)
	{
		if (f >= 0)
		{
			y--;
			ddF_y += 2;
			f     += ddF_y;
		}

		x++;
		ddF_x += 2;
		f     += ddF_x;

		if (cornername & 0x1)
		{
			displayDrawFastVLine(x0 + x, y0 - y, 2 * y + 1 + delta, isInverted);
			displayDrawFastVLine(x0 + y, y0 - x, 2 * x + 1 + delta, isInverted);
		}

		if (cornername & 0x2)
		{
			displayDrawFastVLine(x0 - x, y0 - y, 2 * y + 1 + delta, isInverted);
			displayDrawFastVLine(x0 - y, y0 - x, 2 * x + 1 + delta, isInverted);
		}
	}
}

void displayFillCircle(int16_t x0, int16_t y0, int16_t r, bool isInverted)
{
	displayDrawFastVLine(x0, y0 - r, 2 * r + 1, isInverted);
	displayFillCircleHelper(x0, y0, r, 3, 0, isInverted);
}

/*
 * ***** Arc related functions *****
 */
static float cosDegrees(float angle)
{
	return cos(angle * DEG_TO_RAD);
}

static float sinDegrees(float angle)
{
	return sin(angle * DEG_TO_RAD);
}

/*
 * DrawArc function thanks to Jnmattern and his Arc_2.0 (https://github.com/Jnmattern)
 */
void displayFillArcOffsetted(uint16_t cx, uint16_t cy, uint16_t radius, uint16_t thickness, float start, float end, bool isInverted)
{
	int16_t xmin = 65535, xmax = -32767, ymin = 32767, ymax = -32767;
	float cosStart, sinStart, cosEnd, sinEnd;
	float r, t;
	float startAngle, endAngle;

	startAngle = (start / _arcAngleMax) * 360.0f;	// 252
	endAngle = (end / _arcAngleMax) * 360.0f;		// 807

	while (startAngle < 0.0f)
	{
		startAngle += 360.0f;
	}
	while (endAngle < 0.0f)
	{
		endAngle += 360.0f;
	}
	while (startAngle > 360.0f)
	{
		startAngle -= 360.0f;
	}
	while (endAngle > 360.0f)
	{
		endAngle -= 360.0f;
	}

	if (startAngle > endAngle)
	{
		displayFillArcOffsetted(cx, cy, radius, thickness, ((startAngle) / 360.0f) * _arcAngleMax, _arcAngleMax, isInverted);
		displayFillArcOffsetted(cx, cy, radius, thickness, 0, ((endAngle) / 360.0f) * _arcAngleMax, isInverted);
	}
	else
	{
		// Calculate bounding box for the arc to be drawn
		cosStart = cosDegrees(startAngle);
		sinStart = sinDegrees(startAngle);
		cosEnd = cosDegrees(endAngle);
		sinEnd = sinDegrees(endAngle);

		r = radius;
		// Point 1: radius & startAngle
		t = r * cosStart;
		if (t < xmin)
		{
			xmin = t;
		}
		if (t > xmax)
		{
			xmax = t;
		}
		t = r * sinStart;
		if (t < ymin)
		{
			ymin = t;
		}
		if (t > ymax)
		{
			ymax = t;
		}

		// Point 2: radius & endAngle
		t = r * cosEnd;
		if (t < xmin)
		{
			xmin = t;
		}
		if (t > xmax)
		{
			xmax = t;
		}
		t = r * sinEnd;
		if (t < ymin)
		{
			ymin = t;
		}
		if (t > ymax)
		{
			ymax = t;
		}

		r = radius - thickness;
		// Point 3: radius-thickness & startAngle
		t = r * cosStart;
		if (t < xmin)
		{
			xmin = t;
		}
		if (t > xmax)
		{
			xmax = t;
		}
		t = r * sinStart;
		if (t < ymin)
		{
			ymin = t;
		}
		if (t > ymax)
		{
			ymax = t;
		}

		// Point 4: radius-thickness & endAngle
		t = r * cosEnd;
		if (t < xmin)
		{
			xmin = t;
		}
		if (t > xmax)
		{
			xmax = t;
		}
		t = r * sinEnd;
		if (t < ymin)
		{
			ymin = t;
		}
		if (t > ymax)
		{
			ymax = t;
		}

		// Corrections if arc crosses X or Y axis
		if ((startAngle < 90.0f) && (endAngle > 90.0f))
		{
			ymax = radius;
		}

		if ((startAngle < 180.0f) && (endAngle > 180.0f))
		{
			xmin = -radius;
		}

		if ((startAngle < 270.0f) && (endAngle > 270.0f))
		{
			ymin = -radius;
		}

		// Slopes for the two sides of the arc
		float sslope = cosStart / sinStart;
		float eslope = cosEnd / sinEnd;

		if (endAngle == 360.0f) eslope = -1000000.0f;

		int ir2 = (radius - thickness) * (radius - thickness);
		int or2 = radius * radius;

		for (int x = xmin; x <= xmax; x++)
		{
			bool y1StartFound = false, y2StartFound = false;
			bool y1EndFound = false, y2EndSearching = false;
			int y1s = 0, y1e = 0, y2s = 0;
			for (int y = ymin; y <= ymax; y++)
			{
				int x2 = x * x;
				int y2 = y * y;

				if (
						(x2 + y2 < or2 && x2 + y2 >= ir2) && (
								(y > 0.0f && startAngle < 180.0f && x <= y * sslope) ||
								(y < 0.0f && startAngle > 180.0f && x >= y * sslope) ||
								(y < 0.0f && startAngle <= 180.0f) ||
								(y == 0.0f && startAngle <= 180.0f && x < 0.0f) ||
								(y == 0.0f && startAngle == 0.0f && x > 0.0f)
						) && (
								(y > 0.0f && endAngle < 180.0f && x >= y * eslope) ||
								(y < 0.0f && endAngle > 180.0f && x <= y * eslope) ||
								(y > 0.0f && endAngle >= 180.0f) ||
								(y == 0.0f && endAngle >= 180.0f && x < 0.0f) ||
								(y == 0.0f && startAngle == 0.0f && x > 0.0f)))
				{
					if (!y1StartFound)	//start of the higher line found
					{
						y1StartFound = true;
						y1s = y;
					}
					else if (y1EndFound && !y2StartFound) //start of the lower line found
					{
						y2StartFound = true;
						//drawPixel_cont(cx+x, cy+y, ILI9341_BLUE);
						y2s = y;
						y += y1e - y1s - 1;	// calculate the most probable end of the lower line (in most cases the length of lower line is equal to length of upper line), in the next loop we will validate if the end of line is really there
						if (y > ymax - 1) // the most probable end of line 2 is beyond ymax so line 2 must be shorter, thus continue with pixel by pixel search
						{
							y = y2s;	// reset y and continue with pixel by pixel search
							y2EndSearching = true;
						}
					}
					else if (y2StartFound && !y2EndSearching)
					{
						// we validated that the probable end of the lower line has a pixel, continue with pixel by pixel search, in most cases next loop with confirm the end of lower line as it will not find a valid pixel
						y2EndSearching = true;
					}
				}
				else
				{
					if (y1StartFound && !y1EndFound) //higher line end found
					{
						y1EndFound = true;
						y1e = y - 1;
						displayDrawFastVLine(cx + x, cy + y1s, y - y1s, isInverted);
						if (y < 0)
						{
							y = abs(y); // skip the empty middle
						}
						else
							break;
					}
					else if (y2StartFound)
					{
						if (y2EndSearching)
						{
							// we found the end of the lower line after pixel by pixel search
							displayDrawFastVLine(cx + x, cy + y2s, y - y2s, isInverted);
							y2EndSearching = false;
							break;
						}
						else
						{
							// the expected end of the lower line is not there so the lower line must be shorter
							y = y2s;	// put the y back to the lower line start and go pixel by pixel to find the end
							y2EndSearching = true;
						}
					}
				}
			}
			if (y1StartFound && !y1EndFound)
			{
				y1e = ymax;
				displayDrawFastVLine(cx + x, cy + y1s, y1e - y1s + 1, isInverted);
			}
			else if (y2StartFound && y2EndSearching)	// we found start of lower line but we are still searching for the end
			{										// which we haven't found in the loop so the last pixel in a column must be the end
				displayDrawFastVLine(cx + x, cy + y2s, ymax - y2s + 1, isInverted);
			}
		}
	}
}

void displayFillArc(uint16_t x, uint16_t y, uint16_t radius, uint16_t thickness, float start, float end, bool isInverted)
{
	if (start == 0.0f && end == _arcAngleMax)
	{
		displayFillArcOffsetted(x, y, radius, thickness, 0, _arcAngleMax, isInverted);
	}
	else
	{
		displayFillArcOffsetted(x, y, radius, thickness, start + (_angleOffset / 360.0f)*_arcAngleMax, end + (_angleOffset / 360.0f)*_arcAngleMax, isInverted);
	}
}
/*
 * ***** End of Arc related functions *****
 */

void displayDrawEllipse(int16_t x0, int16_t y0, int16_t x1, int16_t y1, bool isInverted)
{
	int16_t a = abs(x1 - x0), b = abs(y1 - y0), b1 = b & 1; /* values of diameter */
	long dx = 4 * (1 - a) * b * b, dy = 4 * (b1 + 1) * a * a; /* error increment */
	long err = dx + dy + b1 * a * a, e2; /* error of 1.step */

	if (x0 > x1)
	{
		x0 = x1;
		x1 += a;
	} /* if called with swapped points */
	if (y0 > y1)
	{
		y0 = y1; /* .. exchange them */
	}
	y0 += (b + 1) >> 1; /* starting pixel */
	y1 = y0 - b1;
	a *= 8 * a;
	b1 = 8 * b * b;

	do {
		displaySetPixel(x1, y0, isInverted); /*   I. Quadrant */
		displaySetPixel(x0, y0, isInverted); /*  II. Quadrant */
		displaySetPixel(x0, y1, isInverted); /* III. Quadrant */
		displaySetPixel(x1, y1, isInverted); /*  IV. Quadrant */
		e2 = 2 * err;
		if (e2 >= dx)
		{
			x0++;
			x1--;
			err += dx += b1;
		} /* x step */
		if (e2 <= dy)
		{
			y0++;
			y1--;
			err += dy += a;
		}  /* y step */
	} while (x0 <= x1);

	while (y0 - y1 < b) /* too early stop of flat ellipses a=1 */
	{
		displaySetPixel(x0 - 1, ++y0, isInverted); /* -> complete tip of ellipse */
		displaySetPixel(x0 - 1, --y1, isInverted);
	}
}

/*
 * Draw a triangle
 */
void displayDrawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, bool isInverted)
{
	displayDrawLine(x0, y0, x1, y1, isInverted);
	displayDrawLine(x1, y1, x2, y2, isInverted);
	displayDrawLine(x2, y2, x0, y0, isInverted);
}

/*
 * Fill a triangle
 */
void displayFillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, bool isInverted)
{
	int16_t a, b, y, last;

	// Sort coordinates by Y order (y2 >= y1 >= y0)
	if (y0 > y1)
	{
		SAFE_SWAP(y0, y1); SAFE_SWAP(x0, x1);
	}
	if (y1 > y2)
	{
		SAFE_SWAP(y2, y1); SAFE_SWAP(x2, x1);
	}
	if (y0 > y1)
	{
		SAFE_SWAP(y0, y1); SAFE_SWAP(x0, x1);
	}

	if(y0 == y2) // Handle awkward all-on-same-line case as its own thing
	{
		a = b = x0;
		if(x1 < a)
		{
			a = x1;
		}
		else if(x1 > b)
		{
			b = x1;
		}

		if(x2 < a)
		{
			a = x2;
		}
		else if(x2 > b)
		{
			b = x2;
		}

		displayDrawFastHLine(a, y0, b - a + 1, isInverted);
		return;
	}

	int16_t dx01 = x1 - x0,
			dy01 = y1 - y0,
			dx02 = x2 - x0,
			dy02 = y2 - y0,
			dx12 = x2 - x1,
			dy12 = y2 - y1,
			sa   = 0,
			sb   = 0;

	// For upper part of triangle, find scanline crossings for segments
	// 0-1 and 0-2.  If y1=y2 (flat-bottomed triangle), the scanline y1
	// is included here (and second loop will be skipped, avoiding a /0
	// error there), otherwise scanline y1 is skipped here and handled
	// in the second loop...which also avoids a /0 error here if y0=y1
	// (flat-topped triangle).
	if(y1 == y2) last = y1;   // Include y1 scanline
	else         last = y1-1; // Skip it

	for(y = y0; y <= last; y++)
	{
		a   = x0 + sa / dy01;
		b   = x0 + sb / dy02;
		sa += dx01;
		sb += dx02;
		/* longhand:
		a = x0 + (x1 - x0) * (y - y0) / (y1 - y0);
		b = x0 + (x2 - x0) * (y - y0) / (y2 - y0);
		 */
		if(a > b)
		{
			SAFE_SWAP(a,b);
		}

		displayDrawFastHLine(a, y, b - a + 1, isInverted);
	}

	// For lower part of triangle, find scanline crossings for segments
	// 0-2 and 1-2.  This loop is skipped if y1=y2.
	sa = dx12 * (y - y1);
	sb = dx02 * (y - y0);

	for(; y <= y2; y++)
	{
		a   = x1 + sa / dy12;
		b   = x0 + sb / dy02;
		sa += dx12;
		sb += dx02;
		/* longhand:
		a = x1 + (x2 - x1) * (y - y1) / (y2 - y1);
		b = x0 + (x2 - x0) * (y - y0) / (y2 - y0);
		 */
		if(a > b)
		{
			SAFE_SWAP(a,b);
		}

		displayDrawFastHLine(a, y, b - a + 1, isInverted);
	}
}

/*
 * Draw a rounded rectangle
 */
void displayDrawRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, bool isInverted)
{
	// smarter version
	displayDrawFastHLine(x + r    , y        , w - 2 * r, isInverted); // Top
	displayDrawFastHLine(x + r    , y + h - 1, w - 2 * r, isInverted); // Bottom
	displayDrawFastVLine(x        , y + r    , h - 2 * r, isInverted); // Left
	displayDrawFastVLine(x + w - 1, y + r    , h - 2 * r, isInverted); // Right
	// draw four corners
	displayDrawCircleHelper(x + r        , y + r        , r, 1, isInverted);
	displayDrawCircleHelper(x + w - r - 1, y + r        , r, 2, isInverted);
	displayDrawCircleHelper(x + w - r - 1, y + h - r - 1, r, 4, isInverted);
	displayDrawCircleHelper(x + r        , y + h - r - 1, r, 8, isInverted);
}

/*
 * Fill a rounded rectangle
 */
void displayFillRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, bool isInverted)
{
	displayFillRect(x + r, y, w - 2 * r, h, !isInverted);

	// draw four corners
	displayFillCircleHelper(x+w-r-1, y + r, r, 1, h - 2 * r - 1, isInverted);
	displayFillCircleHelper(x+r    , y + r, r, 2, h - 2 * r - 1, isInverted);
}

/*
 *
 */
void displayDrawRoundRectWithDropShadow(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, bool isInverted)
{
	displayFillRoundRect(x + 2, y, w, h, r, isInverted); // Shadow
	displayFillRoundRect(x, y - 2, w, h, r, !isInverted); // Empty box
	displayDrawRoundRect(x, y - 2, w, h, r, isInverted); // Outline
}

/*
 * Draw a rectangle
 */
void displayDrawRect(int16_t x, int16_t y, int16_t w, int16_t h, bool isInverted)
{
	displayDrawFastHLine(x        , y        , w, isInverted);
	displayDrawFastHLine(x        , y + h - 1, w, isInverted);
	displayDrawFastVLine(x        , y        , h, isInverted);
	displayDrawFastVLine(x + w - 1, y        , h, isInverted);
}

/*
 * Fill a rectangle
 */
void displayFillRect(int16_t x, int16_t y, int16_t width, int16_t height, bool isInverted)
{
	uint32_t lineStartOffset;

	for(int yp = 0; yp < height; yp++)
	{
		lineStartOffset = (y + yp) * DISPLAY_SIZE_X;
		for(int xp = 0; xp < width; xp++)
		{
			screenBuf[lineStartOffset + x + xp] = isInverted ? backgroundColour : foregroundColour;
		}
	}
}

#if defined(HAS_COLOURS)
// --- 7-segment ("LED") number rendering ------------------------------------
// Segment bitmask per digit 0-9. Bits: a=0x01 b=0x02 c=0x04 d=0x08 e=0x10 f=0x20 g=0x40
//
//    aaa
//   f   b
//   f   b
//    ggg
//   e   c
//   e   c
//    ddd
static const uint8_t sevenSegDigits[10] =
{
	0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F
};

// Draw one digit's 7 segments inside a w x h cell. Lit segments use litColour,
// the rest use dimColour, giving the "inactive segments faintly visible" look.
static void displayDrawSevenSegDigit(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t seg, uint16_t litColour, uint16_t dimColour, uint16_t bgColour)
{
	int16_t t = (h > 20) ? 3 : 2;   // segment thickness
	int16_t midY = y + (h / 2);
	int16_t vLen = (h / 2) - t;     // vertical segment length

#define SEG_COL(bit) displaySetForegroundAndBackgroundColours((((seg) & (bit)) ? litColour : dimColour), bgColour)
	SEG_COL(0x01); displayFillRect(x + t,       y,                 w - (2 * t), t,    false); // a  top
	SEG_COL(0x02); displayFillRect(x + w - t,   y + t,             t,           vLen, false); // b  top-right
	SEG_COL(0x04); displayFillRect(x + w - t,   midY,              t,           vLen, false); // c  bottom-right
	SEG_COL(0x08); displayFillRect(x + t,       y + h - t,         w - (2 * t), t,    false); // d  bottom
	SEG_COL(0x10); displayFillRect(x,           midY,              t,           vLen, false); // e  bottom-left
	SEG_COL(0x20); displayFillRect(x,           y + t,             t,           vLen, false); // f  top-left
	SEG_COL(0x40); displayFillRect(x + t,       midY - (t / 2),    w - (2 * t), t,    false); // g  middle
#undef SEG_COL
}

// Render a numeric string (digits, '.', spaces) as 7-segment "LED" characters,
// one cellW-wide cell per character, using the given theme foreground colour lit
// and a dimmed version of it for the inactive segments.
void displayDrawFrequencySevenSeg(int16_t x, int16_t y, int16_t cellW, int16_t cellH, const char *str, themeItem_t fgItem)
{
	uint16_t litColour = themeItems[DAYTIME_CURRENT][fgItem];
	uint16_t bgColour = themeItems[DAYTIME_CURRENT][THEME_ITEM_BG];

	// Inactive segments are drawn in the background colour (i.e. off), so only the
	// lit segments show (no dim "ghosting").
	uint16_t dimColour = bgColour;

	for (const char *p = str; *p != '\0'; p++)
	{
		if ((*p >= '0') && (*p <= '9'))
		{
			displayDrawSevenSegDigit(x, y, (cellW - 1), cellH, sevenSegDigits[*p - '0'], litColour, dimColour, bgColour);
			x += cellW;
		}
		else if (*p == '.')
		{
			// The decimal point takes half a cell so the wide digits still fit.
			displaySetForegroundAndBackgroundColours(litColour, bgColour);
			displayFillRect((x + 1), (y + cellH - 3), 3, 3, false);
			x += (cellW / 2);
		}
		else
		{
			x += cellW;
		}
	}
}
#endif // HAS_COLOURS

/*
 *
 */
void displayDrawRectWithDropShadow(int16_t x, int16_t y, int16_t w, int16_t h, bool isInverted)
{
	displayFillRect(x + 2, y, w, h, !isInverted); // Shadow
	displayFillRect(x, y - 2, w, h, isInverted); // Empty box
	displayDrawRect(x, y - 2, w, h, isInverted); // Outline
}

/*
 * Draw a 1-bit image at the specified (x,y) position.
 */
void displayDrawBitmap(int16_t x, int16_t y, uint8_t *bitmap, int16_t w, int16_t h, bool isInverted)
{
	int16_t byteWidth = (w + 7) / 8; // Bitmap scanline pad = whole byte
	uint8_t byte = 0;

	for(int16_t j = 0; j < h; j++, y++)
	{
		for(int16_t i = 0; i < w; i++)
		{
			if(i & 7)
			{
				byte <<= 1;
			}
			else
			{
				byte = *(bitmap + (j * byteWidth + i / 8));
			}

			if(byte & 0x80)
			{
				displaySetPixel(x + i, y, isInverted);
			}
		}
	}
}

/*
 * Draw XBitMap Files (*.xbm), e.g. exported from GIMP.
 */
void displayDrawXBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, bool isInverted)
{
	int16_t byteWidth = (w + 7) / 8; // Bitmap scanline pad = whole byte
	uint8_t byte = 0;

	for(int16_t j = 0; j < h; j++, y++)
	{
		for(int16_t i = 0; i < w; i++)
		{
			if(i & 7)
			{
				byte >>= 1;
			}
			else
			{
				byte = *(bitmap + (j * byteWidth + i / 8));
			}
			// Nearly identical to drawBitmap(), only the bit order
			// is reversed here (left-to-right = LSB to MSB):
			if(byte & 0x01)
			{
				displaySetPixel(x + i, y, isInverted);
			}
		}
	}
}

void displayDrawChoice(ucChoice_t choice, bool clearRegion)
{
#if defined(PLATFORM_RD5R)
	const uint8_t TEXT_Y = 40;
	const uint8_t FILLRECT_Y = 32;
#elif defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
	const uint8_t TEXT_Y = 113 - DISPLAY_Y_OFFSET;
	const uint8_t FILLRECT_Y = 112 - DISPLAY_Y_OFFSET;
#else
	const uint8_t TEXT_Y = 49;
	const uint8_t FILLRECT_Y = 48;
#endif
	const uint8_t TEXT_L_CENTER_X = 12;
	const uint8_t TEXT_R_CENTER_X = (DISPLAY_SIZE_X - 13);

	struct
	{
			char *lText;
			char *rText;
	} choices[] =
	{
			{ "OK"                                       , NULL                                       }, // UC1701_CHOICE_OK
#if defined(PLATFORM_MD9600)
			{ "ENT"                                      , NULL                                       }, // UC1701_CHOICE_ENT
#endif
			{ (char *)currentLanguage->yes___in_uppercase, (char *)currentLanguage->no___in_uppercase }, // UC1701_CHOICE_YESNO
			{ NULL                                       , (char *)currentLanguage->DISMISS           }, // UC1701_CHOICE_DISMISS
			{ "OK"                                       , NULL                                       }  // UC1701_CHOICE_OKARROWS
	};
	char *lText = NULL;
	char *rText = NULL;
	const int ucTriangleArrows[2][6] = {
			{ // Down
					(DISPLAY_SIZE_X/2)-12, (TEXT_Y + (FONT_SIZE_3_HEIGHT / 2) - 1),
					(DISPLAY_SIZE_X/2)-4, (TEXT_Y + (FONT_SIZE_3_HEIGHT / 2) - (FONT_SIZE_3_HEIGHT / 4) - 1),
					(DISPLAY_SIZE_X/2)-4, (TEXT_Y + (FONT_SIZE_3_HEIGHT / 2) + (FONT_SIZE_3_HEIGHT / 4) - 1)
			}, // Up
			{
					(DISPLAY_SIZE_X/2)+4, (TEXT_Y + (FONT_SIZE_3_HEIGHT / 2) + (FONT_SIZE_3_HEIGHT / 4) - 1),
					(DISPLAY_SIZE_X/2)+4, (TEXT_Y + (FONT_SIZE_3_HEIGHT / 2) - (FONT_SIZE_3_HEIGHT / 4) - 1),
					(DISPLAY_SIZE_X/2)+12, (TEXT_Y + (FONT_SIZE_3_HEIGHT / 2) - 1)
			}
	};

	displayThemeResetToDefault();
	if (clearRegion)
	{
		displayFillRect(0, FILLRECT_Y, DISPLAY_SIZE_X, 16, true);
	}

	if (choice >= CHOICES_NUM)
	{
		return;
	}

	lText = choices[choice].lText;
	rText = choices[choice].rText;

	displayThemeApply(THEME_ITEM_FG_NOTIFICATION, THEME_ITEM_BG);
	if (lText)
	{
		int16_t x = (TEXT_L_CENTER_X - ((strlen(lText) * 8) >> 1));

		if (x < 2)
		{
			x = 2;
		}
		displayPrintAt(x, TEXT_Y, lText, FONT_SIZE_3);
	}

	if(rText)
	{
		size_t len = (strlen(rText) * 8);
		int16_t x = (TEXT_R_CENTER_X - (len >> 1));

		if ((x + len) > (DISPLAY_SIZE_X - 2))
		{
			x = ((DISPLAY_SIZE_X - 2) - len);
		}
		displayPrintAt(x, TEXT_Y, rText, FONT_SIZE_3);
	}

	displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG);
	if (choice == CHOICES_OKARROWS)
	{
		displayFillTriangle(ucTriangleArrows[0][0], ucTriangleArrows[0][1],
				ucTriangleArrows[0][2], ucTriangleArrows[0][3],
				ucTriangleArrows[0][4], ucTriangleArrows[0][5], true);
		displayFillTriangle(ucTriangleArrows[1][0], ucTriangleArrows[1][1],
				ucTriangleArrows[1][2], ucTriangleArrows[1][3],
				ucTriangleArrows[1][4], ucTriangleArrows[1][5], true);
	}
	displayThemeResetToDefault();
}

uint16_t *displayGetScreenBuffer(void)
{
	return screenBuf;
}

void displayRestorePrimaryScreenBuffer(void)
{
	screenBuf = screenBufData;
}

uint16_t *displayGetPrimaryScreenBuffer(void)
{
	return &screenBufData[0];
}

void displayOverrideScreenBuffer(uint16_t *buffer)
{
	screenBuf = buffer;
}

static bool isAwake = true;

#if 0
static void dmaCompleteCallback(DMA_HandleTypeDef *hdma)
{
	HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET);

	*((volatile uint8_t*) LCD_FSMC_ADDR_DATA) = 0;// write 0 to the display pins , to pull them all low, so keyboard reads don't need to
}
#endif

void displayRenderRows(int16_t startRow, int16_t endRow)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};

	// GD77 display controller has 8 lines per row.
	startRow *= 8;
	endRow *= 8;

	// Display shares its pins with the keypad, so the pind need to be put into alternate mode to work with the FSMC
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
	GPIO_InitStruct.Alternate = GPIO_AF12_FSMC;

	GPIO_InitStruct.Pin = LCD_D0_Pin | LCD_D1_Pin | LCD_D2_Pin | LCD_D3_Pin;
	HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

	GPIO_InitStruct.Pin = LCD_D4_Pin | LCD_D5_Pin | LCD_D6_Pin | LCD_D7_Pin;
	HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

	HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET);

	// Set start and end rows of the transfer
	{
#if defined(PLATFORM_VARIANT_DM1701)
		uint8_t opts[] = { 0x00, startRow + DISPLAY_Y_OFFSET, 0x00, endRow + DISPLAY_Y_OFFSET };
#else
		uint8_t opts[] = { 0x00, startRow, 0x00, endRow };
#endif
		displayWriteCmds(HX8583_CMD_RASET, sizeof(opts), opts);
	}

	displayWriteCmd(HX8583_CMD_RAMWR);

	uint8_t *framePtr = (uint8_t *)screenBuf + (DISPLAY_SIZE_X * startRow * sizeof(uint16_t));

	//if (HAL_DMA_RegisterCallback(&hdma_memtomem_dma2_stream0, HAL_DMA_XFER_CPLT_CB_ID, dmaCompleteCallback)== HAL_OK)
	{
		HAL_StatusTypeDef status = HAL_DMA_Start(&hdma_memtomem_dma2_stream0, (uint32_t)framePtr, LCD_FSMC_ADDR_DATA, (endRow - startRow) * DISPLAY_SIZE_X * sizeof(uint16_t));
		if (status == HAL_OK)
		{
			HAL_DMA_PollForTransfer(&hdma_memtomem_dma2_stream0, HAL_DMA_FULL_TRANSFER, HAL_MAX_DELAY);
			// need to wait for completion otherwise we CS gets disabled immediately.
			// This could be done using a transfer complete callback
		}
	}


#if false
	// fallback
	for(int y = 0; y < (endRow - startRow) * DISPLAY_SIZE_X * sizeof(uint16_t); y++)
	{
		*((volatile uint8_t*) LCD_FSMC_ADDR_DATA) = *framePtr++;
	}
#endif

	HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET);

	*((volatile uint8_t*) LCD_FSMC_ADDR_DATA) = 0;// write 0 to the display pins , to pull them all low, so keyboard reads don't need to
}

void displaySetInverseVideo(bool isInverted)
{
	inverted = isInverted;
}

// Native color format (RGB565)
void displayBegin(bool isInverted, bool SPIFlashAvailable)
{
	displaySetInvertedState(isInverted);
	themeInit(SPIFlashAvailable);
}

void displaySetContrast(uint8_t contrast)
{
	// this display does not have a contrast control
}

// Note.
// Entering "Sleep" mode makes the display go blank
void displaySetDisplayPowerMode(bool wake)
{
	if (isAwake == wake)
	{
		return;
	}

	isAwake = wake;
}

void displayConvertGD77ImageData(uint8_t *dataBuf)
{
	const uint32_t startOffset = (32 * DISPLAY_SIZE_X) + (DISPLAY_SIZE_X - 128) / 2;

	for(int y = 0; y < 8; y++ )
	{
		for(int x = 0; x < 128; x++)
		{
			uint8_t d = dataBuf[(y * 128) + x];
			for(int r = 0; r < 8; r++)
			{
				screenBuf[startOffset + (((y * 8) + r) * DISPLAY_SIZE_X) + x] = ((d >> r) & 0x01) ? backgroundColour : foregroundColour;
			}
		}
	}

	// clear beginning of display buff used to store the image read from flash
	for(int i = 0; i < (128 * 64 / 8 / 2); i++)
	{
		screenBuf[i] = backgroundColour;
	}
}

//
// Colour related functions
//

#if defined(HAS_COLOURS)
uint16_t displayConvertRGB888ToNative(uint32_t RGB888)
{
	// Convert to native format (RGB565/BGR565, swapped bytes)
	return __builtin_bswap16(RGB888_TO_PLATFORM_COLOUR_FORMAT(RGB888));
}
#endif

//
// Native color format (swapped RGB565/BGR565, swapped bytes) functions
//
void displaySetForegroundAndBackgroundColours(uint16_t fgNativeColour, uint16_t bgNativeColour)
{
	foregroundColour = fgNativeColour;
	backgroundColour = bgNativeColour;
}

void displayGetForegroundAndBackgroundColours(uint16_t *fgNativeColour, uint16_t *bgNativeColour)
{
	*fgNativeColour = foregroundColour;
	*bgNativeColour = backgroundColour;
}

#if defined(HAS_COLOURS)
void themeInitToDefaultValues(DayTime_t daytime, bool invert)
{
	foregroundColour = PLATFORM_COLOUR_FORMAT_SWAP_BYTES(RGB888_TO_PLATFORM_COLOUR_FORMAT((invert ? 0xFFFFFFU : 0x000000)));
	backgroundColour = PLATFORM_COLOUR_FORMAT_SWAP_BYTES(RGB888_TO_PLATFORM_COLOUR_FORMAT((invert ? 0x000000 : 0xFFFFFFU)));

	foregroundThemeItem = THEME_ITEM_FG_DEFAULT;
	backgroundThemeItem = THEME_ITEM_BG;

	themeItems[daytime][THEME_ITEM_FG_DEFAULT] = foregroundColour;
	themeItems[daytime][THEME_ITEM_BG] = backgroundColour;
	themeItems[daytime][THEME_ITEM_FG_DECORATION] = foregroundColour;
	themeItems[daytime][THEME_ITEM_FG_TEXT_INPUT] = foregroundColour;
	themeItems[daytime][THEME_ITEM_FG_SPLASHSCREEN] = foregroundColour;
	themeItems[daytime][THEME_ITEM_BG_SPLASHSCREEN] = backgroundColour;
	themeItems[daytime][THEME_ITEM_FG_NOTIFICATION] = foregroundColour;
	themeItems[daytime][THEME_ITEM_FG_WARNING_NOTIFICATION] = foregroundColour;
	themeItems[daytime][THEME_ITEM_FG_ERROR_NOTIFICATION] = foregroundColour;
	themeItems[daytime][THEME_ITEM_BG_NOTIFICATION] = backgroundColour;
	themeItems[daytime][THEME_ITEM_FG_MENU_NAME] = foregroundColour;
	themeItems[daytime][THEME_ITEM_BG_MENU_NAME] = backgroundColour;
	themeItems[daytime][THEME_ITEM_FG_MENU_ITEM] = foregroundColour;
	themeItems[daytime][THEME_ITEM_BG_MENU_ITEM_SELECTED] = foregroundColour;
	themeItems[daytime][THEME_ITEM_FG_OPTIONS_VALUE] = foregroundColour;
	themeItems[daytime][THEME_ITEM_FG_HEADER_TEXT] = foregroundColour;
	themeItems[daytime][THEME_ITEM_BG_HEADER_TEXT] = backgroundColour;
	themeItems[daytime][THEME_ITEM_FG_RSSI_BAR] = foregroundColour;
	themeItems[daytime][THEME_ITEM_FG_RSSI_BAR_S9P] = foregroundColour;
	themeItems[daytime][THEME_ITEM_FG_CHANNEL_NAME] = foregroundColour;
	themeItems[daytime][THEME_ITEM_FG_CHANNEL_CONTACT] = foregroundColour;
	themeItems[daytime][THEME_ITEM_FG_CHANNEL_CONTACT_INFO] = foregroundColour;
	themeItems[daytime][THEME_ITEM_FG_ZONE_NAME] = foregroundColour;
	themeItems[daytime][THEME_ITEM_FG_RX_FREQ] = foregroundColour;
	themeItems[daytime][THEME_ITEM_FG_TX_FREQ] = foregroundColour;
	themeItems[daytime][THEME_ITEM_FG_CSS_SQL_VALUES] = foregroundColour;
	themeItems[daytime][THEME_ITEM_FG_TX_COUNTER] = foregroundColour;
	themeItems[daytime][THEME_ITEM_FG_POLAR_DRAWING] = foregroundColour;
	themeItems[daytime][THEME_ITEM_FG_SATELLITE_COLOUR] = foregroundColour;
	themeItems[daytime][THEME_ITEM_FG_GPS_NUMBER] = foregroundColour;
	themeItems[daytime][THEME_ITEM_FG_GPS_COLOUR] = PLATFORM_COLOUR_FORMAT_SWAP_BYTES(RGB888_TO_PLATFORM_COLOUR_FORMAT(0x0000FFU));
	themeItems[daytime][THEME_ITEM_FG_BD_COLOUR] = PLATFORM_COLOUR_FORMAT_SWAP_BYTES(RGB888_TO_PLATFORM_COLOUR_FORMAT(0xFF0000U));
}

// Set every theme item to fgRGB, then override the background items with bgRGB, for
// a cohesive single-palette look. The selected-menu-item background is intentionally
// left as the foreground colour so the highlighted row still inverts.
static void themeSetAllItems(DayTime_t daytime, uint32_t fgRGB, uint32_t bgRGB)
{
	uint16_t fg = PLATFORM_COLOUR_FORMAT_SWAP_BYTES(RGB888_TO_PLATFORM_COLOUR_FORMAT(fgRGB));
	uint16_t bg = PLATFORM_COLOUR_FORMAT_SWAP_BYTES(RGB888_TO_PLATFORM_COLOUR_FORMAT(bgRGB));

	for (int i = 0; i < THEME_ITEM_MAX; i++)
	{
		themeItems[daytime][i] = fg;
	}
	themeItems[daytime][THEME_ITEM_BG] = bg;
	themeItems[daytime][THEME_ITEM_BG_SPLASHSCREEN] = bg;
	themeItems[daytime][THEME_ITEM_BG_NOTIFICATION] = bg;
	themeItems[daytime][THEME_ITEM_BG_MENU_NAME] = bg;
	themeItems[daytime][THEME_ITEM_BG_HEADER_TEXT] = bg;
	// THEME_ITEM_BG_MENU_ITEM_SELECTED intentionally left = fg (inverts the selected row).
}

static void themeSetItem(DayTime_t daytime, themeItem_t item, uint32_t rgb)
{
	themeItems[daytime][item] = PLATFORM_COLOUR_FORMAT_SWAP_BYTES(RGB888_TO_PLATFORM_COLOUR_FORMAT(rgb));
}

// 80s synthwave: a dark background with several neon accents mapped onto the
// individual UI elements. Both day and night use the same neon palette so the
// look is preserved; only the background darkens for night.
static void themeSetNeon80s(DayTime_t daytime, uint32_t bg)
{
	const uint32_t cyan    = 0x00F0FF;
	const uint32_t magenta = 0xFF2CA5;
	const uint32_t green   = 0x39FF14;
	const uint32_t violet  = 0xB16CFF;
	const uint32_t yellow  = 0xFFE600;
	const uint32_t red     = 0xFF3131;
	const uint32_t orange  = 0xFF7A18;

	// Base: cyan text on the dark background, everything else overridden below.
	themeSetAllItems(daytime, cyan, bg);

	themeSetItem(daytime, THEME_ITEM_FG_DECORATION,           magenta); // borders / separators / drop shadow
	themeSetItem(daytime, THEME_ITEM_FG_TEXT_INPUT,           green);
	themeSetItem(daytime, THEME_ITEM_FG_SPLASHSCREEN,         magenta);

	themeSetItem(daytime, THEME_ITEM_FG_NOTIFICATION,         cyan);
	themeSetItem(daytime, THEME_ITEM_FG_WARNING_NOTIFICATION, yellow);
	themeSetItem(daytime, THEME_ITEM_FG_ERROR_NOTIFICATION,   red);

	themeSetItem(daytime, THEME_ITEM_FG_MENU_NAME,            magenta); // menu header
	themeSetItem(daytime, THEME_ITEM_BG_MENU_ITEM_SELECTED,   magenta); // selected row = magenta bar
	themeSetItem(daytime, THEME_ITEM_FG_OPTIONS_VALUE,        green);
	themeSetItem(daytime, THEME_ITEM_FG_HEADER_TEXT,          violet);  // mode/PWR/battery header

	themeSetItem(daytime, THEME_ITEM_FG_RSSI_BAR,             cyan);
	themeSetItem(daytime, THEME_ITEM_FG_RSSI_BAR_S9P,         magenta); // over S9 turns hot pink

	themeSetItem(daytime, THEME_ITEM_FG_CHANNEL_NAME,         yellow);
	themeSetItem(daytime, THEME_ITEM_FG_CHANNEL_CONTACT,      magenta);
	themeSetItem(daytime, THEME_ITEM_FG_CHANNEL_CONTACT_INFO, cyan);
	themeSetItem(daytime, THEME_ITEM_FG_ZONE_NAME,            violet);

	themeSetItem(daytime, THEME_ITEM_FG_RX_FREQ,             cyan);
	themeSetItem(daytime, THEME_ITEM_FG_TX_FREQ,             red);
	themeSetItem(daytime, THEME_ITEM_FG_CSS_SQL_VALUES,      green);
	themeSetItem(daytime, THEME_ITEM_FG_TX_COUNTER,         yellow);

	themeSetItem(daytime, THEME_ITEM_FG_POLAR_DRAWING,      violet);
	themeSetItem(daytime, THEME_ITEM_FG_SATELLITE_COLOUR,   magenta);
	themeSetItem(daytime, THEME_ITEM_FG_GPS_NUMBER,         cyan);
	themeSetItem(daytime, THEME_ITEM_FG_GPS_COLOUR,        green);
	themeSetItem(daytime, THEME_ITEM_FG_BD_COLOUR,        orange);
}

// Pride: the background canvas is painted as a rainbow (see pridePixelColour);
// the theme items only need black text plus white "chrome" boxes (menu title,
// header, notifications) so those stay readable over any stripe. The selected
// menu row already inverts to a black bar with white text.
static void themeSetPride(DayTime_t daytime)
{
	themeSetAllItems(daytime, 0x000000, 0xFFFFFF); // black text, white chrome backgrounds

	// Paint the status/header bar and the menu title bar in the top stripe
	// colour (prideRGB[0]) so they blend into the rainbow instead of showing
	// as white boxes.
	themeSetItem(daytime, THEME_ITEM_BG_HEADER_TEXT, 0xE40303);
	themeSetItem(daytime, THEME_ITEM_BG_MENU_NAME,   0xE40303);

	// Keep warning/error notifications colour-coded on their white box.
	themeSetItem(daytime, THEME_ITEM_FG_WARNING_NOTIFICATION, 0xC07800);
	themeSetItem(daytime, THEME_ITEM_FG_ERROR_NOTIFICATION,   0xC00000);
}

void themeApplyPreset(uint8_t preset)
{
	// Start from the monochrome defaults, then overlay the chosen preset.
	themeInitToDefaultValues(NIGHT, true);
	themeInitToDefaultValues(DAY, false);

	prideBackgroundActive = false; // only the Pride preset re-enables the rainbow canvas

	switch (preset)
	{
		case THEME_PRESET_BASIC80S: // Commodore 64 BASIC: light blue on blue (day & night)
			themeSetAllItems(DAY,   0x7C71D5, 0x3E31A2);
			themeSetAllItems(NIGHT, 0x7C71D5, 0x3E31A2);
			break;

		case THEME_PRESET_TERMINAL:
			themeSetAllItems(DAY,   0x000000, 0x00A000); // black on green
			themeSetAllItems(NIGHT, 0x00E000, 0x000000); // green on black
			break;

		case THEME_PRESET_VERTEXIC: // orange (Vertex-style)
			themeSetAllItems(DAY,   0x000000, 0xFF6A00); // black on orange
			themeSetAllItems(NIGHT, 0xFF6A00, 0x000000); // orange on black
			break;

		case THEME_PRESET_RED:
			themeSetAllItems(DAY,   0x000000, 0xFF0000); // black on red
			themeSetAllItems(NIGHT, 0xFF0000, 0x000000); // red on black
			break;

		case THEME_PRESET_NEON80S: // 80s synthwave neon on dark
			themeSetNeon80s(DAY,   0x1B0B2E); // dark indigo
			themeSetNeon80s(NIGHT, 0x08040F); // near-black purple
			break;

		case THEME_PRESET_PRIDE: // rainbow background canvas
			{
				// Classic 6-stripe pride flag; blue/violet lightened a little so
				// black text stays legible over the lower stripes.
				static const uint32_t prideRGB[6] = { 0xE40303, 0xFF8C00, 0xFFED00, 0x22A93F, 0x3D7DF0, 0xB05CD6 };

				for (int s = 0; s < 6; s++)
				{
					prideStripes[s] = PLATFORM_COLOUR_FORMAT_SWAP_BYTES(RGB888_TO_PLATFORM_COLOUR_FORMAT(prideRGB[s]));
				}
				prideBackgroundActive = true;
				themeSetPride(DAY);
				themeSetPride(NIGHT);
			}
			break;

		case THEME_PRESET_CUSTOM:
		default:
			// Overlay the user's Custom theme from the codeplug, if present (CPS edits
			// only ever touch this).
			{
				uint16_t themingTmp[THEME_ITEM_MAX];

				if (codeplugGetOpenGD77CustomData(CODEPLUG_CUSTOM_DATA_TYPE_THEME_DAY, (uint8_t *) &themingTmp))
				{
					memcpy(&themeItems[DAY], &themingTmp, sizeof(themingTmp));
				}
				if (codeplugGetOpenGD77CustomData(CODEPLUG_CUSTOM_DATA_TYPE_THEME_NIGHT, (uint8_t *) &themingTmp))
				{
					memcpy(&themeItems[NIGHT], &themingTmp, sizeof(themingTmp));
				}
			}
			break;
	}

	foregroundColour = themeItems[DAY][THEME_ITEM_FG_DEFAULT];
	backgroundColour = themeItems[DAY][THEME_ITEM_BG];
}

uint8_t themeGetPreset(void)
{
	uint8_t buf[4] = { THEME_PRESET_CUSTOM, 0, 0, 0 };

	if (codeplugGetOpenGD77CustomData(CODEPLUG_CUSTOM_DATA_TYPE_THEME_PRESET, buf))
	{
		return (buf[0] < NUM_THEME_PRESETS) ? buf[0] : THEME_PRESET_CUSTOM;
	}
	return THEME_PRESET_CUSTOM;
}

void themeSetPreset(uint8_t preset, bool save)
{
	if (preset >= NUM_THEME_PRESETS)
	{
		preset = THEME_PRESET_CUSTOM;
	}

	themeApplyPreset(preset);

	if (save)
	{
		uint8_t buf[1] = { preset };
		codeplugSetOpenGD77CustomData(CODEPLUG_CUSTOM_DATA_TYPE_THEME_PRESET, buf, sizeof(buf));
	}
}

void themeInit(bool SPIFlashAvailable)
{
	if (SPIFlashAvailable)
	{
		themeApplyPreset(themeGetPreset());
	}
	else
	{
		themeInitToDefaultValues(NIGHT, true);
		themeInitToDefaultValues(DAY, false);
	}

	if (settingsIsOptionBitSet(BIT_AUTO_NIGHT_OVERRIDE) && (uiDataGlobal.daytimeOverridden == NIGHT))
	{
		displayThemeResetToDefault(); // Update colours to NIGHT theme (as default is DAY).
	}
}

void displayThemeApply(themeItem_t fgItem, themeItem_t bgItem)
{
	if (fgItem < THEME_ITEM_MAX)
	{
		foregroundThemeItem = fgItem;
		foregroundColour = themeItems[DAYTIME_CURRENT][fgItem];
	}

	if (bgItem < THEME_ITEM_MAX)
	{
		backgroundThemeItem = bgItem;
		backgroundColour = themeItems[DAYTIME_CURRENT][bgItem];
	}
}

void displayThemeResetToDefault(void)
{
	foregroundThemeItem = THEME_ITEM_FG_DEFAULT;
	backgroundThemeItem = THEME_ITEM_BG;
	displaySetForegroundAndBackgroundColours(themeItems[DAYTIME_CURRENT][THEME_ITEM_FG_DEFAULT], themeItems[DAYTIME_CURRENT][THEME_ITEM_BG]);
}

bool displayThemeIsForegroundColourEqualTo(themeItem_t fgItem)
{
	return (foregroundThemeItem == fgItem);
}

void displayThemeGetForegroundAndBackgroundItems(themeItem_t *fgItem, themeItem_t *bgItem)
{
	*fgItem = foregroundThemeItem;
	*bgItem = backgroundThemeItem;
}

bool displayThemeSaveToFlash(DayTime_t daytime)
{
	return codeplugSetOpenGD77CustomData(((daytime == DAY) ? CODEPLUG_CUSTOM_DATA_TYPE_THEME_DAY : CODEPLUG_CUSTOM_DATA_TYPE_THEME_NIGHT),
			(uint8_t *)&themeItems[daytime], (sizeof(uint16_t) * THEME_ITEM_MAX));
}
#endif // HAS_COLOURS
