use fmt;
use log;
use net;
use net::tcp;
use net::ip;
use types;
use bufio;
use shlex;
use io;
use unix::poll;
use unix::signal;
use encoding::utf8;
use strings;
use regex;
use strconv;
use fs;
use os;
use strings;

type nofreefd = !void;
type invalidquery = !str;
type quit = void;
type saveposerr = !(fs::error | io::error);
type error = !(invalidquery | shlex::syntaxerr | utf8::invalid | strconv::overflow | strconv::invalid | saveposerr);

fn savepos(filename: str, position: str) (void | saveposerr) = {
	let f = os::open(filename, fs::flag::WRONLY, fs::flag::CREATE, fs::flag::TRUNC)?;
	defer io::close(f)!;
	os::chmod(filename, fs::mode::USER_RW | fs::mode::GROUP_R | fs::mode::OTHER_R)?;
	fmt::fprint(f, position)?;
};

fn handlequery(query: const []u8) (void | []u8 | quit | error) = {
	log::printfln("query: {}", strings::fromutf8_unsafe(query));
	let s = strings::fromutf8(query)?;
	let spl = shlex::split(s)?;
	defer strings::freeall(spl);
	if (len(spl) == 0) {
		return "Empty query": invalidquery;
	};
	switch (spl[0]) {
	case "save" =>
		if (len(spl) != 3) {
			return "save takes 2 arguments (filename, position)": invalidquery;
		};
		//let re = regex::compile("[0-9]{2}:[0-9]{2}:[0-9]{2}")?;
		//defer regex::finish(&re);
		//const res = regex::replace(&re, spl[2], "")?;
		//if (res != "") {
		//	return "save position format: hh:mm:ss": invalidquery;
		//};
		//let re2 = regex::compile("[0-9]{2}")?;
		//defer regex::finish(&re2);
		//const result = regex::findall(&re2, spl[2]);
		//if (len(result) != 3) {
		//	return "save position format: hh:mm:ss": invalidquery;
		//};
		//if (strconv::stou(result[1][0].content)? >= 60 || strconv::stou(result[2][0].content)? >= 60) {
		//	return "save position format: hh:mm:ss (mm < 60 && ss < 60)": invalidquery;
		//};
		//log::printfln("Saving {} with position {}", spl[1], spl[2]);
		savepos(spl[1], spl[2])?;
	case "get" =>
		if (len(spl) != 2) {
			return "get takes 1 argument (filename)": invalidquery;
		};
		let f = os::open(spl[1])?;
		let buf = io::drain(f)?;
		log::printfln("sending buf: {}", strings::fromutf8_unsafe(buf));
		return buf;
	case "quit" =>
		return quit;
	case =>
		return "Unknown query": invalidquery;
	};
};

fn logerror(err: (void | error)) void = {
	match (err) {
	case void =>
		return;
	case let e: invalidquery =>
		log::println(e);
	case shlex::syntaxerr =>
		log::println("Syntax error");
	case utf8::invalid =>
		log::println("Invalid utf8");
	case let e: strconv::error =>
		log::println(strconv::strerror(e));
	case let e: fs::error =>
		log::println(fs::strerror(e));
	case let e: io::error =>
		log::println(io::strerror(e));
	};
};

fn close(pollfds: []poll::pollfd) void = {
	for (let i = 2z; i < len(pollfds); i += 1) {
		if (pollfds[i].fd >= 0) {
			net::close(pollfds[i].fd)!;
		};
	};
	//if (pollfds[0].fd >= 0) net::close(pollfds[0].fd)!;
};

fn nextfreefd(fds: []poll::pollfd) (size | nofreefd) = {
	for (let i = 0z; i < len(fds); i += 1) {
		if (fds[i].fd < 0) {
			return i;
		};
	};
	return nofreefd;
};

export fn main() void = {
	let addr = ip::parse("127.0.0.1")!;
	let sock = tcp::listen(addr, 6789u16)!;
	defer net::close(sock)!;

	signal::block(signal::sig::INT);
	let sfd = signal::signalfd(signal::sig::INT)!;

	// Use array, growable slice doesn't work with poll (new accept socket
	// fds overwrite old accept socket fds for some reason)
	let pollfds: [200]poll::pollfd = [
		poll::pollfd {
			fd = sock,
			events = poll::event::POLLIN,
			...
		},
		poll::pollfd {
			fd = sfd,
			events = poll::event::POLLIN,
			...
		}, poll::pollfd{fd = -1, ...}...
	];

	defer close(pollfds);

	for (true) {
		let i = poll::poll(pollfds, poll::INDEF)!;
		if (i > 0) {
			if (pollfds[0].revents & poll::event::POLLIN != 0) {
				let s= tcp::accept(sock)!;
				const fd = nextfreefd(pollfds)!;
				pollfds[fd] = poll::pollfd {
					fd = s,
					events = poll::event::POLLIN,
					...
				};
			};
			if (pollfds[1].revents & poll::event::POLLIN != 0) {
				signal::read(pollfds[1].fd)!;
				break;
			};
			for (let j = 2z; j < len(pollfds); j += 1) {
				if (pollfds[j].revents & poll::event::POLLIN != 0) {
					let s = pollfds[j].fd;
					log::printfln("client at index {} with fd {}", j, s: int);
					let line = match (bufio::scanline(s)) {
					case let line: []u8 =>
						yield line;
					case io::EOF =>
						log::printfln("Closing connection to client at index {} with fd {}", j, s: int);
						net::close(s)!;
						pollfds[j].fd = -1;
						break;
					case let e: io::error =>
						log::println(io::strerror(e));
						break;
					};
					defer free(line);
					match (handlequery(line)) {
					case let e: error =>
						logerror(e);
					case quit =>
						log::printfln("Closing connection to client at index {} with fd {}", j, s: int);
						net::close(s)!;
						pollfds[j].fd = -1;
					case void =>
						yield;
					case let buf: []u8 =>
						defer free(buf);
						match (fmt::fprintfln(s, "{}", strings::fromutf8_unsafe(buf))) {
						case let e: io::error =>
							log::println(io::strerror(e));
						case size =>
							yield;
						};
					};
				};
			};
		};
	};
};
