/*
 * coffee.cpp
 *
 *  Created on: Sep 25, 2017
 *      Author: sebastian
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <wiringPi.h>
#include <pthread.h>
#include <unistd.h>
#include <iostream>
#include <csignal>
#include <time.h>
#include <ctime>
#include "coffee.h"
#include "hal.h"
#include "logger.h"
#include "timer.h"
#include "database.h"


coffee_status_t state;
int sigValue;
int brewTime; //Brew time in ms
timer brewTimer(&brewTimeHandler);

uint64_t totalHeatingTime; //local copies of the corresponding database entries
uint16_t brewCounter;
coffee_status_t lastState;


const char* StateName[] = {"OFF", "HEATING", "INITHEAT", "IDLE", "BREW", "BREWMAN", "CLEAN", "ERROR", "WAITOFF"};

/**
 * Thread for the finite state machine
 * It represents the current state of the machine and handles signals coming from
 * the pressure control, buttons, the brew switch and the proximity sensor
 * @param threadid the ID of the thread
 */

void *coffeeThread(void *threadid) {
	logger(V_BASIC, "Initializing coffee thread...\n");

	//installing new Signal handler for coffeethread
	struct sigaction action;
	sigemptyset(&action.sa_mask);
	action.sa_flags = SA_SIGINFO;
	action.sa_sigaction = coffeeHandler;
	sigaction(SIGUSR2, &action, NULL);

	brewTimer.setDivider(4);
	brewTimer.stop();
	brewTime = 0;

	lastState = STATE_OFF;

	//read the database values
	if(!(totalHeatingTime = sqlGetConf(CFGHeatingTime))){
		logger_error("coffee.cpp: Couldn't read the heating time from the database");
		//pthread_exit(EXIT_SUCCESS);
		exit(EXIT_FAILURE);
	}
	if(!(brewCounter = sqlGetConf(CFGbrewcounter))){
		logger_error("coffee.cpp: Couldn't read the brew counter from the database");
		//pthread_exit(EXIT_SUCCESS);
		exit(EXIT_FAILURE);
	}

	event_subscribe("terminate", &coffeeTerminate);

	logger(V_BREW, "Determining inital state\n");
	//determine inital state
	if (halGetRelaisState(RELAIS_POWER) && halGetRelaisState(RELAIS_HEAT)
			&& !halGetRelaisState(RELAIS_PUMP)) {
		//wait for heat relais to switch
		sleep(1);
		if (halIsHeating()) { //Heating is on
			changeState(STATE_INITALHEATING);
		} else {
			changeState(STATE_IDLE);
		}
	} else if (halGetRelaisState(RELAIS_PUMP)) {
		logger_error("Whoops, why is the pump running...\n");
		changeState(STATE_ERROR);
	} else {
		changeState(STATE_OFF);
	}

	logger(V_BREW, "Starting Coffee FSM\n");
	//begin FSM
	while (1) {
		switch (state) {
		/*
		 *
		 */
		case STATE_OFF:
			if (SigValueEmpty())
				pause();
			if (getSigValue() == SigInt0Rls) {
				if (!halProxSensorCovered()) { //Check waterlevel in gray water tank
					//turn machine on
					halMachineOn();
					sleep(1);
					if (halIsHeating()) { //check if System starts to heat when turned on
						changeState(STATE_INITALHEATING);
					} else {
						changeState(STATE_IDLE);
					}
				} else {
					logger_error("Empty Tank please!\n");
					changeState(STATE_ERROR);
				}
				break;
			}
			break;
			/*
			 *
			*/
		case STATE_WAIT_OFF:
			if (halIsHeating()) {
				halMachineOff();
				coffeeIncreaseHeatingTime(halgetHeatingTime());
				writeBackCache();
				changeState(STATE_OFF);
			}
			if (SigValueEmpty())
				pause();
			switch (getSigValue()) {
			case SigPressOpn:
				halMachineOff();
				coffeeIncreaseHeatingTime(halgetHeatingTime());
				writeBackCache();
				changeState(STATE_OFF);
				break;

			case SigInt0Psh:
			case SigInt1Psh:
				changeState(STATE_INITALHEATING);
				break;
			}
			break;
			/*
			 *
			 */
		case STATE_INITALHEATING:
			if (SigValueEmpty())
				pause();
			switch (getSigValue()) {
			case SigInt1RlsLong:
				//Turn machine off again
				halMachineOff();
				coffeeIncreaseHeatingTime(halgetHeatingTime());
				writeBackCache();
				changeState(STATE_OFF);
				break;

			case SigInt1Rls:
				changeState(STATE_WAIT_OFF);
				break;

			case SigPressOpn:
				//Inital heating finished
				coffeeIncreaseHeatingTime(halgetHeatingTime());
				changeState(STATE_IDLE);
				break;
			}
			break;

			/*
			 *
			 */
		case STATE_HEATING:
			if (SigValueEmpty())
				pause();
			switch (getSigValue()) {
			case SigInt1RlsLong:
				//Turn machine _immediately_ off again
				halMachineOff();
				coffeeIncreaseHeatingTime(halgetHeatingTime());
				writeBackCache();
				changeState(STATE_OFF);
				break;

			case SigInt1Rls:
				//turn machine off when heating is finished
				changeState(STATE_WAIT_OFF);
			break;

			case SigPressOpn:
				coffeeIncreaseHeatingTime(halgetHeatingTime());
				changeState(STATE_IDLE);
				break;

			case SigInt0Psh:
				//start to brew a delicious coffee
				changeState(STATE_BREW);
				break;

			case SigBrewOn:
				//someone brews manually
				changeState(STATE_BREWMANUAL);
				break;
			}
			break;
			/*
			 *
			 */
		case STATE_IDLE:
			if (SigValueEmpty())
				pause();
			switch (getSigValue()) {
			case SigInt1RlsLong:
				//turn machine _immediately_ off
				halMachineOff();
				writeBackCache();
				changeState(STATE_OFF);
				break;

			case SigInt1Rls:
				//turn machine off when heating is finished
				changeState(STATE_WAIT_OFF);
				break;

			case SigPressCls:
				changeState(STATE_HEATING);
				break;

			case SigInt0Psh:
				//start to brew a delicious coffee
				if(!halProxSensorCovered()){
					changeState(STATE_BREW);
				}
				else {
					changeState(STATE_ERROR);
				}
				break;

			case SigBrewOn:
				//someone brews manually
				changeState(STATE_BREWMANUAL);
				break;
			}
			break;
			/*
			 *
			 */
		case STATE_BREW:
			coffeeBrew();
			logger(V_BREW, "Finishing brewing\n");
			if(!halProxSensorCovered()){
				changeState(STATE_IDLE);
			}
			else {
				changeState(STATE_ERROR);
			}
			break;

			/*
			 *
			 */
		case STATE_BREWMANUAL:
			if (SigValueEmpty())
				pause();
			break;

			/*
			 *
			 */
		case STATE_CLEANING:
			if (SigValueEmpty())
				pause();
			break;

			/*
			 *
			 */
		case STATE_ERROR:
			if (SigValueEmpty())
				pause();
		switch (getSigValue()) {
		case SigInt1Psh:
		case SigInt0Psh:
			changeState(RETURN_STATE);
			break;
		}
		}
	}
	pthread_exit(EXIT_SUCCESS);
}
/**
 * Handler for the Signal send to this thread
 * It saves the type of signal received and tracks the time between a push and release event of up to 4 signals
 * The time is stored in the HalEvent variable when a release event is received
 * @param signum
 * @param siginfo
 * @param context
 */
void coffeeHandler(int signum, siginfo_t *siginfo, void *context) {
	sigval_t sigVal = (siginfo->si_value);
	sigValue = sigVal.sival_int;
	logger(V_BREW, "coffee.cpp: CoffeeHandler called with Signal %d\n", sigValue);
}

/**
 * returns the Signal value from the last received Signal and clears the variable
 * @return value sent with the last signal
 */
int getSigValue(void) {
	int tmp = sigValue;
	sigValue = 0;
	return tmp;
}

bool SigValueEmpty(void) {
	if (sigValue == 0)
		return true;
	else
		return false;
}

/**
 * Changes the state of the machine to newState
 * prints the change to the logger
 * @param newState
 */
void changeState(coffee_status_t newState) {
	if(newState == RETURN_STATE)
		newState = lastState;

	logger(V_BREW, "Changing state to %s\n", StateName[newState]);
	lastState = state;
	state = newState;
	event_trigger("statechange", &state, sizeof(state));
}

/**
 * Returns the current state of the FSM
 */
coffee_status_t getState(void) {
	return state;
}

/**
 * Counter for the brew time
 * refresh every 200ms
 */
void brewTimeHandler(void) {
	brewTime += 200;
}

/**
 * handles program termination
 */
void coffeeTerminate(event_t *event) {
	logger(V_BREW, "Coffee.cpp: Thread terminating");
	//stop brewing
	halRelaisOff(RELAIS_PUMP);
	brewTimer.stop();
	writeBackCache();
}

/**
 * Function to write back the values of the local copies
 * brewCounter and totalHeatingTime
 *
 */
void writeBackCache(void){
	if (sqlSetConf(CFGbrewcounter, brewCounter)) {
		logger_error("coffee.cpp: Couldn't write brewcounter to database");
		return;
	}
	if (sqlSetConf(CFGHeatingTime, totalHeatingTime)) {
		logger_error("coffee.cpp: Couldn't write heating time to database");
		return;
	}
}

/**
 * Brewing process
 */
void coffeeBrew(void) {
	coffeeIncreaseBrewCounter();

	/*
	 * Preinfusion
	 */
	logger(V_BREW, "Starting preinfusion...\n");
	halResetFlow();
	halRelaisOn(RELAIS_PUMP);
	brewTime = 0;
	brewTimer.start();
	while (halGetFlow() < AMOUNT_PREINFUSION) {
		usleep(50000);
		if (getSigValue() == SigInt1Psh)
			return;
	}
	brewTimer.stop();
	brewTime = 0;
	halRelaisOff(RELAIS_PUMP);

	/*
	 * Wait for coffee to soak in infused water
	 */
	brewTimer.start();
	while (brewTime < TIME_SOAK) {
		usleep(100000);
		if (getSigValue() == SigInt1Psh)
			return;
	}
	brewTimer.stop();
	brewTime = 0;
	halResetFlow();

	/*
	 * Brewing the actual espresso
	 */
	logger(V_BREW, "Starting infusion...\n");
	halRelaisOn(RELAIS_PUMP);
	brewTimer.start();
	while (brewTime < TIME_INFUSION && halGetFlow() < AMOUNT_DBLESPRESSO) {
		usleep(100000);
		if (getSigValue() == SigInt1Psh)
			return;
	}
	halRelaisOff(RELAIS_PUMP);
	halResetFlow();
	brewTimer.stop();
	brewTime = 0;
	return;
}

/**
 *
 */
void coffeeIncreaseBrewCounter(void) {
	brewCounter++;
}


/**
 *
 */
void coffeeIncreaseHeatingTime(uint64_t heatingTime) {
	totalHeatingTime += heatingTime;
}
