# coding: UTF-8
"""
Copyright (c) 2010 Marian Tietz
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
"""
import time

import traceback

from gettext import gettext as _

from tekka.helper import code
from tekka.helper import color
from tekka.helper import markup

from tekka import com
from tekka import gui
from tekka import config
from tekka import signals
from tekka import commands

from tekka.com import sushi

def warnNoConnection(tab):
	if tab.is_server():
		name = tab.name
	elif tab.is_channel() or tab.is_query():
		name = tab.server.name

	gui.mgmt.show_inline_message(
		_("Warning:"),
		_("You are not connected to server %(server)s.") % {"server": name },
		dtype="warning")

def warnNotJoined(cTab):
	gui.mgmt.show_inline_message(
		_("Warning:"),
		_("The channel %(channel)s is not joined. Everything you "
		  "write will not be send.") % { "channel": cTab.name },
		dtype="warning")




def cmd_connect(currentServer, currentChannel, args):
	"""
		Connect to the given server.

		Usage: /connect <server>
	"""
	if not args:
		return gui.mgmt.myPrint("Usage: /connect <servername>")

	sushi.connect(args[0])


def cmd_quit(currentServer, currentChannel, args):
	"""
		Quit the given server with an optional reason.
		If no server is given, the current server is quit.

		Usage: /quit <server> [<reason>]
			   /quit [<reason>]
	"""
	if args:
		# /quit <server> [<reason>]
		if gui.tabs.search_tab(args[0]):
			reason = " ".join(args[1:])
			if not reason:
				reason = config.get("chatting", "quit_message", "")
			sushi.quit(args[0], reason)
		else:
			# /quit [<reason>]
			if not currentServer:
				return gui.mgmt.myPrint("Could not determine server.")
			reason = " ".join(args)
			if not reason:
				reason = config.get("chatting", "quit_message", "")
			sushi.quit(currentServer.name, reason)
	else:
		# /quit
		if not currentServer:
			return gui.mgmt.myPrint("Could not determine server.")
		sushi.quit(currentServer.name,
			config.get("chatting", "quit_message", ""))


def cmd_nick(currentServer, currentChannel, args):
	"""
		Change your current nick to the given nick.

		Usage: /nick <new nick>
	"""
	if not args:
		return gui.mgmt.myPrint("Usage: /nick <new nick>")

	if not currentServer:
		return gui.mgmt.myPrint("Can't determine my server.")

	sushi.nick(currentServer.name, args[0])


def cmd_part(currentServer, currentChannel, args):
	"""
		Part the given channel with an optional reason.
		If no channel is given, the current channel is parted.

		Usage: /part <channel> [<reason>]
			   /part [<reason>]
	"""
	if args and currentServer:
		# /part <channel> [<reason>]
		if gui.tabs.search_tab(currentServer.name, args[0]):
			reason = " ".join(args[1:])
			if not reason:
				reason = config.get("chatting", "part_message", "")
			sushi.part(currentServer.name, args[0], reason)
		else:
			# /part [<reason>]
			if not currentChannel:
				return gui.mgmt.myPrint("Could not determine channel.")
			reason = " ".join(args)
			if not reason:
				reason = config.get("chatting", "part_message", "")
			sushi.part(currentServer.name, currentChannel.name, reason)
	else:
		# /part
		if not currentChannel:
			return gui.mgmt.myPrint("Could not determine channel.")
		sushi.part(currentServer.name, currentChannel.name, config.get("chatting", "part_message", ""))


def cmd_join(currentServer, currentChannel, args):
	"""
		Joins the given channel with the optional key.
		If no channel is given, the command tries to
		join the current activated channel if it's parted.

		Usage: /join [<channel> [<key>]]
	"""
	if not currentServer:
		return gui.mgmt.myPrint("Can't determine server.")

	if not args:
		if currentChannel and not currentChannel.joined:
			return sushi.join(currentServer.name, currentChannel.name, "")
		else:
			return gui.mgmt.myPrint("Usage: /join <channel> [<key>]")

	sushi.join(currentServer.name, args[0], " ".join(args[1:]))


def cmd_action(currentServer, currentChannel, args):
	"""
		Sends an action in third person view.

		Usage: /me <text>

		Example: nemo types: /me giggles.
		Results in: nemo giggles.
	"""
	if not args:
		return gui.mgmt.myPrint("Usage: /me <text>")

	if not currentChannel:
		return gui.mgmt.myPrint("Can't find active channel.")

	sushi.action(currentServer.name, currentChannel.name, " ".join(args))


def cmd_kick(currentServer, currentTab, args):
	"""
		Kick the given user with an optional reason from the
		current channel.

		Usage: /kick <user> [<reason>]
	"""
	if not args:
		return gui.mgmt.myPrint("Usage: /kick <user> [<reason>]")

	if not currentTab or not currentTab.is_channel():
		return gui.mgmt.myPrint("You're not on a channel")

	sushi.kick(currentServer.name, currentTab.name, args[0], " ".join(args[1:]))


def cmd_mode(currentServer, currentChannel, args):
	"""
		Sets a mode on the target.

		Usage: /mode <target> (+|-)<mode> [<param>]

		Example: /mode #xesio +o nemo
		OR:	  /mode nemo +x
		OR:	  /mode #xesio +m
	"""
	if not args or len(args) < 1:
		return gui.mgmt.myPrint("Usage: /mode <target> (+|-)<mode> [<param>]")

	if not currentServer:
		return gui.mgmt.myPrint("Could not determine server.")

	if len(args) >= 2:
		# a parameter is given
		sushi.mode(currentServer.name, args[0], "%s %s" % (args[1], " ".join(args[2:])))
	else:
		sushi.mode(currentServer.name, args[0], "")


def cmd_topic(serverTab, channelTab, args):
	"""
		Sets the topic in the current channel.
		If no argument is given, print the current topic.

		Usage: /topic [<text>]
	"""
	if not channelTab or not channelTab.is_channel():
		return gui.mgmt.myPrint("No channel active.")

	if not args:
		return gui.mgmt.myPrint(
		"Topic for channel %(channel)s: '%(topic)s'" % {
			"channel": channelTab.name,
			"topic": color.parse_color_codes_to_tags(channelTab.topic)
		}, html=True)

	else:
		topic = " ".join(args)


	sushi.topic(serverTab.name, channelTab.name, topic)


def cmd_cleartopic(serverTab, channelTab, args):
	"""
		Clears the topic.

		Usage: /cleartopic
	"""
	if not channelTab or not channelTab.is_channel():
		return gui.mgmt.myPrint("No channel is active")

	sushi.topic(serverTab.name, channelTab.name, "")


def cmd_away(serverTab, channelTab, args):
	"""
		Sets you away with an optional reason.

		Usage: /away [<reason>]
	"""
	if not serverTab:
		return gui.mgmt.myPrint("Can't determine server.")

	sushi.away(serverTab.name, " ".join(args))
	serverTab.current_write(time.time(), _("You are now away."))


def cmd_back(serverTab, channelTab, args):
	"""
		Sets you back from being away.

		Usage: /back
	"""
	if not serverTab:
		return gui.mgmt.myPrint("Can't determine server.")

	sushi.back(serverTab.name)
	serverTab.current_write(time.time(), _("You are now back."))


def cmd_nickserv(serverTab, channelTab, args):
	"""
		Authenticates you at NickServ with
		the data stored in maki.

		Usage: /nickserv
	"""
	if not serverTab:
		return gui.mgmt.myPrint("Can't determine server.")

	sushi.nickserv(serverTab.name)


def cmd_ctcp(serverTab, channelTab, args):
	"""
		Sends a CTCP message to the given target.

		Usage: /ctcp <target> <message>
	"""
	if not args or len(args) < 2:
		return gui.mgmt.myPrint("Usage: /ctcp <target> <message>")

	if not serverTab:
		return gui.mgmt.myPrint("Could not determine server.")

	sushi.ctcp(serverTab.name, args[0], " ".join(args[1:]))


def cmd_names(serverTab, channelTab, args):
	"""
		Sends a NAMES request and prints the result
		as text to the channel.

		Usage: /names [<channel>]
	"""
	def request(server,channel):
		""" print the name listing in the queried tab (if it's active)
			or in the current tab
		"""
		def names_cb(time, server, channel, nicks, prefixes):

			self = names_cb

			def print_message(message):
				if self.tab.is_active():
					# print in the channel we query
					self.tab.write(time, message, "action")
				elif self.tab.is_server():
					self.tab.current_write(time, message, "action")
				else:
					# print in the current channel
					self.tab.server.current_write(time, message, "action")

			if self.first:
				print_message(_("• Begin of names"))
				self.first = False

			if not nicks: # eol
				signals.disconnect_signal("names", self)
				print_message(_("• End of names"))

			else:
				max_nicklen = max([len(n)+len(p)
					for (p,n) in self.tab.nickList.get_nicks_mode()])

				message = ""
				for i in xrange(len(nicks)):
					nick_color = color.get_nick_color(nicks[i])

					c_start = "<font foreground='%s'>" % (nick_color)
					c_end = "</font>"

					tmp = "[{prefix}{cstart}{nick}{cend}]".format(
						prefix=prefixes[i],
						nick=nicks[i],
						cstart=c_start,
						cend=c_end)

					overhead = len(tmp) - len(nicks[i]) - len(prefixes[i])

					message += "{0:{align}<{fill}}".format(tmp,
									align=" ", fill=max_nicklen + overhead)

					if (i+1) % self.max_col == 0:
						print_message(message)
						message = ""
					else:
						message += " "

				print_message(message)


		names_cb.first = True
		names_cb.max_col = int(config.get("tekka","names_columns"))
		s,c = gui.tabs.search_tabs(server, channel)
		names_cb.tab = c or s

		signals.connect_signal("names", names_cb)
		sushi.names(server, channel)

	if channelTab and not channelTab.is_channel():
		gui.mgmt.myPrint("/names can only be used on channel tabs.")
		return

	if not args:
		if channelTab:
			request(serverTab.name, channelTab.name)
		else:
			pass # which channel was meant?
	elif len(args) == 1:
		request(serverTab.name, args[0])
	else:
		gui.mgmt.myPrint("Usage: /names [<channel>]")


def cmd_notice(serverTab, channelTab, args):
	"""
		Sends a notice to the given target.
		The difference between /ctcp and /notice
		is, that /ctcp sends directly to the user
		while /notice sends the message over the
		server.

		Usage: /notice <target> <message>
	"""
	if not args or len(args) < 2:
		return gui.mgmt.myPrint("Usage: /notice <target> <message>")

	if not serverTab:
		return gui.mgmt.myPrint("Could not determine server.")

	sushi.notice(serverTab.name, args[0], " ".join(args[1:]))


def cmd_message(serverTab, channelTab, args):
	"""
		Sends a message (PRIVMSG) to the target.
		The target can be a channel or a user.

		Usage: /msg <target> <message>
	"""
	if not args or len(args) < 2:
		return gui.mgmt.myPrint("Usage: /msg <target> <message>")

	if not serverTab:
		return gui.mgmt.myPrint("Could not determine server.")

	com.sendMessage(serverTab.name, args[0], " ".join(args[1:]))


def cmd_oper(serverTab, channelTab, args):
	"""
		Authentificate as IRC operator.

		Usage: /oper <user> <pass>
	"""
	if not args or len(args) < 2:
		return gui.mgmt.myPrint("Usage: /oper <user> <pass>")

	if not serverTab:
		return gui.mgmt.myPrint("Could not determine server.")

	sushi.oper(serverTab.name, args[0], " ".join(args[1:]))


def cmd_list(serverTab, channelTab, args):
	"""
		Start a channel listing.
		If channel is given, only the channel
		is listed.

		Usage: /list [<channel>]
	"""
	import gobject

	def channelList_cb(time, server, channel, users, topic):
		""" Signal for /list command.
			Prints content of the listing.
		"""

		self = code.init_function_attrs(channelList_cb,
			_text=[],
			_line=0,
			_tab=gui.tabs.search_tab(server))

		def print_listing():
			self._tab.write_raw("<br/>".join(self._text))

			self._text = []
			self._line = 0
			return False

		if not channel and not topic and users == -1:
			# listing ended, reset variables

			def print_end():
				self._tab.write(time, "End of list.")
				return False

			if self._line > 0:
				# print rest
				gobject.idle_add(print_listing)

			gobject.idle_add(print_end)

			code.reset_function_attrs(channelList_cb)

			signals.disconnect_signal("list", channelList_cb)

		else:
			self._text.append(("• <b>%s</b><br/>\t%d "+_("User")+"<br/>"+
								"\t"+_("Topic")+": \"%s\"") % (
									markup.escape(channel),
									users,
									markup.escape(topic)))
			self._line += 1

			if self._line == 10:

				gobject.idle_add(print_listing)


	# cmd_list:

	# make the callback accessible for cmd_stop_list
	cmd_list.list_cb = channelList_cb

	if not serverTab:
		return gui.mgmt.myPrint("Could not determine server.")

	try:
		# channel specific listing?
		channel = args[0]
	except IndexError:
		# start a complete list..
		channel = ""

	serverTab.write(time.time(), "Start of list.")

	signals.connect_signal("list", channelList_cb)
	sushi.list(serverTab.name, channel)


def cmd_raw(serverTab, channelTab, args):
	"""
		Sends a command with optional args to maki
		which acts only as forwarder. The command
		goes unchanged to the server.

		Usage: /raw <command> [<further text>]
	"""
	if not args:
		return gui.mgmt.myPrint("Usage: /raw <command>")

	if not serverTab:
		return gui.mgmt.myPrint("Could not determine server.")

	# upper-case the command
	args[0] = args[0].upper()

	sushi.raw(serverTab.name, " ".join(args))


def cmd_stop_list(serverTab, channelTab, args):
	""" Aborts printing the channel list.

		Usage: /stoplist
	"""
	try:
		signals.disconnect_signal("list", cmd_list.list_cb)
	except AttributeError:
		# already stopped
		return


def cmd_whois(currentServer, currentChannel, args):
	"""
		Query a user's identity on the current server.

		Usage: /whois <user mask>
	"""
	def whois_cb(time, server, nick, message):
		""" message = "" => end of whois """

		server_tab = gui.tabs.search_tab(server)

		if message:
			server_tab.write(time,
				_(u"[%(nick)s] %(message)s") % {
					"nick": markup.escape(nick),
					"message": markup.escape(message) })
		else:
			server_tab.write(time,
				_(u"[%(nick)s] End of whois.") % {
					"nick": markup.escape(nick) })


	# begin of command function

	if not args:
		return gui.mgmt.myPrint("No server activated.")

	def handler(time, server, nick, message):
		if message:
			whois_cb(time, server, nick, message)
		else:
			signals.disconnect_signal("whois", handler)

	signals.connect_signal("whois", handler)
	sushi.whois(currentServer.name, args[0])


def cmd_query(currentServer, currentTab, args):
	"""
		Starts a query dialog with the given user.

		Usage: /query <nick>
	"""
	if not args:
		return gui.mgmt.myPrint("Usage: /query <nick>")

	if not currentServer:
		return gui.mgmt.myPrint("Can't determine server.")

	nick = args[0]

	tab = gui.tabs.search_tab(currentServer.name, nick)
	if not tab:
		# no query started

		tab = gui.tabs.create_query(currentServer, nick)
		tab.connected = True

		gui.tabs.add_tab(currentServer, tab)
		gui.shortcuts.assign_numeric_tab_shortcuts(gui.tabs.get_all_tabs())

		output = tab.window.textview.get_buffer()

		# fetch and write history to query (if any)
		tab.print_last_log()

	# jump to query
	tab.switch_to()


def cmd_clear(currentServer, currentTab, args):
	"""
		Clears the output of the current channel.

		Usage: /clear
	"""
	gui.mgmt.clear_all_outputs()


def cmd_help(currentServer, currentTab, args):
	"""
		Prints the doc-string of the given command.

		Usage: /help <command>
	"""
	if not args:
		commandList = ", ".join(commands._commands.keys())
		return gui.mgmt.myPrint("Usage: /help <command>\nAvailable commands are: %s" % (commandList))
	if commands._commands.has_key(args[0]):
		gui.mgmt.myPrint(commands._commands[args[0]].__doc__.replace("\t",""))
	else:
		gui.mgmt.myPrint("No help for %s available." % (args[0]))


def cmd_invoke_test(currentServer, currentTab, args):
	"""
		Loads a test file and executes it.

		Usage: /invoke_test <path>
	"""
	if len(args) != 1:
		return gui.myPrint("Usage: /invoke_test <path>")

	import os
	import imp

	path, file = os.path.split(args[0])

	name = file.split(".")[0]

	try:
		mod_info = imp.find_module(name, [path])
		test_module = imp.load_module(name, *mod_info)

	except ImportError as e:
		gui.mgmt.myPrint("invoke_test failed: %s" % (e))
		return

	try:
		test_module.start_test(gui)
	except Exception as e:
		gui.mgmt.myPrint("invoke_test run failed: %s\n%s" % (e,
						traceback.format_exc()))
		return


def cmd_ignore(currentServer, currentTab, args):
	"""
		Ignores a user pattern.

		Usage: /ignore <pattern>
	"""
	if not args:
		return gui.myPrint("Usage: /ignore <pattern>")
	if not currentServer:
		return gui.myPrint("Could not determine server")

	sushi.ignore(currentServer.name, args[0])
	gui.mgmt.myPrint(_("Ignoring %(user)s.") % {"user": args[0]})


def cmd_unignore(currentServer, currentTab, args):
	"""
		Unignores a user pattern.

		Usage: /unignore <pattern>
	"""
	if not args:
		return gui.myPrint("Usage: /unignore <pattern>")
	if not currentServer:
		return gui.myPrint("Could not determine server")

	sushi.unignore(currentServer.name, args[0])
	gui.mgmt.myPrint(_("Unignoring %(user)s.") % {"user":args[0]} )


def cmd_ignores(currentServer, currentTab, args):
	"""
		Shows all ignored users.

		Usage: /ignores
	"""
	if not currentServer:
		return gui.myPrint("Could not determine server")
	ignores = sushi.ignores(currentServer.name)

	if not ignores:
		gui.mgmt.myPrint(_("No users are ignored on this server."))
	else:
		gui.mgmt.myPrint(_("The following users are ignored: %(ulist)s.") % {
			"ulist": ", ".join(ignores)} )


def setup():
	_commands = {
		"connect" : cmd_connect,
		"nick" : cmd_nick,
		"part" : cmd_part,
		"join" : cmd_join,
			"j" : cmd_join,
		"me"   : cmd_action,
		"kick" : cmd_kick,
		"mode" : cmd_mode,
		"topic": cmd_topic,
		"cleartopic" : cmd_cleartopic,
		"quit" : cmd_quit,
		"away" : cmd_away,
		"back" : cmd_back,
	"nickserv" : cmd_nickserv,
		"ctcp" : cmd_ctcp,
	"invoke_test": cmd_invoke_test,
	"ignore" : cmd_ignore,
	"unignore": cmd_unignore,
	"ignores" : cmd_ignores,
		"names" :  cmd_names,
		"notice" : cmd_notice,
		"msg" : cmd_message,
		"oper" : cmd_oper,
		"list" : cmd_list,
		"raw" : cmd_raw,
		"stoplist" : cmd_stop_list,
		"whois" : cmd_whois,
		"query":  cmd_query,
		"clear":  cmd_clear,
		"help":  cmd_help
	}

	for (cmd,hdl) in _commands.items():
		commands.add_command(cmd, hdl)
