use tui;
use fmt;
use unix::tty;
use io;
use strings;
use memio;
use os;
use strconv;

export type coords = (u16, u16);

export type printfn = fn(w: *widget) void;
export type resizefn = fn(w: *widget, ttysize: tty::ttysize) void;
export type finishfn = fn(w: *widget) void;

export type widgetsize = (tty::ttysize | void);

export type damagenone = void;
export type damageall = void;
export type damagerow = uint;
export type damagecol = uint;

//export type damageitem = (row | col | coords);
export type damageitem = damagerow;

export type damage = (damagenone | damageall | []damageitem);

export type stylesfn = fn(w: *widget, txt: str, idx: size) str;

//export type line = struct {
//	txt: str,
//};

export type linesbuf = struct {
	lines: []str,
	styles: nullable *stylesfn,
};

export type widget = struct {
	state: *tui::tui,
	print: *printfn,
	resize: *resizefn,
	finish: *finishfn,
	buf: linesbuf,
	pos: coords,
	sz: widgetsize,
	style: (*style | void),
	damage: damage,
};

export type color = enum uint {
	BLACKFG = 30,
	REDFG = 31,
	GREENFG = 32,
	BROWNFG = 33,
	BLUEFG = 34,
	MAGENTAFG = 35,
	CYANFG = 36,
	WHITEFG = 37,
	DEFAULTFG = 39,

	BLACKBG = 40,
	REDBG = 41,
	GREENBG = 42,
	BROWNBG = 43,
	BLUEBG = 44,
	MAGENTABG = 45,
	CYANBG = 46,
	WHITEBG = 47,
	DEFAULTBG = 49,
};

export type style = struct {
	border: bool,
};

export def DEFAULT_STYLE: style = style {
	border = false,
};

export def NEWLINE: str = "\r\n";

def gotoroot: str = "\x1B[1;1H";

def UNDERLINE: str = "\x1B[4m";
def NOUNDERLINE: str = "\x1B[0m";

def OVERLINE: rune = '\u0305';
def NOOVERLINE: str = "\x1B[0m";

def CLEAR: str = "\x1B[2J";
def CLEARROW: str = "\x1B[2K";

// Must free return value
fn underline(s: str) str = {
	return strings::concat(UNDERLINE, s, NOUNDERLINE);
};

// Must free return value
fn overline(s: str) str = {
	const st = memio::dynamic();
	defer io::close(&st)!;
	let iter = strings::iter(s);
	let iscolorcode = false;
	for (let r: rune => strings::next(&iter)) {
		if (r == '\x1B') {
			iscolorcode = true;
		};
		const char = if (!iscolorcode) {
			yield strings::fromrunes([r, OVERLINE]);
		} else {
			yield strings::fromrunes([r]);
		};
		defer free(char);
		memio::concat(&st, char)!;
		if (iscolorcode && r == 'm') iscolorcode = false;
	};
	return strings::dup(memio::string(&st)!);
};

fn minrows(out: io::file, x: u16, y: widgetsize) (u16 | tty::error) = {
	const y = match (y) {
	case let y: tty::ttysize =>
		yield y;
	case void =>
		yield tty::winsize(out)?;
	};
	return if (x < y.rows) x else y.rows;
};

// Must free return string
export fn color_to_str(color: color) str = fmt::asprintf("\x1B[{}m", strconv::utos(color));
//{
//	const cu = match (color) {
//	case let c: colorfg => yield c: uint;
//	case let c: colorbg => yield c: uint;
//	};
//	return 
//};

fn clearrow(row: uint) str = fmt::asprintf("\x1B[{}d{}", row, CLEARROW);

export fn print(w: *widget) void = {

	//tui::unraw(w.state);
	//defer tui::raw(w.state)!;

	const clear = match (w.damage) {
	case damageall =>
		yield strings::dup(CLEAR);
	case damagenone =>
		yield strings::dup("");
	case let dam: []damageitem =>
		let st = memio::dynamic();
		defer io::close(&st)!;
		for (let item: damageitem .. dam) {
			memio::concat(&st, clearrow(item))!;
			//match (item) {
			//case let r: row =>
			//	yield clearrow(r);
			////case let c: col =>
			////case let co: coords =>
			//};
		};
		yield strings::dup(memio::string(&st)!);
	};
	defer free(clear);

	let s = truncate_to_size(w);
	defer strings::freeall(s);

	let sstyle = applystyles(w.style, s);
	defer strings::freeall(sstyle);

	//const clear = if (w.state.clear) "\x1B[2J" else "";

	let st = memio::dynamic();
	defer io::close(&st)!;

	for(let i = 0z; i < len(sstyle); i += 1) {
		const line = match (w.buf.styles) {
		case let f: *stylesfn =>
			yield f(w, sstyle[i], i);
		case null =>
			yield strings::dup(sstyle[i]);
		};
		defer free(line);
		memio::concat(&st, line, NEWLINE)!;
	};

	const joined = memio::string(&st)!;

	let seekpos = fmt::asprintf("{}\x1B[{};{}H", clear, w.pos.0, w.pos.1);
	defer free(seekpos);

	const sout = strings::concat(seekpos, joined);
	defer free(sout);

	fmt::fprint(w.state.out, sout)!;

	w.state.clear = false;
};

// Applies styling (style) to the given string slice.
fn applystyles(st: (*style | void), s: []str) []str = {
	return match (st) {
	case let st: *style =>
		let sborder = if (st.border) {
			yield border(s);
		} else {
			yield strings::dupall(s);
		};
		//defer strings::freeall(sborder);
		//const scolor = color_to_str(st.colorfg);
		//defer free(scolor);
		//const defcolor = color_to_str(colorfg::DEFAULTFG);
		//defer free(defcolor);
		//const sb = strings::concat(scolor, sborder[0]);
		//free(sborder[0]);
		//sborder[0] = sb;
		//const endidx = len(sborder) - 1;
		//const sb = strings::concat(sborder[endidx], defcolor);
		//free(sborder[endidx]);
		//sborder[endidx] = sb;
		yield sborder;
	case void =>
		yield strings::dupall(s);
	};
};

// Truncates the text of the widget to the widget's size (rows and columns) and
// returns the resulting lines. Must call [[strings::freeall]] on the result.
fn truncate_to_size(w: *widget) []str = {
	let lines: []str = [];
	const nbrows = minrows(w.state.out, len(w.buf.lines): u16, w.sz)!;
	for (let i = 0z; i < nbrows; i += 1) {
		const line = w.buf.lines[i];
		let item = match (w.sz) {
		case let sz: tty::ttysize =>
			const s = if (tui::strwidth(line) > sz.columns) tui::subwidth(line, sz.columns) else line;
			yield strings::rpad(s, ' ', sz.columns);
		case void =>
			const wsz = tty::winsize(w.state.out)!;
			const s = if (tui::strwidth(line) > wsz.columns) tui::subwidth(line, wsz.columns) else line;
			yield strings::dup(s);
		};
		append(lines, item)!;
		//fmt::println(item)!;
	};
	return lines;
};

// Add a border around a slice of strings. Must call [[strings::freeall]] on the
// result.
fn border(s: []str) []str = {
	let lines: []str = [];
	for (let i = 0z; i < len(s); i += 1) {
		const si = strings::concat("|", s[i], "|");
		si = if (i == 0) {
			defer free(si);
			yield overline(si);
		} else {
			yield si;
		};
		si = if (i == len(s) - 1) {
			defer free(si);
			yield underline(si);
		} else {
			yield si;
		};

		append(lines, si)!;
	};
	return lines;
};

export fn prints(out: io::file, s: str, pos: coords) void = {
	fmt::fprintf(out, "\x1B[{};{}H", pos.0, pos.1)!;
	fmt::fprint(out, s)!;
};

export fn finish(w: *widget) void = {
	match (w.damage) {
	case let dam: []damageitem =>
		free(dam);
	case damageall =>
		return;
	};
};

export fn cleardamage(w: *widget) void = {
	let dam = if (w.damage is damageall) {
		return;
	} else {
		yield w.damage as []damageitem;
	};
	for (let i = 0z; i < len(dam); i += 1) {
		delete(dam[i]);
	};
};

fn splitstr(s: str, delim: str) []str = {
	let spl = strings::split(s, delim);
	for (let i = 0z; i < len(spl); i += 1) {
		if (spl[i] == "") {
			delete(spl[i]);
		};
	};
	return spl;
};
