#include "Led.hpp"

#include "stm.hpp"
#include "FreeRTOS.h"
#include "task.h"

#if HW_REVISION == 'B'

#define LED_STATUS_TASK_STACK_SIZE_WORDS	128

extern TIM_HandleTypeDef htim2;

enum class Mode {
	Off,
	On,
	Blink,
	Pulsating,
	Error,
};

static Mode mode;
static uint8_t led_statecnt;
static int8_t led_ncnt;

static StackType_t LedStatusStack[LED_STATUS_TASK_STACK_SIZE_WORDS];
static StaticTask_t LedStatusCB;
static xTaskHandle LedStatusHandle = NULL;

static uint8_t err_cnt;

static void led_set_percentage(uint8_t val) {
	uint16_t compare = val * val / 100;
	TIM2->CCR1 = compare;
}

static void LedStatus(void * const argument) {
	UNUSED(argument);
	while (1) {
		if (led_statecnt < 199) {
			led_statecnt++;
		} else {
			led_statecnt = 0;
		}
		switch (mode) {
		case Mode::Off:
			led_set_percentage(0);
			vTaskSuspend(NULL);
			break;
		case Mode::On:
			led_set_percentage(100);
			vTaskSuspend(NULL);
			break;
		case Mode::Error:
			if (led_statecnt == 0) {
				err_cnt++;
				if (err_cnt > led_ncnt) {
					// reached number of blinks + 1
					err_cnt = 0;
				}
			}
			if (err_cnt >= led_ncnt) {
				break;
			}
			// fall through
			/* no break */
		case Mode::Blink:
			if(led_statecnt < 100) {
				led_set_percentage(100);
			} else {
				led_set_percentage(0);
			}
			break;
		case Mode::Pulsating:
			if (led_statecnt < 100) {
				led_set_percentage(led_statecnt);
			} else {
				led_set_percentage(200 - led_statecnt);
			}
			break;
		}
		vTaskDelay(5);
	}
}
#endif

void LED::Init() {
#if HW_REVISION == 'B'
	led_ncnt = 0;
	mode = Mode::Off;
	HAL_TIM_Base_Start(&htim2);
	HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);

	LedStatusHandle = xTaskCreateStatic(LedStatus, "LedStatusTask", LED_STATUS_TASK_STACK_SIZE_WORDS,
										NULL, 6, LedStatusStack, &LedStatusCB);

	vTaskSuspend(LedStatusHandle);
#endif
}

void LED::Pulsating() {
#if HW_REVISION == 'B'
	if(led_ncnt) {
		return;
	}
	vTaskSuspend(LedStatusHandle);
	mode = Mode::Pulsating;
	vTaskResume(LedStatusHandle);
#endif
}

void LED::Off() {
#if HW_REVISION == 'B'
	if(led_ncnt) {
		return;
	}
	vTaskSuspend(LedStatusHandle);
	mode = Mode::Off;
	vTaskResume(LedStatusHandle);
#endif
}

void LED::Error(uint8_t code) {
#if HW_REVISION == 'B'
	if(led_ncnt) {
		return;
	}
	vTaskSuspend(LedStatusHandle);
	mode = Mode::Error;
	led_statecnt = 0;
	err_cnt = 0;
	led_ncnt = code;
	vTaskResume(LedStatusHandle);
#endif
}

void LED::On() {
	GPIOA->BSRR = GPIO_PIN_15;
}

void LED::Toggle() {
	GPIOA->ODR ^= GPIO_PIN_15;
}
