/*-
 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
 *
 * Copyright (c) 2021 Tobias Kortkamp <tobik@FreeBSD.org>
 * All rights reserved.
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 THE AUTHOR OR CONTRIBUTORS 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.
 */

#include "config.h"

#include <sys/param.h>
#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#define _WITH_GETLINE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "flow.h"
#include "io.h"
#include "mem.h"
#include "mempool.h"
#include "str.h"
#include "util.h"

struct LineIterator {
	FILE *f;
	char *line;
	size_t linecap;
	ssize_t linelen;

	bool forwarded;
	size_t i;
	size_t len;
};

bool
can_use_colors(FILE *fp)
{
	if (getenv("CLICOLOR_FORCE") != NULL) {
		return true;
	}

	if (getenv("NO_COLOR") != NULL) {
		return false;
	}

	unless (fp) {
		return false;
	}

	if (isatty(fileno(fp)) == 0) {
		return false;
	}

	return true;
}

struct LineIterator *
line_iterator(FILE *f, ssize_t a, ssize_t b)
{
	struct LineIterator *iter = xmalloc(sizeof(struct LineIterator));
	slice_to_range(SSIZE_MAX, a, b, &iter->i, &iter->len);
	iter->f = f;
	return iter;
}

void
line_iterator_cleanup(struct LineIterator **iter_)
{
	struct LineIterator *iter = *iter_;
	if (iter != NULL) {
		free(iter->line);
		free(iter);
		*iter_ = NULL;
	}
}

bool
line_iterator_next(struct LineIterator **iter_, size_t *index, char **line, size_t *linelen)
{
	struct LineIterator *iter = *iter_;

	unless (iter->forwarded) {
		iter->forwarded = true;
		for (size_t i = 0; i < iter->i; i++) {
			unless ((iter->linelen = getline(&iter->line, &iter->linecap, iter->f)) > 0) {
				goto done;
			}
		}
	}

	if ((iter->linelen = getline(&iter->line, &iter->linecap, iter->f)) > 0 && iter->i < iter->len) {
		if (iter->linelen > 0 && iter->line[iter->linelen - 1] == '\n') {
			iter->line[iter->linelen - 1] = 0;
			iter->linelen--;
		}
		*index = iter->i++;
		*line = iter->line;
		*linelen = iter->linelen;
		return true;
	}

done:
	line_iterator_cleanup(iter_);
	*iter_ = NULL;
	return false;
}

char *
slurp(FILE *f, struct Mempool *pool)
{
#define SLURP_BUF_SIZE	(8*1024*1024)
	size_t bufsize = SLURP_BUF_SIZE + 1;
	char *buf = xrecallocarray(NULL, 0, bufsize, 1);
	size_t left = SLURP_BUF_SIZE;
	ssize_t bytes;
	size_t pos = 0;
	while ((bytes = fread(buf + pos, left, 1, f)) != 0) {
		if (bytes < 0) {
			if (errno == EAGAIN) {
				continue;
			}
			free(buf);
			return NULL;
		}
		left -= bytes;
		pos += bytes;
		if (left == 0) {
			size_t oldsize = bufsize;
			bufsize += SLURP_BUF_SIZE;
			left = SLURP_BUF_SIZE;
			buf = xrecallocarray(buf, oldsize, bufsize, 1);
		}
	}

	return mempool_take(pool, buf);
}

char *
symlink_read(int dir, const char *path, struct Mempool *pool)
{
	char buf[PATH_MAX];
	ssize_t len = readlinkat(dir, path, buf, sizeof(buf));
	if (len != -1) {
		return str_ndup(pool, buf, len);
	}
	return NULL;
}

bool
symlink_update(int dir, const char *path1, const char *path2, struct Mempool *pool, char **prev)
{
	if (prev != NULL) {
		*prev = NULL;
	}
	while (symlinkat(path1, dir, path2) == -1) {
		if (errno == EEXIST) {
			if (prev != NULL) {
				*prev = symlink_read(dir, path2, pool);
			}
			if (unlinkat(dir, path2, 0) == -1) {
				if (prev != NULL) {
					*prev = NULL;
				}
				return false;
			}
		} else {
			if (prev != NULL) {
				*prev = NULL;
			}
			return false;
		}
	}

	return true;
}
