/*
 * Copyright (c) 2020 EUROS Embedded Systems GmbH
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#if CONFIG_NETWORKING
#include <zephyr.h>
#include <nrf_socket.h>
#include <net/socket.h>
#include <sys/types.h>
#include <device.h>
#include <devicetree.h>
#include <drivers/gpio.h>
#include <drivers/sensor.h>
#include <stdio.h>
#include <string.h>

 // LTE modem
#include <modem/at_cmd.h>
#include <modem/at_notif.h>
#include <modem/nrf_modem_lib.h>
#include <modem/lte_lc.h>
#include <modem/modem_info.h>


#define OK 0
#define FAIL -1

#define CONFIG_TCP_SERVER_ADDRESS_STATIC "87.118.177.198" // gvalkov.com
//#define CONFIG_TCP_SERVER_ADDRESS_STATIC "87.138.172.131" // euros-embedded.com
#define CONFIG_TCP_SERVER_PORT 81


// LED
#if CONFIG_BOARD_THINGY91_NRF9160NS
#define LED_R_NODE DT_ALIAS(led0)
#define LED_G_NODE DT_ALIAS(led1)
#define LED_B_NODE DT_ALIAS(led2)
#define LED_SR_NODE DT_ALIAS(sense_led0)
#define LED_SG_NODE DT_ALIAS(sense_led1)
#define LED_SB_NODE DT_ALIAS(sense_led2)
#elif CONFIG_BOARD_NRF9160DK_NRF9160NS
#define LED_R_NODE DT_ALIAS(led1)
#define LED_G_NODE DT_ALIAS(led2)
#define LED_B_NODE DT_ALIAS(led3)
#define LED_SR_NODE DT_ALIAS(led1)
#define LED_SG_NODE DT_ALIAS(led2)
#define LED_SB_NODE DT_ALIAS(led3)
#else
#error "Unsupported board: please use nRF9160dk or Thingy:91"
#endif

#if DT_NODE_HAS_STATUS(LED_R_NODE, okay)
#define LED_R	  DT_GPIO_LABEL(LED_R_NODE, gpios)
#define LED_G	  DT_GPIO_LABEL(LED_G_NODE, gpios)
#define LED_B	  DT_GPIO_LABEL(LED_B_NODE, gpios)
#define LED_SR	  DT_GPIO_LABEL(LED_SR_NODE, gpios)
#define LED_SG	  DT_GPIO_LABEL(LED_SG_NODE, gpios)
#define LED_SB	  DT_GPIO_LABEL(LED_SB_NODE, gpios)
#define PIN_R	  DT_GPIO_PIN(LED_R_NODE, gpios)
#define PIN_G	  DT_GPIO_PIN(LED_G_NODE, gpios)
#define PIN_B	  DT_GPIO_PIN(LED_B_NODE, gpios)
#define PIN_SR	  DT_GPIO_PIN(LED_SR_NODE, gpios)
#define PIN_SG	  DT_GPIO_PIN(LED_SG_NODE, gpios)
#define PIN_SB	  DT_GPIO_PIN(LED_SB_NODE, gpios)
#define FLAGS_R	  DT_GPIO_FLAGS(LED_R_NODE, gpios)
#define FLAGS_G	  DT_GPIO_FLAGS(LED_G_NODE, gpios)
#define FLAGS_B	  DT_GPIO_FLAGS(LED_B_NODE, gpios)
#define FLAGS_SR  DT_GPIO_FLAGS(LED_SR_NODE, gpios)
#define FLAGS_SG  DT_GPIO_FLAGS(LED_SG_NODE, gpios)
#define FLAGS_SB  DT_GPIO_FLAGS(LED_SB_NODE, gpios)

const char * led_names[] = { LED_R, LED_G, LED_B, LED_SR, LED_SG, LED_SB };
gpio_pin_t led_pins[] = { PIN_R, PIN_G, PIN_B, PIN_SR, PIN_SG, PIN_SB };
gpio_flags_t led_flags[] = { FLAGS_R, FLAGS_G, FLAGS_B, FLAGS_SR, FLAGS_SG, FLAGS_SB };

#define LED_COUNT (sizeof(led_names) / sizeof(*led_names))
#define LED_COUNT_PINS (sizeof(led_pins) / sizeof(*led_pins))
#define LED_COUNT_FLAGS (sizeof(led_flags) / sizeof(*led_flags))
#else
#error "Unsupported board: led0 devicetree alias is not defined"
#endif

struct LED_obj_t
{
	const struct device * dev;
	gpio_pin_t pin;
};

struct LED_t
{
	struct LED_obj_t r;
	struct LED_obj_t g;
	struct LED_obj_t b;
};

struct LEDS_t
{
	union
	{
		struct
		{
			struct LED_t main;
			struct LED_t sense;
		};

		struct LED_obj_t list[LED_COUNT];
	};
};

static struct LEDS_t led;
static int led_g_state = false;

int led_init(void)
{
	int b = OK;
	size_t i = 0;

	memset(&led, 0, sizeof(led));

	if ((LED_COUNT != LED_COUNT_PINS) || (LED_COUNT != LED_COUNT_FLAGS))
	{
		printk("led_init()  led_names, led_nodes, and led_flags should have the same length\n");
		return FAIL;
	}

	for (i = 0; i < LED_COUNT; i++)
	{
		led.list[i].dev = device_get_binding(led_names[i]);
		led.list[i].pin = led_pins[i];

		if (
			!led.list[i].dev ||
			(gpio_pin_configure(led.list[i].dev, led.list[i].pin, led_flags[i] | GPIO_OUTPUT_INACTIVE) < 0)
			)
		{
			led.list[i].dev = NULL;
			b = FAIL;
		}
	}

	return b;
}

int led_set(struct LED_obj_t led, int on)
{
	if (!led.dev)
	{
		return FAIL;
	}

	return gpio_pin_set(led.dev, led.pin, on);
}

int led_set_rgb(struct LED_t * led, int r, int g, int b)
{
	int err = 0;
	err |= led_set(led->r, r);
	err |= led_set(led->g, g);
	err |= led_set(led->b, b);

	return err;
}

int led_toggle_g(void)
{
	led_g_state = led_g_state ? false : true;

	return led_set(led.main.g, led_g_state);
}


// BUTTONS
#define BTN_0_NODE DT_ALIAS(sw0)

#if DT_NODE_HAS_STATUS(BTN_0_NODE, okay)
#define BTN_0		DT_GPIO_LABEL(BTN_0_NODE, gpios)
#define BTN_0_PIN	DT_GPIO_PIN(BTN_0_NODE, gpios)
#define BTN_0_FLAGS	DT_GPIO_FLAGS(BTN_0_NODE, gpios)
#else
#error "Unsupported board: sw0 devicetree alias is not defined"
#endif

struct BTN_obj_t
{
	const struct device * dev;
	gpio_pin_t pin;
};

static struct gpio_callback btn_0_cb_data;
static struct BTN_obj_t btn_0;
static int btn_0_trigger;

void btn_0_pressed(const struct device * dev, struct gpio_callback * cb, uint32_t pins)
{
	btn_0_trigger = 1;
	led_set_rgb(&led.sense, 1, 1, 1);
}

int btn_init(void)
{
	int b = OK;

	btn_0.dev = device_get_binding(BTN_0);
	btn_0.pin = BTN_0_PIN;

	if (
		!btn_0.dev ||
		(OK != gpio_pin_configure(btn_0.dev, btn_0.pin, BTN_0_FLAGS | GPIO_INPUT)) ||
		(OK != gpio_pin_interrupt_configure(btn_0.dev, btn_0.pin, GPIO_INT_EDGE_TO_ACTIVE))
		)
	{
		btn_0.dev = NULL;
		b = FAIL;
	}
	else
	{
		gpio_init_callback(&btn_0_cb_data, btn_0_pressed, BIT(btn_0.pin));
		gpio_add_callback(btn_0.dev, &btn_0_cb_data);
	}

	return OK;
}

int btn_get(struct BTN_obj_t btn)
{
	if (!btn.dev)
	{
		return 0;
	}

	return gpio_pin_get(btn.dev, btn.pin);
}


static struct modem_param_info modem_param;
static struct sockaddr_storage host_addr;
static int lte_enabled = 0;
static int lte_ready = 0;
static int so = FAIL;


// connect to TCP server
int tcp_connect(void)
{
	led_set_rgb(&led.main, 1, 1, 1);
	printk("connecting %s:%u  ", CONFIG_TCP_SERVER_ADDRESS_STATIC, CONFIG_TCP_SERVER_PORT);

	// server address init
	struct sockaddr_in * server4 = ((struct sockaddr_in *)&host_addr);

	server4->sin_family = AF_INET;
	server4->sin_port = htons(CONFIG_TCP_SERVER_PORT);

	inet_pton(AF_INET, CONFIG_TCP_SERVER_ADDRESS_STATIC, &server4->sin_addr);

	// server connect
	int so = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	if (so < 0)
	{
		led_set_rgb(&led.main, 1, 0, 1);
		printk("\n\tcannot create TCP socket  %u %s\n\n", errno, strerror(errno));

		return FAIL;
	}

	int err = connect(so, (struct sockaddr *)&host_addr, sizeof(struct sockaddr_in));

	if (err < 0)
	{
		close(so);
		led_set_rgb(&led.main, 1, 0, 0);
		printk("\n\tFAILED  %u %s\n\n", errno, strerror(errno));

		return FAIL;
	}

	led_set_rgb(&led.main, 0, 1, 0);
	printk("OK\n\n");

	return so;
}

int lte_init(void)
{
	// init LTE
	led_set_rgb(&led.sense, 0, 0, 1);
	printk("LTE modem starting...  ");
	int b = lte_lc_init_and_connect();

	if (b != OK)
	{
		led_set_rgb(&led.sense, 1, 0, 0);
		printk("FAILED %u\n\n", -b);
		return FAIL;
	}

	led_set_rgb(&led.sense, 0, 1, 1);
	printk("OK  ");

	// init AT CMD parser
	b = modem_info_init();

	if (b != OK)
	{
		led_set_rgb(&led.sense, 1, 0, 1);
		printk("\n\tLTE modem AT CMD parser failed to start: %d\n\n", -b);

		return FAIL;
	}

	// init the modem information structure
	b = modem_info_params_init(&modem_param);

	if (b != OK)
	{
		led_set_rgb(&led.sense, 1, 1, 0);
		printk("\n\tLTE modem_param is NULL: %d\n\n", -b);

		return FAIL;
	}

	// LTE link ready
	led_set_rgb(&led.sense, 0, 0, 0);
	printk("link ready\n");
	lte_ready = true;

	return OK;
}

ssize_t lte_send(const char * msg, size_t size)
{
	if (!msg || !size)
	{
		return FAIL;
	}

	if (btn_0_trigger)
	{
		// toggle TCP connection
		lte_enabled = !lte_enabled;
		btn_0_trigger = 0;

#if CONFIG_BOARD_THINGY91_NRF9160NS
		led_set_rgb(&led.sense, 0, 0, 0);
#endif
	}

	if (!lte_ready && lte_enabled)
	{
		lte_ready = (OK == lte_init());

		if (!lte_ready)
		{
			return FAIL;
		}
	}

	if (so == FAIL)
	{
		if (lte_enabled)
		{
			so = tcp_connect();
		}

		if (so == FAIL)
		{
			if (btn_0_trigger)
			{
				led_set_rgb(&led.main, 0, 0, 0);
			}

			return FAIL;
		}
	}
	else if (!lte_enabled)
	{
		shutdown(so, SHUT_RDWR);
		close(so);
		so = FAIL;
		led_set_rgb(&led.main, 0, 0, 0);
		printk("TCP log disabled\n\n");

		return FAIL;
	}

	led_set(led.main.g, 1);
	ssize_t bytes_sent = send(so, msg, size, 0);

	if (bytes_sent <= 0)
	{
		close(so);
		so = FAIL;
		led_set_rgb(&led.main, 1, 1, 0);
		printk("send failed\n\n");

		return FAIL;
	}

	led_set(led.main.g, 0);

	return bytes_sent;
}
#endif

