// License: MPL-2.0
// (c) 2022 Julian Hurst <ark@mansus.space>

use fmt;
use os;
use io;
use unix::tty;
use errors;
use bufio;
use encoding::utf8;
use strings;

export type key = (rune | specialkey);

// A listener on a rune input that returns if the ui needs to terminate or not
export type listener = *fn(ui: *ttyui, r: key) bool;

export type ttyui = struct {
	term: tty::termios,
	f: io::file,
	listeners: []listener,
};

// Initializes the UI and returns a ttyui.
export fn init() ttyui = {
	let f = match (tty::open()) {
	case let f: io::file =>
		yield f;
	case let e: tty::error =>
		fmt::fatal(tty::strerror(e));
	};
	if (!tty::isatty(f)) {
		fmt::fatal("stream is not a tty");
	};
	let term = match (tty::termios_query(f)) {
	case let t: tty::termios =>
		yield t;
	case let e: errors::error =>
		fmt::fatal(errors::strerror(e));
	};
	tty::makeraw(&term)!;
	//tty::noecho(&term)!;

	let ui = ttyui {
		term = term,
		f = f,
		listeners = [],
	};
	hidecursor(ui);
	return ui;
};

export fn hidecursor(ui: ttyui) void = {
	print(ui, "\x1B[?25l");
};

export fn showcursor(ui: ttyui) void = {
	print(ui, "\x1B[?25h");
};

// Returns the window size for the given ttyui.
export fn getwinsize(ui: ttyui) (tty::ttysize | tty::error) = {
	return tty::winsize(ui.f);
};

// Suspend the UI. To restore it, use [[resume]].
export fn suspend(ui: *ttyui) void = {
	showcursor(*ui);
	tty::termios_restore(&ui.term);
};

// Resumes the UI after a [[suspend]].
export fn resume(ui: *ttyui) void = {
	tty::makeraw(&ui.term)!;
	tty::noecho(&ui.term)!;
	hidecursor(*ui);
};

// Restores the UI state and closes and frees the resources associated with the
// given ttyui.
export fn finish(ui: *ttyui) void = {
	showcursor(*ui);
	tty::termios_restore(&ui.term);
	io::close(ui.f)!;
	free(ui.listeners);
};

// Scans a rune. A convenience function for [[bufio::scanrune]].
export fn scan(ui: ttyui) (key | utf8::invalid | io::EOF | io::error) = {
	//const r = bufio::scanrune(ui.f)?;
	const r = match (bufio::scanrune(ui.f)?) {
	case let r: rune =>
		yield r;
	case =>
		return io::EOF;
	};
	return getkey(r);
};

// Notify (call) the ttyui's listeners with the ttyui and r as a parameter.
// Returns true if a listener returned true, false otherwise.
export fn notify(ui: *ttyui, r: (rune | specialkey)) bool = {
	for (let i = 0z; i < len(ui.listeners); i += 1) {
		if (ui.listeners[i](ui, r)) {
			return true;
		};
	};
	return false;
};

fn loop(ui: *ttyui) void = {
	for (true) {
		let r = match (bufio::scanrune(ui.f)) {
		case let r: rune =>
			yield r;
		case utf8::invalid =>
			fmt::fatal("Invalid utf8 sequence found");
		case io::EOF =>
			fmt::fatal("EOF");
		case let e: io::error =>
			fmt::fatal(io::strerror(e));
		};
		for (let i = 0z; i < len(ui.listeners); i += 1) {
			if (ui.listeners[i](ui, r)) {
				return;
			};
		};
	};
};

// Add a listener to the given ttyui.
export fn addlistener(ui: *ttyui, l: listener) void = {
	append(ui.listeners, l);
};

// Print a string or rune to the ttyui.
export fn print(ui: ttyui, arg: (str | rune)) void = {
	fmt::fprint(ui.f, arg)!;
	//fmt::fprintf(ui.f, "{}\r", arg)!;
};

// Clear the ttyui.
export fn clear(ui: ttyui) void = {
	fmt::fprintf(ui.f, "\x1B[2J\x1B[1;1H\r")!;
};
