use fmt;
use strings;
use getopt;
use os;
use io;
use path;
use os::exec;
use strconv;
use ascii;
use format::tsv;

type error = !(!str | io::error | path::error | exec::error | strconv::error);

type func = fn(cfg: config, tasks: []task, args: arguments) (void | task | error);

type command = struct {
	names: []str,
	func: *func,
};

type arguments = *getopt::command;

const PADDING: size = 30z;

const commands: [_]command = [
	command {
		names = ["f", "filter"],
		func = &filter,
	},
	command {
		names = ["s", "show"],
		func = &show,
	},
	command {
		names = ["a", "add"],
		func = &add,
	},
	command {
		names = ["w", "write"],
		func = &write,
	},
	command {
		names = ["d", "done"],
		func = &do,
	},
	command {
		names = ["t", "tsv"],
		func = &tsv,
	},
];

fn execcommand(cfg: config, name: str, tasks: []task, args: arguments) (void | error) = {
	for (const c .. commands) {
		for (const n .. c.names) {
			if (n == name) {
				c.func(cfg, tasks, args)?;
			};
		};
	};
};

fn add(cfg: config, tasks: []task, a: arguments) (void | task | error) = {
	const args = a.args;
	if (len(args) != 1z) {
		getopt::printhelp(os::stderr, "write", a.help)?;
		return;
	};

	let buf = path::init(cfg.tasksdir, args[0])?;
	const c = exec::cmd("vim", path::string(&buf))?;
	exec::exec(&c);
};

fn write(cfg: config, tasks: []task, a: arguments) (void | task | error) = {
	const args = a.args;
	if (len(args) != 1z) {
		getopt::printhelp(os::stderr, "write", a.help)?;
		return;
	};

	const id = strconv::stoz(args[0])?;
	const t = if (len(tasks) > id) {
		yield tasks[id];
	} else {
		return "No such task";
	};
	const c = exec::cmd("vim", t.path)?;
	exec::exec(&c);
};

fn show(cfg: config, tasks: []task, a: arguments) (void | task | error) = {
	const args = a.args;
	// if no args, print all
	if (len(args) == 0) {
		for (const t .. tasks) {
			fmt::print(t.content)!;
		};
		return;
	};

	for (const arg .. args) {
		const id = strconv::stoz(arg)?;
		const t = if (len(tasks) > id) {
			yield tasks[id];
		} else {
			return "No such task";
		};
		fmt::print(t.content)!;
	};
};

fn printtasktsv(cfg: config, t: task, id: size) (void | error) = {
	const sid = strings::dup(strconv::ztos(id));
	defer free(sid);
	const spriority = strconv::ztos(t.priority);
	const buf = path::init(t.path)?;
	const stags = strings::join(",", t.tags...);
	defer free(stags);
	const p = path::trimprefix(&buf, cfg.tasksdir)?;
	tsv::writerecord(os::stdout, [sid, t.name, spriority, stags, p])!;
};

fn printtask(cfg: config, t: task, id: size) (void | error) = {
	let name = strings::dup(t.name);
	defer free(name);
	if (len(t.name) > PADDING - 4) {
		name = strings::concat(strings::sub(t.name, 0z, PADDING - 4),
			"...");
	};

	const pad = PADDING - len(name) + len(strconv::utos(t.priority));
	const namepad = 10 - len(strconv::ztos(id)) + len(name);
	const buf = path::init(t.path)?;
	const stags = strings::join(",", t.tags...);
	defer free(stags);
	const tagspad = 15 - len(strconv::utos(t.priority)) + len(stags);
	const p = path::trimprefix(&buf, cfg.tasksdir)?;
	const pathpad = 20 - len(stags) + len(p);
	fmt::printfln("{}{%}{%}{%}{%}", id, name, &fmt::mods {
			pad = ' ',
			width = namepad,
			...
		},
		t.priority, &fmt::mods {
			pad = ' ',
			width = pad,
			...
		},
		stags, &fmt::mods {
			pad = ' ',
			width = tagspad,
			...
		},
		p, &fmt::mods {
			pad = ' ',
			width = pathpad,
			...
		},
	)!;
};

fn do(cfg: config, tasks: []task, a: arguments) (void | task | error) = {
	if (len(a.args) != 1z) {
		getopt::printhelp(os::stderr, "done", a.help)?;
		os::exit(os::status::FAILURE);
	};
	const id = strconv::stoz(a.args[0])?;
	if (id >= len(tasks)) {
		return "No such task";
	};
	const t = tasks[id];
	os::remove(t.path)?;
	fmt::printfln("Task {}: \"{}\" done (deleted)", id, t.name)!;
};

fn filter(cfg: config, tasks: []task, a: arguments) (void | task | error) = {
	const headpad = PADDING - len("name") + len("priority");
	const namepad = 10 - len("id") + len("name");
	const tagspad = 15 - len("priority") + len("tags");
	const pathpad = 20 - len("priority") + len("path");
	fmt::printfln("id{%}{%}{%}{%}",
		"name", &fmt::mods {
			pad = ' ',
			width = namepad,
			...
		},"priority", &fmt::mods {
			pad = ' ',
			width = headpad,
			...
		},"tags", &fmt::mods {
			pad = ' ',
			width = tagspad,
			...
		},"path", &fmt::mods {
			pad = ' ',
			width = pathpad,
			...
		},
	)!;
	const args = a.args;
	for (let i = 0z; i < len(tasks); i += 1) {
		const t = tasks[i];
		if (len(args) == 0z) {
			printtask(cfg, t, i)?;
		};
		for (const s .. args) {
			const lname = ascii::strlower(t.name);
			defer free(lname);
			const ls = ascii::strlower(s);
			defer free(ls);
			if (strings::contains(lname, ls)) {
				printtask(cfg, t, i)?;
			};
		};
	};
};

fn tsv(cfg: config, tasks: []task, a: arguments) (void | task | error) = {
	tsv::writerecord(os::stdout, ["id" ,"name", "priority", "tags", "path"])!;
	for (let i = 0z; i < len(tasks); i += 1) {
		const t = tasks[i];
		printtasktsv(cfg, t, i)?;
	};
};

fn listall(cfg: config, tasks: []task) void = {
	const headpad = PADDING - len("name") + len("priority");
	const namepad = 10 - len("id") + len("name");
	const tagspad = 15 - len("priority") + len("tags");
	const pathpad = 20 - len("tags") + len("path");
	fmt::printfln("id{%}{%}{%}{%}",
		"name", &fmt::mods {
			pad = ' ',
			width = namepad,
			...
		},"priority", &fmt::mods {
			pad = ' ',
			width = headpad,
			...
		},"tags", &fmt::mods {
			pad = ' ',
			width = tagspad,
			...
		},"path", &fmt::mods {
			pad = ' ',
			width = pathpad,
			...
		},
	)!;
	for (let i = 0z; i < len(tasks); i += 1) {
		printtask(cfg, tasks[i], i)!;
	};
};

fn strerror(e: error) str = {
	match (e) {
	case let e: !str =>
		return e;
	case let e: io::error =>
		return io::strerror(e);
	case let e: path::error =>
		return path::strerror(e);
	case let e: exec::error =>
		return exec::strerror(e);
	case let e: strconv::error =>
		return strconv::strerror(e);
	};
};
