/*
 * display.cpp
 *
 *  Created on: Sep 26, 2017
 *      Author: Philipp Hinz
 */
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
#include <unistd.h>
#include <string.h>

#include "global.h"
#include "display.h"
#include "logger.h"
#include "database.h"
#include "timer.h"
#include "lcd.h"
#include "coffee.h"
#include "hal.h"
#include "events.h"


display_lang_t displayLang;
timer displayTimer(displayTimerHandler);
int lcd = 0;
volatile int timerScaler = 0;
volatile int elapsedCnt = 0;
coffee_status_t coffeeState = STATE_OFF;

/**
 * Prints out the current time in a centered position
 * @param line Target line in display
 */

void displayPrintTime(int line) {
	time_t rawtime;
	struct tm * timeinfo;
	if (line > DISPLAY_ROWS)
		line = 0;

	time(&rawtime);
	timeinfo = localtime(&rawtime);
	lcdPosition(lcd, 0, line);
	lcdPrintf(lcd, "    %.2d:%.2d:%.2d    ", timeinfo->tm_hour,
			timeinfo->tm_min, timeinfo->tm_sec);
}

/**
 * Prints out the total volume flow
 * @param line Target line in display
 */

void displayPrintFlow(int line) {
	float flow = halGetFlow();
	lcdPosition(lcd, 0, line);
	lcdPrintf(lcd, "%s: %.0f ml    ", displayGetString(str_flow), flow);
}

/**
 * Prints a string to a specific line, optionally centered.
 * This function also fills out the remaining row of the display with spaces,
 * to ensure there is no old data left.
 * @param line Target line in display
 * @param *str String to print
 * @param centered Print centered or not
 */

void displayPrintLn(int line, const char* str, bool centered) {
	char buf[DISPLAY_COLS + 1];
	int len = strlen(str);
	int i = 0;
	int spaces = 0;
	if (len > DISPLAY_COLS)
		len = DISPLAY_COLS;
	if (line > DISPLAY_ROWS)
		line = 0;
	if (centered) {
		spaces = (DISPLAY_COLS - len) / 2;
		if (spaces) {
			for (i = 0; i < spaces; i++) {
				buf[i] = ' ';
			}
		}
	}
	for (i = 0; i < len; i++) {
		buf[spaces + i] = str[i];
	}
	if ((len + spaces) < DISPLAY_COLS) { // fill remaining space
		for (i = i + spaces; i < DISPLAY_COLS; i++) {
			buf[i] = ' ';
		}
	}
	lcdPosition(lcd, 0, line);
	buf[DISPLAY_COLS] = '\0';
	lcdPrintf(lcd, buf);
	//logger(V_HAL, "Printed out on display: \"%s\"\n", buf);
}

/**
 * Updates the display state to the matching coffee state
 * @param event Event data
 */

void displayStateUpdated(event_t *event) {
	if (event->len != sizeof(coffee_status_t)) {
		logger_error("Invalid use of event %s\n", event->event);
		return;
	}
	coffee_status_t state = *(coffee_status_t*)event->data;
	if (state != coffeeState) {
		coffeeState = state;
		timerScaler--;
		elapsedCnt = 0;
		displayTimer.call();
	}
}

/**
 * Handles cleanup before program termination
 */

void displayTerminate(event_t *e) {
	displayPrintLn(0, "CoffeePi", true);
	displayPrintLn(1, displayGetString(str_bye), true);
}

/**
 * Main thread to handle display data
 * @param *threadid Thread ID
 */

void* displayThread(void* threadid) {
	sleep(1); // Wait for other components to initialize
	int tmp = sqlGetConf(CFGdisplaylang);
	if (!tmp || tmp >= lang_last)
		tmp = DEFAULT_LANG;
	displaySetLang((display_lang_t) tmp);
	displayTimer.start();
	event_subscribe("statechange", &displayStateUpdated);
	event_subscribe("terminate", &displayTerminate);
	while (1) {
		pause();
		if (1) {
			timerScaler--;
			displayTimer.call();
		}
	}
	pthread_exit(EXIT_SUCCESS);
}

/**
 * Timer handler for display update
 * @param *threadid Thread ID
 */

void* displayTimerHandler(void* threadid) {
	int scale1Hz = 0;
	if (timerScaler++ >= REFRESH_RATE) { // 1s elapsed, reset
		timerScaler = 0;
		scale1Hz = 1;
	}
	int scale5Hz = (timerScaler == (REFRESH_RATE / 5) ? 0 : 1);
	if (scale1Hz) {
		switch (coffeeState) {
		case STATE_OFF:
		case STATE_HEATING:
		case STATE_INITALHEATING:
		case STATE_IDLE:
		case STATE_CLEANING:
		case STATE_ERROR:
		case STATE_WAIT_OFF:
			displayRefresh();
			break;
		default:
			break;
		}
	}
	if (scale5Hz) {
		switch (coffeeState) {
		case STATE_BREW:
		case STATE_BREWMANUAL:
			displayRefresh();
			break;
		default:
			break;
		}
	}
	if (elapsedCnt < (5 * REFRESH_RATE)) // Don't let it grow too large
		elapsedCnt++;
	pthread_exit(EXIT_SUCCESS);
}

/**
 * Initializes display
 */

void displayInit(void) {
	lcd = lcdInit();
	if (lcd < 0)
		logger_error("Error: unable to init LCD (%d)\n", lcd);
	lcdClear(lcd);
	lcdHome(lcd);
	displayPrintLn(0, (char*) "CoffeePi", true);
	displayPrintLn(1, (char*) "booting...", true);
	//lcdPrintf(lcd, "    CoffeePi       booting...");

	timerScaler = 0;
	displayTimer.setDivider((1000000 / TIMER_DELAY_US) / REFRESH_RATE);

	logger(V_BASIC, "Initialized display with a refresh rate of %d Hz\n",
	REFRESH_RATE);
}

/**
 * Sets the language of the display text
 * @param lang New language
 */

void displaySetLang(display_lang_t lang) {
	displayLang = lang;
}

/**
 * Refreshed the display content and outputs it
 */

void displayRefresh(void) {
	switch (coffeeState) { //coffeeGetState()
	case STATE_IDLE:
		displayPrintLn(0, "CoffeePi", true);
		displayPrintLn(1, displayGetString(str_ready), true);
		break;

	case STATE_INITALHEATING:
		displayPrintLn(0, "CoffeePi", true);
		displayPrintLn(1, displayGetString(str_heating), true);
		break;

	case STATE_HEATING:
		displayPrintLn(0, "CoffeePi", true);
		displayPrintLn(1, displayGetString(str_heatingready), true);
		break;

	case STATE_BREW:
		if (elapsedCnt <= 2 * REFRESH_RATE) {
			displayPrintLn(0, "CoffeePi", true);
			displayPrintLn(1, displayGetString(str_brewing), true);
		} else {
			displayPrintLn(0, displayGetString(str_brewing), true);
			displayPrintFlow(1);
		}
		break;

	case STATE_BREWMANUAL:
		displayPrintLn(0, displayGetString(str_brewing), true);
		displayPrintFlow(1);
		break;

	case STATE_CLEANING:
		displayPrintLn(0, "CoffeePi", true);
		displayPrintLn(1, displayGetString(str_cleaning), true);
		break;

	case STATE_ERROR:
		displayPrintLn(0, "CoffeePi", true);
		displayPrintLn(1, displayGetString(str_error), true);
		break;

	case STATE_WAIT_OFF:
		displayPrintLn(0, "CoffeePi", true);
		displayPrintLn(1, displayGetString(str_waitoff), true);
		break;

	case STATE_OFF:
	default:
		displayPrintLn(0, "CoffeePi", true);
		displayPrintTime(1);
		break;
	}
}

/**
 * Returns the matching translation of a string
 * @param string Requested string
 * @return Translated string
 */

const char* displayGetString(display_strings_t string) {
	if (displayLang >= lang_last)
		displayLang = DEFAULT_LANG;
	return display_strings[string].text[displayLang];
}
