/*
 * Copyright (c) 2020 Neels Hofmeyr <neels@hofmeyr.de>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#define DEBUG 0

#if DEBUG
#include <stdio.h>
#include <unistd.h>
#define print(args...) fprintf(stderr, ##args)
#define debug print
#define debug_dump dump
#define debug_dump_atom dump_atom
#define debug_dump_atoms dump_atoms

static inline void
print_atom_byte(unsigned char c) {
	if (c == '\r')
		print("\\r");
	else if (c == '\n')
		print("\\n");
	else if ((c < 32 || c >= 127) && (c != '\t'))
		print("\\x%02x", c);
	else
		print("%c", c);
}

static inline void
dump_atom(const struct diff_data *left, const struct diff_data *right,
	  const struct diff_atom *atom)
{
	if (!atom) {
		print("NULL atom\n");
		return;
	}
	if (left)
		print(" %3u '", diff_atom_root_idx(left, atom));

	if (atom->at == NULL) {
		off_t remain = atom->len;
		if (fseek(atom->root->f, atom->pos, SEEK_SET) == -1)
			abort(); /* cannot return error */
		while (remain > 0) {
			char buf[16];
			size_t r;
			int i;
			r = fread(buf, 1, MIN(remain, sizeof(buf)),
			    atom->root->f);
			if (r == 0)
				break;
			remain -= r;
			for (i = 0; i < r; i++)
				print_atom_byte(buf[i]);
		}
	} else {
		const char *s;
		for (s = atom->at; s < (const char*)(atom->at + atom->len); s++)
			print_atom_byte(*s);
	}
	print("'\n");
}

static inline void
dump_atoms(const struct diff_data *d, struct diff_atom *atom,
	   unsigned int count)
{
	if (count > 42) {
		dump_atoms(d, atom, 20);
		print("[%u lines skipped]\n", count - 20 - 20);
		dump_atoms(d, atom + count - 20, 20);
		return;
	} else {
		struct diff_atom *i;
		foreach_diff_atom(i, atom, count) {
			dump_atom(d, NULL, i);
		}
	}
}

static inline void
dump(struct diff_data *d)
{
	dump_atoms(d, d->atoms.head, d->atoms.len);
}

/* kd is a quadratic space myers matrix from the original Myers algorithm.
 * kd_forward and kd_backward are linear slices of a myers matrix from the Myers
 * Divide algorithm.
 */
static inline void
dump_myers_graph(const struct diff_data *l, const struct diff_data *r,
		 int *kd, int *kd_forward, int kd_forward_d,
		 int *kd_backward, int kd_backward_d)
{
	#define COLOR_YELLOW "\033[1;33m"
	#define COLOR_GREEN "\033[1;32m"
	#define COLOR_BLUE "\033[1;34m"
	#define COLOR_RED "\033[1;31m"
	#define COLOR_END "\033[0;m"
	int x;
	int y;
	print("   ");
	for (x = 0; x <= l->atoms.len; x++)
		print("%2d", x % 100);
	print("\n");

	for (y = 0; y <= r->atoms.len; y++) {
		print("%3d ", y);
		for (x = 0; x <= l->atoms.len; x++) {

			/* print d advancements from kd, if any. */
			char label = 'o';
			char *color = NULL;
			if (kd) {
				int max = l->atoms.len + r->atoms.len;
				size_t kd_len = max + 1 + max;
				int *kd_pos = kd;
				int di;
#define xk_to_y(X, K) ((X) - (K))
				for (di = 0; di < max; di++) {
					int ki;
					for (ki = di; ki >= -di; ki -= 2) {
						if (x != kd_pos[ki]
						    || y != xk_to_y(x, ki))
							continue;
						label = '0' + (di % 10);
						color = COLOR_YELLOW;
						break;
					}
					if (label != 'o')
						break;
					kd_pos += kd_len;
				}
			}
			if (kd_forward && kd_forward_d >= 0) {
#define xc_to_y(X, C, DELTA) ((X) - (C) + (DELTA))
				int ki;
				for (ki = kd_forward_d;
				     ki >= -kd_forward_d;
				     ki -= 2) {
					if (x != kd_forward[ki])
						continue;
					if (y != xk_to_y(x, ki))
						continue;
					label = 'F';
					color = COLOR_GREEN;
					break;
				}
			}
			if (kd_backward && kd_backward_d >= 0) {
				int delta = (int)r->atoms.len
					    - (int)l->atoms.len;
				int ki;
				for (ki = kd_backward_d;
				     ki >= -kd_backward_d;
				     ki -= 2) {
					if (x != kd_backward[ki])
						continue;
					if (y != xc_to_y(x, ki, delta))
						continue;
					if (label == 'o') {
						label = 'B';
						color = COLOR_BLUE;
					} else {
						label = 'X';
						color = COLOR_RED;
					}
					break;
				}
			}
			if (color)
				print("%s", color);
			print("%c", label);
			if (color)
				print("%s", COLOR_END);
			if (x < l->atoms.len)
				print("-");
		}
		print("\n");
		if (y == r->atoms.len)
			break;

		print("    ");
		for (x = 0; x < l->atoms.len; x++) {
			bool same;
			diff_atom_same(&same, &l->atoms.head[x],
			    &r->atoms.head[y]);
			if (same)
				print("|\\");
			else
				print("| ");
		}
		print("|\n");
	}
}

static inline void
debug_dump_myers_graph(const struct diff_data *l, const struct diff_data *r,
		       int *kd, int *kd_forward, int kd_forward_d,
		       int *kd_backward, int kd_backward_d)
{
	if (l->atoms.len > 99 || r->atoms.len > 99)
		return;
	dump_myers_graph(l, r, kd, kd_forward, kd_forward_d,
			 kd_backward, kd_backward_d);
}

#else
#define debug(args...)
#define debug_dump(args...)
#define debug_dump_atom(args...)
#define debug_dump_atoms(args...)
#define debug_dump_myers_graph(args...)
#endif