/*
 * LEIGH:plc
 * Copyright 2022 Alex Leigh
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cJSON.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/semphr.h>
#include <esp_log.h>
#include "eth.h"
#include "ssd1306.h"
#include "font8x8_basic.h"
#include "ele.h"
#include "json.h"

#ifdef BOARD_WESP32
#define CONFIG_SDA_GPIO 15
#define CONFIG_SCL_GPIO 4
#define CONFIG_RESET_GPIO -1
#define CONFIG_MODEL 0
#elif BOARD_ESPRESSIF_DEV
// These are wrong
#define CONFIG_SDA_GPIO 33
#define CONFIG_SCL_GPIO 5
#define CONFIG_RESET_GPIO -1
#define CONFIG_MODEL 0
#endif

#define tag "lcd"

static cJSON *lcdG_screen;
static cJSON *lcdF_init_screen();
static SemaphoreHandle_t lcdL_screen;
static cJSON *lcdG_avail_ram_field;

/**
 * Return a pointer to the LCD screen to the caller. The screen should not be read or
 * manpulated without holding the corresponding lock.
 */
cJSON *lcdFget_screen()
{
	return lcdG_screen;
}

int lcdFlock_screen()
{
	if (xSemaphoreTake(lcdL_screen, (TickType_t)10) == pdTRUE)
	{
		return 1;
	}
	return 0;
}

void lcdFunlock_screen()
{
	xSemaphoreGive(lcdL_screen);
}

void lcdFinit()
{
	lcdG_screen = lcdF_init_screen();
	assert(lcdG_screen != NULL);
	lcdL_screen = xSemaphoreCreateMutex();
	assert(lcdL_screen != NULL);
}

static cJSON *lcdF_init_screen()
{
	cJSON *panels = cJSON_CreateArray();
	cJSON *panel = cJSON_CreateObject();
	cJSON *fields = cJSON_CreateArray();

	cJSON_AddItemToArray(panels, panel);
	cJSON_AddStringToObject(panel, ELE_PANEL_TITLE, "System");

	cJSON_AddItemToObject(panel, ELE_PANEL_CONTENT, fields);

	cJSON *ver = cJSON_CreateObject();
	cJSON_AddStringToObject(ver, ELE_FIELD_TITLE, "VER");
	cJSON_AddStringToObject(ver, ELE_FIELD_VALUE, "mk1 mod0");
	cJSON_AddItemToArray(fields, ver);

	lcdG_avail_ram_field = cJSON_CreateObject();
	cJSON_AddStringToObject(lcdG_avail_ram_field, ELE_FIELD_TITLE, "FREE");
	cJSON_AddItemToArray(fields, lcdG_avail_ram_field);

	cJSON *screen = cJSON_CreateObject();
	cJSON_AddItemToObject(screen, ELE_SCREEN_CONTENT, panels);

	return screen;
}

/**
 * @brief Task to manage LCD display
 *
 * FreeRTOS task to manage an OLED I2C display. This implementation currently only supports
 * the SSD1306 chipset.
 *
 */
void lcdFtask(void)
{
	SSD1306_t dev;
	i2c_master_init(&dev, CONFIG_SDA_GPIO, CONFIG_SCL_GPIO, CONFIG_RESET_GPIO);

#if CONFIG_FLIP
	dev._flip = true;
	ESP_LOGW(tag, "Flip upside down");
#endif
	ssd1306_init(&dev, 128, 64);
	ssd1306_clear_screen(&dev, false);
	ssd1306_contrast(&dev, 0xff);

	char buf[17];

	cJSON *panels = cJSON_GetObjectItem(lcdG_screen, ELE_SCREEN_CONTENT);
	cJSON *panel;

	// FIXME: This needs to lock properly
	while (1)
	{
		// We can't clear the screen here because it flashes
		cJSON_ArrayForEach(panel, panels)
		{
			// TODO: Only do this at the right time instead of all the time
			uint32_t freeBytes = xPortGetFreeHeapSize();
			snprintf(buf, sizeof(buf), "%lu KB", freeBytes/1024);
			jsonFset(lcdG_avail_ram_field, ELE_FIELD_VALUE, buf);
		
			char *title = cJSON_GetObjectItem(panel, ELE_PANEL_TITLE)->valuestring;
			snprintf(buf, sizeof(buf), "%-16s", title);
			ssd1306_display_text(&dev, 0, buf, strlen(buf), true);

			cJSON *fields = cJSON_GetObjectItem(panel, ELE_PANEL_CONTENT);
			cJSON *panel;
			int i = 2;
			cJSON_ArrayForEach(panel, fields)
			{
				cJSON *value = cJSON_GetObjectItem(panel, ELE_FIELD_VALUE);
				if (value != NULL)
				{
					char *v = value->valuestring;
					assert(v != NULL);
					snprintf(buf, sizeof(buf), "%-16s", v);
					ssd1306_display_text(&dev, i, buf, strlen(buf), false);
					i++;
				}
			}

			for(int x=i;x<16;x++) {
				// TODO: Wasteful to keep re-formatting
				snprintf(buf, sizeof(buf), "%-16s", "");
				ssd1306_display_text(&dev, x, buf, strlen(buf), false);
			}

			vTaskDelay(3000 / portTICK_PERIOD_MS);
		}
	}
}