use tui;
use tui::widget;
use tui::widget::list;
use tui::layout;
use bufio;
use os;
use strings;
use fmt;
use io;
use getopt;

fn strtocolor(s: str, df: widget::color) widget::color = {
	return switch (s) {
	case "red" =>
		yield widget::color::REDFG;
	case "blue" =>
		yield widget::color::BLUEFG;
	case "green" =>
		yield widget::color::GREENFG;
	case "brown" =>
		yield widget::color::BROWNFG;
	case "cyan" =>
		yield widget::color::CYANFG;
	case "black" =>
		yield widget::color::BLACKFG;
	case "white" =>
		yield widget::color::WHITEFG;
	case "magenta" =>
		yield widget::color::MAGENTAFG;
	case =>
		yield widget::color::DEFAULTFG;
	};
};

export fn main() void = {
	const cmd = getopt::parse(os::args,
		"interactive list",
		('n', "colour", "normal colour"),
		('m', "colour", "marked colour"),
		('b', "draw border"),
	);
	defer getopt::finish(&cmd);

	let normalst = list::DEFAULTSTYLE.normal;
	let markedst = list::DEFAULTSTYLE.marked;
	let border = false;
	for (let opt .. cmd.opts) {
		switch (opt.0) {
		case 'n' =>
			normalst = strtocolor(opt.1, normalst);
		case 'm' =>
			markedst = strtocolor(opt.1, markedst) + 10;
		case 'b' =>
			border = true;
		case => abort();
		};
	};

	const scanner = bufio::newscanner(os::stdin);
	defer bufio::finish(&scanner);

	let items: []str = [];
	for (let line: const str => bufio::scan_line(&scanner)!) {
		append(items, strings::dup(line))!;
	};
	defer strings::freeall(items);

	// static slice allocation
	//let buf: [1024]str = [""...];
	//let items = buf[..0];
	//for (let line: const str => bufio::scan_line(&scanner)!) {
	//	match (static append(items, strings::dup(line))) {
	//	case void =>
	//		void;
	//	case nomem =>
	//		fmt::fatal("Error: too many items");
	//	};
	//};
	//defer for (let s .. items) free(s);

	const state = tui::init()!;
	defer tui::finish(&state);
	//let li = list::newlist(
	//	&state,
	//	(1, 1),
	//	void,
	//	&list::DEFAULTSTYLE,
	//	items...
	//)!;
	let li = list::newlist(
		&state,
		(1, 1),
		void,
		&list::style {
			style = &widget::style {
				border = border,
			},
			normal = normalst,
			marked = markedst,
		},
		items...
	)!;
	let vl = layout::newvlayout(&li);
	defer layout::finishvlayout(&vl);

	vl.layout.print(&vl);

	let term: (str | void) = void;
	defer if (term is str) free(term as str) else void;
	let revsearch = false;
	for (true) {
		const r = tui::read(&state)!;
		if (r == 'j') {
			list::down(&li);
		};
		if (r == 'k') {
			list::up(&li);
		};
		if (r == 'J') {
			list::framedown(&li);
		};
		if (r == 'K') {
			list::frameup(&li);
		};
		if (r == 'd') {
			const nextpage = li.cursor + li.frame.end - li.frame.start;
			list::setcursor(&li, nextpage);
		};
		if (r == 'u') {
			const prevpage = li.cursor - (li.frame.end - li.frame.start);
			list::setcursor(&li, prevpage);
		};
		if (r == 'g') {
			list::top(&li);
		};
		if (r == 'G') {
			list::bottom(&li);
		};
		if (r == 'q') {
			break;
		};
		if (r == 'l') {
			tui::unraw(&state);
			if (len(li.marked) > 0) {
				for (let idx .. li.marked) {
					fmt::println(li.items[idx])!;
				};
			} else {
				fmt::println(li.items[li.cursor])!;
			};
			break;
		};
		if (r == '/' || r == '?') {
			revsearch = r == '?';
			term = search(&state, &li, r as rune);
			if (revsearch) prevsearch(&li, term) else nextsearch(&li, term);
		};
		if (r == 'n') {
			if (revsearch) prevsearch(&li, term) else nextsearch(&li, term);
		};
		if (r == 'N') {
			if (!revsearch) prevsearch(&li, term) else nextsearch(&li, term);
		};
		if (r == 'm') {
			list::mark(&li);
		};
		vl.layout.print(&vl);
	};
};

fn search(state: *tui::tui, li: *list::list, prefix: (str | rune) = '/') (str | void) = {
	tui::unraw(state);
	defer tui::raw(state)!;
	fmt::fprint(state.out, prefix)!;
	const uline = match (bufio::read_line(state.out)!) {
	case let u: []u8 =>
		yield u;
	case io::EOF =>
		return;
	};
	defer free(uline);
	return strings::dup(strings::fromutf8(uline)!);
};

fn nextsearch(li: *list::list, term: (str | void)) void = {
	const term = match (term) {
	case let term: str =>
		yield term;
	case void =>
		return;
	};
	for (let i = li.cursor + 1; i < len(li.items): int; i += 1) {
		if (strings::contains(li.items[i], term)) {
			list::setcursor(li, i);
			return;
		};
	};
};

fn prevsearch(li: *list::list, term: (str | void)) void = {
	const term = match (term) {
	case let term: str =>
		yield term;
	case void =>
		return;
	};
	for (let i: int = li.cursor: int - 1; i >= 0; i -= 1) {
		if (strings::contains(li.items[i], term)) {
			list::setcursor(li, i);
			return;
		};
	};
};
