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

/* GPIO chip and line iterators. */

#include <dirent.h>
#include <gpiod.h>
#include <string.h>

struct gpiod_chip_iter {
	struct gpiod_chip **chips;
	unsigned int num_chips;
	unsigned int offset;
};

struct gpiod_line_iter {
	struct gpiod_line **lines;
	unsigned int num_lines;
	unsigned int offset;
};

static int dir_filter(const struct dirent *dir)
{
	return !strncmp(dir->d_name, "gpiochip", 8);
}

static void free_dirs(struct dirent **dirs, unsigned int num_dirs)
{
	unsigned int i;

	for (i = 0; i < num_dirs; i++)
		free(dirs[i]);
	free(dirs);
}

struct gpiod_chip_iter *gpiod_chip_iter_new(void)
{
	struct gpiod_chip_iter *iter;
	struct dirent **dirs;
	int i, num_chips;

	num_chips = scandir("/dev", &dirs, dir_filter, alphasort);
	if (num_chips < 0)
		return NULL;

	iter = malloc(sizeof(*iter));
	if (!iter)
		goto err_free_dirs;

	iter->num_chips = num_chips;
	iter->offset = 0;

	if (num_chips == 0) {
		iter->chips = NULL;
		return iter;
	}

	iter->chips = calloc(num_chips, sizeof(*iter->chips));
	if (!iter->chips)
		goto err_free_iter;

	for (i = 0; i < num_chips; i++) {
		iter->chips[i] = gpiod_chip_open_by_name(dirs[i]->d_name);
		if (!iter->chips[i])
			goto err_close_chips;
	}

	free_dirs(dirs, num_chips);

	return iter;

err_close_chips:
	for (i = 0; i < num_chips; i++) {
		if (iter->chips[i])
			gpiod_chip_close(iter->chips[i]);
	}

	free(iter->chips);

err_free_iter:
	free(iter);

err_free_dirs:
	free_dirs(dirs, num_chips);

	return NULL;
}

void gpiod_chip_iter_free(struct gpiod_chip_iter *iter)
{
	if (iter->offset > 0 && iter->offset < iter->num_chips)
		gpiod_chip_close(iter->chips[iter->offset - 1]);
	gpiod_chip_iter_free_noclose(iter);
}

void gpiod_chip_iter_free_noclose(struct gpiod_chip_iter *iter)
{
	unsigned int i;

	for (i = iter->offset; i < iter->num_chips; i++) {
		if (iter->chips[i])
			gpiod_chip_close(iter->chips[i]);
	}

	if (iter->chips)
		free(iter->chips);

	free(iter);
}

struct gpiod_chip *gpiod_chip_iter_next(struct gpiod_chip_iter *iter)
{
	if (iter->offset > 0) {
		gpiod_chip_close(iter->chips[iter->offset - 1]);
		iter->chips[iter->offset - 1] = NULL;
	}

	return gpiod_chip_iter_next_noclose(iter);
}

struct gpiod_chip *gpiod_chip_iter_next_noclose(struct gpiod_chip_iter *iter)
{
	return iter->offset < (iter->num_chips)
					? iter->chips[iter->offset++] : NULL;
}

struct gpiod_line_iter *gpiod_line_iter_new(struct gpiod_chip *chip)
{
	struct gpiod_line_iter *iter;
	unsigned int i;

	iter = malloc(sizeof(*iter));
	if (!iter)
		return NULL;

	iter->num_lines = gpiod_chip_num_lines(chip);
	iter->offset = 0;

	iter->lines = calloc(iter->num_lines, sizeof(*iter->lines));
	if (!iter->lines) {
		free(iter);
		return NULL;
	}

	for (i = 0; i < iter->num_lines; i++) {
		iter->lines[i] = gpiod_chip_get_line(chip, i);
		if (!iter->lines[i]) {
			free(iter->lines);
			free(iter);
			return NULL;
		}
	}

	return iter;
}

void gpiod_line_iter_free(struct gpiod_line_iter *iter)
{
	free(iter->lines);
	free(iter);
}

struct gpiod_line *gpiod_line_iter_next(struct gpiod_line_iter *iter)
{
	return iter->offset < (iter->num_lines)
					? iter->lines[iter->offset++] : NULL;
}
