import socket, re, json, argparse, emoji, csv
# from decouple import config

class DefaultUser(Exception):
	"""Raised when you try send a message with the default user"""
	pass

class CallbackFunction(Exception):
	"""Raised when the callback function does not have (only) one required positional argument"""
	pass

class TwitchChatIRC():
	__HOST = 'irc.chat.twitch.tv'
	__DEFAULT_NICK = 'justinfan67420'
	__DEFAULT_PASS = 'SCHMOOPIIE'
	__PORT = 6667

	__PATTERN = re.compile(r'@(.+?(?=\s+:)).*PRIVMSG[^:]*:([^\r\n]*)')

	__CURRENT_CHANNEL = None

	def __init__(self, username = None, password = None):

		self.__NICK = self.__DEFAULT_NICK
		self.__PASS = self.__DEFAULT_PASS

		# overwrite if specified
		if(username is not None):
			self.__NICK = username
		if(password is not None):
			self.__PASS = 'oauth:'+str(password).lstrip('oauth:')
		
		# create new socket
		self.__SOCKET = socket.socket()
		
		# start connection
		self.__SOCKET.connect((self.__HOST, self.__PORT))
		print('Connected to',self.__HOST,'on port',self.__PORT)

		# log in
		self.__send_raw('CAP REQ :twitch.tv/tags')
		self.__send_raw('PASS ' + self.__PASS)
		self.__send_raw('NICK ' + self.__NICK)
	
	def __send_raw(self, string):
		self.__SOCKET.send((string+'\r\n').encode('utf-8'))

	def __print_message(self, message):
		print('['+message['tmi-sent-ts']+']',message['display-name']+':',emoji.demojize(message['message']).encode('utf-8').decode('utf-8','ignore'))

	def __recvall(self, buffer_size):
		data = b''
		while True:
			part = self.__SOCKET.recv(buffer_size)
			data += part
			if len(part) < buffer_size:
				break
		return data.decode('utf-8')#,'ignore'

	def __join_channel(self,channel_name):
		channel_lower = channel_name.lower()

		if(self.__CURRENT_CHANNEL != channel_lower):
			self.__send_raw('JOIN #{}'.format(channel_lower))
			self.__CURRENT_CHANNEL = channel_lower

	def is_default_user(self):
		return self.__NICK == self.__DEFAULT_NICK

	def close_connection(self):
		self.__SOCKET.close()
		print('Connection closed')

	def listen(self, channel_name, messages = [], timeout=None, message_timeout=1.0, on_message = None, buffer_size = 4096, message_limit = None, output=None):
		self.__join_channel(channel_name)
		self.__SOCKET.settimeout(message_timeout)

		if(on_message is None):
			on_message = self.__print_message
		
		print('Begin retrieving messages:')

		time_since_last_message = 0
		readbuffer = ''
		try:
			while True:
				try:
					new_info = self.__recvall(buffer_size)
					readbuffer += new_info
					
					if('PING :tmi.twitch.tv' in readbuffer):
						self.__send_raw('PONG :tmi.twitch.tv')

					matches = list(self.__PATTERN.finditer(readbuffer))

					if(matches):

						time_since_last_message = 0

						if(len(matches) > 1):
							matches = matches[:-1] # assume last one is incomplete

						last_index = matches[-1].span()[1]
						readbuffer = readbuffer[last_index:]

						for match in matches:
							
							data = {}
							for item in match.group(1).split(';'):
								keys = item.split('=',1)
								data[keys[0]]=keys[1]
							data['message'] = match.group(2)
							print(data)

							messages.append(data)

							if(callable(on_message)):
								try:
									on_message(data)
								except TypeError:
									raise Exception('Incorrect number of parameters for function '+on_message.__name__)
							
							if(message_limit is not None and len(messages) >= message_limit):
								return messages
							
				except socket.timeout:
					if(timeout != None):
						time_since_last_message += message_timeout
					
						if(time_since_last_message >= timeout):
							print('No data received in',timeout,'seconds. Timing out.')
							break
		
		except KeyboardInterrupt:
			print('Interrupted by user.')
			
		except Exception as e:
			print('Unknown Error:',e)
			raise e		
		
		return messages

	def send(self, channel_name, message):
		self.__join_channel(channel_name)
 
		# check that is using custom login, not default
		if(self.is_default_user()):
			raise DefaultUser
		else:
			self.__send_raw('PRIVMSG #{} :{}'.format(channel_name.lower(),message))
			print('Sent "{}" to {}'.format(message,channel_name))


if __name__ == '__main__':
	parser = argparse.ArgumentParser(description='Send and receive Twitch chat messages over IRC with python web sockets. For more info, go to https://dev.twitch.tv/docs/irc/guide')

	parser.add_argument('channel_name', help='Twitch channel name (username)')
	parser.add_argument('-timeout','-t', default=None, type=float, help='time in seconds needed to close connection after not receiving any new data (default: None = no timeout)')
	parser.add_argument('-message_timeout','-mt', default=1.0, type=float, help='time in seconds between checks for new data (default: 1 second)')
	parser.add_argument('-buffer_size','-b', default=4096, type=int, help='buffer size (default: 4096 bytes = 4 KB)')
	parser.add_argument('-message_limit','-l', default=None, type=int, help='maximum amount of messages to get (default: None = unlimited)')
	
	parser.add_argument('-username','-u', default=None, help='username (default: None)')
	parser.add_argument('-oauth', '-password','-p', default=None, help='oath token (default: None). Get custom one from https://twitchapps.com/tmi/')
	
	parser.add_argument('--send', action='store_true', help='send mode (default: False)')
	parser.add_argument('-output','-o', default=None, help='output file (default: None = print to standard output)')

	args = parser.parse_args()
	
	twitch_chat_irc = TwitchChatIRC(username=args.username,password=args.oauth)

	if(args.send):
		if(twitch_chat_irc.is_default_user()):
			print('Unable to send messages with default user. Please provide valid authentication.')
		else:
			try:
				while True:
					message = input('>>> Enter message (blank to exit): \n')
					if(not message):
						break
					twitch_chat_irc.send(args.channel_name, message)
			except KeyboardInterrupt:
				print('\nInterrupted by user.')

	else:
		messages = twitch_chat_irc.listen(
			args.channel_name,
			timeout=args.timeout,
			message_timeout=args.message_timeout,
			buffer_size=args.buffer_size,
			message_limit=args.message_limit)

		if(args.output != None):
			if(args.output.endswith('.json')):
				with open(args.output, 'w') as fp:
					json.dump(messages, fp)
			elif(args.output.endswith('.csv')):
				with open(args.output, 'w', newline='',encoding='utf-8') as fp:
					fieldnames = []
					for message in messages:
						fieldnames+=message.keys()

					if(len(messages)>0):
						fc = csv.DictWriter(fp,fieldnames=list(set(fieldnames)))
						fc.writeheader()
						fc.writerows(messages)
			else:
				f = open(args.output,'w', encoding='utf-8')
				for message in messages:
					print('['+message['tmi-sent-ts']+']',message['display-name']+':',message['message'],file=f)
				f.close()

			print('Finished writing',len(messages),'messages to',args.output)
		
	twitch_chat_irc.close_connection()