// SPDX-License-Identifier: LGPL-3.0-only

#include <rz_cons.h>
#include <rz_util/rz_assert.h>
#define I rz_cons_singleton()

#if __WINDOWS__
static void __fill_tail(int cols, int lines) {
	lines++;
	if (lines > 0) {
		char white[1024];
		cols = RZ_MIN(cols, sizeof(white));
		memset(white, ' ', cols - 1);
		lines--;
		white[cols] = '\n';
		while (lines-- > 0) {
			rz_xwrite(1, white, cols);
		}
	}
}

RZ_API void rz_cons_w32_clear(void) {
	static HANDLE hStdout = NULL;
	static CONSOLE_SCREEN_BUFFER_INFO csbi;
	COORD startCoords;
	DWORD dummy;
	if (I->vtmode) {
		rz_cons_strcat(Color_RESET RZ_CONS_CLEAR_SCREEN);
		return;
	}
	if (I->is_wine == 1) {
		rz_xwrite(1, "\033[0;0H\033[0m\033[2J", 6 + 4 + 4);
	}
	if (!hStdout) {
		hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
	}
	GetConsoleScreenBufferInfo(hStdout, &csbi);
	startCoords = (COORD){
		csbi.srWindow.Left,
		csbi.srWindow.Top
	};
	DWORD nLength = csbi.dwSize.X * (csbi.srWindow.Bottom - csbi.srWindow.Top + 1);
	FillConsoleOutputCharacter(hStdout, ' ',
		nLength, startCoords, &dummy);
	FillConsoleOutputAttribute(hStdout,
		FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY,
		nLength, startCoords, &dummy);
}

RZ_API void rz_cons_w32_gotoxy(int fd, int x, int y) {
	static HANDLE hStdout = NULL;
	static HANDLE hStderr = NULL;
	HANDLE *hConsole = fd == 1 ? &hStdout : &hStderr;
	COORD coord;
	coord.X = x;
	coord.Y = y;
	if (I->vtmode) {
		rz_cons_printf("\x1b[%d;%dH", y, x);
		return;
	}
	if (I->is_wine == 1) {
		rz_xwrite(fd, "\x1b[0;0H", 6);
	}
	if (!*hConsole) {
		*hConsole = GetStdHandle(fd == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);
	}
	CONSOLE_SCREEN_BUFFER_INFO info;
	GetConsoleScreenBufferInfo(*hConsole, &info);
	coord.X += info.srWindow.Left;
	coord.Y += info.srWindow.Top;
	SetConsoleCursorPosition(*hConsole, coord);
}

static int wrapline(const char *s, int len) {
	int l = 0, n = 0;
	for (; n < len;) {
		l = rz_str_len_utf8char(s + n, (len - n));
		n += l;
	}
	return n - ((n > len) ? l : 1);
}

// Dupe from canvas.c
static int utf8len_fixed(const char *s, int n) {
	int i = 0, j = 0, fullwidths = 0;
	while (s[i] && n > 0) {
		if ((s[i] & 0xc0) != 0x80) {
			j++;
			if (rz_str_char_fullwidth(s + i, n)) {
				fullwidths++;
			}
		}
		n--;
		i++;
	}
	return j + fullwidths;
}

static int bytes_utf8len(const char *s, int n) {
	int ret = 0;
	while (n > 0) {
		int sz = rz_str_utf8_charsize(s);
		ret += sz;
		s += sz;
		n--;
	}
	return ret;
}

static int rz_cons_w32_hprint(DWORD hdl, const char *ptr, int len, bool vmode) {
	HANDLE hConsole = GetStdHandle(hdl);
	int fd = hdl == STD_OUTPUT_HANDLE ? 1 : 2;
	int esc = 0;
	int bg = 0, fg = 1 | 2 | 4 | 8;
	const char *ptr_end, *str = ptr;
	int ret = 0;
	int inv = 0;
	int linelen = 0;
	int ll = 0;
	int raw_ll = 0;
	int lines, cols = rz_cons_get_size(&lines);
	if (I->is_wine == -1) {
		I->is_wine = rz_file_is_directory("/proc") ? 1 : 0;
	}
	if (len < 0) {
		len = strlen((const char *)ptr);
	}
	ptr_end = ptr + len;
	if (ptr && hConsole)
		for (; *ptr && ptr < ptr_end; ptr++) {
			if (ptr[0] == 0xa) {
				raw_ll = (size_t)(ptr - str);
				ll = utf8len_fixed(str, raw_ll);
				lines--;
				if (vmode && lines < 1) {
					break;
				}
				if (raw_ll < 1) {
					continue;
				}
				if (vmode) {
					/* only chop columns if necessary */
					if (ll + linelen >= cols) {
						// chop line if too long
						ll = (cols - linelen) - 1;
						if (ll < 0) {
							continue;
						}
					}
				}
				if (ll > 0) {
					raw_ll = bytes_utf8len(str, ll);
					rz_xwrite(fd, str, raw_ll);
					linelen += ll;
				}
				esc = 0;
				str = ptr + 1;
				if (vmode) {
					int wlen = cols - linelen;
					char white[1024];
					if (wlen > 0 && wlen < sizeof(white)) {
						memset(white, ' ', sizeof(white));
						rz_xwrite(fd, white, wlen - 1);
					}
				}
				rz_xwrite(fd, "\n\r", 2);
				// reset colors for next line
				SetConsoleTextAttribute(hConsole, 1 | 2 | 4 | 8);
				linelen = 0;
				continue;
			}
			if (ptr[0] == 0x1b) {
				raw_ll = (size_t)(ptr - str);
				ll = utf8len_fixed(str, raw_ll);
				if (str[0] == '\n') {
					str++;
					ll--;
					if (vmode) {
						int wlen = cols - linelen - 1;
						char white[1024];
						//wlen = 5;
						if (wlen > 0) {
							memset(white, ' ', sizeof(white));
							rz_xwrite(fd, white, wlen);
						}
					}
					rz_xwrite(fd, "\n\r", 2);
					//write (fd, "\r\n", 2);
					//lines--;
					linelen = 0;
				}
				if (vmode) {
					if (linelen + ll >= cols) {
						// chop line if too long
						ll = (cols - linelen) - 1;
						if (ll > 0) {
							// fix utf8 len here
							ll = wrapline((const char *)str, cols - linelen - 1);
						}
					}
				}
				if (ll > 0) {
					raw_ll = bytes_utf8len(str, ll);
					rz_xwrite(fd, str, raw_ll);
					linelen += ll;
				}
				esc = 1;
				str = ptr + 1;
				continue;
			}
			if (esc == 1) {
				// \x1b[2J
				if (ptr[0] != '[') {
					eprintf("Oops invalid escape char\n");
					esc = 0;
					str = ptr + 1;
					continue;
				}
				esc = 2;
				continue;
			} else if (esc == 2) {
				const char *ptr2 = NULL;
				int x, y, i, state = 0;
				for (i = 0; ptr[i] && state >= 0; i++) {
					switch (state) {
					case 0:
						if (ptr[i] == ';') {
							y = atoi((const char *)ptr);
							state = 1;
							ptr2 = (const char *)ptr + i + 1;
						} else if (ptr[i] >= '0' && ptr[i] <= '9') {
							// ok
						} else {
							state = -1; // END FAIL
						}
						break;
					case 1:
						if (ptr[i] == 'H') {
							x = atoi(ptr2);
							state = -2; // END OK
						} else if (ptr[i] >= '0' && ptr[i] <= '9') {
							// ok
						} else {
							state = -1; // END FAIL
						}
						break;
					}
				}
				if (state == -2) {
					rz_cons_w32_gotoxy(fd, x, y);
					ptr += i;
					str = ptr; // + i-2;
					continue;
				}
				bool bright = false;
				if (ptr[0] == '0' && ptr[1] == ';' && ptr[2] == '0') {
					// \x1b[0;0H
					/** clear screen if gotoxy **/
					if (vmode) {
						// fill row here
						__fill_tail(cols, lines);
					}
					rz_cons_w32_gotoxy(fd, 0, 0);
					lines = 0;
					esc = 0;
					ptr += 3;
					str = ptr + 1;
					continue;
				} else if (ptr[0] == '2' && ptr[1] == 'J') {
					rz_cons_w32_clear();
					esc = 0;
					ptr = ptr + 1;
					str = ptr + 1;
					continue;
				} else if (ptr[0] == '0' && (ptr[1] == 'm' || ptr[1] == 'K')) {
					SetConsoleTextAttribute(hConsole, 1 | 2 | 4 | 8);
					fg = 1 | 2 | 4 | 8;
					bg = 0;
					inv = 0;
					esc = 0;
					ptr++;
					str = ptr + 1;
					continue;
					// reset color
				} else if (ptr[0] == '2' && ptr[1] == '7' && ptr[2] == 'm') {
					SetConsoleTextAttribute(hConsole, bg | fg);
					inv = 0;
					esc = 0;
					ptr = ptr + 2;
					str = ptr + 1;
					continue;
					// invert off
				} else if (ptr[0] == '7' && ptr[1] == 'm') {
					SetConsoleTextAttribute(hConsole, bg | fg | COMMON_LVB_REVERSE_VIDEO);
					inv = COMMON_LVB_REVERSE_VIDEO;
					esc = 0;
					ptr = ptr + 1;
					str = ptr + 1;
					continue;
					// invert
				} else if ((ptr[0] == '3' || (bright = ptr[0] == '9')) && (ptr[2] == 'm' || ptr[2] == ';')) {
					switch (ptr[1]) {
					case '0': // BLACK
						fg = 0;
						break;
					case '1': // RED
						fg = 4;
						break;
					case '2': // GREEN
						fg = 2;
						break;
					case '3': // YELLOW
						fg = 2 | 4;
						break;
					case '4': // BLUE
						fg = 1;
						break;
					case '5': // MAGENTA
						fg = 1 | 4;
						break;
					case '6': // CYAN
						fg = 1 | 2;
						break;
					case '7': // WHITE
						fg = 1 | 2 | 4;
						break;
					case '8': // ???
					case '9':
						break;
					}
					if (bright) {
						fg |= 8;
					}
					SetConsoleTextAttribute(hConsole, bg | fg | inv);
					esc = 0;
					ptr = ptr + 2;
					str = ptr + 1;
					continue;
				} else if ((ptr[0] == '4' && ptr[2] == 'm') || (bright = ptr[0] == '1' && ptr[1] == '0' && ptr[3] == 'm')) {
					/* background color */
					ut8 col = bright ? ptr[2] : ptr[1];
					switch (col) {
					case '0': // BLACK
						bg = 0x0;
						break;
					case '1': // RED
						bg = 0x40;
						break;
					case '2': // GREEN
						bg = 0x20;
						break;
					case '3': // YELLOW
						bg = 0x20 | 0x40;
						break;
					case '4': // BLUE
						bg = 0x10;
						break;
					case '5': // MAGENTA
						bg = 0x10 | 0x40;
						break;
					case '6': // CYAN
						bg = 0x10 | 0x20;
						break;
					case '7': // WHITE
						bg = 0x10 | 0x20 | 0x40;
						break;
					case '8': // ???
					case '9':
						break;
					}
					if (bright) {
						bg |= 0x80;
					}
					SetConsoleTextAttribute(hConsole, bg | fg | inv);
					esc = 0;
					ptr = ptr + (bright ? 3 : 2);
					str = ptr + 1;
					continue;
				}
			}
			ret++;
		}
	if (vmode) {
		/* fill partial line */
		int wlen = cols - linelen - 1;
		if (wlen > 0) {
			char white[1024];
			memset(white, ' ', sizeof(white));
			rz_xwrite(fd, white, wlen);
		}
		/* fill tail */
		__fill_tail(cols, lines);
	} else {
		int ll = (size_t)(ptr - str);
		if (ll > 0) {
			rz_xwrite(fd, str, ll);
			linelen += ll;
		}
	}
	return ret;
}

RZ_API int rz_cons_w32_print(const char *ptr, int len, bool vmode) {
	return rz_cons_w32_hprint(STD_OUTPUT_HANDLE, ptr, len, vmode);
}

RZ_API int rz_cons_win_vhprintf(DWORD hdl, bool vmode, const char *fmt, va_list ap) {
	va_list ap2;
	int ret = -1;
	FILE *con = hdl == STD_OUTPUT_HANDLE ? stdout : stderr;
	if (!strchr(fmt, '%')) {
		size_t len = strlen(fmt);
		if (I->vtmode) {
			return fwrite(fmt, 1, len, con);
		}
		return rz_cons_w32_hprint(hdl, fmt, len, vmode);
	}
	va_copy(ap2, ap);
	int num_chars = vsnprintf(NULL, 0, fmt, ap2);
	num_chars++;
	char *buf = calloc(1, num_chars);
	if (buf) {
		(void)vsnprintf(buf, num_chars, fmt, ap);
		if (I->vtmode) {
			ret = fwrite(buf, 1, num_chars - 1, con);
		} else {
			ret = rz_cons_w32_hprint(hdl, buf, num_chars - 1, vmode);
		}
		free(buf);
	}
	va_end(ap2);
	return ret;
}

RZ_API int rz_cons_win_printf(bool vmode, const char *fmt, ...) {
	va_list ap;
	int ret;
	rz_return_val_if_fail(fmt, -1);

	va_start(ap, fmt);
	ret = rz_cons_win_vhprintf(STD_OUTPUT_HANDLE, vmode, fmt, ap);
	va_end(ap);
	return ret;
}

RZ_API int rz_cons_win_eprintf(bool vmode, const char *fmt, ...) {
	va_list ap;
	int ret;
	rz_return_val_if_fail(fmt, -1);

	va_start(ap, fmt);
	ret = rz_cons_win_vhprintf(STD_ERROR_HANDLE, vmode, fmt, ap);
	va_end(ap);
	return ret;
}
#endif
