/*
 * iio_info - Part of Industrial I/O (IIO) utilities
 *
 * Copyright (C) 2014-2020 Analog Devices, Inc.
 * Author: Paul Cercueil <paul.cercueil@analog.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * */

#include <errno.h>
#include <getopt.h>
#include <iio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MY_NAME "iio_info"

#ifdef _WIN32
#define snprintf sprintf_s
#endif

enum backend {
	LOCAL,
	XML,
	NETWORK,
	AUTO,
};

static const struct option options[] = {
	  {"help", no_argument, 0, 'h'},
	  {"xml", required_argument, 0, 'x'},
	  {"network", required_argument, 0, 'n'},
	  {"uri", required_argument, 0, 'u'},
	  {"scan", no_argument, 0, 's'},
	  {"auto", no_argument, 0, 'a'},
	  {0, 0, 0, 0},
};

static const char *options_descriptions[] = {
	"Show this help and quit.",
	"Use the XML backend with the provided XML file.",
	"Use the network backend with the provided hostname.",
	"Use the context at the provided URI.",
	"Scan for available backends.",
	"Scan for available contexts and if only one is available use it.",
};

/*
 * internal buffers need to be big enough for attributes
 * coming back from the kernel. Because of virtual memory,
 * only the amount of ram that is needed is used.
 */
#define BUF_SIZE 16384

static void * xmalloc(size_t n)
{
	void *p = malloc(n);

	if (!p && n != 0) {
		fprintf(stderr, MY_NAME
				" fatal error: allocating %zu bytes failed\n",n);
		exit(EXIT_FAILURE);
	}

	return p;
}

static void usage(void)
{
	unsigned int i;

	printf("Usage:\n\t" MY_NAME " [-x <xml_file>]\n\t"
			MY_NAME " [-n <hostname>]\n\t"
			MY_NAME " [-u <uri>]\n\nOptions:\n");
	for (i = 0; options[i].name; i++)
		printf("\t-%c, --%s\n\t\t\t%s\n",
					options[i].val, options[i].name,
					options_descriptions[i]);
}

static void scan(void)
{
	struct iio_scan_context *ctx;
	struct iio_context_info **info;
	unsigned int i;
	ssize_t ret;

	ctx = iio_create_scan_context(NULL, 0);
	if (!ctx) {
		fprintf(stderr, "Unable to create scan context\n");
		return;
	}

	ret = iio_scan_context_get_info_list(ctx, &info);
	if (ret < 0) {
		fprintf(stderr, "Unable to scan: %li\n", (long) ret);
		goto err_free_ctx;
	}

	if (ret == 0) {
		printf("No contexts found.\n");
		goto err_free_info_list;
	}

	printf("Available contexts:\n");

	for (i = 0; i < (size_t) ret; i++) {
		printf("\t%d: %s [%s]\n", i,
			iio_context_info_get_description(info[i]),
			iio_context_info_get_uri(info[i]));
	}

err_free_info_list:
	iio_context_info_list_free(info);
err_free_ctx:
	iio_scan_context_destroy(ctx);
}

static struct iio_context * autodetect_context(void)
{
	struct iio_scan_context *scan_ctx;
	struct iio_context_info **info;
	struct iio_context *ctx = NULL;
	unsigned int i;
	ssize_t ret;

	scan_ctx = iio_create_scan_context(NULL, 0);
	if (!scan_ctx) {
		fprintf(stderr, "Unable to create scan context\n");
		return NULL;
	}

	ret = iio_scan_context_get_info_list(scan_ctx, &info);
	if (ret < 0) {
		char err_str[1024];
		iio_strerror(-ret, err_str, sizeof(err_str));
		fprintf(stderr, "Scanning for IIO contexts failed: %s\n", err_str);
		goto err_free_ctx;
	}

	if (ret == 0) {
		printf("No IIO context found.\n");
		goto err_free_info_list;
	}

	if (ret == 1) {
		printf("Using auto-detected IIO context at URI \"%s\"\n",
				iio_context_info_get_uri(info[0]));
		ctx = iio_create_context_from_uri(iio_context_info_get_uri(info[0]));
	} else {
		fprintf(stderr, "Multiple contexts found. Please select one using --uri:\n");

		for (i = 0; i < (size_t) ret; i++) {
			fprintf(stderr, "\t%d: %s [%s]\n", i,
				iio_context_info_get_description(info[i]),
				iio_context_info_get_uri(info[i]));
		}
	}

err_free_info_list:
	iio_context_info_list_free(info);
err_free_ctx:
	iio_scan_context_destroy(scan_ctx);

	return ctx;
}

static int dev_is_buffer_capable(const struct iio_device *dev)
{
	unsigned int i;

	for (i = 0; i < iio_device_get_channels_count(dev); i++) {
		struct iio_channel *chn = iio_device_get_channel(dev, i);

		if (iio_channel_is_scan_element(chn))
			return true;
	}

	return false;
}

int main(int argc, char **argv)
{
	struct iio_context *ctx;
	int c, option_index = 0;
	const char *arg_uri = NULL;
	const char *arg_ip = NULL;
	const char *arg_xml = NULL;
	enum backend backend = LOCAL;
	bool do_scan = false, detect_context = false;
	unsigned int i, major, minor;
	char git_tag[8];
	int ret;

	while ((c = getopt_long(argc, argv, "+hn:x:u:sa",
					options, &option_index)) != -1) {
		switch (c) {
		case 'h':
			usage();
			return EXIT_SUCCESS;
		case 'n':
			if (backend != LOCAL) {
				fprintf(stderr, "-x, -n and -u are mutually exclusive\n");
				return EXIT_FAILURE;
			}
			backend = NETWORK;
			arg_ip = optarg;
			break;
		case 'x':
			if (backend != LOCAL) {
				fprintf(stderr, "-x, -n and -u are mutually exclusive\n");
				return EXIT_FAILURE;
			}
			backend = XML;
			arg_xml = optarg;
			break;
		case 's':
			do_scan = true;
			break;
		case 'u':
			if (backend != LOCAL) {
				fprintf(stderr, "-x, -n and -u are mutually exclusive\n");
				return EXIT_FAILURE;
			}
			backend = AUTO;
			arg_uri = optarg;
			break;
		case 'a':
			detect_context = true;
			break;
		case '?':
			return EXIT_FAILURE;
		}
	}

	if (optind != argc) {
		fprintf(stderr, "Incorrect number of arguments.\n\n");
		usage();
		return EXIT_FAILURE;
	}

	iio_library_get_version(&major, &minor, git_tag);
	printf("Library version: %u.%u (git tag: %s)\n", major, minor, git_tag);

	printf("Compiled with backends:");
	for (i = 0; i < iio_get_backends_count(); i++)
		printf(" %s", iio_get_backend(i));
	printf("\n");

	if (do_scan) {
		scan();
		return EXIT_SUCCESS;
	}

	if (detect_context)
		ctx = autodetect_context();
	else if (backend == XML)
		ctx = iio_create_xml_context(arg_xml);
	else if (backend == NETWORK)
		ctx = iio_create_network_context(arg_ip);
	else if (backend == AUTO)
		ctx = iio_create_context_from_uri(arg_uri);
	else
		ctx = iio_create_default_context();

	if (!ctx) {
		if (!detect_context) {
			char buf[1024];

			iio_strerror(errno, buf, sizeof(buf));
			fprintf(stderr, "Unable to create IIO context: %s\n",
					buf);
		}

		return EXIT_FAILURE;
	}

	printf("IIO context created with %s backend.\n",
			iio_context_get_name(ctx));

	ret = iio_context_get_version(ctx, &major, &minor, git_tag);
	if (!ret)
		printf("Backend version: %u.%u (git tag: %s)\n",
				major, minor, git_tag);
	else
		fprintf(stderr, "Unable to get backend version: %i\n", ret);

	printf("Backend description string: %s\n",
			iio_context_get_description(ctx));

	unsigned int nb_ctx_attrs = iio_context_get_attrs_count(ctx);
	if (nb_ctx_attrs > 0)
		printf("IIO context has %u attributes:\n", nb_ctx_attrs);

	for (i = 0; i < nb_ctx_attrs; i++) {
		const char *key, *value;

		iio_context_get_attr(ctx, i, &key, &value);
		printf("\t%s: %s\n", key, value);
	}

	unsigned int nb_devices = iio_context_get_devices_count(ctx);
	printf("IIO context has %u devices:\n", nb_devices);
	char *buf = xmalloc(BUF_SIZE);

	for (i = 0; i < nb_devices; i++) {
		const struct iio_device *dev = iio_context_get_device(ctx, i);
		const char *name = iio_device_get_name(dev);
		printf("\t%s:", iio_device_get_id(dev));
		if (name)
			printf(" %s", name);
		if (dev_is_buffer_capable(dev))
			printf(" (buffer capable)");
		printf("\n");

		unsigned int nb_channels = iio_device_get_channels_count(dev);
		printf("\t\t%u channels found:\n", nb_channels);

		unsigned int j;
		for (j = 0; j < nb_channels; j++) {
			struct iio_channel *ch = iio_device_get_channel(dev, j);
			const char *type_name;

			if (iio_channel_is_output(ch))
				type_name = "output";
			else
				type_name = "input";

			name = iio_channel_get_name(ch);
			printf("\t\t\t%s: %s (%s",
					iio_channel_get_id(ch),
					name ? name : "", type_name);

			if (iio_channel_get_type(ch) == IIO_CHAN_TYPE_UNKNOWN)
				printf(", WARN:iio_channel_get_type()=UNKNOWN");

			if (iio_channel_is_scan_element(ch)) {
				const struct iio_data_format *format =
					iio_channel_get_data_format(ch);
				char sign = format->is_signed ? 's' : 'u';
				char repeat[12] = "";

				if (format->is_fully_defined)
					sign += 'A' - 'a';

				if (format->repeat > 1)
					snprintf(repeat, sizeof(repeat), "X%u",
						format->repeat);

				printf(", index: %lu, format: %ce:%c%u/%u%s>>%u)\n",
					iio_channel_get_index(ch),
					format->is_be ? 'b' : 'l',
					sign, format->bits,
					format->length, repeat,
					format->shift);
			} else {
				printf(")\n");
			}

			unsigned int nb_attrs = iio_channel_get_attrs_count(ch);
			if (!nb_attrs)
				continue;

			printf("\t\t\t%u channel-specific attributes found:\n",
					nb_attrs);

			unsigned int k;
			for (k = 0; k < nb_attrs; k++) {
				const char *attr = iio_channel_get_attr(ch, k);
				ret = (int) iio_channel_attr_read(ch,
						attr, buf, BUF_SIZE);

				printf("\t\t\t\tattr %2u: %s ", k, attr);

				if (ret > 0) {
					printf("value: %s\n", buf);
				} else {
					iio_strerror(-ret, buf, BUF_SIZE);
					printf("ERROR: %s (%i)\n", buf, ret);
				}
			}
		}

		unsigned int nb_attrs = iio_device_get_attrs_count(dev);
		if (nb_attrs) {
			printf("\t\t%u device-specific attributes found:\n",
					nb_attrs);
			for (j = 0; j < nb_attrs; j++) {
				const char *attr = iio_device_get_attr(dev, j);
				ret = (int) iio_device_attr_read(dev,
						attr, buf, BUF_SIZE);

				printf("\t\t\t\tattr %2u: %s ",
						j, attr);

				if (ret > 0) {
					printf("value: %s\n", buf);
				} else {
					iio_strerror(-ret, buf, BUF_SIZE);
					printf("ERROR: %s (%i)\n", buf, ret);
				}
			}
		}

		nb_attrs = iio_device_get_buffer_attrs_count(dev);
		if (nb_attrs) {
			printf("\t\t%u buffer-specific attributes found:\n",
					nb_attrs);
			for (j = 0; j < nb_attrs; j++) {
				const char *attr = iio_device_get_buffer_attr(dev, j);
				ret = (int) iio_device_buffer_attr_read(dev,
						attr, buf, BUF_SIZE);

				printf("\t\t\t\tattr %2u: %s ",
						j, attr);

				if (ret > 0) {
					printf("value: %s\n", buf);
				} else {
					iio_strerror(-ret, buf, BUF_SIZE);
					printf("ERROR: %s (%i)\n", buf, ret);
				}
			}
		}

		nb_attrs = iio_device_get_debug_attrs_count(dev);
		if (nb_attrs) {
			printf("\t\t%u debug attributes found:\n", nb_attrs);
			for (j = 0; j < nb_attrs; j++) {
				const char *attr =
					iio_device_get_debug_attr(dev, j);

				ret = (int) iio_device_debug_attr_read(dev,
						attr, buf, BUF_SIZE);
				printf("\t\t\t\tdebug attr %2u: %s ",
						j, attr);
				if (ret > 0) {
					printf("value: %s\n", buf);
				} else {
					iio_strerror(-ret, buf, BUF_SIZE);
					printf("ERROR: %s (%i)\n", buf, ret);
				}
			}
		}

		const struct iio_device *trig;
		ret = iio_device_get_trigger(dev, &trig);
		if (ret == 0) {
			if (trig == NULL) {
				printf("\t\tNo trigger assigned to device\n");
			} else {
				name = iio_device_get_name(trig);
				printf("\t\tCurrent trigger: %s(%s)\n",
						iio_device_get_id(trig),
						name ? name : "");
			}
		}
	}

	free(buf);
	iio_context_destroy(ctx);
	return EXIT_SUCCESS;
}
