#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

#if !__APPLE__
/*
 * Appends src to string dst of size dsize (unlike strncat, dsize is the
 * full size of dst, not space left).  At most dsize-1 characters
 * will be copied.  Always NUL terminates (unless dsize <= strlen(dst)).
 * Returns strlen(src) + MIN(dsize, strlen(initial dst)).
 * If retval >= dsize, truncation occurred.
 */
size_t
strlcat(char *dst, const char *src, size_t dsize)
{
	const char *odst = dst;
	const char *osrc = src;
	size_t n = dsize;
	size_t dlen;

	/* Find the end of dst and adjust bytes left but don't go past end. */
	while (n-- != 0 && *dst != '\0')
		dst++;
	dlen = dst - odst;
	n = dsize - dlen;

	if (n-- == 0)
		return(dlen + strlen(src));
	while (*src != '\0') {
		if (n != 0) {
			*dst++ = *src;
			n--;
		}
		src++;
	}
	*dst = '\0';

	return(dlen + (src - osrc));	/* count does not include NUL */
}
#endif /* !__APPLE__ */

#define MAXSIZE 64000
#define MAXINDENTSIZE 80
#define MAXARGSIZE 20

char *argv0;

int
efgetc(FILE *fp)
{
	int c;

	c = fgetc(fp);

	if (c == EOF &&
		ferror(fp) != 0) {
			perror(argv0);
			exit(1);
	}

	return c;
}

int
efseek(FILE *fp, long offset, int whence)
{
	int s;

	s = fseek(fp, offset, whence);

	if (s == -1) {
		perror(argv0);
		exit(1);
	}

	return s;
}

int
eungetc(int c, FILE *fp)
{
	int uc;

	uc = ungetc(c, fp);

	if (uc == EOF) {
		perror(argv0);
		exit(1);
	}

	return uc;
}

size_t
estrlcat(char *dst, const char *src, size_t dstsize)
{
	size_t n;

	n = strlcat(dst, src, dstsize);

	if (n >= dstsize) {
		fprintf(stderr, "%s: Buffer overflow\n", argv0);
		exit(1);
	}

	return n;
}

int
eclose(int d)
{
	int s;

	s = close(d);

	if (s == -1) {
		perror(argv0);
		exit(1);
	}

	return s;
}

char *
strappend(char *s, char c)
{
	size_t len;

	len = strlen(s);

	s[len] = c;
	s[len + 1] = '\0';

	return s ;
}

int
shebang(FILE *fp)
{
	int c;

	c = efgetc(fp);

	if (c != '#') {
		eungetc(c, fp);
		return 0;
	}

	c = efgetc(fp);

	if (c != '!') {
		eungetc(c, fp);
		eungetc('#', fp);
		return 0;
	}

	c = efgetc(fp);

	if (c == '\n') {
		eungetc(c, fp);
		eungetc('!', fp);
		eungetc('#', fp);
		return 0;
	}

	for (;;) {
		c = efgetc(fp);
		if (c == '\n')
			break;
	}

	return 1;
}

int
token(FILE *fp, int consume)
{
	int c;

	c = efgetc(fp);

	if (c != '#') {
		eungetc(c, fp);
		return 0;
	}

	c = efgetc(fp);
	if (c != '!') {
		eungetc(c, fp);
		eungetc('#', fp);
		return 0;
	};

	c = efgetc(fp);

	if (c != '\n') {
		eungetc(c, fp);
		eungetc('!', fp);
		eungetc('#', fp);
		return 0;
	}

	if (consume == 0) {
		eungetc(c, fp);
		eungetc('!', fp);
		eungetc('#', fp);
	}

	return 1;
}

size_t
output(char *s)
{
	return estrlcat(s, "echo \"", MAXSIZE);
}

size_t
outputend(char *s)
{
	return estrlcat(s, "\"\n", MAXSIZE);
}

int main(int argc, char **argv)
{
	char s[MAXSIZE] = "", indent[MAXINDENTSIZE] = "",
		*eargv[MAXARGSIZE] = {"sh", "-c", s, "sh", NULL};
	FILE *fp;
	int size, c, i, fd[2], o = 0, status = 0, d = 0;


	argv0 = argv[0];
	if (argc == 1) {
		fprintf(stderr, "usage: %s file [args ...]\n", argv[0]);
		return 1;

	}

	if (strcmp(argv[1], "-d") == 0) {
		if (argc == 2) {
			fprintf(stderr, "usage: %s file [args ...]\n", argv[0]);
			return 1;

		}
		d = 1;
		fp = fopen(argv[2], "r");
	} else {
		fp = fopen(argv[1], "r");
	}


	if (!fp) {
		fprintf(stderr, "%s: %s: %s\n",
			argv[0], d ? argv[2] : argv[1], strerror(errno));
		return 1;
	}

	efseek (fp, 0, SEEK_END);
	size = ftell(fp);

	if(size == 0) return 0;

	efseek(fp, -1, SEEK_END);
	c = efgetc(fp);

	if (c != '\n') {
		fprintf(stderr, "%s: %s: No newline at end of file\n",
			argv[0], d ? argv[2] : argv[1]);
		exit(1);
	}

	efseek(fp, 0, SEEK_SET);

	shebang(fp);

	if (!token(fp, 1)) {
		output(s);
		o = 1;
	}

	for(;;) {
		if (token(fp, 1)) {
			if (!o) {
				c = efgetc(fp);
				if (c == EOF)
					goto done;
				eungetc(c, fp);
				output(s);
			} else
				outputend(s);
			o = !o;
		}

		c = efgetc(fp);
		if (c == EOF)
			break;

		if (c == '\n') {
			indent[0] = 0;
			c = efgetc(fp);
			if (c == EOF)
				break;

			for (;;) {
				if (c != ' ' && c != '\t')
					break;
				strappend(indent, c);
				c = efgetc(fp);
				if (c == EOF)
					break;
			}

			eungetc(c, fp);

			if (token(fp, 0) && o)
				continue;

			strappend(s, '\n');
			estrlcat(s, indent, MAXSIZE);
			continue;
		}

		strappend(s, c);
	}
	if (o) {
		outputend(s);
		o = 0;
	}
	done:

	if(d) {
		puts(s);
		return 0;
	}

	if (pipe(fd) == -1) {
		perror(argv0);
		return 1;
	}

	switch (fork()) {
		case -1:
			perror(argv0);
			return 1;
		case 0:
			eclose(fd[1]);
			eclose(fd[0]);
			for (i = 2; i < argc; i++)
				eargv[i+2] = argv[i];

			if (execv("/bin/sh", eargv) == -1) {
				perror(argv0);
				return 1;
			}
		break;
		default:
			eclose(fd[1]);
			eclose(fd[0]);
			if (wait(&status) == -1) {
				perror(argv0);
				return 1;
			}
	}

	return status;
}
