/*
 * hal.cpp
 *
 *  Created on: Aug 3, 2016
 *      Author: Philipp Hinz, Sebastian Vendt
 */
#include <wiringPi.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <ctime>
#include "hal.h"
#include "global.h"
#include "logger.h"
#include "timer.h"

volatile int flowcnt = 0;

int Int0Time, Int1Time;
bool flagIgnoreRlsInt0, flagIgnoreRlsInt1;

//storage for the last state of the buttons and the proximity sensor
int pinState[3] = {0, 0, 0};

timer Int0Timer(&halInt0TimerHandler);
timer Int1Timer(&halInt1TimerHandler);

clock_t heatingCycle[2] = {0, 0};

//delay of the debounce in milliseconds
#define DELAY_DEBOUNCE	50

/**
 * Initializes HAL
 */

void halInit(void) {
	pinMode(RELAIS_HEAT, OUTPUT);
	pinMode(RELAIS_PUMP, OUTPUT);
	pinMode(RELAIS_POWER, OUTPUT);
	pinMode(PIN_PRESSURE_CTRL, INPUT);
	pinMode(PIN_PROXIMITY_SENSOR, INPUT);
	pinMode(PIN_INT0, INPUT);
	pinMode(PIN_INT1, INPUT);
	pinMode(PIN_FLOW, INPUT);

	if (optPower) {
		halMachineOn();
	} else {
		halMachineOff();
	}

	Int0Timer.setDivider(4); //200ms
	Int1Timer.setDivider(4);
	Int0Time = 0;
	Int1Time = 0;

	flagIgnoreRlsInt0 = false;
	flagIgnoreRlsInt1 = false;

	if (wiringPiISR(PIN_INT0, INT_EDGE_BOTH, &halInt0) < 0) {
		logger_error("Unable to setup ISR0: %s\n", strerror(errno));
		return;
	}
	if (wiringPiISR(PIN_INT1, INT_EDGE_BOTH, &halInt1) < 0) {
		logger_error("Unable to setup ISR1: %s\n", strerror(errno));
		return;
	}
	if (wiringPiISR(PIN_FLOW, INT_EDGE_FALLING, &halIntFlow) < 0) {
		logger_error("Unable to setup ISRFLOW: %s\n", strerror(errno));
		return;
	}
	if (wiringPiISR(PIN_PRESSURE_CTRL, INT_EDGE_BOTH, &halIntPressure) < 0) {
		logger_error("Unable to setup ISRPressure: %s\n", strerror(errno));
		return;
	}
	if (wiringPiISR(PIN_PROXIMITY_SENSOR, INT_EDGE_BOTH, &halIntProximity) < 0) {
		logger_error("Unable to setup ISRProximity: %s\n", strerror(errno));
		return;
	}
}

/**
 * Switches relais on
 * @param relais Relais ID
 */
void halRelaisOn(int relais) {
	halRelaisSet(relais, LOW);
}

/**
 * Switches relais off
 * @param relais Relais ID
 */
void halRelaisOff(int relais) {
	halRelaisSet(relais, HIGH);
}

/**
 * Switches relais to state
 * @param relais Relais ID
 * @param state LOW(0) or HIGH(1)
 */
void halRelaisSet(int relais, int state) {
	if (state != HIGH && state != LOW)
		return;
	switch (relais) {
	case RELAIS_POWER:
	case RELAIS_HEAT:
	case RELAIS_PUMP:
		digitalWrite(relais, state);
		break;
	}
}

/**
 * Returns the state of the relais relais
 * Returns HIGH when Relais is ON
 * @param relais Relais ID
 */
int halGetRelaisState(int relais) {
	switch (relais) {
	case RELAIS_POWER:
	case RELAIS_HEAT:
	case RELAIS_PUMP:
		return !digitalRead(relais);
		break;
	}
	return -1;
}

/**
 * Interrupt routine for Int0 (Top button)
 */
void halInt0(void) {
	//wait for a debounce
	delay(DELAY_DEBOUNCE);
	if (halGetInt0() && !pinState[0]) { //released
		logger(V_HAL, "Int0 released\n");
		pinState[0] = 1;
		if (flagIgnoreRlsInt0) {
			flagIgnoreRlsInt0 = false;
		} else {
			Int0Time = 0;
			Int0Timer.stop();
			halSendSignal(SigInt0Rls);
		}
	} else if(!halGetInt0() && pinState[0]) { //pressed
		logger(V_HAL, "Int0 pushed\n");
		pinState[0] = 0;
		halSendSignal(SigInt0Psh);
		Int0Time = 0;
		Int0Timer.start();
	}
}

/**
 *
 */
void halInt0TimerHandler(void) {
	Int0Time += 200;
	if (Int0Time >= (TIME_BUTTONLONGPRESS * 1000)) {
		halSendSignal(SigInt0RlsLong);
		flagIgnoreRlsInt0 = true;
		Int0Time = 0;
		Int0Timer.stop();
	}
}

/**
 * Interrupt routine for Int1 (Bottom button)
 */
void halInt1(void) {
	delay(DELAY_DEBOUNCE);
	if (halGetInt1() && !pinState[1]) {
		logger(V_HAL, "Int1 released\n");
		pinState[1] = 1;
		if (flagIgnoreRlsInt1) {
			flagIgnoreRlsInt1 = false;
		} else {
			Int0Time = 0;
			Int0Timer.stop();
			halSendSignal(SigInt1Rls);
		}
	} else if(!halGetInt1() && pinState[1]) {
		logger(V_HAL, "Int1 pushed\n");
		pinState[1] = 0;
		halSendSignal(SigInt1Psh);
		Int1Time = 0;
		Int1Timer.start();
	}
}

/*
 *
 */
void halInt1TimerHandler(void) {
	Int1Time += 200;
	if (Int1Time >= (TIME_BUTTONLONGPRESS * 1000)) {
		halSendSignal(SigInt1RlsLong);
		flagIgnoreRlsInt1 = true;
		Int1Time = 0;
		Int1Timer.stop();
	}
}

/**
 * Interrupt routine for the flow sensor
 * It counts the edgdes and stores the value in flowcnt
 */
void halIntFlow(void) {
	//halRelaisOff(RELAIS_POWER);
	logger(V_HAL, "IntFlow triggered #%d total: %.2fml\n", flowcnt, halGetFlow());
	if (flowcnt == 99) {
		halRelaisOff(RELAIS_PUMP);
	}
	flowcnt++;
}

/**
 * Interrupt routine for the pressure control
 * It captures the time at closing point and opening point
 * Reading heating time via the getHeatingTime function
 */
void halIntPressure(void) {
	logger(V_HAL, "IntPressure Control triggered\n");
	if (halIsHeating()) {
		heatingCycle[0] = clock();
		halSendSignal(SigPressCls);
	} else {
		heatingCycle[1] = clock();
		halSendSignal(SigPressOpn);
	}
}

/**
 * Function to read the heating time in sec
 * If called during a heating process, it returns the time elapsed since the heating started
 * If called after a heating process, it returns the total time elapes during the heating cycle
 */
double halgetHeatingTime(void){
	if (halIsHeating()) {
		return (double)(clock() - heatingCycle[0]) / CLOCKS_PER_SEC;
	}
	else {
		return (double)(heatingCycle[1] - heatingCycle[0]) / CLOCKS_PER_SEC;
	}
}

/**
 * Method to handle toggle of the proximity sensor
 */
void halIntProximity(void) {
	delay(DELAY_DEBOUNCE);
	if (halProxSensorCovered() && !pinState[2]) {
		logger(V_HAL, "IntProximity triggered\n");
		pinState[2] = 1;
		halSendSignal(SigProxCvrd);
	} else if(!halProxSensorCovered() && pinState[2]){
		logger(V_HAL, "IntProximity triggered\n");
		pinState[2] = 0;
		halSendSignal(SigProxOpn);
	}
}

/**
 * Returns total flow trough sensor in ml
 */
float halGetFlow(void) {
	return flowcnt * FLOW_ML_PULSE;
}

/**
 * Resets the Flow counter
 */
void halResetFlow(void) {
	logger(V_HAL, "Flow counter reset, amount so far: %.2f ml\n", halGetFlow());
	flowcnt = 0;
}

/**
 * Reads the status of the Pressure Control
 * @return 1 (true) for closed Pressure Control(heating) and 0 (false) for open
 */
bool halIsHeating(void) {
	if (digitalRead(PIN_PRESSURE_CTRL) == 0) {
		return true;
	} else {
		return false;
	}
}

/**
 * Returns status of the proximity switch
 * @return 1 if the proximity switch is covered and 0 if uncovered
 */
bool halProxSensorCovered(void) {
	if(digitalRead(PIN_PROXIMITY_SENSOR) == 0){
	 return false;
	 } else {
	 return true;
	 }
}

/**
 * Returns the value of the top button Int0 (low active)
 * @return LOW or HIGH
 */
int halGetInt0(void) {
	return digitalRead(PIN_INT0);
}

/**
 * Returns the value of the bottom button Int1 (low active)
 * @return LOW or HIGH
 */
int halGetInt1(void) {
	return digitalRead(PIN_INT1);
}

/**
 * send Signal to coffee thread
 * @param val Integer value assigned to signal
 */
void halSendSignal(HalSig val) {
	sigval value = { 0 };
	value.sival_int = (int) val;

	try {
		if (pthread_sigqueue(thread[THREAD_COFFEE], SIGUSR2, value)) {
			logger_error("hal.cpp: Failed to queue signal %d %s\n", val,
					strerror(errno));
			//No Signals reach the state machine anymore...
			exit(EXIT_FAILURE);
		}
	} catch (int e) {
		logger_error("Whoops.. %d\n", e);
	}

}

/**
 * Turn machine on
 */
void halMachineOn(void) {
	halRelaisOn(RELAIS_HEAT);
	halRelaisOff(RELAIS_PUMP);
	halRelaisOn(RELAIS_POWER);
	logger(V_HAL, "Turning machine on\n");
}

/**
 * Turn machine off
 */
void halMachineOff(void) {
	halRelaisOff(RELAIS_HEAT);
	halRelaisOff(RELAIS_PUMP);
	halRelaisOff(RELAIS_POWER);
	logger(V_HAL, "Turning machine off\n");
}
