/* radare - LGPL - Copyright 2013-2017 - pancake, sghctoma */

#include <r_cons.h>

R_API void r_cons_pal_free () {
	int i;
	RCons *cons = r_cons_singleton ();
	for (i = 0; i < R_CONS_PALETTE_LIST_SIZE; i++) {
		if (cons->pal.list[i]) R_FREE (cons->pal.list[i]);
	}
}

R_API void r_cons_pal_init (const char *foo) {
	RCons *cons = r_cons_singleton ();
	memset (&cons->pal, 0, sizeof (cons->pal));
	cons->pal.rainbow = NULL;
	cons->pal.rainbow_sz = 0;
	cons->pal.b0x00 = Color_GREEN;
	cons->pal.b0x7f = Color_CYAN;
	cons->pal.b0xff = Color_RED;
	cons->pal.args = Color_YELLOW;
	cons->pal.bin = Color_CYAN;
	cons->pal.btext = Color_YELLOW;
	cons->pal.call = Color_BGREEN;
	cons->pal.cjmp = Color_GREEN;
	cons->pal.cmp = Color_CYAN;
	cons->pal.comment = Color_RED;
	cons->pal.usercomment = Color_WHITE;
	cons->pal.creg = Color_CYAN;
	cons->pal.flag = Color_CYAN;
	cons->pal.fline = Color_CYAN;
	cons->pal.floc = Color_CYAN;
	cons->pal.flow = Color_CYAN;
	cons->pal.flow2 = Color_BLUE;
	cons->pal.fname = Color_RED;
	cons->pal.help = Color_GREEN;
	cons->pal.input = Color_WHITE;
	cons->pal.invalid = Color_BRED;
	cons->pal.jmp = Color_GREEN;
	cons->pal.label = Color_CYAN;
	cons->pal.math = Color_YELLOW;
	cons->pal.mov = Color_WHITE;
	cons->pal.nop = Color_BLUE;
	cons->pal.num = Color_YELLOW;
	cons->pal.offset = Color_GREEN;
	cons->pal.other = Color_WHITE;
	cons->pal.pop = Color_BMAGENTA;
	cons->pal.prompt = Color_YELLOW;
	cons->pal.push = Color_MAGENTA;
	cons->pal.crypto = Color_BGBLUE;
	cons->pal.reg = Color_CYAN;
	cons->pal.reset = Color_RESET;
	cons->pal.ret = Color_RED;
	cons->pal.swi = Color_MAGENTA;
	cons->pal.trap = Color_BRED;

	cons->pal.ai_read = Color_GREEN;
	cons->pal.ai_write = Color_BLUE;
	cons->pal.ai_exec = Color_RED;
	cons->pal.ai_seq = Color_MAGENTA;
	cons->pal.ai_ascii = Color_YELLOW;

	cons->pal.gui_cflow = Color_YELLOW;
	cons->pal.gui_dataoffset = Color_YELLOW;
	cons->pal.gui_background = Color_BLACK;
	cons->pal.gui_alt_background = Color_WHITE;
	cons->pal.gui_border = Color_BLACK;
	cons->pal.highlight = Color_BGRED;

	cons->pal.graph_box = Color_RESET;
	cons->pal.graph_box2 = Color_BLUE;
	cons->pal.graph_box3 = Color_MAGENTA;
	cons->pal.graph_box4 = Color_GRAY;
	cons->pal.graph_true = Color_GREEN;
	cons->pal.graph_false = Color_RED;
	cons->pal.graph_trufae = Color_BLUE; // single jump
	cons->pal.graph_traced = Color_YELLOW;
	cons->pal.graph_current = Color_BLUE;

	r_cons_pal_free ();
	cons->pal.list[0] = strdup (Color_RED);
	cons->pal.list[1] = strdup (Color_YELLOW);
	cons->pal.list[2] = strdup (Color_BGREEN);
	cons->pal.list[3] = strdup (Color_CYAN);
	cons->pal.list[4] = strdup (Color_MAGENTA);
	cons->pal.list[5] = strdup (Color_GRAY);
	cons->pal.list[6] = strdup (Color_BLUE);
	cons->pal.list[7] = strdup (Color_GREEN);

	r_cons_pal_update_event();
}

struct {
	const char *name;
	const char *code;
	const char *bgcode;
} colors[] = {
	{ "black",    Color_BLACK,    Color_BGBLACK },
	{ "red",      Color_RED,      Color_BGRED },
	{ "bred",     Color_BRED,     Color_BGRED },
	{ "white",    Color_WHITE,    Color_BGWHITE },
	{ "green",    Color_GREEN,    Color_BGGREEN },
	{ "bgreen",   Color_BGREEN,   Color_BGGREEN },
	{ "magenta",  Color_MAGENTA,  Color_BGMAGENTA },
	{ "bmagenta", Color_BMAGENTA, Color_BGMAGENTA },
	{ "yellow",   Color_YELLOW,   Color_BGYELLOW },
	{ "byellow",  Color_BYELLOW,  Color_BGBYELLOW },
	{ "cyan",     Color_CYAN,     Color_BGCYAN },
	{ "bcyan",    Color_BCYAN,    Color_BGCYAN },
	{ "blue",     Color_BLUE,     Color_BGBLUE },
	{ "bblue",    Color_BBLUE,    Color_BGBLUE },
	{ "gray",     Color_GRAY,     Color_BGGRAY },
	{ "bgray",    Color_BGRAY,    Color_BGGRAY },
	{ "none",     Color_RESET,    Color_RESET },
	{ NULL, NULL, NULL }
};

static inline ut8 rgbnum (const char ch1, const char ch2) {
	ut8 r = 0, r2 = 0;
	r_hex_to_byte (&r, ch1);
	r_hex_to_byte (&r2, ch2);
	return r << 4 | r2;
}

R_API void r_cons_pal_random () {
	RCons *cons = r_cons_singleton ();
	ut8 r, g, b;
	char val[32];
	const char *k;
	int i;
	for (i = 0; ; i++) {
		k = r_cons_pal_get_i (i);
		if (!k) break;
		if (cons->truecolor > 0) {
			r = r_num_rand (0xff);
			g = r_num_rand (0xff);
			b = r_num_rand (0xff);
			sprintf (val, "rgb:%02x%02x%02x", r, g, b);
			r_cons_pal_set (k, val);
		} else {
			char *s = r_cons_color_random_string (0);
			if (s) {
				r_cons_pal_set (k, s);
				free (s);
			} else {
				r_cons_pal_set (k, "red");
			}
		}
	}
	for (i = 0; i < R_CONS_PALETTE_LIST_SIZE; i++) {
		if (cons->pal.list[i]) R_FREE (cons->pal.list[i]);
		cons->pal.list[i] = r_cons_color_random (0);
	}
	r_cons_pal_update_event();
}

R_API char *r_cons_pal_parse (const char *str) {
	int i;
	ut8 r, g, b;
	char out[128];
	char *s = strdup (str);
	if (!s) {
		return NULL;
	}
	char *p = strchr (s + 1, ' ');
	out[0] = 0;
	if (p) *p++ = 0;
	if (!strcmp (str, "random")) {
		free (s);
		return r_cons_color_random (0);
	}
	if (!strncmp (s, "#", 1)) {
		if (strlen (s) == 7) {
#define C(x) (x >> 4)
			int R, G, B;
			sscanf (s, "%02x%02x%02x", &R, &G, &B);
			r_cons_rgb_str (out, C(R), C(G), C(B), 0);
		} else {
			eprintf ("Invalid html color code\n");
		}
	} else if (!strncmp (s, "rgb:", 4)) {
		if (strlen (s) == 7) {
			r = rgbnum (s[4], '0');
			g = rgbnum (s[5], '0');
			b = rgbnum (s[6], '0');
			r_cons_rgb_str (out, r, g, b, 0);
		} else if (strlen (s) == 10) {
			r = rgbnum (s[4], s[5]);
			g = rgbnum (s[6], s[7]);
			b = rgbnum (s[8], s[9]);
			r_cons_rgb_str (out, r, g, b, 0);
		}
	}
	if (p && !strncmp (p, "rgb:", 4)) {
		if (strlen (p) == 7) {
			r = rgbnum (p[4], '0');
			g = rgbnum (p[5], '0');
			b = rgbnum (p[6], '0');
			r_cons_rgb_str (out + strlen (out), r, g, b, 1);
		} else if (strlen (p) == 10) {
			r = rgbnum (p[4], p[5]);
			g = rgbnum (p[6], p[7]);
			b = rgbnum (p[8], p[9]);
			r_cons_rgb_str (out + strlen (out), r, g, b, 1);
		}
	}
	for (i = 0; colors[i].name; i++) {
		if (!strcmp (s, colors[i].name)) {
			strncat (out, colors[i].code,
				sizeof (out) - strlen (out) - 1);
		}
		if (p && !strcmp (p, colors[i].name)) {
			strncat (out, colors[i].bgcode,
				sizeof (out) - strlen (out) - 1);
		}
	}
	free (s);
	return *out ? strdup (out) : NULL;
}

static struct {
	const char *name;
	int off;
} keys[] = {
	{ "comment", r_offsetof (RConsPalette, comment) },
	{ "usrcmt", r_offsetof (RConsPalette, usercomment) },
	{ "args", r_offsetof (RConsPalette, args) },
	{ "fname", r_offsetof (RConsPalette, fname) },
	{ "floc", r_offsetof (RConsPalette, floc) },
	{ "fline", r_offsetof (RConsPalette, fline) },
	{ "flag", r_offsetof (RConsPalette, flag) },
	{ "label", r_offsetof (RConsPalette, label) },
	{ "help", r_offsetof (RConsPalette, help) },
	{ "flow", r_offsetof (RConsPalette, flow) },
	{ "flow2", r_offsetof (RConsPalette, flow2) },
	{ "prompt", r_offsetof (RConsPalette, prompt) },
	{ "offset", r_offsetof (RConsPalette, offset) },
	{ "input", r_offsetof (RConsPalette, input) },
	{ "invalid", r_offsetof (RConsPalette, invalid) },
	{ "other", r_offsetof (RConsPalette, other) },
	{ "b0x00", r_offsetof (RConsPalette, b0x00) },
	{ "b0x7f", r_offsetof (RConsPalette, b0x7f) },
	{ "b0xff", r_offsetof (RConsPalette, b0xff) },
	{ "math", r_offsetof (RConsPalette, math) },
	{ "bin", r_offsetof (RConsPalette, bin) },
	{ "btext", r_offsetof (RConsPalette, btext) },
	{ "push",  r_offsetof (RConsPalette, push) },
	{ "pop", r_offsetof (RConsPalette, pop) },
	{ "crypto", r_offsetof (RConsPalette, crypto) },
	{ "jmp", r_offsetof (RConsPalette, jmp) },
	{ "cjmp", r_offsetof (RConsPalette, cjmp) },
	{ "call", r_offsetof (RConsPalette, call) },
	{ "nop", r_offsetof (RConsPalette, nop) },
	{ "ret", r_offsetof (RConsPalette, ret) },
	{ "trap", r_offsetof (RConsPalette, trap) },
	{ "swi", r_offsetof (RConsPalette, swi) },
	{ "cmp", r_offsetof (RConsPalette, cmp) },
	{ "reg", r_offsetof (RConsPalette, reg) },
	{ "creg", r_offsetof (RConsPalette, creg) },
	{ "num", r_offsetof (RConsPalette, num) },
	{ "mov", r_offsetof (RConsPalette, mov) },

	{ "ai.read", r_offsetof (RConsPalette, ai_read) },
	{ "ai.write", r_offsetof (RConsPalette, ai_write) },
	{ "ai.exec", r_offsetof (RConsPalette, ai_exec) },
	{ "ai.seq", r_offsetof (RConsPalette, ai_seq) },
	{ "ai.ascii", r_offsetof (RConsPalette, ai_ascii) },

	{ "graph.box", r_offsetof (RConsPalette, graph_box) },
	{ "graph.box2", r_offsetof (RConsPalette, graph_box2) },
	{ "graph.box3", r_offsetof (RConsPalette, graph_box3) },
	{ "graph.box4", r_offsetof (RConsPalette, graph_box4) },
	{ "graph.true", r_offsetof (RConsPalette, graph_true) },
	{ "graph.false", r_offsetof (RConsPalette, graph_false) },
	{ "graph.trufae", r_offsetof (RConsPalette, graph_trufae) },
	{ "graph.current", r_offsetof (RConsPalette, graph_current) },
	{ "graph.traced", r_offsetof (RConsPalette, graph_traced) },

	{ "gui.cflow", r_offsetof (RConsPalette, gui_cflow) },
	{ "gui.dataoffset", r_offsetof (RConsPalette, gui_dataoffset) },
	{ "gui.background", r_offsetof (RConsPalette, gui_background) },
	{ "gui.alt_background", r_offsetof (RConsPalette, gui_alt_background) },
	{ "gui.border", r_offsetof (RConsPalette, gui_border) },
	{ "highlight", r_offsetof (RConsPalette, highlight) },
	{ NULL, 0 }
};

static void r_cons_pal_show_gs () {
	int i, n;
	r_cons_print ("\nGreyscale:\n");
	for (i = 0x08, n = 0;  i <= 0xee; i += 0xa) {
		char fg[32], bg[32];

		if (i < 0x76) strcpy (fg, Color_WHITE);
		else strcpy (fg, Color_BLACK);
		r_cons_rgb_str (bg, i, i, i, 1);
		r_cons_printf ("%s%s rgb:%02x%02x%02x "Color_RESET,
			fg, bg, i, i, i);
		if (n++ == 5) {
			n = 0;
			r_cons_newline ();
		}
	}
}

static void r_cons_pal_show_256 () {
	int r, g, b;
	r_cons_print ("\n\nXTerm colors:\n");
	for (r = 0x00; r <= 0xff; r += 0x28) {
		if (r == 0x28) {
			r = 0x5f;
		}
		for (b = 0x00; b <= 0xff; b += 0x28) {
			if (b == 0x28) {
				b = 0x5f;
			}
			for (g = 0x00; g <= 0xff; g += 0x28) {
				char fg[32], bg[32];
				if (g == 0x28) {
					g = 0x5f;
				}
				if ((r <= 0x5f) && (g <= 0x5f)) {
					strcpy (fg, Color_WHITE);
				} else {
					strcpy (fg, Color_BLACK);
				}
				r_cons_rgb_str (bg, r, g, b, 1);
				r_cons_printf ("%s%s rgb:%02x%02x%02x "
					Color_RESET, fg, bg, r, g, b);
				if (g == 0xff) {
					r_cons_newline ();
				}
			}
		}
	}
}

static void r_cons_pal_show_rgb () {
	const int inc = 3;
	int i, j, k, n = 0;
	r_cons_print ("\n\nRGB:\n");
	for (i = n = 0; i <= 0xf; i += inc) {
		for (k = 0; k <= 0xf; k += inc) {
			for (j = 0; j <= 0xf; j += inc) {
				char fg[32], bg[32];
				int r = i * 16;
				int g = j * 16;
				int b = k * 16;
				strcpy (fg, ((i < 6) && (j < 5))
					? Color_WHITE: Color_BLACK);
				r_cons_rgb_str (bg, r, g, b, 1);
				r_cons_printf ("%s%s rgb:%02x%02x%02x "
					Color_RESET, fg, bg, r, g, b);
				//if (n++==7) {
				if (n ++== 5) {
					n = 0;
					r_cons_newline ();
				}
			}
		}
	}
}

R_API void r_cons_pal_show () {
	int i;
	for (i = 0; colors[i].name; i++) {
		r_cons_printf ("%s%s__"Color_RESET" %s\n",
			colors[i].code,
			colors[i].bgcode,
			colors[i].name);
	}
	switch (r_cons_singleton ()->truecolor) {
	case 1: // 256 color palette
		r_cons_pal_show_gs ();
		r_cons_pal_show_256 ();
		break;
	case 2: // 16M
		r_cons_pal_show_gs ();
		r_cons_pal_show_rgb ();
		break;
	}
}

R_API const char *r_cons_pal_get_color (int n) {
	RConsPalette *pal = & (r_cons_singleton ()->pal);
	ut8 *p = (ut8*)pal;
	int i;
	for (i = 0; keys[i].name; i++) {
		if (i >= n) {
			const char **color = (const char**) (p + keys[i].off);
			color = (const char**)*color;
			return (const char *)color;
		}
	}
	return NULL;
}

R_API void r_cons_pal_list (int rad, const char *arg) {
	RConsPalette *pal = & (r_cons_singleton ()->pal);
	ut8 r, g, b, *p = (ut8*)pal;
	char *name, **color, rgbstr[32];
	const char *hasnext;
	int i;
	if (rad == 'j') {
		r_cons_print ("{");
	}
	for (i = 0; keys[i].name; i++) {
		color = (char**) (p + keys[i].off);
		switch (rad) {
		case 'j':
			r = g = b = 0;
			r_cons_rgb_parse (*color, &r, &g, &b, NULL);
			hasnext = (keys[i + 1].name) ? "," : "";
			r_cons_printf ("\"%s\":[%d,%d,%d]%s",
				keys[i].name, r, g, b, hasnext);
			break;
		case 'c': {
			const char *prefix = r_str_trim_ro (arg);
			if (!prefix) {
				prefix = "";
			}
			r = g = b = 0;
			r_cons_rgb_parse (*color, &r, &g, &b, NULL);
			hasnext = (keys[i + 1].name) ? "\n" : "";
			//Need to replace the '.' char because this is not
			//valid CSS
			char *name = strdup (keys[i].name);
			int j, len = strlen (name);
			for (j = 0; j < len; j++) {
				if (name[j] == '.') {
					name[j] = '_';
				}
			}
			r_cons_printf (".%s%s { color: rgb(%d, %d, %d); }%s",
				prefix, name, r, g, b, hasnext);
			free (name);
			}
			break;
		case 'h':
			r = g = b = 0;
			r_cons_rgb_parse (*color, &r, &g, &b, NULL);
			rgbstr[0] = 0;
			name = strdup (keys[i].name);
			r_str_replace_char (name, '.', '_');
			r_cons_printf (".%s { color:#%02x%02x%02x }\n",
				name, r, g, b);
			free (name);
			break;
		case '*':
		case 'r':
		case 1:
			r = g = b = 0;
			r_cons_rgb_parse (*color, &r, &g, &b, NULL);
			rgbstr[0] = 0;
			r_cons_rgb_str (rgbstr, r, g, b, 0);
			r_cons_printf ("ec %s rgb:%02x%02x%02x\n",
				keys[i].name, r, g, b);
			break;
		default:
			r_cons_printf (" %s##"Color_RESET"  %s\n", *color,
				keys[i].name);
		}
	}
	if (rad == 'j') {
		r_cons_print ("}\n");
	}
}

R_API int r_cons_pal_set(const char *key, const char *val) {
	int i;
	char **p;
	for (i = 0; keys[i].name; i++) {
		if (!strcmp (key, keys[i].name)) {
			p = (char **) ((char *)& (r_cons_singleton ()->pal) + keys[i].off);
			*p = r_cons_pal_parse (val);
			return true;
		}
	}
	return false;
}

R_API const char *r_cons_pal_get_i(int n) {
	int i;
	for (i = 0; i < n && keys[i].name; i++) {}
	return (i == n) ? keys[n].name : NULL;
}

R_API const char *r_cons_pal_get (const char *key) {
	int i;
	for (i = 0; keys[i].name; i++) {
		if (!strcmp (key, keys[i].name)) {
			char **p = (char **) ((char *)& (r_cons_singleton ()->pal) + keys[i].off);
			return p? *p: "";
		}
	}
	return "";
}

R_API void r_cons_pal_update_event() {
	RConsPalette *pal = & (r_cons_singleton ()->pal);
	ut8 *p = (ut8*)pal;
	Sdb *db = sdb_new0 ();
	RCons *cons = r_cons_singleton ();
	int i, n = 0;
	for (i = 0; keys[i].name; i++) {
		char **color = (char**) (p + keys[i].off);
		ut8 r, g, b;
		r = g = b = 0;
		r_cons_rgb_parse (*color, &r, &g, &b, NULL);
		if (r <= 0x50 && g <= 0x50 && b < 0x50) {
			continue;
		}
		if (r >= 0xe0  && g >= 0xe0 && b >= 0xe0) {
			continue;
		}
		const char *rgb = sdb_fmt (0, "rgb:%02x%02x%02x", r, g, b);
		sdb_set (db, rgb, "1", 0);
	}
	SdbList *list = sdb_foreach_list (db, true);
	SdbListIter *iter;
	SdbKv *kv;
	r_cons_rainbow_free ();
	r_cons_rainbow_new (list->length);
	ls_foreach (list, iter, kv) {
		cons->pal.rainbow[n++] = strdup (kv->key);
	}
	cons->pal.rainbow_sz = n;
	ls_free (list);
	sdb_free (db);
}

R_API void r_cons_rainbow_new(int sz) {
	RCons *cons = r_cons_singleton ();
	cons->pal.rainbow_sz = sz;
	free (cons->pal.rainbow);
	cons->pal.rainbow = calloc (sizeof (char *), sz);
}

R_API void r_cons_rainbow_free() {
	RCons *cons = r_cons_singleton ();
	int i, sz = cons->pal.rainbow_sz;
	if (cons->pal.rainbow) {
		for (i = 0; i < sz ; i++) {
			free (cons->pal.rainbow[i]);
		}
	}
	cons->pal.rainbow_sz = 0;
	R_FREE (cons->pal.rainbow);
}

R_API char *r_cons_rainbow_get(int idx, int last, bool bg) {
	RCons *cons = r_cons_singleton ();
	if (last < 0) {
		last = cons->pal.rainbow_sz;
	}
	if (idx < 0 || idx >= last || !cons->pal.rainbow) {
		return NULL;
	}
	int x = (last == cons->pal.rainbow_sz)
		? idx : (cons->pal.rainbow_sz * idx) / (last + 1);
	const char *a = cons->pal.rainbow[x];
	if (bg) {
		char *dup = r_str_newf ("%s %s", a, a);
		char *res = r_cons_pal_parse (dup);
		free (dup);
		return res;
	}
	return r_cons_pal_parse (a);
}
