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

/*
 * More specific variants of the core API and misc functions that don't need
 * access to neither the internal library data structures nor the kernel UAPI.
 */

#include <ctype.h>
#include <errno.h>
#include <gpiod.h>
#include <stdio.h>
#include <string.h>

static bool isuint(const char *str)
{
	for (; *str && isdigit(*str); str++)
		;

	return *str == '\0';
}

struct gpiod_chip *gpiod_chip_open_by_name(const char *name)
{
	struct gpiod_chip *chip;
	char *path;
	int rv;

	rv = asprintf(&path, "/dev/%s", name);
	if (rv < 0)
		return NULL;

	chip = gpiod_chip_open(path);
	free(path);

	return chip;
}

struct gpiod_chip *gpiod_chip_open_by_number(unsigned int num)
{
	struct gpiod_chip *chip;
	char *path;
	int rv;

	rv = asprintf(&path, "/dev/gpiochip%u", num);
	if (!rv)
		return NULL;

	chip = gpiod_chip_open(path);
	free(path);

	return chip;
}

struct gpiod_chip *gpiod_chip_open_by_label(const char *label)
{
	struct gpiod_chip_iter *iter;
	struct gpiod_chip *chip;

	iter = gpiod_chip_iter_new();
	if (!iter)
		return NULL;

	gpiod_foreach_chip(iter, chip) {
		if (strcmp(label, gpiod_chip_label(chip)) == 0) {
			gpiod_chip_iter_free_noclose(iter);
			return chip;
		}
	}

	errno = ENOENT;
	gpiod_chip_iter_free(iter);

	return NULL;
}

struct gpiod_chip *gpiod_chip_open_lookup(const char *descr)
{
	struct gpiod_chip *chip;

	if (isuint(descr)) {
		chip = gpiod_chip_open_by_number(strtoul(descr, NULL, 10));
	} else {
		chip = gpiod_chip_open_by_label(descr);
		if (!chip) {
			if (strncmp(descr, "/dev/", 5))
				chip = gpiod_chip_open_by_name(descr);
			else
				chip = gpiod_chip_open(descr);
		}
	}

	return chip;
}

int gpiod_chip_get_lines(struct gpiod_chip *chip, unsigned int *offsets,
			 unsigned int num_offsets, struct gpiod_line_bulk *bulk)
{
	struct gpiod_line *line;
	unsigned int i;

	gpiod_line_bulk_init(bulk);

	for (i = 0; i < num_offsets; i++) {
		line = gpiod_chip_get_line(chip, offsets[i]);
		if (!line)
			return -1;

		gpiod_line_bulk_add(bulk, line);
	}

	return 0;
}

int gpiod_chip_get_all_lines(struct gpiod_chip *chip,
			     struct gpiod_line_bulk *bulk)
{
	struct gpiod_line_iter *iter;
	struct gpiod_line *line;

	gpiod_line_bulk_init(bulk);

	iter = gpiod_line_iter_new(chip);
	if (!iter)
		return -1;

	gpiod_foreach_line(iter, line)
		gpiod_line_bulk_add(bulk, line);

	gpiod_line_iter_free(iter);

	return 0;
}

struct gpiod_line *
gpiod_chip_find_line(struct gpiod_chip *chip, const char *name)
{
	struct gpiod_line_iter *iter;
	struct gpiod_line *line;
	const char *tmp;

	iter = gpiod_line_iter_new(chip);
	if (!iter)
		return NULL;

	gpiod_foreach_line(iter, line) {
		tmp = gpiod_line_name(line);
		if (tmp && strcmp(tmp, name) == 0) {
			gpiod_line_iter_free(iter);
			return line;
		}
	}

	errno = ENOENT;
	gpiod_line_iter_free(iter);

	return NULL;
}

int gpiod_chip_find_lines(struct gpiod_chip *chip,
			  const char **names, struct gpiod_line_bulk *bulk)
{
	struct gpiod_line *line;
	int i;

	gpiod_line_bulk_init(bulk);

	for (i = 0; names[i]; i++) {
		line = gpiod_chip_find_line(chip, names[i]);
		if (!line)
			return -1;

		gpiod_line_bulk_add(bulk, line);
	}

	return 0;
}

int gpiod_line_request_input(struct gpiod_line *line, const char *consumer)
{
	struct gpiod_line_request_config config = {
		.consumer = consumer,
		.request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT,
	};

	return gpiod_line_request(line, &config, 0);
}

int gpiod_line_request_output(struct gpiod_line *line,
			      const char *consumer, int default_val)
{
	struct gpiod_line_request_config config = {
		.consumer = consumer,
		.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
	};

	return gpiod_line_request(line, &config, default_val);
}

int gpiod_line_request_input_flags(struct gpiod_line *line,
				   const char *consumer, int flags)
{
	struct gpiod_line_request_config config = {
		.consumer = consumer,
		.request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT,
		.flags = flags,
	};

	return gpiod_line_request(line, &config, 0);
}

int gpiod_line_request_output_flags(struct gpiod_line *line,
				    const char *consumer, int flags,
				    int default_val)
{
	struct gpiod_line_request_config config = {
		.consumer = consumer,
		.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
		.flags = flags,
	};

	return gpiod_line_request(line, &config, default_val);
}

static int line_event_request_type(struct gpiod_line *line,
				   const char *consumer, int flags, int type)
{
	struct gpiod_line_request_config config = {
		.consumer = consumer,
		.request_type = type,
		.flags = flags,
	};

	return gpiod_line_request(line, &config, 0);
}

int gpiod_line_request_rising_edge_events(struct gpiod_line *line,
					  const char *consumer)
{
	return line_event_request_type(line, consumer, 0,
				       GPIOD_LINE_REQUEST_EVENT_RISING_EDGE);
}

int gpiod_line_request_falling_edge_events(struct gpiod_line *line,
					   const char *consumer)
{
	return line_event_request_type(line, consumer, 0,
				       GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE);
}

int gpiod_line_request_both_edges_events(struct gpiod_line *line,
					 const char *consumer)
{
	return line_event_request_type(line, consumer, 0,
				       GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES);
}

int gpiod_line_request_rising_edge_events_flags(struct gpiod_line *line,
						const char *consumer,
						int flags)
{
	return line_event_request_type(line, consumer, flags,
				       GPIOD_LINE_REQUEST_EVENT_RISING_EDGE);
}

int gpiod_line_request_falling_edge_events_flags(struct gpiod_line *line,
						 const char *consumer,
						 int flags)
{
	return line_event_request_type(line, consumer, flags,
				       GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE);
}

int gpiod_line_request_both_edges_events_flags(struct gpiod_line *line,
					       const char *consumer, int flags)
{
	return line_event_request_type(line, consumer, flags,
				       GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES);
}

int gpiod_line_request_bulk_input(struct gpiod_line_bulk *bulk,
				  const char *consumer)
{
	struct gpiod_line_request_config config = {
		.consumer = consumer,
		.request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT,
	};

	return gpiod_line_request_bulk(bulk, &config, 0);
}

int gpiod_line_request_bulk_output(struct gpiod_line_bulk *bulk,
				   const char *consumer,
				   const int *default_vals)
{
	struct gpiod_line_request_config config = {
		.consumer = consumer,
		.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
	};

	return gpiod_line_request_bulk(bulk, &config, default_vals);
}

static int line_event_request_type_bulk(struct gpiod_line_bulk *bulk,
					const char *consumer,
					int flags, int type)
{
	struct gpiod_line_request_config config = {
		.consumer = consumer,
		.request_type = type,
		.flags = flags,
	};

	return gpiod_line_request_bulk(bulk, &config, 0);
}

int gpiod_line_request_bulk_rising_edge_events(struct gpiod_line_bulk *bulk,
					       const char *consumer)
{
	return line_event_request_type_bulk(bulk, consumer, 0,
					GPIOD_LINE_REQUEST_EVENT_RISING_EDGE);
}

int gpiod_line_request_bulk_falling_edge_events(struct gpiod_line_bulk *bulk,
						const char *consumer)
{
	return line_event_request_type_bulk(bulk, consumer, 0,
					GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE);
}

int gpiod_line_request_bulk_both_edges_events(struct gpiod_line_bulk *bulk,
					      const char *consumer)
{
	return line_event_request_type_bulk(bulk, consumer, 0,
					GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES);
}

int gpiod_line_request_bulk_input_flags(struct gpiod_line_bulk *bulk,
					const char *consumer, int flags)
{
	struct gpiod_line_request_config config = {
		.consumer = consumer,
		.request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT,
		.flags = flags,
	};

	return gpiod_line_request_bulk(bulk, &config, 0);
}

int gpiod_line_request_bulk_output_flags(struct gpiod_line_bulk *bulk,
					 const char *consumer, int flags,
					 const int *default_vals)
{
	struct gpiod_line_request_config config = {
		.consumer = consumer,
		.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
		.flags = flags,
	};

	return gpiod_line_request_bulk(bulk, &config, default_vals);
}

int gpiod_line_request_bulk_rising_edge_events_flags(
					struct gpiod_line_bulk *bulk,
					const char *consumer, int flags)
{
	return line_event_request_type_bulk(bulk, consumer, flags,
					GPIOD_LINE_REQUEST_EVENT_RISING_EDGE);
}

int gpiod_line_request_bulk_falling_edge_events_flags(
					struct gpiod_line_bulk *bulk,
					const char *consumer, int flags)
{
	return line_event_request_type_bulk(bulk, consumer, flags,
					GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE);
}

int gpiod_line_request_bulk_both_edges_events_flags(
					struct gpiod_line_bulk *bulk,
					const char *consumer, int flags)
{
	return line_event_request_type_bulk(bulk, consumer, flags,
					GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES);
}

struct gpiod_line *gpiod_line_get(const char *device, unsigned int offset)
{
	struct gpiod_chip *chip;
	struct gpiod_line *line;

	chip = gpiod_chip_open_lookup(device);
	if (!chip)
		return NULL;

	line = gpiod_chip_get_line(chip, offset);
	if (!line) {
		gpiod_chip_close(chip);
		return NULL;
	}

	return line;
}

struct gpiod_line *gpiod_line_find(const char *name)
{
	struct gpiod_chip_iter *iter;
	struct gpiod_chip *chip;
	struct gpiod_line *line;

	iter = gpiod_chip_iter_new();
	if (!iter)
		return NULL;

	gpiod_foreach_chip(iter, chip) {
		line = gpiod_chip_find_line(chip, name);
		if (line) {
			gpiod_chip_iter_free_noclose(iter);
			return line;
		}

		if (errno != ENOENT)
			goto out;
	}

	errno = ENOENT;

out:
	gpiod_chip_iter_free(iter);

	return NULL;
}

void gpiod_line_close_chip(struct gpiod_line *line)
{
	struct gpiod_chip *chip = gpiod_line_get_chip(line);

	gpiod_chip_close(chip);
}