/***************************************************************************//**
 *   @file   iio_app.c
 *   @brief  C file of iio_app
 *   @author Mihail Chindris (mihail.chindris@analog.com)
********************************************************************************
 * Copyright 2020(c) Analog Devices, Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. Neither the name of Analog Devices, Inc. nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES, INC. “AS IS” AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL ANALOG DEVICES, INC. BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/

#ifdef IIO_SUPPORT

#include <stdlib.h>
#include <stdio.h>
#include "iio_app.h"
#include "parameters.h"
#include "no_os_alloc.h"

#if defined(ADUCM_PLATFORM)
#include "aducm3029_uart.h"
#include "aducm3029_irq.h"
#endif
#if defined(XILINX_PLATFORM)
#include "xilinx_uart.h"
#include "xilinx_irq.h"
#endif
#if defined(STM32_PLATFORM)
#include <errno.h>
#include "stm32_uart.h"
#include "stm32_irq.h"
#endif
#if defined(MAXIM_PLATFORM)
#include "maxim_irq.h"
#include "maxim_uart.h"
#endif

#ifdef NO_OS_NETWORKING
/* Fix: Use static buffers instead of calloc for new connections */
#warning "iio may not work with WIFI on aducm3029."
#ifdef ADUCM_PLATFORM
#include "wifi.h"
#endif
#include "tcp_socket.h"
#endif

#ifdef LINUX_PLATFORM
#include "linux_socket.h"
#include "tcp_socket.h"
#endif

#ifdef NO_OS_LWIP_NETWORKING
#include "lwip_socket.h"
#endif

// The default baudrate iio_app will use to print messages to console.
#define UART_BAUDRATE_DEFAULT	115200
#define UART_STOPBITS_DEFAULT	NO_OS_UART_STOP_1_BIT

static inline uint32_t _calc_uart_xfer_time(uint32_t len, uint32_t baudrate)
{
	uint32_t ms = 1000ul * len * 8 / UART_BAUDRATE_DEFAULT;
	ms += ms / 10; // overhead
	return ms;
}

#if !defined(LINUX_PLATFORM) && !defined(NO_OS_NETWORKING) && !defined(NO_OS_USB_UART)
static int32_t iio_print_uart_info_message(struct no_os_uart_desc **uart_desc,
		struct no_os_uart_init_param *user_uart_params,
		char *message, int32_t msglen)
{
	int32_t status;
	uint32_t delay_ms;

	status = no_os_uart_write(*uart_desc, (uint8_t *)message, msglen);
	if (status < 0)
		return status;

	delay_ms = _calc_uart_xfer_time(msglen, UART_BAUDRATE_DEFAULT);
	no_os_mdelay(delay_ms);

	/** Reinitialize UART with parameters required by the IIO application */
	no_os_uart_remove(*uart_desc);
	return no_os_uart_init(uart_desc, user_uart_params);
}
#endif

static int32_t print_uart_hello_message(struct no_os_uart_desc **uart_desc,
					struct no_os_uart_init_param *user_uart_params)
{
#if defined(LINUX_PLATFORM) || defined(NO_OS_NETWORKING) || defined(NO_OS_LWIP_NETWORKING) || defined(NO_OS_USB_UART)
	return 0;
#else
	const char *uart_data_size[] = { "5", "6", "7", "8", "9" };
	const char *uart_parity[] = { "none", "mark", "space", "odd", "even" };
	const char *uart_stop[] = { "1", "2" };
	char message[512];
	uint32_t msglen = sprintf(message,
				  "Running IIOD server...\n"
				  "If successful, you may connect an IIO client application by:\n"
				  "1. Disconnecting the serial terminal you use to view this message.\n"
				  "2. Connecting the IIO client application using the serial backend configured as shown:\n"
				  "\tBaudrate: %lu\n"
				  "\tData size: %s bits\n"
				  "\tParity: %s\n"
				  "\tStop bits: %s\n"
				  "\tFlow control: none\n",
				  user_uart_params->baud_rate,
				  uart_data_size[user_uart_params->size],
				  uart_parity[user_uart_params->parity],
				  uart_stop[user_uart_params->stop]);

	return iio_print_uart_info_message(uart_desc, user_uart_params,
					   message, msglen);
#endif
}

static int32_t print_uart_error_message(struct no_os_uart_desc **uart_desc,
					struct no_os_uart_init_param *user_uart_params,
					int32_t status)
{
	char message[512];
	uint32_t msglen = sprintf(message,
				  "IIOD server failed with code %d.\n",
				  (int)status);
#if defined(LINUX_PLATFORM) || defined(NO_OS_NETWORKING) || defined(NO_OS_LWIP_NETWORKING) || defined(NO_OS_USB_UART)
	(void)msglen;
	printf("%s", message);
	return 0;
#else
	return iio_print_uart_info_message(uart_desc, user_uart_params,
					   message, msglen);
#endif
}

#if defined(NO_OS_LWIP_NETWORKING)
static int32_t lwip_network_setup(struct iio_app_desc *app,
				  struct iio_app_init_param param,
				  struct iio_init_param *iio_init_param)
{
	static struct tcp_socket_init_param socket_param;
	static struct lwip_network_desc lwip_desc;
	static bool is_initialized = false;
	int ret;

	if (NO_OS_LWIP_INIT_ONETIME && is_initialized) {
		socket_param.net = &lwip_desc.no_os_net;
	} else {
		ret = no_os_lwip_init(&app->lwip_desc, &param.lwip_param);
		if (ret)
			return ret;

		socket_param.net = &app->lwip_desc->no_os_net;
		memcpy(&lwip_desc, app->lwip_desc, sizeof(lwip_desc));
	}

	is_initialized = true;
	socket_param.max_buff_size = 0;

	iio_init_param->phy_type = USE_NETWORK;
	iio_init_param->tcp_socket_init_param = &socket_param;

	return 0;
}
#endif

#if defined(NO_OS_NETWORKING) || defined(LINUX_PLATFORM)
static int32_t network_setup(struct iio_init_param *iio_init_param,
			     struct no_os_uart_desc *uart_desc,
			     void *irq_desc)
{
	static struct tcp_socket_init_param socket_param;

#ifdef LINUX_PLATFORM
	socket_param.net = &linux_net;
#endif
#ifdef ADUCM_PLATFORM
	int32_t status;
	static struct wifi_desc *wifi;
	struct wifi_init_param wifi_param = {
		.irq_desc = irq_desc,
		.uart_desc = uart_desc,
		.uart_irq_conf = uart_desc,
		.uart_irq_id = UART_IRQ_ID,
		.sw_reset_en = true
	};
	status = wifi_init(&wifi, &wifi_param);
	if (status < 0)
		return status;

	status = wifi_connect(wifi, WIFI_SSID, WIFI_PWD);
	if (status < 0)
		return status;

	char buff[100];
	wifi_get_ip(wifi, buff, 100);
	printf("iiod ip is: %s\n", buff);

	wifi_get_network_interface(wifi, &socket_param.net);
#endif

	socket_param.max_buff_size = 0;
	iio_init_param->phy_type = USE_NETWORK;
	iio_init_param->tcp_socket_init_param = &socket_param;

	return 0;
}
#endif

static int32_t uart_setup(struct no_os_uart_desc **uart_desc,
			  struct no_os_uart_init_param *uart_init_par)
{
#if defined(LINUX_PLATFORM) || defined(NO_OS_LWIP_NETWORKING)
	*uart_desc = NULL;
	return 0;
#endif
	struct no_os_uart_init_param luart_par = {
		.device_id = uart_init_par->device_id,
		/* TODO: remove this ifdef when asynchrounous rx is implemented on every platform. */
#if defined(STM32_PLATFORM) || defined(MAXIM_PLATFORM) || defined(ADUCM_PLATFORM) || defined(PICO_PLATFORM)
		.irq_id = uart_init_par->irq_id,
		.asynchronous_rx = true,
#endif
		.baud_rate = UART_BAUDRATE_DEFAULT,
		.size = NO_OS_UART_CS_8,
		.parity = NO_OS_UART_PAR_NO,
		.stop = NO_OS_UART_STOP_1_BIT,
		.platform_ops = uart_init_par->platform_ops,
#ifndef ADUCM_PLATFORM
		.extra = uart_init_par->extra
#endif
	};

	return no_os_uart_init(uart_desc, &luart_par);
}

#if defined(ADUCM_PLATFORM) || (defined(STM32_PLATFORM)) || defined(MAXIM_PLATFORM)
static int32_t irq_setup(struct no_os_irq_ctrl_desc **irq_desc)
{
	int32_t status;
#if defined(ADUCM_PLATFORM)
	void *platform_irq_init_par = NULL;
	const struct no_os_irq_platform_ops *platform_irq_ops = &aducm_irq_ops;
#elif defined(STM32_PLATFORM)
	void *platform_irq_init_par = NULL;
	const struct no_os_irq_platform_ops *platform_irq_ops = &stm32_irq_ops;
#elif defined(MAXIM_PLATFORM)
	void *platform_irq_init_par = NULL;
	const struct no_os_irq_platform_ops *platform_irq_ops = &max_irq_ops;
#endif

	struct no_os_irq_init_param irq_init_param = {
		.irq_ctrl_id = INTC_DEVICE_ID,
		.platform_ops = platform_irq_ops,
		.extra = platform_irq_init_par
	};

	status = no_os_irq_ctrl_init(irq_desc, &irq_init_param);
	if (status < 0)
		return status;

	return no_os_irq_global_enable(*irq_desc);
}
#endif

/**
 * @brief Register devices for an iio application
 *
 * Configuration for communication is done in parameters.h
 * @param app - the iio application descriptor
 * @param app_init_param - the iio application initialization parameters
 * @return 0 on success, negative value otherwise
 */
int iio_app_init(struct iio_app_desc **app,
		 struct iio_app_init_param app_init_param)
{
	struct iio_device_init *iio_init_devs = NULL;
	struct iio_init_param iio_init_param;
	struct no_os_uart_desc *uart_desc;
	struct iio_app_desc *application;
	struct iio_data_buffer *buff;
	unsigned int i;
	int status;
	void *irq_desc = app_init_param.irq_desc;

	application = (struct iio_app_desc *)no_os_calloc(1, sizeof(*application));
	if (!application)
		return -ENOMEM;

	application->post_step_callback = app_init_param.post_step_callback;
	application->arg = app_init_param.arg;

#if defined(ADUCM_PLATFORM) || defined(STM32_PLATFORM)
	/* Only one irq controller can exist and be initialized in
	 * any of the iio_devices. */
	for (i = 0; i < app_init_param.nb_devices; i++) {
		if (app_init_param.devices[i].dev_descriptor->irq_desc) {
			irq_desc = (struct no_os_irq_ctrl_desc *)
				   app_init_param.devices[i].dev_descriptor->irq_desc;
			break;
		}
	}

	if (!irq_desc) {
		status = irq_setup((struct no_os_irq_ctrl_desc **)&irq_desc);
		if (status < 0)
			return status;
	}
#endif

	status = uart_setup(&uart_desc, &app_init_param.uart_init_params);
	if (status < 0)
		goto error_uart;

	status = print_uart_hello_message(&uart_desc,
					  &app_init_param.uart_init_params);
	if (status < 0)
		goto error;

	application->uart_desc = uart_desc;
#if defined(NO_OS_LWIP_NETWORKING)
	status = lwip_network_setup(application, app_init_param, &iio_init_param);
	if (status)
		goto error;
#elif defined(NO_OS_NETWORKING) || defined(LINUX_PLATFORM)
	status = network_setup(&iio_init_param, uart_desc, application->irq_desc);
	if (status < 0)
		goto error;
#else
	iio_init_param.phy_type = USE_UART;
	iio_init_param.uart_desc = uart_desc;
#endif

	iio_init_devs = no_os_calloc(app_init_param.nb_devices, sizeof(*iio_init_devs));
	if (!iio_init_devs) {
		status = -ENOMEM;
		goto error;
	}

	for (i = 0; i < app_init_param.nb_devices; ++i) {
		/*
		 * iio_app_device is from iio_app.h and we don't want to include
		 * this in iio.h.
		 * At the moment iio_device_init has the same parameters but
		 * it will change.
		 * When changes are done in iio. iio_app_device may be removed
		 *iio_init_param and use iio_device_init instead.
		 * This way faster changes can be done without changing all
		 * project for each change.
		 */
		iio_init_devs[i].name = app_init_param.devices[i].name;
		iio_init_devs[i].dev = app_init_param.devices[i].dev;
		iio_init_devs[i].dev_descriptor = app_init_param.devices[i].dev_descriptor;
		iio_init_devs[i].trigger_id = app_init_param.devices[i].default_trigger_id;
		buff = app_init_param.devices[i].read_buff ?
		       app_init_param.devices[i].read_buff :
		       app_init_param.devices[i].write_buff;
		if (buff) {
			iio_init_devs[i].raw_buf = buff->buff;
			iio_init_devs[i].raw_buf_len = buff->size;
		} else {
			iio_init_devs[i].raw_buf = NULL;
			iio_init_devs[i].raw_buf_len = 0;
		}
	}

	iio_init_param.devs = iio_init_devs;
	iio_init_param.nb_devs = app_init_param.nb_devices;
	iio_init_param.trigs = app_init_param.trigs;
	iio_init_param.nb_trigs = app_init_param.nb_trigs;
	iio_init_param.ctx_attrs = app_init_param.ctx_attrs;
	iio_init_param.nb_ctx_attr = app_init_param.nb_ctx_attr;

	status = iio_init(&application->iio_desc, &iio_init_param);
	if (status < 0)
		goto error;

	no_os_free(iio_init_devs);

	*app = application;

	return 0;
error_uart:
	/** We might have to reinit UART, settings might have changed for IIO */
	uart_setup(&uart_desc, &app_init_param.uart_init_params);
error:
	no_os_free(iio_init_devs);

	no_os_free(application);

	print_uart_error_message(&uart_desc, &app_init_param.uart_init_params, status);
	no_os_uart_remove(uart_desc);

	return status;
}

/**
 * @brief Start an IIO application
 *
 * Configuration for communication is done through iio_app_init_param
 * @param app - the iio application parameters
 * @return 0 on success, negative value otherwise
 */
int iio_app_run(struct iio_app_desc *app)
{
	int status;

	do {
		status = iio_step(app->iio_desc);
		if (status && status != -EAGAIN && status != -ENOTCONN
		    && status != -NO_OS_EOVERRUN)
			return status;
		if (app->post_step_callback) {
			status = app->post_step_callback(app->arg);
			if (status)
				return status;
		}
	} while (true);
}

/**
 * @brief Remove resources allocated by the IIO application
 *
 * Configuration for communication is done through iio_app_init_param
 * @param app - the iio application parameters
 * @return 0 on success, negative value otherwise
 */
int iio_app_remove(struct iio_app_desc *app)
{
	int ret;

#if defined(ADUCM_PLATFORM) || (defined(XILINX_PLATFORM) && !defined(PLATFORM_MB)) || defined(STM32_PLATFORM)
	ret = no_os_irq_ctrl_remove(app->irq_desc);
	if (ret)
		return ret;
#endif

	if (app->uart_desc) {
		ret = no_os_uart_remove(app->uart_desc);
		if (ret)
			return ret;
	}

	ret = iio_remove(app->iio_desc);
	if (ret)
		return ret;

	no_os_free(app);

	return 0;
}

#endif
