/* See LICENSE file for copyright and license details. */
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include "utf.h"
#include "util.h"

static int     aflag      = 0;
static size_t *tablist    = NULL;
static size_t  tablistlen = 8;

static size_t
parselist(const char *s)
{
	size_t i;
	char  *p, *tmp;

	tmp = estrdup(s);
	for (i = 0; (p = strsep(&tmp, " ,")); i++) {
		if (*p == '\0')
			eprintf("empty field in tablist\n");
		tablist = ereallocarray(tablist, i + 1, sizeof(*tablist));
		tablist[i] = estrtonum(p, 1, MIN(LLONG_MAX, SIZE_MAX));
		if (i > 0 && tablist[i - 1] >= tablist[i])
			eprintf("tablist must be ascending\n");
	}
	tablist = ereallocarray(tablist, i + 1, sizeof(*tablist));

	return i;
}

static void
unexpandspan(size_t last, size_t col)
{
	size_t off, i, j;
	Rune r;

	if (tablistlen == 1) {
		i = 0;
		off = last % tablist[i];

		if ((col - last) + off >= tablist[i] && last < col)
			last -= off;

		r = '\t';
		for (; last + tablist[i] <= col; last += tablist[i])
			efputrune(&r, stdout, "<stdout>");
		r = ' ';
		for (; last < col; last++)
			efputrune(&r, stdout, "<stdout>");
	} else {
		for (i = 0; i < tablistlen; i++)
			if (col < tablist[i])
				break;
		for (j = 0; j < tablistlen; j++)
			if (last < tablist[j])
				break;
		r = '\t';
		for (; j < i; j++) {
			efputrune(&r, stdout, "<stdout>");
			last = tablist[j];
		}
		r = ' ';
		for (; last < col; last++)
			efputrune(&r, stdout, "<stdout>");
	}
}

static void
unexpand(const char *file, FILE *fp)
{
	Rune r;
	size_t last = 0, col = 0, i;
	int bol = 1;

	while (efgetrune(&r, fp, file)) {
		switch (r) {
		case ' ':
			if (!bol && !aflag)
				last++;
			col++;
			break;
		case '\t':
			if (tablistlen == 1) {
				if (!bol && !aflag)
					last += tablist[0] - col % tablist[0];
				col += tablist[0] - col % tablist[0];
			} else {
				for (i = 0; i < tablistlen; i++)
					if (col < tablist[i])
						break;
				if (!bol && !aflag)
					last = tablist[i];
				col = tablist[i];
			}
			break;
		case '\b':
			if (bol || aflag)
				unexpandspan(last, col);
			col -= (col > 0);
			last = col;
			bol = 0;
			break;
		case '\n':
			if (bol || aflag)
				unexpandspan(last, col);
			last = col = 0;
			bol = 1;
			break;
		default:
			if (bol || aflag)
				unexpandspan(last, col);
			last = ++col;
			bol = 0;
			break;
		}
		if ((r != ' ' && r != '\t') || (!aflag && !bol))
			efputrune(&r, stdout, "<stdout>");
	}
	if (last < col && (bol || aflag))
		unexpandspan(last, col);
}

static void
usage(void)
{
	eprintf("usage: %s [-a] [-t tablist] [file ...]\n", argv0);
}

int
main(int argc, char *argv[])
{
	FILE *fp;
	int ret = 0;
	char *tl = "8";

	ARGBEGIN {
	case 't':
		tl = EARGF(usage());
		if (!*tl)
			eprintf("tablist cannot be empty\n");
		/* Fallthrough: -t implies -a */
	case 'a':
		aflag = 1;
		break;
	default:
		usage();
	} ARGEND

	tablistlen = parselist(tl);

	if (!argc) {
		unexpand("<stdin>", stdin);
	} else {
		for (; *argv; argc--, argv++) {
			if (!strcmp(*argv, "-")) {
				*argv = "<stdin>";
				fp = stdin;
			} else if (!(fp = fopen(*argv, "r"))) {
				weprintf("fopen %s:", *argv);
				ret = 1;
				continue;
			}
			unexpand(*argv, fp);
			if (fp != stdin && fshut(fp, *argv))
				ret = 1;
		}
	}

	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>");

	return ret;
}
