#
# OK, here's the plan:
#
# Tabkey cuts the input line up into edible chunks, and selects a function
# to do the actual completion.  The function will receive a single word as
# its argument, and return a list of potential matches.  The word is whatever
# is under the cursor, up to the insertion point at the current time.  The
# word may be empty or contain spaces and the function should be able to
# cope with this.
#
# The exact function chosen works like this:
# * If the current _word_ is a command (first word, begins with $cmdchars),
#   call command_completion by way of parsekey (don't call any aliases).
# * Or, if the current line is a command, it gets handed to $tabkey.cmd().
# * Otherwise, call tabkey.default, which is a stub that can be changed
#   to suit your preferences.  The default tabkey.default calls
#   tabkey.nickchan which matches against all channels you're currently on
#   and either the nicks in your current channel, or failing that, the
#   nicks in all channels.
# * tabkey.default is also called if tabkey.cmd() returns nothing.  If
#   necessary, the function can prevent this behaviour by returning a single
#   space, but this is discouraged.
# * tabkey.default is also called by $tabkey.cmd() if it cannot find an
#   appropriate command handler.
# * $tabkey.cmd() will search for an appropriate function by joining the
#   command and all its arguments together with dots and progressively
#   removing those arguments until a function is found.
#
# tabkey "exports" a number of local variables that the functions may use
#   or alter after using "bless":
# * $curword is what tabkey believes the current word is.  The function
#   may make use of this for context sensitivity.  I say believes because
#   this may be open to different interpretations.
# * $wordind is the character index of the _beginning_ of the word that is
#   being completed.  If it is equal to $curpos(), then the first argument
#   will be empty and the user has hit tab at the beginning of the word or
#   in between words and the function should return all possible matches.
#   The reason you may need to use it is if the function is designed to
#   complete something that is not a single word.  It, and the current
#   cursor position may be altered to cause tabkey to replace the chosen
#   part of the string instead of just the word.
#
# The organisation of the supporting functions goes like this:
# * "Context sensitive" functions are to be named with "tabkey.cmd." as a
#   prefix followed by the name of the command that they are used to
#   complete.  This is where tabkey.cmd will look for them.
# * Generally, completion "methods" should be placed under tabkey.method
#   Context sensitive functions should be as simple as possible and the bulk
#   of the work should be done by the methods.
#
# NOTE:  The "first argument" description above is no longer true.  The
#        functions argument list is to be treated as a single argument.
#
# Remaining known issues:
#   Doesn't work (very well) with commands with dots in them.

package tabkey.ce
bind ^i parse_command tabkey.main
alias tabkey.default tabkey.method.nickchan

alias tabkey.main {
	@ :oxd = xdebug(extractw)
	xdebug extractw
	@ :curpos = curpos()
	@ :curword = indextoword($curpos $L )
	@ :wordind = wordtoindex($curword $L)
	@ :cmdind = 0
	if (32 >= (127 & ascii($mid(${curpos-1} 1 $L )))) {
		@ curword++
		@ wordind = curpos
	}
	@ :word = mid($wordind ${curpos-wordind} $L)
	if (word=~["*"]) {
		@ :word = shift(word)
	} elsif (word=~["*]) {
		@ :word#=["]
		@ :word = shift(word)
	}
	if (!index($cmdchars $L)) {
		wait for @ :matches = tabkey.cmd($word)
	} else {
		wait for @ :matches = tabkey.default($word)
	}
	@ :prefix = prefix($matches)
	if (1 < numwords($matches) && word == prefix) {
		echo Completions for \"$word\": $matches
	} elsif (#matches && strlen($word) <= strlen($prefix)) {
		@ :prefix = 0 > index("$chr($jot(32 1))" $prefix) ? prefix : ["$prefix"]
		@ :line = mid(0 $wordind $L)
		parsekey erase_to_beg_of_line
		xtype -l $line$prefix${1<#matches?[]:[ ]}
	}
	xdebug $oxd
}

alias tabkey.cmd {
	bless
	@ :ret = :cmd = []
	@ :pass = chr($jot($ascii(AZ)))
	@ :pass#= chr($jot($ascii(az)))
	@ :pass#= chr($jot($ascii(09)))
	@ :args = wordtoindex($cmdind $L)
	@ :args = mid($args $curpos $L)
	@ :args = unsplit(. $args)
	@ :args = pass(._$pass $args)
	if (!curword) {
		@ ret = tabkey.method.commands($args)
		repeat $#ret {push ret /$shift(ret)}
		return $ret
	}
	while (args) {
		@ :matches = aliasctl(alias pmatch tabkey.cmd.$args*)
		@ :matches = prefix($matches)
		if (curword == count(. $args)) {
		} elsif (matches != [tabkey.cmd.$args]) {
		} elsif (aliasctl(alias exists $matches)) {
			@ args = matches
			break
		}
		@ args = before(-1 . $args)
	}
	if (args) {
		wait for @ ret = ${args}($*)
	}
	unless (strlen($ret)) {
		wait for @ ret = tabkey.default($*)
	}
	return $ret
}

# Used for things like /eval and /repeat where what you are completing
# is another command.  depth is the arg number where the command begins.
# This is not well cpu optimised.  See examples below.
alias tabkey.recurse (depth,...) {
	bless
	@ :curpos += wordtoindex($cmdind $L )
	@ :curpos -= wordtoindex(${cmdind+depth} $L )
	@ :cmdind += depth
	@ :curword -= depth
	@ function_return = 0 > curword ? [] : tabkey.cmd($*)
	@ :curword += depth
	@ :cmdind -= depth
	@ :curpos += wordtoindex(${cmdind+depth} $L )
	@ :curpos -= wordtoindex($cmdind $L )
}

# Context sensitive completion goes here.

alias tabkey.cmd.eval tabkey.recurse 1
alias tabkey.cmd.repeat tabkey.recurse 2

alias tabkey.cmd.dcc (args) {
	bless
	switch ($curword) {
		(1) {return $pattern("$args*" CHAT CLOSE CLOSEALL GET LIST RAW RENAME RESUME SEND)}
	}
}
alias tabkey.cmd.dcc.resume tabkey.cmd.dcc.send
alias tabkey.cmd.dcc.send {
	bless
	switch ($curword) {
		(0) (1) {echo This should never happen.;call;local}
		(2) {return $tabkey.method.nick($*)}
		(*) {return $tabkey.method.filei($*)}
	}
}

alias tabkey.cmd.help {
	bless
	@ :matches = []
	@ :path = restw(1 $left($curpos $L))
	if (wordind == curpos) {@ push(path *)}
	repeat $#path @ push(path $shift(path)*)
	@ matches = globi($unsplit(/ $getset(HELP_PATH) $path))
	repeat $#matches @ push(matches $rightw(1 $remws(/ $split(/ $shift(matches)))))
	return $matches
}

# Methods.

alias tabkey.method.commands {
	@ :ret = sort($uniq($getcommands($**) $aliasctl(alias match $*) $aliasctl(alias match $**)))
	return $ret
}

alias tabkey.method.chan (args) { return $pattern("$args*" $mychannels()); }
alias tabkey.method.notify (args) { return $pattern("$args*" $notify(on)); }

stack push alias alias.foo
alias alias.foo alias $*;alias $sar(g/globi/glob/$sar(g/filei/file/$*))
alias.foo tabkey.method.filei (f0) {
	@ :f0 = sar(gr/\\/\\\\/f0)
	@ :f0 = sar(gr/*/\\*/f0)
	@ :f0 = sar(gr/?/\\?/f0)
	@ :f0 = sar(gr/[/\\[/f0)
	@ :f0 = sar(gr/]/\\]/f0)
	@ :f0 = :f1 = globi("" $f0*)
	while (numwords($f0) == 1 && f0 =~ [*/]) {
		@ f0 = globi("" $f0*)
	}
	return ${f0 ? f0 : f1}
}
stack pop alias alias.foo

alias tabkey.method.nick (args) {
	if (match("$args*" $chanusers())) {
		return $pattern("$args*" $chanusers());
	} else {
		@ :chanusers = mychannels()
		repeat $#chanusers @ push(chanusers $chanusers($shift(chanusers)))
		return $pattern("$args*" $sort($uniq($chanusers)));
	}
}

alias tabkey.method.nickchan {
	return $remws(/ $tabkey.method.chan($*) $tabkey.method.nick($*))
}

alias tabkey.method.nickchannotify {
	return $remws(/ $tabkey.method.chan($*) $sort($uniq($tabkey.method.nick($*) $tabkey.method.notify($*))))
}
