/**
 * @brief hexify - Convert binary to hex.
 *
 * This is based on the output of xxd.
 *
 * @copyright
 * This file is part of ToaruOS and is released under the terms
 * of the NCSA / University of Illinois License - see LICENSE.md
 * Copyright (C) 2018 K. Lange
 */
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>

void print_line(unsigned char * buf, unsigned int width, unsigned int sizer, unsigned int offset) {
	fprintf(stdout, "%08x: ", sizer);
	for (unsigned int i = 0; i < width; ) {
		if (i >= offset) {
			fprintf(stdout, "  ");
		} else {
			fprintf(stdout, "%02x", buf[i]);
		}
		i++;
		if (i == width) break; /* in case of odd width */
		if (i >= offset) {
			fprintf(stdout, "   ");
		} else {
			fprintf(stdout, "%02x ", buf[i]);
		}
		i++;
	}
	fprintf(stdout, " ");
	for (unsigned int i = 0; i < width; i++) {
		if (i >= offset) {
			fprintf(stdout, " ");
		} else {
			if (isprint(buf[i])) {
				fprintf(stdout, "%c", buf[i]);
			} else {
				fprintf(stdout, ".");
			}
		}
	}
	fprintf(stdout, "\n");
}

static int stoih(int w, char c[w], unsigned int *out) {
	*out = 0;
	for (int i = 0; i < w; ++i) {
		(*out) <<= 4;
		if (c[i] >= '0' && c[i] <= '9') {
			*out |= (c[i] - '0');
		} else if (c[i] >= 'A' && c[i] <= 'F') {
			*out |= (c[i] - 'A' + 0xA);
		} else if (c[i] >= 'a' && c[i] <= 'f') {
			*out |= (c[i] - 'a' + 0xA);
		} else {
			*out = 0;
			return 1;
		}
	}

	return 0;
}

int main(int argc, char * argv[]) {
	unsigned int width = 16; /* TODO make configurable */
	int opt;
	int direction = 0;

	while ((opt = getopt(argc, argv, "?w:d")) != -1) {
		switch (opt) {
			default:
			case '?':
				fprintf(stderr, "%s: convert to/from hexadecimal dump\n", argv[0]);
				return 1;
			case 'w':
				width = atoi(optarg);
				break;
			case 'd':
				direction = 1;
				break;
		}
	}

	FILE * f;
	char * name;

	if (optind < argc) {
		f = fopen(argv[optind], "r");
		name = argv[optind];
		if (!f) {
			fprintf(stderr, "%s: %s: %s\n", argv[0], argv[optind], strerror(errno));
			return 1;
		}
	} else {
		name = "[stdin]";
		f = stdin;
	}

	if (direction == 0) {
		/* Convert to hexadecimal */

		unsigned int sizer = 0;
		unsigned int offset = 0;
		unsigned char buf[width];
		while (!feof(f)) {
			unsigned int r = fread(buf+offset, 1, width-offset, f);
			offset += r;

			if (offset == width) {
				print_line(buf, width, sizer, offset);
				offset = 0;
				sizer += width;
			}
		}

		if (offset != 0) {
			print_line(buf, width, sizer, offset);
		}

	} else {
		/* Convert from hexify's output format */
		unsigned int eoffset = 0;
		unsigned int lineno = 1;
		while (!feof(f)) {
			/* Read offset */
			char offset_bytes[8];
			fread(&offset_bytes, 1, 8, f);

			/* Convert offset for verification */
			unsigned int offset;
			if (stoih(8, offset_bytes, &offset)) {
				fprintf(stderr, "%s: %s: syntax error (bad offset) on line %d\n", argv[0], name, lineno);
				fprintf(stderr, "offset bytes: %8s\n", offset_bytes);
				return 1;
			}

			if (offset != eoffset) {
				fprintf(stderr, "%s: %s: offset mismatch on line %d\n", argv[0], name, lineno);
				fprintf(stderr, "expected 0x%x, got 0x%x\n", offset, eoffset);
				return 1;
			}

			/* Read ": " */
			char tmp[2];
			fread(&tmp, 1, 2, f);

			if (tmp[0] != ':' || tmp[1] != ' ') {
				fprintf(stderr, "%s: %s: syntax error (unexpected characters after offset) on line %d\n", argv[0], name, lineno);
				return 1;
			}

			/* Read [width] characters */
			for (unsigned int i = 0; i < width; ) {
				unsigned int byte = 0;
				for (unsigned int j = 0; i < width && j < 2; ++j, ++i) {
					fread(&tmp, 1, 2, f);
					if (tmp[0] == ' ' && tmp[1] == ' ') {
						/* done; return */
						return 0;
					}
					if (stoih(2, tmp, &byte)) {
						fprintf(stderr, "%s: %s: syntax error (bad byte) on line %d\n", argv[0], name, lineno);
						fprintf(stderr, "byte bytes: %2s\n", tmp);
						return 1;
					}
					fwrite(&byte, 1, 1, stdout);
				}
				/* Read space */
				fread(&tmp, 1, 1, f);
				if (tmp[0] != ' ') {
					fprintf(stderr, "%s: %s: syntax error (unexpected characters after byte) on line %d\n", argv[0], name, lineno);
					fprintf(stderr, "unexpected character: %c\n", tmp[0]);
					return 1;
				}
			}

			fread(&tmp, 1, 1, f);
			if (tmp[0] != ' ') {
				fprintf(stderr, "%s: %s: syntax error (unexpected characters after bytes) on line %d\n", argv[0], name, lineno);
			}

			/* Read correct number of characters, plus line feed */
			char tmp2[width+2];
			fread(&tmp2, 1, width+1, f);
			tmp2[width+1] = '\0';
			if (tmp2[width] != '\n') {
				fprintf(stderr, "%s: %s: syntax error: expected end of line, got garbage on line %d\n", argv[0], name, lineno);
				fprintf(stderr, "eol data: %s\n", tmp2);
			}

			lineno++;
			eoffset += width;
		}
	}
	return 0;
}
