import socket, re, sys
from codecs import encode, decode
from . import shared

def get_whois_raw(domain, server="", previous=None, rfc3490=True, never_cut=False, with_server_list=False, server_list=None):
	previous = previous or []
	server_list = server_list or []
	# Sometimes IANA simply won't give us the right root WHOIS server
	exceptions = {
		".ac.uk": "whois.ja.net",
		".ps": "whois.pnina.ps",
		".buzz": "whois.nic.buzz",
		".moe": "whois.nic.moe",
		# The following is a bit hacky, but IANA won't return the right answer for example.com because it's a direct registration.
		"example.com": "whois.verisign-grs.com"
	}
	
	if rfc3490:
		if sys.version_info < (3, 0):
			domain = encode( domain if type(domain) is unicode else decode(domain, "utf8"), "idna" )
		else:
			domain = encode(domain, "idna").decode("ascii")

	if len(previous) == 0 and server == "":
		# Root query
		is_exception = False
		for exception, exc_serv in exceptions.items():
			if domain.endswith(exception):
				is_exception = True
				target_server = exc_serv
				break
		if is_exception == False:
			target_server = get_root_server(domain)
	else:
		target_server = server
	if target_server == "whois.jprs.jp":
		request_domain = "%s/e" % domain # Suppress Japanese output
	elif domain.endswith(".de") and ( target_server == "whois.denic.de" or target_server == "de.whois-servers.net" ):
		request_domain = "-T dn,ace %s" % domain # regional specific stuff
	elif target_server == "whois.verisign-grs.com":
		request_domain = "=%s" % domain # Avoid partial matches
	else:
		request_domain = domain
	response = whois_request(request_domain, target_server)
	if never_cut:
		# If the caller has requested to 'never cut' responses, he will get the original response from the server (this is
		# useful for callers that are only interested in the raw data). Otherwise, if the target is verisign-grs, we will
		# select the data relevant to the requested domain, and discard the rest, so that in a multiple-option response the
		# parsing code will only touch the information relevant to the requested domain. The side-effect of this is that
		# when `never_cut` is set to False, any verisign-grs responses in the raw data will be missing header, footer, and
		# alternative domain options (this is handled a few lines below, after the verisign-grs processing).
		new_list = [response] + previous
	if target_server == "whois.verisign-grs.com":
		# VeriSign is a little... special. As it may return multiple full records and there's no way to do an exact query,
		# we need to actually find the correct record in the list.
		for record in response.split("\n\n"):
			if re.search("Domain Name: %s\n" % domain.upper(), record):
				response = record
				break
	if never_cut == False:
		new_list = [response] + previous
	server_list.append(target_server)
	for line in [x.strip() for x in response.splitlines()]:
		match = re.match("(refer|whois server|referral url|whois server|registrar whois):\s*([^\s]+\.[^\s]+)", line, re.IGNORECASE)
		if match is not None:
			referal_server = match.group(2)
			if referal_server != server and "://" not in referal_server: # We want to ignore anything non-WHOIS (eg. HTTP) for now.
				# Referal to another WHOIS server...
				return get_whois_raw(domain, referal_server, new_list, server_list=server_list, with_server_list=with_server_list)
	if with_server_list:
		return (new_list, server_list)
	else:
		return new_list
	
def get_root_server(domain):
	if domain is not None and '.' in domain:
		data = whois_request(domain, "whois.iana.org")
		for line in [x.strip() for x in data.splitlines()]:
			match = re.match("refer:\s*([^\s]+)", line)
			if match is None:
				continue
			return match.group(1)
		raise shared.WhoisException("No root WHOIS server found for domain %s" % domain)
	else:
		raise shared.WhoisException("Invalid domain supplied")
	
def whois_request(domain, server, port=43):
	sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	# Timeout in seconds
	sock.settimeout(5)
	# Buffer size
	buff_size = 1024
	data = b""
	
	try:
		sock.connect((server, port))
		# Set a 5 second timeout
		sock.settimeout(5)
		sock.send(("%s\r\n" % domain).encode("utf-8"))
		while True:
			buff = sock.recv(buff_size)
			if buff:
				data += buff
			else:
				break
			if len(buff) < buff_size:
				# len=0 or end of data
				break
		# Close the socket to prevent CLOSE_WAIT state and hung connections
		sock.close()
	except socket.error as exc:
		if sock:
			sock.close()
		raise shared.WhoisException("Caught exception socket.error : %s" % exc)
	return data.decode("utf-8")
