
class RouteBlock[T] {
	route: ?Route[T]
	blocks: Map[RouteBlock[T]]
}

class RouteArg {
	name: String
	i: uint
}

+ class Router[T] {

	- block: RouteBlock[T]
	- inited: bool (false)

	+ static fn new() SELF {
		return SELF {
			block: RouteBlock[T] {
				blocks: Map[RouteBlock[T]]{}
			}
		}
	}

	+ fn add(method: String, url: String, handler: T) !invalid_route {

		if url.bytes == 0 || url.get(0) != '/' {
			throw invalid_route
		}

		let url_tmp = url.part(1, url.bytes - 1)
		let parts = url_tmp.split("/")
		parts.prepend(method)

		let block = this.block
		let args = Array[RouteArg].new(2)
        let has_wcard = false
		let wcard_index : uint = 0

		each parts as part, i {
			if part.bytes == 0 : continue

			let blocks = block.blocks

			let is_arg = part.get(0) == '@'
			let is_wcard = part == "*"
			let key = part

            if is_wcard && has_wcard {
				throw invalid_route
            }

			if is_arg {
				let name = key.part(1, key.bytes - 1)
				if name.bytes == 0 {
					throw invalid_route
				}
				args.append(RouteArg { i: i, name: name })
				key = "@"
			} else if is_wcard {
				key = "*"
				wcard_index = i
                has_wcard = true
			}

			let find = blocks.get(key) !? <{
				let v = RouteBlock[T]{ blocks: Map[RouteBlock[T]]{} }
				blocks.set(key, v)
				return v
			}

			block = find
		}

		block.route = Route[T]{ handler: handler, args: args }
	}

	+ fn find(method: String, url: String) Route[T] !not_found {
		let parts_ = url.split("/")
		let parts = Array[String].new(2)
		each parts_ as part, i {
			if part.bytes == 0 {
				continue
			}
			parts.append(part)
		}

		parts.prepend(method)
		let arg_c = parts.length

		let r = Router[T].find_route(this.block, 0, arg_c.@cast(u8), parts)
        if isset(r) {
            return r
        }
        throw not_found
	}

	- static fn find_route(block: RouteBlock[T], index: u8, arg_c: u8, parts: Array[String]) ?Route[T] {

		if index == arg_c {
			return block.route
		}

		let p = parts.get(index) ! return null

		let part = p

		if part != "*" {
			let sub_block = block.blocks.get(part) !? null
			if isset(sub_block) {
				let route = SELF.find_route(sub_block, index + 1, arg_c, parts)
				if isset(route) : return route
			}
		}

		part = "@"
		let sub_block = block.blocks.get(part) !? null
		if isset(sub_block) {
			let route = SELF.find_route(sub_block, index + 1, arg_c, parts)
			if isset(route) : return route
		}


		let sub_block_ = block.blocks.get("*") ! return null
		return sub_block_.route
	}
}
