/* Copyright (C) 2016 by NuRAN Wireless <support@nuranwireless.com>
 *
 * All Rights Reserved
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
#include "oc2gbts_led.h"
#include "oc2gbts_bts.h"
#include <osmocom/core/talloc.h>
#include <osmocom/core/linuxlist.h>

static struct oc2gbts_led led_entries[] = {
	{
		.name = "led0",
		.fullname = "led red",
		.path = "/var/oc2g/leds/led0/brightness"
	},
	{
		.name = "led1",
		.fullname = "led green",
		.path = "/var/oc2g/leds/led1/brightness"
	}
};

static const struct value_string oc2gbts_led_strs[] = {
	{ OC2GBTS_LED_RED, "LED red" },
	{ OC2GBTS_LED_GREEN, "LED green" },
	{ OC2GBTS_LED_ORANGE, "LED orange" },
	{ OC2GBTS_LED_OFF, "LED off" },
	{ 0, NULL }
};

static uint8_t led_priority[] = {
	BLINK_PATTERN_INIT,
	BLINK_PATTERN_INT_PROC_MALFUNC,
	BLINK_PATTERN_SUPPLY_PWR_MAX,
	BLINK_PATTERN_PA_PWR_MAX,
	BLINK_PATTERN_VSWR_MAX,
	BLINK_PATTERN_SUPPLY_VOLT_MIN,
	BLINK_PATTERN_TEMP_MAX,
	BLINK_PATTERN_EXT_LINK_MALFUNC,
	BLINK_PATTERN_SUPPLY_VOLT_LOW,
	BLINK_PATTERN_TEMP_HIGH,
	BLINK_PATTERN_VSWR_HIGH,
	BLINK_PATTERN_SUPPLY_PWR_HIGH,
	BLINK_PATTERN_PA_PWR_HIGH,
	BLINK_PATTERN_GPS_FIX_LOST,
	BLINK_PATTERN_NORMAL
};


char *blink_pattern_command[] = BLINK_PATTERN_COMMAND;

static int oc2gbts_led_write(char *path, char *str)
{
	int fd;

	if ((fd = open(path, O_WRONLY)) == -1)
	{
		return 0;
	}

	write(fd, str, strlen(str)+1);
	close(fd);
	return 1;
}

static void led_set_red()
{
	oc2gbts_led_write(led_entries[0].path, "1");
	oc2gbts_led_write(led_entries[1].path, "0");
}

static void led_set_green()
{
	oc2gbts_led_write(led_entries[0].path, "0");
	oc2gbts_led_write(led_entries[1].path, "1");
}

static void led_set_orange()
{
	oc2gbts_led_write(led_entries[0].path, "1");
	oc2gbts_led_write(led_entries[1].path, "1");
}

static void led_set_off()
{
	oc2gbts_led_write(led_entries[0].path, "0");
	oc2gbts_led_write(led_entries[1].path, "0");
}

static void led_sleep( struct oc2gbts_mgr_instance *mgr, struct oc2gbts_led_timer *led_timer, void (*led_timer_cb)(void *_data)) {
	/* Cancel any pending timer */
	osmo_timer_del(&led_timer->timer);
	/* Start LED timer */
	led_timer->timer.cb = led_timer_cb;
	led_timer->timer.data = mgr;
	mgr->oc2gbts_leds.active_timer = led_timer->idx;
	osmo_timer_schedule(&led_timer->timer, led_timer->param.sleep_sec, led_timer->param.sleep_usec);
	LOGP(DTEMP, LOGL_DEBUG,"%s timer scheduled for %d sec + %d usec\n",
			get_value_string(oc2gbts_led_strs, led_timer->idx),
			led_timer->param.sleep_sec,
			led_timer->param.sleep_usec);

	switch (led_timer->idx) {
	case OC2GBTS_LED_RED:
		led_set_red();
		break;
	case OC2GBTS_LED_GREEN:
		led_set_green();
		break;
	case OC2GBTS_LED_ORANGE:
		led_set_orange();
		break;
	case OC2GBTS_LED_OFF:
		led_set_off();
		break;
	default:
		led_set_off();
	}
}

static void led_sleep_cb(void *_data) {
	struct oc2gbts_mgr_instance *mgr = _data;
	struct oc2gbts_led_timer_list *led_list;

	/* make sure the timer list is not empty */
	if (llist_empty(&mgr->oc2gbts_leds.list))
		return;

	llist_for_each_entry(led_list, &mgr->oc2gbts_leds.list, list) {
		if (led_list->led_timer.idx == mgr->oc2gbts_leds.active_timer) {
			LOGP(DTEMP, LOGL_DEBUG,"Delete expired %s timer %d sec + %d usec\n",
				get_value_string(oc2gbts_led_strs, led_list->led_timer.idx),
				led_list->led_timer.param.sleep_sec,
				led_list->led_timer.param.sleep_usec);

			/* Delete current timer */
			osmo_timer_del(&led_list->led_timer.timer);
			/* Rotate the timer list */
			 llist_move_tail(&led_list->list, &mgr->oc2gbts_leds.list);
			break;
		}
	}

	/* Execute next timer */
	led_list = llist_first_entry(&mgr->oc2gbts_leds.list, struct oc2gbts_led_timer_list, list);
	if (led_list) {
		LOGP(DTEMP, LOGL_DEBUG,"Execute %s timer %d sec + %d usec, total entries=%d\n",
			get_value_string(oc2gbts_led_strs, led_list->led_timer.idx),
			led_list->led_timer.param.sleep_sec,
			led_list->led_timer.param.sleep_usec,
			llist_count(&mgr->oc2gbts_leds.list));

		led_sleep(mgr, &led_list->led_timer, led_sleep_cb);
	}

}

static void delete_led_timer_entries(struct oc2gbts_mgr_instance *mgr)
{
	struct oc2gbts_led_timer_list *led_list, *led_list2;

	if (llist_empty(&mgr->oc2gbts_leds.list))
		return;

	llist_for_each_entry_safe(led_list, led_list2, &mgr->oc2gbts_leds.list, list) {
		/* Delete the timer in list */
		if (led_list) {
			LOGP(DTEMP, LOGL_DEBUG,"Delete %s timer entry from list, %d sec + %d usec\n",
				get_value_string(oc2gbts_led_strs, led_list->led_timer.idx),
				led_list->led_timer.param.sleep_sec,
				led_list->led_timer.param.sleep_usec);

			/* Delete current timer */
			osmo_timer_del(&led_list->led_timer.timer);
			llist_del(&led_list->list);
			talloc_free(led_list);
		}
	}
	return;
}

static int add_led_timer_entry(struct oc2gbts_mgr_instance *mgr, char *cmdstr)
{
	double sec, int_sec, frac_sec;
	struct oc2gbts_sleep_time led_param;

	led_param.sleep_sec = 0;
	led_param.sleep_usec = 0;

	if (strstr(cmdstr, "set red") != NULL)
		mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_RED;
	else if (strstr(cmdstr, "set green") != NULL)
		mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_GREEN;
	else if (strstr(cmdstr, "set orange") != NULL)
		mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_ORANGE;
	else if (strstr(cmdstr, "set off") != NULL)
		mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_OFF;
	else if (strstr(cmdstr, "sleep") != NULL) {
		sec = atof(cmdstr + 6);
		/* split time into integer and fractional of seconds */
		frac_sec = modf(sec, &int_sec) * 1000000.0;
		led_param.sleep_sec = (int)int_sec;
		led_param.sleep_usec = (int)frac_sec;

		if ((mgr->oc2gbts_leds.led_idx >= OC2GBTS_LED_RED) && (mgr->oc2gbts_leds.led_idx < _OC2GBTS_LED_MAX)) {
			struct oc2gbts_led_timer_list *led_list;

			/* allocate timer entry */
			led_list = talloc_zero(tall_mgr_ctx, struct oc2gbts_led_timer_list);
			if (led_list) {
				led_list->led_timer.idx = mgr->oc2gbts_leds.led_idx;
				led_list->led_timer.param.sleep_sec = led_param.sleep_sec;
				led_list->led_timer.param.sleep_usec = led_param.sleep_usec;
				llist_add_tail(&led_list->list, &mgr->oc2gbts_leds.list);

				LOGP(DTEMP, LOGL_DEBUG,"Add %s timer to list, %d sec + %d usec, total entries=%d\n",
					get_value_string(oc2gbts_led_strs, mgr->oc2gbts_leds.led_idx),
					led_list->led_timer.param.sleep_sec,
					led_list->led_timer.param.sleep_usec,
					llist_count(&mgr->oc2gbts_leds.list));
			}
		}
	} else
		return -1;

	return 0;
}

static int parse_led_pattern(char *pattern, struct oc2gbts_mgr_instance *mgr)
{
	char str[1024];
	char *pstr;
	char *sep;
	int rc = 0;

	strcpy(str, pattern);
	pstr = str;
	while ((sep = strsep(&pstr, ";")) != NULL) {
		rc = add_led_timer_entry(mgr, sep);
		if (rc < 0) {
			break;
		}

	}
	return rc;
}

/*** led interface ***/

void led_set(struct oc2gbts_mgr_instance *mgr, int pattern_id)
{
	int rc;
	struct oc2gbts_led_timer_list *led_list;

	if (pattern_id > BLINK_PATTERN_MAX_ITEM - 1) {
		LOGP(DTEMP, LOGL_ERROR, "Invalid LED pattern : %d. LED pattern must be between %d..%d\n",
				pattern_id,
				BLINK_PATTERN_POWER_ON,
				BLINK_PATTERN_MAX_ITEM - 1);
		return;
	}
	if (pattern_id == mgr->oc2gbts_leds.last_pattern_id)
		return;

	mgr->oc2gbts_leds.last_pattern_id = pattern_id;

	LOGP(DTEMP, LOGL_INFO, "blink pattern command : %d\n", pattern_id);
	LOGP(DTEMP, LOGL_INFO, "%s\n", blink_pattern_command[pattern_id]);

	/* Empty existing LED timer in the list */
	delete_led_timer_entries(mgr);

	/* parse LED pattern */
	rc = parse_led_pattern(blink_pattern_command[pattern_id], mgr);
	if (rc < 0) {
		LOGP(DTEMP, LOGL_ERROR,"LED pattern not found or invalid LED pattern\n");
		return;
	}

	/* make sure the timer list is not empty */
	if (llist_empty(&mgr->oc2gbts_leds.list))
		return;

	/* Start the first LED timer in the list */
	led_list = llist_first_entry(&mgr->oc2gbts_leds.list, struct oc2gbts_led_timer_list, list);
	if (led_list) {
		LOGP(DTEMP, LOGL_DEBUG,"Execute timer %s for %d sec + %d usec\n",
			get_value_string(oc2gbts_led_strs, led_list->led_timer.idx),
			led_list->led_timer.param.sleep_sec,
			led_list->led_timer.param.sleep_usec);

		led_sleep(mgr, &led_list->led_timer, led_sleep_cb);
	}

}

void select_led_pattern(struct oc2gbts_mgr_instance *mgr)
{
	int i;
	uint8_t led[BLINK_PATTERN_MAX_ITEM] = {0};

	/* set normal LED pattern at first */
	led[BLINK_PATTERN_NORMAL] = 1;

	/* check on-board sensors for new LED pattern */
	check_sensor_led_pattern(mgr, led);

	/* check BTS status for new LED pattern */
	check_bts_led_pattern(led);

	/* check by priority */
	for (i = 0; i < sizeof(led_priority)/sizeof(uint8_t); i++) {
		if(led[led_priority[i]] == 1) {
			led_set(mgr, led_priority[i]);
			break;
		}
	}
}
