--
-- slack.lua: try to fix up UX when using the Slack IRC gateway
--
-- Copyright (c) 2017, Přemysl Eric Janouch <p@janouch.name>
--
-- Permission to use, copy, modify, and/or distribute this software for any
-- purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--

local servers = {}
local read_servers = function (v)
	servers = {}
	for name in v:lower ():gmatch "[^,]+" do
		servers[name] = true
	end
end

-- This is a reverse list of Slack's automatic emoji, noseless forms
local unemojify, emoji, emoji_default = false, {}, {
	heart                        = "<3",
	broken_heart                 = "</3",
	sunglasses                   = "8)",
	anguished                    = "D:",
	cry                          = ":'(",
	monkey_face                  = ":o)",
	kiss                         = ":*",
	smiley                       = "=)",
	smile                        = ":D",
	wink                         = ";)",
	laughing                     = ":>",
	neutral_face                 = ":|",
	open_mouth                   = ":o",
	angry                        = ">:(",
	slightly_smiling_face        = ":)",
	disappointed                 = ":(",
	confused                     = ":/",
	stuck_out_tongue             = ":p",
	stuck_out_tongue_winking_eye = ";p",
}
local load_emoji = function (extra)
	emoji = {}
	for k, v in pairs (emoji_default) do emoji[k] = v end
	for k, v in extra:gmatch "([^,]+) ([^,]+)" do emoji[k] = v end
end

xC.setup_config {
	servers = {
		type = "string_array",
		default = "\"\"",
		comment = "list of server names that are Slack IRC gateways",
		on_change = read_servers
	},
	unemojify = {
		type = "boolean",
		default = "true",
		comment = "convert emoji to normal ASCII emoticons",
		on_change = function (v) unemojify = v end
	},
	extra_emoji = {
		type = "string_array",
		default = "\"grinning :)),joy :'),innocent o:),persevere >_<\"",
		comment = "overrides or extra emoji for unemojify",
		on_change = function (v) load_emoji (v) end
	}
}

-- We can handle external messages about what we've supposedly sent just fine,
-- so let's get rid of that "[username] some message sent from the web UI" crap
xC.hook_irc (function (hook, server, line)
	local msg, us = xC.parse (line), server.user
	if not servers[server.name] or msg.command ~= "PRIVMSG" or not us
		or msg.params[1]:lower () ~= us.nickname:lower () then return line end

	-- Taking a shortcut to avoid lengthy message reassembly
	local quoted_nick = us.nickname:gsub ("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0")
	local text = line:match ("^.- PRIVMSG .- :%[" .. quoted_nick .. "%] (.*)$")
	if not text then return line end
	return ":" .. us.nickname .. "!" .. server.irc_user_host .. " PRIVMSG "
		.. msg.prefix:match "^[^!@]*" .. " :" .. text
end)

-- Unfuck emoji and :nick!nick@irc.tinyspeck.com MODE #channel +v nick : active
xC.hook_irc (function (hook, server, line)
	if not servers[server.name] then return line end
	if unemojify then
		local start, text = line:match ("^(.- PRIVMSG .- :)(.*)$")
		if start then return start .. text:gsub (":([a-z_]+):", function (name)
			if emoji[name] then return emoji[name] end
			return ":" .. name .. ":"
		end) end
	end
	return line:gsub ("^(:%S+ MODE .+) : .*", "%1")
end)

-- The gateway simply ignores the NAMES command altogether
xC.hook_input (function (hook, buffer, input)
	if not buffer.channel or not servers[buffer.server.name]
		or not input:match "^/names%s*$" then return input end

	local users = buffer.channel.users
	table.sort (users, function (a, b)
		if a.prefixes > b.prefixes then return true end
		if a.prefixes < b.prefixes then return false end
		return a.user.nickname < b.user.nickname
	end)

	local names = "Users on " .. buffer.channel.name .. ":"
	for i, chan_user in ipairs (users) do
		names = names .. " " .. chan_user.prefixes .. chan_user.user.nickname
	end
	buffer:log (names)
end)

xC.hook_completion (function (hook, data, word)
	local chan = xC.current_buffer.channel
	local server = xC.current_buffer.server
	if not chan or not servers[server.name] then return end

	-- In /commands there is typically no desire at all to add the at sign
	if data.location == 1 and data.words[1]:match "^/" then return end

	-- Handle both when the at sign is already there and when it is not
	local needle = word:gsub ("^@", ""):lower ()

	local t = {}
	local try = function (name)
		if data.location == 0 then name = name .. ":" end
		if name:sub (1, #needle):lower () == needle then
			table.insert (t, "@" .. name)
		end
	end
	for _, chan_user in ipairs (chan.users) do
		try (chan_user.user.nickname)
	end
	for _, special in ipairs { "channel", "here" } do
		try (special)
	end
	return t
end)