"""	An :py:class:`~aleph.reactor.Handler` for the IRC protocol. """

import socket

from aleph.protocols.generic import Handler
from aleph.utils.log import getlog

from events import events
from parser import parse_line, format_line

class Client (Handler):
	"""
	An event handler that communicates with a server
	
	@sort: on_*
	"""
	
	def __init__ (self, address, port):
		"""	Prepare configuration """
		self.log = getlog(self)
		
		self.address = address	#: The servers address
		self.port = port		#: The servers port
		
		self.socket = None		#: The socket that has a connection to the server
		self._buffer = ""		#: The primary buffer for incoming data
		self.buffer = list()	#: The secondary buffer for lines read from the primary buffer
	
	def fileno (self):
		return self.socket.fileno()
	
	def on_connect (self):
		"""	Connect to the server """
		self.socket = socket.socket()
		self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
		self.socket.connect((self.address, self.port))

	def on_connect_fail (self, exception):
		"""	The connection failed """
		self.log.error("Could not connect to {}:{}".format(self.address, self.port))
		raise exception
		
	#: The characters to use to split lines in the primary buffer
	newline = "\r\n"	
	
	def on_input (self):
		"""
		Read socket input
		
		This function must be overwritten.
		
		However, super().on_input() may be useful so as
		to not reimplement reading the lines from the socket
		"""
		self._buffer += self.socket.recv(1024)
		
		# While the server has completed lines in the
		# primary buffer, read a new line from it
		while self.newline in self._buffer:
			line, self._buffer = self._buffer.split(self.newline,1)
			self.buffer.append(line)
	
	def write (self, line):
		"""
		Send data to the server
		
		:Parameters:
			- `line` (str): The line to send
		"""
		self.socket.send(line + self.newline)
	
	def on_disconnect (self):
		"""
		The socket was disconnected
		
		Shutdown and close the socket, ignoring any errors.
		"""
		try:
			self.socket.shutdown(socket.SHUT_RDWR)
		except socket.error:
			self.log.warning("Socket could not be shut down.")
		
		try:
			self.socket.close()
		except socket.error:
			self.log.warning("Socket could not be closed, it may have been closed already.")
		
		del self.socket

class Commands (object):
	""" Functions that send a client->server IRC command. """
	
	def write (self, *args):
		"""	Format an irc message and send it """
		super(Commands, self).write(format_line(*args))

	""" 4.1 - Connection Registration """

	def nick (self, nick):
		"""	4.1.2 """
		self.write('NICK', [nick])

	def user (self, user, real):
		"""	4.1.3 """
		self.write('USER', [user, "''", "''"], real)

	def quit (self, message):
		"""	4.1.6 """
		self.write('QUIT', [], message)

	""" 4.2 - Channel Operations """

	def join (self, channel, key = None):
		"""	4.2.1 """
		self.write('JOIN', [channel, key] if key else [channel])

	def part (self, channel):
		""" 4.2.2 """
		self.write('PART', channel)

	""" 4.4 Sending messages """

	def privmsg (self, to, message):
		"""	4.4.1 """
		for line in message.split("\n"):
			self.write('PRIVMSG', [str(to)], line)

	def notice (self, to, message):
		""" 4.4.2 """
		self.write('NOTICE', [to], message)

class IRCClient (Commands, Client):
	"""
	A client interfacing with the IRC protocol
	
	Implements the `Client` `Handler`.
	
	@sort: on_*
	"""
	
	#: The name of the protocol provided by this event handler
	__protocol__ = 'irc'
	
	def __init__ (self, config):
		"""
		Create an IRCClient object from a configuration dictionary.
		
		Valid configuration options include:
			- ``'address': <address>`` (required)
			- ``'port': 6667``
		
		:Parameters:
			- `config` (dict): A dictionary of configuration key->values.
		"""
		super(IRCClient, self).__init__(config.pop('address'), config.pop('port', 6667))
		
		#: The clients configuration
		self.config = config
		
		#: Temporary data store
		self.tmp = dict()

	def on_connect_complete (self):
		"""	Login to the server once a connection has been made """
		self.login()
		
	def on_input (self, reactor):
		"""	Yield incoming `Message` objects """
		super(IRCClient, self).on_input()
		while self.buffer:
			line = self.buffer.pop(0)

			# Skip the line if it is a PING
			if line.startswith('PING :'):
				self.socket.send("PONG :" + line[6:] + "\r\n")
				break
			
			# Handler disconnections
			if line.startswith('ERROR :'):
				reactor.disconnect(self)
				break
			
			yield self.parse(handler=self, **parse_line(line))
	
	def parse (self, **message):
		"""
		Call the object associated with the message's command
		
		..seealso: :py:data:`~events.events`
		"""
		try:
			tag, callable = events[message['command']]
		except KeyError:
			tag, callable = events['default']
		
		# Return the result of calling the object
		return tag, self.__protocol__, callable(**message)
	
	def nick (self, nick):
		super(IRCClient, self).nick(nick)
		self.config['nick'] = nick

	#: The default nickname
	_default_nickname = "AlephBot"
	
	#: The default username
	_default_username = "aleph"
	
	#: The default realname
	_default_realname = "Aleph irc bot - https://github.com/ziaix/aleph/"
	
	def login (self):
		"""	Login to an irc server with information from self.config """
		self.nick(self.config.get('nick', IRCClient._default_nickname))
		self.user(
			self.config.get('user', IRCClient._default_username),
			self.config.get('real', IRCClient._default_realname)
		)

		# Nickserv identification
		if 'nickserv-pass' in self.config:
			self.privmsg(
				self.config.get('nickserv-name',"NickServ"),
				"IDENTIFY %s" % self.config.get('nickserv-pass'))

		# Channel
		for channel in self.config.get('channels', []):
			self.join(channel)
			
	def __repr__ (self):
		return "<IRC client - {}:{}>".format(self.address, self.port)
