#define MODULE_LOG_PREFIX "led"

#include "globals.h"

#ifdef LEDSUPPORT

#include "module-led.h"
#include "oscam-string.h"
#include "oscam-time.h"

#if defined(__arm__)
struct s_arm_led
{
	int32_t led;
	int32_t action;
	time_t start_time;
};

static pthread_t arm_led_thread;
static LLIST *arm_led_actions;

#define ARM_LED_TYPES 3
#define ARM_LED_FILES 4

struct arm_leds
{
	char *machine;
	struct led_file
	{
		uint8_t id;
		char    *file;
	} leds[ARM_LED_FILES];
};

static const struct arm_leds arm_leds[ARM_LED_TYPES] =
{
	{
		"nslu2", {
			{ LED1A, "red:status" },
			{ LED1B, "green:ready" },
			{ LED2,  "green:disk-1" },
			{ LED3,  "green:disk-2" }
		},
	},
	{
		"dockstar", {
			{ LED1A, "orange:misc" },
			{ LED1B, "green:health" },
			{ LED2,  "green:health" },
			{ LED3,  "orange:misc" }
		},
	},
	{
		"wrt350nv2", {
			{ LED1A, "orange:power" },
			{ LED1B, "green:power" },
			{ LED2,  "green:wireless" },
			{ LED3,  "green:security" }
		},
	}
};

static int32_t arm_init_led_file(uint8_t led_type, uint8_t led_no, char *buf, int32_t buflen)
{
	uint8_t i;
	if(led_type >= ARM_LED_TYPES) { return 0; }
	if(led_no >= ARM_LED_FILES) { return 0; }
	for(i = 0; i < ARM_LED_FILES; i++)
	{
		if(arm_leds[led_type].leds[i].id == led_no)
		{
			return snprintf(buf, buflen, "/sys/class/leds/%s:%s/brightness",
							arm_leds[led_type].machine, arm_leds[led_type].leds[i].file);
		}
	}
	return 0;
}

#define LED_TYPE_UNKNOWN 0xff
static uint8_t arm_led_type = LED_TYPE_UNKNOWN;

static void arm_detect_led_type(void)
{
	uint8_t i;
	char led_file[256];
	for(i = 0; i < ARM_LED_TYPES; i++)
	{
		if(!arm_init_led_file(i, 0, led_file, sizeof(led_file)))
			{ break; }
		if(access(led_file, W_OK) == 0)
		{
			arm_led_type = i;
			cs_log("LED support for %s is activated.", arm_leds[arm_led_type].machine);
			break;
		}
	}
	if(arm_led_type == LED_TYPE_UNKNOWN)
		{ cs_log("LED support is not active. Can't detect machine type."); }
}

static void arm_switch_led_from_thread(int32_t led, int32_t action)
{
	if(action < 2)    // only LED_ON and LED_OFF
	{
		char led_file[256];
		if(!arm_init_led_file(arm_led_type, led, led_file, sizeof(led_file)))
			{ return; }
		FILE *f = fopen(led_file, "w");
		if(!f)
			{ return; }
		fprintf(f, "%d", action);
		fclose(f);
	}
	else     // LED Macros
	{
		switch(action)
		{
		case LED_DEFAULT:
			arm_switch_led_from_thread(LED1A, LED_OFF);
			arm_switch_led_from_thread(LED1B, LED_OFF);
			arm_switch_led_from_thread(LED2, LED_ON);
			arm_switch_led_from_thread(LED3, LED_OFF);
			break;
		case LED_BLINK_OFF:
			arm_switch_led_from_thread(led, LED_OFF);
			cs_sleepms(100);
			arm_switch_led_from_thread(led, LED_ON);
			break;
		case LED_BLINK_ON:
			arm_switch_led_from_thread(led, LED_ON);
			cs_sleepms(300);
			arm_switch_led_from_thread(led, LED_OFF);
			break;
		}
	}
}

static void *arm_led_thread_main(void *UNUSED(thread_data))
{
	uint8_t running = 1;
	set_thread_name(__func__);
	while(running)
	{
		LL_ITER iter = ll_iter_create(arm_led_actions);
		struct s_arm_led *arm_led;
		while((arm_led = ll_iter_next(&iter)))
		{
			int32_t led, action;
			time_t now, start;
			led = arm_led->led;
			action = arm_led->action;
			now = time(0);
			start = arm_led->start_time;
			ll_iter_remove_data(&iter);
			if(action == LED_STOP_THREAD)
			{
				running = 0;
				break;
			}
			if(now - start < ARM_LED_TIMEOUT)
			{
				arm_switch_led_from_thread(led, action);
			}
		}
		if(running)
		{
			sleep(60);
		}
	}
	ll_clear_data(arm_led_actions);
	pthread_exit(NULL);
	return NULL;
}

static void arm_led_start_thread(void)
{
	arm_detect_led_type();
	if(!cfg.enableled || arm_led_type == LED_TYPE_UNKNOWN)
		{ return; }
	// call this after signal handling is done
	if(!arm_led_actions)
	{
		arm_led_actions = ll_create("arm_led_actions");
	}

	start_thread("arm led", arm_led_thread_main, NULL, &arm_led_thread, 1, 1);
}

static void arm_led(int32_t led, int32_t action)
{
	struct s_arm_led *data;
	if(!cfg.enableled || arm_led_type == LED_TYPE_UNKNOWN)
		{ return; }
	if(!arm_led_actions)
	{
		arm_led_actions = ll_create("arm_led_actions");
	}
	if(cs_malloc(&data, sizeof(struct s_arm_led)))
	{
		data->start_time = time(0);
		data->led = led;
		data->action = action;
		ll_append(arm_led_actions, (void *)data);
	}
	if(arm_led_thread)
	{
		// arm_led_thread_main is not started at oscam startup
		// when first arm_led calls happen
		pthread_kill(arm_led_thread, OSCAM_SIGNAL_WAKEUP);
	}
}

static void arm_led_stop_thread(void)
{
	if(!cfg.enableled || arm_led_type == LED_TYPE_UNKNOWN)
		{ return; }
	arm_led(0, LED_STOP_THREAD);
}
#else
static inline void arm_led_start_thread(void) { }
static inline void arm_led_stop_thread(void) { }
static inline void arm_led(int32_t UNUSED(led), int32_t UNUSED(action)) { }
#endif


#ifdef QBOXHD
static void qboxhd_led_blink(int32_t color, int32_t duration)
{
	int32_t f;
	if(cfg.enableled != 2)
		{ return; }
	// try QboxHD-MINI first
	if((f = open(QBOXHDMINI_LED_DEVICE, O_RDWR | O_NONBLOCK)) > -1)
	{
		qboxhdmini_led_color_struct qbminiled;
		uint32_t qboxhdmini_color = 0x000000;
		if(color != QBOXHD_LED_COLOR_OFF)
		{
			switch(color)
			{
			case QBOXHD_LED_COLOR_RED:
				qboxhdmini_color = QBOXHDMINI_LED_COLOR_RED;
				break;
			case QBOXHD_LED_COLOR_GREEN:
				qboxhdmini_color = QBOXHDMINI_LED_COLOR_GREEN;
				break;
			case QBOXHD_LED_COLOR_BLUE:
				qboxhdmini_color = QBOXHDMINI_LED_COLOR_BLUE;
				break;
			case QBOXHD_LED_COLOR_YELLOW:
				qboxhdmini_color = QBOXHDMINI_LED_COLOR_YELLOW;
				break;
			case QBOXHD_LED_COLOR_MAGENTA:
				qboxhdmini_color = QBOXHDMINI_LED_COLOR_MAGENTA;
				break;
			}
			// set LED on with color
			qbminiled.red = (uint8_t)((qboxhdmini_color & 0xFF0000) >> 16);  // R
			qbminiled.green = (uint8_t)((qboxhdmini_color & 0x00FF00) >> 8); // G
			qbminiled.blue = (uint8_t)(qboxhdmini_color & 0x0000FF);         // B
			ioctl(f, QBOXHDMINI_IOSET_RGB, &qbminiled);
			cs_sleepms(duration);
		}
		// set LED off
		qbminiled.red = 0;
		qbminiled.green = 0;
		qbminiled.blue = 0;
		ioctl(f, QBOXHDMINI_IOSET_RGB, &qbminiled);
		close(f);
	}
	else if((f = open(QBOXHD_LED_DEVICE, O_RDWR | O_NONBLOCK)) > -1)
	{
		qboxhd_led_color_struct qbled;
		if(color != QBOXHD_LED_COLOR_OFF)
		{
			// set LED on with color
			qbled.H = color;
			qbled.S = 99;
			qbled.V = 99;
			ioctl(f, QBOXHD_SET_LED_ALL_PANEL_COLOR, &qbled);
			cs_sleepms(duration);
		}
		// set LED off
		qbled.H = 0;
		qbled.S = 0;
		qbled.V = 0;
		ioctl(f, QBOXHD_SET_LED_ALL_PANEL_COLOR, &qbled);
		close(f);
	}
}
#else
static inline void qboxhd_led_blink(int32_t UNUSED(color), int32_t UNUSED(duration)) { }
#endif

void led_status_stopping(void)
{
	if(cfg.enableled == 1)
	{
		arm_led(LED1B, LED_OFF);
		arm_led(LED2,  LED_OFF);
		arm_led(LED3,  LED_OFF);
		arm_led(LED1A, LED_ON);
	}
	if(cfg.enableled == 2)
	{
		qboxhd_led_blink(QBOXHD_LED_COLOR_YELLOW,  QBOXHD_LED_BLINK_FAST);
		qboxhd_led_blink(QBOXHD_LED_COLOR_RED,     QBOXHD_LED_BLINK_FAST);
		qboxhd_led_blink(QBOXHD_LED_COLOR_GREEN,   QBOXHD_LED_BLINK_FAST);
		qboxhd_led_blink(QBOXHD_LED_COLOR_BLUE,    QBOXHD_LED_BLINK_FAST);
		qboxhd_led_blink(QBOXHD_LED_COLOR_MAGENTA, QBOXHD_LED_BLINK_FAST);
	}
}

void led_status_cw_not_found(ECM_REQUEST *er)
{
	if(!er->rc)
		{ arm_led(LED2, LED_BLINK_OFF); }
	if(er->rc < E_NOTFOUND)
	{
		qboxhd_led_blink(QBOXHD_LED_COLOR_GREEN, QBOXHD_LED_BLINK_MEDIUM);
	}
	else if(er->rc <= E_STOPPED)
	{
		qboxhd_led_blink(QBOXHD_LED_COLOR_RED, QBOXHD_LED_BLINK_MEDIUM);
	}
}

void led_status_default(void)
{
	arm_led(LED1A, LED_DEFAULT);
	arm_led(LED1A, LED_ON);
}

void led_status_starting(void)
{
	arm_led(LED1A, LED_OFF);
	arm_led(LED1B, LED_ON);
	qboxhd_led_blink(QBOXHD_LED_COLOR_YELLOW,  QBOXHD_LED_BLINK_FAST);
	qboxhd_led_blink(QBOXHD_LED_COLOR_RED,     QBOXHD_LED_BLINK_FAST);
	qboxhd_led_blink(QBOXHD_LED_COLOR_GREEN,   QBOXHD_LED_BLINK_FAST);
	qboxhd_led_blink(QBOXHD_LED_COLOR_BLUE,    QBOXHD_LED_BLINK_FAST);
	qboxhd_led_blink(QBOXHD_LED_COLOR_MAGENTA, QBOXHD_LED_BLINK_FAST);
}

void led_status_card_activation_error(void)
{
	qboxhd_led_blink(QBOXHD_LED_COLOR_MAGENTA, QBOXHD_LED_BLINK_MEDIUM);
}

void led_status_found_cardsystem(void)
{
	qboxhd_led_blink(QBOXHD_LED_COLOR_YELLOW, QBOXHD_LED_BLINK_MEDIUM);
	qboxhd_led_blink(QBOXHD_LED_COLOR_GREEN,  QBOXHD_LED_BLINK_MEDIUM);
	qboxhd_led_blink(QBOXHD_LED_COLOR_YELLOW, QBOXHD_LED_BLINK_MEDIUM);
	qboxhd_led_blink(QBOXHD_LED_COLOR_GREEN,  QBOXHD_LED_BLINK_MEDIUM);
}

void led_status_unsupported_card_system(void)
{
	qboxhd_led_blink(QBOXHD_LED_COLOR_MAGENTA, QBOXHD_LED_BLINK_MEDIUM);
}

void led_status_card_detected(void)
{
	qboxhd_led_blink(QBOXHD_LED_COLOR_YELLOW, QBOXHD_LED_BLINK_SLOW);
}

void led_status_card_ejected(void)
{
	qboxhd_led_blink(QBOXHD_LED_COLOR_RED, QBOXHD_LED_BLINK_SLOW);
}

void led_status_emm_ok(void)
{
	arm_led(LED3, LED_BLINK_ON);
	qboxhd_led_blink(QBOXHD_LED_COLOR_BLUE, QBOXHD_LED_BLINK_MEDIUM);
}

void led_init(void)
{
	arm_led_start_thread();
}

void led_stop(void)
{
	arm_led_stop_thread();
}

#endif
