// SPDX-License-Identifier: LGPL-2.1-or-later
/*
 * This file is part of libgpiod.
 *
 * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
 */

#include <errno.h>
#include <getopt.h>
#include <gpiod.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

#include "tools-common.h"

typedef bool (*is_set_func)(struct gpiod_line *);

struct flag {
	const char *name;
	is_set_func is_set;
};

static bool line_bias_is_pullup(struct gpiod_line *line)
{
	return gpiod_line_bias(line) == GPIOD_LINE_BIAS_PULL_UP;
}

static bool line_bias_is_pulldown(struct gpiod_line *line)
{
	return gpiod_line_bias(line) == GPIOD_LINE_BIAS_PULL_DOWN;
}

static bool line_bias_is_disabled(struct gpiod_line *line)
{
	return gpiod_line_bias(line) == GPIOD_LINE_BIAS_DISABLE;
}

static const struct flag flags[] = {
	{
		.name = "used",
		.is_set = gpiod_line_is_used,
	},
	{
		.name = "open-drain",
		.is_set = gpiod_line_is_open_drain,
	},
	{
		.name = "open-source",
		.is_set = gpiod_line_is_open_source,
	},
	{
		.name = "pull-up",
		.is_set = line_bias_is_pullup,
	},
	{
		.name = "pull-down",
		.is_set = line_bias_is_pulldown,
	},
	{
		.name = "bias-disabled",
		.is_set = line_bias_is_disabled,
	},
};

static const struct option longopts[] = {
	{ "help",	no_argument,	NULL,	'h' },
	{ "version",	no_argument,	NULL,	'v' },
	{ GETOPT_NULL_LONGOPT },
};

static const char *const shortopts = "+hv";

static void print_help(void)
{
	printf("Usage: %s [OPTIONS] <gpiochip1> ...\n", get_progname());
	printf("\n");
	printf("Print information about all lines of the specified GPIO chip(s) (or all gpiochips if none are specified).\n");
	printf("\n");
	printf("Options:\n");
	printf("  -h, --help:\t\tdisplay this message and exit\n");
	printf("  -v, --version:\tdisplay the version and exit\n");
}

static PRINTF(3, 4) void prinfo(bool *of,
				unsigned int prlen, const char *fmt, ...)
{
	char *buf, *buffmt = NULL;
	size_t len;
	va_list va;
	int rv;

	va_start(va, fmt);
	rv = vasprintf(&buf, fmt, va);
	va_end(va);
	if (rv < 0)
		die("vasprintf: %s\n", strerror(errno));

	len = strlen(buf) - 1;

	if (len >= prlen || *of) {
		*of = true;
		printf("%s", buf);
	} else {
		rv = asprintf(&buffmt, "%%%us", prlen);
		if (rv < 0)
			die("asprintf: %s\n", strerror(errno));

		printf(buffmt, buf);
	}

	free(buf);
	if (fmt)
		free(buffmt);
}

static void list_lines(struct gpiod_chip *chip)
{
	struct gpiod_line_iter *iter;
	int direction, active_state;
	const char *name, *consumer;
	struct gpiod_line *line;
	unsigned int i, offset;
	bool flag_printed, of;

	iter = gpiod_line_iter_new(chip);
	if (!iter)
		die_perror("error creating line iterator");

	printf("%s - %u lines:\n",
	       gpiod_chip_name(chip), gpiod_chip_num_lines(chip));

	gpiod_foreach_line(iter, line) {
		offset = gpiod_line_offset(line);
		name = gpiod_line_name(line);
		consumer = gpiod_line_consumer(line);
		direction = gpiod_line_direction(line);
		active_state = gpiod_line_active_state(line);

		of = false;

		printf("\tline ");
		prinfo(&of, 3, "%u", offset);
		printf(": ");

		name ? prinfo(&of, 12, "\"%s\"", name)
		     : prinfo(&of, 12, "unnamed");
		printf(" ");

		if (!gpiod_line_is_used(line))
			prinfo(&of, 12, "unused");
		else
			consumer ? prinfo(&of, 12, "\"%s\"", consumer)
				 : prinfo(&of, 12, "kernel");

		printf(" ");

		prinfo(&of, 8, "%s ", direction == GPIOD_LINE_DIRECTION_INPUT
							? "input" : "output");
		prinfo(&of, 13, "%s ",
		       active_state == GPIOD_LINE_ACTIVE_STATE_LOW
							? "active-low"
							: "active-high");

		flag_printed = false;
		for (i = 0; i < ARRAY_SIZE(flags); i++) {
			if (flags[i].is_set(line)) {
				if (flag_printed)
					printf(" ");
				else
					printf("[");
				printf("%s", flags[i].name);
				flag_printed = true;
			}
		}
		if (flag_printed)
			printf("]");

		printf("\n");
	}

	gpiod_line_iter_free(iter);
}

int main(int argc, char **argv)
{
	struct gpiod_chip_iter *chip_iter;
	struct gpiod_chip *chip;
	int i, optc, opti;

	for (;;) {
		optc = getopt_long(argc, argv, shortopts, longopts, &opti);
		if (optc < 0)
			break;

		switch (optc) {
		case 'h':
			print_help();
			return EXIT_SUCCESS;
		case 'v':
			print_version();
			return EXIT_SUCCESS;
		case '?':
			die("try %s --help", get_progname());
		default:
			abort();
		}
	}

	argc -= optind;
	argv += optind;

	if (argc == 0) {
		chip_iter = gpiod_chip_iter_new();
		if (!chip_iter)
			die_perror("error accessing GPIO chips");

		gpiod_foreach_chip(chip_iter, chip)
			list_lines(chip);

		gpiod_chip_iter_free(chip_iter);
	} else {
		for (i = 0; i < argc; i++) {
			chip = gpiod_chip_open_lookup(argv[i]);
			if (!chip)
				die_perror("looking up chip %s", argv[i]);

			list_lines(chip);

			gpiod_chip_close(chip);
		}
	}

	return EXIT_SUCCESS;
}
