
/*
 * display.cpp
 *
 *  Created on: 30 Nov 2020
 *      Author: mike
 */

#include "main.h"
#include "display.h"
#include "switches.h"
#include "nvram.h"
#include <cstring>
#include "libOLED/stm32_halDisplay.H"
#include "libOLED/fontclass.H"
#include "libOLED/displayDial.H"
#include "libPlx/displayInfo.H"
#include "libOLED/ap_math.h"
#include "libSmallPrintf/small_printf.h"

#include "splash.H"

namespace
{
	int const WIDTH = 128;
	int const HEIGHT = 64;
	int const DISPLAY_RAMWIDTH = 132;

}

uint8_t displayBuffer[2][dataSize(WIDTH, HEIGHT)];

stm32_halDisplay_t displays[MAX_DISPLAYS] =
	{stm32_halDisplay_t(WIDTH, HEIGHT, DISPLAY_RAMWIDTH, displayBuffer[0],
						&hspi1,
						SPI_CD_GPIO_Port,
						SPI_CD_Pin,
						SPI_RESET_GPIO_Port,
						SPI_RESET_Pin,
						SPI_NSS1_GPIO_Port,
						SPI_NSS1_Pin),
	 stm32_halDisplay_t(WIDTH, HEIGHT,
						DISPLAY_RAMWIDTH,
						displayBuffer[1],
						&hspi1,
						SPI_CD_GPIO_Port,
						SPI_CD_Pin,
						SPI_RESET_GPIO_Port,
						SPI_RESET_Pin,
						SPI_NSS2_GPIO_Port,
						SPI_NSS2_Pin)};

displayDial_t dials[MAX_DISPLAYS] =
	{displayFullDial_t(displays[0]), displayFullDial_t(displays[1])};
#if defined __cplusplus
extern "C"
{
#endif
	static void
	showMinMax(display_t &display, uint8_t dp_pos, int16_t int_min,
			   uint16_t int_max)
	{
		const char padding[] = "      ";
		// left justified display of minimum
		int8_t width = display.fontSigDigits(small_font, 0, 0, 0, dp_pos, int_min, WHITE);
		// pad with spaces if fewer than 6 characters are used.
		if (width != 6)
			display.printString(small_font, padding, 6 - width, WHITE);

		display.gotoxy(0, 8);
		display.printString(small_font, "Min", 3, WHITE);

		// right justified display of maximum
		width = display.fontSigDigits(small_font, 120, 0, 1, dp_pos, int_max, WHITE);
		// right justified display of maximum : pad spaces to left
		if (width != 6)
			display.printString(small_font, padding, 6 - width, WHITE);

		display.gotoxy(110, 8);
		display.printString(small_font, "Max", 3, WHITE);
	}

	void
	cc_init()
	{
		for (auto i = 0; i < MAX_DISPLAYS; i++)
		{
			display_t &display = displays[i];
			if (i == 0)
				display.reset();
			display.init();
			display.clearDisplay(BLACK);
			displaySplash(display);
			display.gotoxy(8, 32);
			display.printString(large_font, i == 0 ? "1" : "2", 1, BLACK);
			display.display();
		}

		HAL_Delay(1000);

		for (auto i = 0; i < MAX_DISPLAYS; i++)
		{
			display_t &display = displays[i];
			display.clearDisplay(BLACK);
			display.setPixelMode(WHITE);
			display.display();
			context_t &context = contexts[i];
			context.dial_timer = 500; // enough time to see at least one frame of PLX before NVRAM check
			context.dial1 = -1;
			context.OldObservation = nullObs;
		}
	}

	// Check to see if there is an observation/instance in the dynamic data array
	// that matches the current observation/instance in the NVRAM

	void
	cc_check_nvram(int dialIndex)
	{
		if (dialIndex < 0 && dialIndex > MAX_DISPLAYS)
			return;
		// algorithm only works when there is a vector of observations

		context_t &context = contexts[dialIndex];

		// check for timer timeout on consistent timer

		if (context.dial_timer)
		{
			context.dial_timer--;
		}
		if (context.dial_timer == 0)
		{
			context.dial_timer = DialTimeout;
			int i;
			// use dialIndex+1 as tag for data : always non-zero.
			nvram_info_t *dial_nvram = find_nvram_data(dialIndex + 1);

			if (dial_nvram && context.knobPos < 0)
			{
				for (i = 0; i < MAXRDG; i++)
					if (isValid(i) && (Info[i].observation.Obs == dial_nvram->data.observation) && (Info[i].observation.Instance == dial_nvram->data.instance))
					{
						context.knobPos = i;
						return;
					}
			}
			if (context.knobPos == -1)
				context.knobPos = dialIndex; // timed out , not in NVRAM, use a default

			// dont save dial info for invalid data
			if (!isValid(context.knobPos))
				return;
			// is this a change since the last timeout ?

			if (!dial_nvram || (Info[context.knobPos].observation.Obs != dial_nvram->data.observation) || (Info[context.knobPos].observation.Instance != dial_nvram->data.instance))
			{

				// store the observation and instance in the NVRAM, not dial position.
				nvram_info_t curr_val;
				curr_val.data.observation = Info[context.knobPos].observation.Obs;
				curr_val.data.instance = Info[context.knobPos].observation.Instance;
				curr_val.data.tag = dialIndex + 1;

				write_nvram_data(curr_val);
			}
		}
	}

	int
	cc_display(int dialIndex, int suppressIndex)

	{

		if (dialIndex < 0 && dialIndex > MAX_DISPLAYS)
			return -1;
		context_t &context = contexts[dialIndex];
		displayDial_t &dial = dials[dialIndex];
		stm32_halDisplay_t &display = displays[dialIndex];
		int itemIndex = context.knobPos;
		char buff[10];
		int i;
		const char *msg;
		int len;
		// check for startup phase
		if (itemIndex < 0)
		{
			display.clearDisplay(BLACK);
			i = small_sprintf(buff, "Wait");
			display.gotoxy(64 - i * 4, 48);
			display.printString(large_font, buff, i, WHITE);

			display.display();
			return -1;
		}

		// check for item suppression
		if (itemIndex == suppressIndex)
		{
			context.dial1 = -1;
			context.OldObservation = nullObs;
			display.clearDisplay(BLACK);
			i = small_sprintf(buff, "Supp-%02d", itemIndex);
			display.gotoxy(64 - i * 4, 48);
			display.printString(large_font, buff, i, WHITE);

			display.display();
			return -1; // we suppressed this display
		}

		// check for item validity
		if (!isValid(itemIndex))
		{
			context.dial1 = -1;
			context.OldObservation = nullObs;
			display.clearDisplay(BLACK);
			i = small_sprintf(buff, "Inval-%02d", itemIndex);
			display.gotoxy(64 - i * 4, 48);
			display.printString(large_font, buff, i, WHITE);

			display.display();
			return itemIndex;
		}

		// clear startup display off the screen
		if (context.OldObservation.Obs == -1)
			display.clearDisplay(BLACK);

		int DataVal = Info[itemIndex].data; // data reading
		PLX_Observations Observation = Info[itemIndex].observation.Obs;
		uint8_t Instance = Info[itemIndex].observation.Instance;
		// now to convert the readings and format strings
		// find out limits
		// if the user presses the dial then reset min/max to current value
		if (push_pos[dialIndex] == 1)
		{
			Info[itemIndex].Max = DataVal;
			Info[itemIndex].Min = DataVal; // 12 bit max value
		}

		// detect change in observation being displayed, reset the dial
		if (Observation < PLX_MAX_OBS)
		{
			if (Observation != context.OldObservation.Obs || Instance != context.OldObservation.Instance)
			{

				display.clearDisplay(BLACK);
				dial.draw_scale(DisplayInfo[Observation].Low,
								DisplayInfo[Observation].High, 12, 1,
								DisplayInfo[Observation].TickScale);

				dial.draw_limits();

				msg = DisplayInfo[Observation].name;
				len = 7;
				int len1 = Instance > 0 ? len - 1 : len;
				for (i = 0; i < len1 && msg[i]; i++)
				{
					buff[i] = msg[i];
				}
				if (Instance > 0 && i < len)
				{
					buff[i++] = Instance + '1';
				}

				display.gotoxy(64 - i * 4, 48);
				display.printString(large_font, buff, i, WHITE);

				context.OldObservation.Obs = Observation;
				context.OldObservation.Instance = Instance;
				context.dial1 = -1; // do not display old needle, cleared screen
				display.display();
			}
		}

		if (Info[itemIndex].updated)
		{
			Info[itemIndex].updated = 0;

			double max_rdg;
			double min_rdg;
			double cur_rdg;
			int int_rdg;
			int int_max;
			int int_min;

			max_rdg = ConveriMFDRaw2Data((enum PLX_Observations)Observation, DisplayInfo[Observation].Units,
										 Info[itemIndex].Max);
			min_rdg = ConveriMFDRaw2Data((enum PLX_Observations)Observation, DisplayInfo[Observation].Units,
										 Info[itemIndex].Min);
			cur_rdg = ConveriMFDRaw2Data((enum PLX_Observations)Observation, DisplayInfo[Observation].Units,
										 Info[itemIndex].data);
			int dp_pos; // where to print the decimal place
			float scale = 1.0;
			switch (DisplayInfo[Observation].DP)
			{
			default:
			case 0:
				scale = 1.0;
				dp_pos = display_t::NO_DECIMAL;
				break;
			case 1:
				scale = 10.0;
				dp_pos = 1;
				break;
			case 2:
				scale = 100.0;
				dp_pos = 2;
				break;
			}
			int_rdg = (int)(cur_rdg * scale);
			int_max = (int)(max_rdg * scale);
			int_min = (int)(min_rdg * scale);

			cur_rdg -= DisplayInfo[Observation].Low;
			cur_rdg = ap_math::SINE_STEPS * cur_rdg / (DisplayInfo[Observation].High - DisplayInfo[Observation].Low);

			context.dial0 = (int)cur_rdg;

			display.gotoxy(32, 28);
			display.fontDigits(large_font, 4, dp_pos, int_rdg, WHITE);

			display.printString(small_font, DisplayInfo[Observation].suffix,
								strlen(DisplayInfo[Observation].suffix));
			display.printString(small_font, "    ",
								3 - strlen(DisplayInfo[Observation].suffix));
			// print value overlaid by needle

			/* old needle un-draw */
			if (context.dial1 >= 0)
			{
				dial.draw_needle(context.dial1);
			}
			dial.draw_needle(context.dial0);
			context.dial1 = context.dial0;
			showMinMax(display, dp_pos, int_min, int_max);
		}
		else
		{
			if (Info[itemIndex].lastUpdated && ((HAL_GetTick() - Info[itemIndex].lastUpdated) > 1000))
			{
				context.OldObservation = nullObs; // force a redraw on next update
				Info[itemIndex].lastUpdated = 0;  // and stop further timeouts.
			}
		}

		display.gotoxy(0, 32);

		// display BT connection status
		display.printString(small_font, btConnected() ? "\x81" : " ", 1);

		display.display();

		return itemIndex;
	}
}