#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <locale.h>
#include <wchar.h>
#include <errno.h>

#include "atables.h"
#include "wtable.h"

const char **table = ata2utf;

/* XXX: hard-coded ANSI/vt100 escape sequences. would be
   better but more complex to use terminfo to support any ol'
   terminal... */
/* 20250316 bkw: used to use this:
const char *inverse_off = "\x1b[0m";
   ...but 27m is widely supported, and makes it easier for
   colorize-amsb to postprocess a8cat's output. */
const char *inverse_on  = "\x1b[7m";
const char *inverse_off = "\x1b[27m";

int verbose = 0;
int underline = 0, reverse = 0, textmode = 0, ics = 0;
int magazine = 0, stripinv = 0;

const char *self;

void set_self(const char *argv0) {
   char *p;

   self = argv0;
   p = strrchr(self, '/');
   if(p) self = p + 1;
}

void die(const char *msg) {
   fprintf(stderr, "%s: %s\n", self, msg);
   exit(1);
}

void print_help(void) {
	printf("Usage: %s [-i] [-u] [file ...]\n", self);
}

FILE *open_input(const char *file) {
	FILE *input;

	if(file[0] == '-' && file[1] == 0) {
		if(verbose)
			fprintf(stderr, "%s: reading from standard input.\n", self);
		if(freopen(NULL, "rb", stdin)) {
			input = stdin;
		} else {
			perror("(standard input)");
			return NULL;
		}
	} else if(!(input = fopen(file, "rb"))) {
		perror(file);
		return NULL;
	}

	if(verbose)
		fprintf(stderr, "%s: reading from file '%s'.\n", self, file);

	return input;
}

int handle_escape_seq(int inv, FILE *input, const char *file, int line) {
	int count, c;
	char buf[5] = { 0x1b, 0, 0, 0, 0 };

	for(count = 1; count < 4; count++) {
		c = fgetwc(input);
		if(c == WEOF) break;
		buf[count] = c; /* FIXME: might be a wide char! */
	}

	if(strcmp(inverse_on, buf) == 0) {
		return 0x80;
	} else if(strcmp(inverse_off, buf) == 0) {
		return 0;
	} else {
		fprintf(stderr, "%s: warning: %s:%d: unrecognized ANSI escape sequence.\n", self, file, line);
		fputs(buf, stdout);
		return inv;
	}
}

int a8revcat(const char *file) {
	FILE *input;
	int c, d, inv = 0, line = 1;

	if( !(input = open_input(file)) )
		return 1;

	while( (c = fgetwc(input)) != WEOF ) {
		if(c == 0x1b) {
			inv = handle_escape_seq(inv, input, file, line);
		} else if(c == '\r') {
			continue; /* swallow carriage returns */
		} else if(c == '\n') {
			putchar(0x9b);
			line++;
		} else if(c == '\t') {
			putchar(0x7f);
		} else if(c == '\b' || c == 0x7f) {
			putchar(0x7e);
		} else if(c == '\a') {
			putchar(0xfd);
		} else if(c < 0x80) {
			putchar(c | inv);
		} else {
			d = wchar2atascii(c, ics);
			if(d == -1) {
				fprintf(stderr, "%s: warning: %s:%d: unrecognized Unicode character %04x.\n", self, file, line, c);
			} else {
				putchar(d | inv);
			}
		}
	}

	if(verbose)
		fprintf(stderr, "%s: %s: converted %d lines, closing file.\n", self, file, line - 1);

	fclose(input);
	return 0;
}

void inverse(int onoff) {
	fputs((onoff ? inverse_on : inverse_off ), stdout);
}

int a8cat(const char *file) {
	FILE *input;
	int c, inv = 0, line = 1;

	if( !(input = open_input(file)) )
		return 1;

	while( (c = fgetc(input)) != EOF ) {
		if(c == 0x9b) {
			putchar('\n');
			line++;
			continue;
		}

		if(stripinv) c &= 0x7f;

		if(textmode) {
			switch(c) {
				case 0x7f: /* tab */
					putchar('\t');
					continue;
				case 0xfd: /* bell */
					putchar('\a');
					continue;
				case 0x7e: /* backspace */
					putchar('\b');
					continue;
				default: break;
			}
		}

		if(!underline) {
			/* strings of inverse chars only get one "inverse on" ANSI
			   sequence, and one "inverse off" afterwards. */
			if(c & 0x80) {
				if(!inv) {
					inv = 1;
					if(magazine)
						fputs("{inv}", stdout);
					else
						inverse(1);
				}
			} else {
				if(inv) {
					inv = 0;
					if(magazine)
						fputs("{norm}", stdout);
					else
						inverse(0);
				}
			}
		}

		if(magazine) {
			/* special cases: control codes with bit 7 set can't go
			   in the table since it's only got 128 entries. */
			switch(c) {
				case 0x9c:
					fputs("{del-line}", stdout); continue;
				case 0x9d:
					fputs("{ins-line}", stdout); continue;
				case 0x9e:
					fputs("{clr-tab}", stdout); continue;
				case 0x9f:
					fputs("{set-tab}", stdout); continue;
				case 0xfd:
					fputs("{bell}", stdout); continue;
				case 0xfe:
					fputs("{del-char}", stdout); continue;
				case 0xff:
					fputs("{ins-char}", stdout); continue;
			}
		}

		fputs(table[c & 0x7f], stdout);

		if(underline && (c & 0x80)) {
			putchar('\b');
			putchar('_');
		}
	}

	/* gotta turn off inverse, so if there's another file after this one,
	   it doesn't start out being printed in inverse. */
	if(inv && !underline) {
		if(magazine)
			fputs("{norm}", stdout);
		else
			inverse(0);
	}

	if(verbose)
		fprintf(stderr, "%s: %s: converted %d lines, closing file.\n", self, file, line - 1);

	fclose(input);
	return 0;
}

int main(int argc, char **argv) {
	int opt, result = 0;

	set_self(argv[0]);

	if(argc > 1 && strcmp(argv[1], "--help") == 0) {
		print_help();
		exit(0);
	}

	if(argc > 1 && strcmp(argv[1], "--version") == 0) {
		printf("%s %s\n", self, VERSION);
		exit(0);
	}

	while( (opt = getopt(argc, argv, "ihurtmsv")) != -1) {
		switch(opt) {
			case 'i': table = ics2utf; ics = 1; break;
			case 'h': print_help(); exit(0); break;
			case 'u': underline = 1; break;
			case 'r': reverse = 1; break;
			case 't': textmode = 1; break;
			case 'm': table = ata2mag; magazine = 1; break;
			case 's': stripinv = 1; break;
			case 'v': verbose = 1; break;
			default: print_help(); exit(1); break;
		}
	}

	if(reverse) {
		if(isatty(fileno(stdout)))
			die("Refusing to write ATASCII to a terminal.");

		if(underline || textmode || stripinv || magazine) {
			die("-t, -u, -m, -s options don't make sense with -r.\n");
		}
		/* the language_country part of the locale doesn't matter,
         since we aren't doing localization. the encoding *has*
         to be UTF-8. */
		setlocale(LC_CTYPE, "en_US.UTF-8");
	}

	if(magazine) {
		if(ics || stripinv || underline) {
			die("-i, -s, -u options don't make sense with -m.\n");
		}
	}

	if(optind >= argc) {
		result = (reverse ? a8revcat("-") : a8cat("-"));
	} else {
		while(optind < argc) {
			result += (reverse ? a8revcat(argv[optind]) : a8cat(argv[optind]));
			optind++;
		}
	}

	exit(result);
}