################################################################################
#	  Copyright (C) 2019 drinfernoo										   #
#																			  #
#  This Program is free software; you can redistribute it and/or modify		#
#  it under the terms of the GNU General Public License as published by		#
#  the Free Software Foundation; either version 2, or (at your option)		 #
#  any later version.														  #
#																			  #
#  This Program is distributed in the hope that it will be useful,			 #
#  but WITHOUT ANY WARRANTY; without even the implied warranty of			  #
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the				#
#  GNU General Public License for more details.								#
#																			  #
#  You should have received a copy of the GNU General Public License		   #
#  along with XBMC; see the file COPYING.  If not, write to					#
#  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.	   #
#  http://www.gnu.org/copyleft/gpl.html										#
################################################################################

import xbmc
import xbmcgui

import hashlib
import math
import os
import re
import socket
import sys
import threading
import timeit

import xml.etree.ElementTree as ET
from xml.dom import minidom as DOM

try:
	from urllib.request import urlopen
	from urllib.request import Request
	from urllib.request import HTTPError
	from urllib.request import URLError
	from urllib.parse import urlparse
	from urllib.parse import parse_qs
	from http.client import HTTPConnection
	from http.client import HTTPSConnection
	from queue import Queue
except ImportError:
	from urllib2 import urlopen
	from urllib2 import Request
	from urllib2 import HTTPError
	from urllib2 import URLError
	from urlparse import urlparse
	from urlparse import parse_qs
	from httplib import HTTPConnection
	from httplib import HTTPSConnection
	from Queue import Queue

__version__ = '0.3.5'
user_agent = 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36 SE 2.X MetaSr 1.0'
source = None
shutdown_event = None
scheme = 'http'
socket_socket = socket.socket


class SpeedtestCliServerListError(Exception):
	"""
"""


def bound_socket(*args, **kwargs):
	global source
	sock = socket_socket(*args, **kwargs)
	sock.bind((source, 0))
	return sock


def distance(origin, destination):
	(lat1, lon1) = origin
	(lat2, lon2) = destination
	radius = 6371  # km

	dlat = math.radians(lat2 - lat1)
	dlon = math.radians(lon2 - lon1)
	a = math.sin(dlat / 2) * math.sin(dlat / 2) \
		+ math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) \
		* math.sin(dlon / 2) * math.sin(dlon / 2)
	c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
	d = radius * c

	return d


def build_request(url, data=None, headers={}):
	if url[0] == ':':
		schemed_url = '{0}{1}'.format(scheme, url)
	else:
		schemed_url = url
	headers['User-Agent'] = user_agent
	return Request(schemed_url, data=data, headers=headers)


def catch_request(request):
	try:
		uh = urlopen(request)
		return uh
	except (HTTPError, URLError, socket.error):
		e = sys.exc_info()[1]
		xbmc.log("Speedtest Error: {0}".format(e), level=xbmc.LOGDEBUG)

		return None, e


class FileGetter(threading.Thread):

	def __init__(self, url, start):
		self.url = url
		self.result = None
		self.starttime = start
		threading.Thread.__init__(self)

	def run(self):
		self.result = [0]
		try:
			if timeit.default_timer() - self.starttime <= 10:
				request = build_request(self.url)
				f = urlopen(request)
				while 1 and not shutdown_event.is_set():
					self.result.append(len(f.read(10240)))
					if self.result[-1] == 0:
						break
				f.close()
		except IOError:
			pass


def download_speed(files, quiet=False):
	start = timeit.default_timer()

	def producer(q, files):
		for file in files:
			thread = FileGetter(file, start)
			thread.start()
			q.put(thread, True)
			if not quiet and not shutdown_event.is_set():
				sys.stdout.write('.')
				sys.stdout.flush()

	finished = []

	def consumer(q, total_files):
		while len(finished) < total_files:
			thread = q.get(True)
			while thread.is_alive():
				thread.join(timeout=0.1)
			finished.append(sum(thread.result))
			del thread

	q = Queue(6)
	prod_thread = threading.Thread(target=producer, args=(q, files))
	cons_thread = threading.Thread(target=consumer, args=(q, len(files)))
	start = timeit.default_timer()
	prod_thread.start()
	cons_thread.start()

	while prod_thread.is_alive():
		prod_thread.join(timeout=0.1)

	while cons_thread.is_alive():
		cons_thread.join(timeout=0.1)

	return sum(finished) / (timeit.default_timer() - start)


class FilePutter(threading.Thread):

	def __init__(self, url, start, size):
		self.url = url
		chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
		data = chars * int(round(int(size) / 36.0))
		self.data = ('content1={0}'.format(data[0:int(size) - 9])).encode('utf-8')
		del data
		self.result = None
		self.starttime = start
		threading.Thread.__init__(self)

	def run(self):
		try:
			if timeit.default_timer() - self.starttime <= 10 and not shutdown_event.is_set():
				request = build_request(self.url, data=self.data)
				f = urlopen(request)
				f.read(11)
				f.close()
				self.result = len(self.data)
			else:
				self.result = 0
		except IOError:
			self.result = 0


def upload_speed(url, sizes, quiet=False):
	start = timeit.default_timer()

	def producer(q, sizes):
		for size in sizes:
			thread = FilePutter(url, start, size)
			thread.start()
			q.put(thread, True)
			if not quiet and not shutdown_event.is_set():
				sys.stdout.write('.')
				sys.stdout.flush()

	finished = []

	def consumer(q, total_sizes):
		while len(finished) < total_sizes:
			thread = q.get(True)
			while thread.is_alive():
				thread.join(timeout=0.1)
			finished.append(thread.result)
			del thread

	q = Queue(6)
	prod_thread = threading.Thread(target=producer, args=(q, sizes))
	cons_thread = threading.Thread(target=consumer, args=(q,
								   len(sizes)))
	start = timeit.default_timer()
	prod_thread.start()
	cons_thread.start()

	while prod_thread.is_alive():
		prod_thread.join(timeout=0.1)

	while cons_thread.is_alive():
		cons_thread.join(timeout=0.1)

	return sum(finished) / (timeit.default_timer() - start)


def get_attributes_by_tag_name(dom, tagName):
	elem = dom.getElementsByTagName(tagName)[0]
	return dict(list(elem.attributes.items()))


def get_config():
	request = build_request('http://www.speedtest.net/speedtest-config.php')
	uh = catch_request(request)
	if uh is False:
		xbmc.log('Could not retrieve speedtest.net configuration: {0}'.format(uh), level=xbmc.LOGDEBUG)
		sys.exit(1)

	configxml = []

	while 1:
		configxml.append(uh.read(10240))
		if len(configxml[-1]) == 0:
			break
	if int(uh.code) != 200:
		return None
	uh.close()
	try:
		try:
			root = ET.fromstring(''.encode('utf-8').join(configxml))
			config = {
				'client': root.find('client').attrib,
				'times': root.find('times').attrib,
				'download': root.find('download').attrib,
				'upload': root.find('upload').attrib,
				}
		except Exception:
			root = DOM.parseString(''.join(configxml))
			config = {
				'client': get_attributes_by_tag_name(root, 'client'),
				'times': get_attributes_by_tag_name(root, 'times'),
				'download': get_attributes_by_tag_name(root, 'download'),
				'upload': get_attributes_by_tag_name(root, 'upload'),
				}
	except SyntaxError:
		xbmc.log('Failed to parse speedtest.net configuration', level=xbmc.LOGDEBUG)
		sys.exit(1)

	del root
	del configxml
	return config


def closest_servers(client, all=False):
	urls = ['http://www.speedtest.net/speedtest-servers-static.php',
			'https://www.speedtest.net/speedtest-servers-static.php']
	errors = []
	servers = {}
	for url in urls:
		try:
			request = build_request(url)
			uh = catch_request(request)
			if uh is False:
				errors.append('{0}'.format(uh))
				raise SpeedtestCliServerListError
			serversxml = []
			while 1:
				serversxml.append(uh.read(10240))
				if len(serversxml[-1]) == 0:
					break
			if int(uh.code) != 200:
				uh.close()
				raise SpeedtestCliServerListError
			uh.close()
			try:
				try:
					root = ET.fromstring(''.encode().join(serversxml))
					elements = root.getiterator('server')
				except Exception:
					try:
						serversxml =[a.decode() for a in serversxml]
					except:
						pass
					root = DOM.parseString(''.join(serversxml))
					elements = root.getElementsByTagName('server')
			except SyntaxError:
				raise SpeedtestCliServerListError
			for server in elements:
				try:
					attrib = server.attrib
				except AttributeError:
					attrib = dict(list(server.attributes.items()))
				d = distance([float(client['lat']), float(client['lon'])], [float(attrib.get('lat')),
																			float(attrib.get('lon'))])
				attrib['d'] = d
				if d not in servers:
					servers[d] = [attrib]
				else:
					servers[d].append(attrib)

			del root
			del serversxml
			del elements
		except SpeedtestCliServerListError:
			continue
		if servers:
			break
	if not servers:
		xbmc.log('Failed to retrieve list of speedtest.net servers: {0}'.format('\n'.join(errors)), level=xbmc.LOGDEBUG)
		sys.exit(1)
	closest = []
	for d in sorted(servers.keys()):
		for s in servers[d]:
			closest.append(s)
			if len(closest) == 5 and not all:
				break
		else:
			continue
		break
	del servers
	return closest


def get_best_server(servers):
	results = {}
	for server in servers:
		cum = []
		url = '{0}/latency.txt'.format(os.path.dirname(server['url']))
		urlparts = urlparse(url)
		for i in range(0, 3):
			try:
				if urlparts[0] == 'https':
					h = HTTPSConnection(urlparts[1])
				else:
					h = HTTPConnection(urlparts[1])
				headers = {'User-Agent': user_agent}
				start = timeit.default_timer()
				h.request('GET', urlparts[2], headers=headers)
				r = h.getresponse()
				total = timeit.default_timer() - start
			except (HTTPError, URLError, socket.error):
				cum.append(3600)
				continue
			text = r.read(9)
			if int(r.status) == 200 and text == 'test=test'.encode():
				cum.append(total)
			else:
				cum.append(3600)
			h.close()
		avg = round(sum(cum) / 6 * 1000, 3)
		results[avg] = server
	fastest = sorted(results.keys())[0]
	best = results[fastest]
	best['latency'] = fastest
	return best


def ctrl_c():
	global shutdown_event
	shutdown_event.set()
	raise SystemExit('\nCancelling...')


def version():
	raise SystemExit(__version__)


def speedtest(list=False, mini=None, server=None, share=True, simple=False, src=None, timeout=10, units=('bit', 8), version=False):
	global shutdown_event, source, scheme
	shutdown_event = threading.Event()
	global line1, line2, line3

	dp = xbmcgui.DialogProgress()
	line1 = '[COLOR {0}]Starting test..[/COLOR]'.format('orange')
	dp.create('{0}: [COLOR {1}]Speed Test[/COLOR]'.format('TOOLS', 'yellow'), line1)
	dp.update(0)
	xbmc.log('Retrieving speedtest.net configuration...', level=xbmc.LOGDEBUG)
	line2 = '[COLOR {0}]Retrieving speedtest.net configuration...[/COLOR]'.format('orange')
	dp.update(2, line1+'\n'+line2)
	try:
		config = get_config()
	except URLError as e:
		xbmc.log('Cannot retrieve speedtest configuration: {0}'.format(e), level=xbmc.LOGDEBUG)
		sys.exit(1)

	xbmc.log('Retrieving speedtest.net server list...', level=xbmc.LOGDEBUG)
	line3 = '[COLOR {0}]Retrieving speedtest.net server list...[/COLOR]'.format('orange')
	dp.update(4, line1+'\n'+line2+'\n'+line3)

	servers = closest_servers(config['client'])

	xbmc.log('Testing from %(isp)s (%(ip)s)...' % config['client'], level=xbmc.LOGDEBUG)
	line1 = '[COLOR ' + 'orange' + ']Testing From:[/COLOR] [COLOR ' \
		+ 'yellow' + ']%(isp)s (%(ip)s)[/COLOR]' % config['client']
	dp.update(6, line1)

	xbmc.log('Selecting best server based on latency...', level=xbmc.LOGDEBUG)
	line2 = '[COLOR {0}]Selecting best server based on latency...[/COLOR]'.format('orange')
	dp.update(8, '\n'+line2)
	best = get_best_server(servers)

	xbmc.log('Hosted by %(sponsor)s (%(name)s) [%(d)0.2f km]: %(latency)s ms' % best)

	line2 = ('[COLOR ' + 'orange'
			 + ']Server location: %(name)s [%(d)0.2f km]: %(latency)s ms[/COLOR]' % best)
	dp.update(10, '\n'+line2)

	sizes = [350, 500, 750, 1000, 1500, 2000, 2500, 3000, 3500, 4000]
	urls = []
	for size in sizes:
		for i in range(0, 4):
			urls.append('{0}/random{1}x{2}.jpg'.format(os.path.dirname(best['url']), size, size))

	xbmc.log('Testing download speed', level=xbmc.LOGDEBUG)
	line3 = '[COLOR {0}]Testing download speed...[/COLOR]'.format('orange')
	dp.update(15, '\n\n'+line3)
	dlspeed = download_speed(urls, simple)

	xbmc.log('Download: %0.2f M%s/s' % (dlspeed / 1000 / 1000 * units[1], units[0]))

	sizesizes = [int(.25 * 1000 * 1000), int(.5 * 1000 * 1000)]
	sizes = []
	for size in sizesizes:
		for i in range(0, 25):
			sizes.append(size)

	xbmc.log('[COLOR red]Testing upload speed[/COLOR]', level=xbmc.LOGDEBUG)
	line2 = '[COLOR %s]Testing download speed:[/COLOR] [COLOR %s]%0.2f M%s/s[/COLOR]' % ('orange', 'yellow', dlspeed / 1000 / 1000 * units[1], units[0])
	line3 = '[COLOR {0}]Testing upload speed...[/COLOR]'.format('orange')
	dp.update(65, '\n'+line2+'\n'+line3)
	ulspeed = upload_speed(best['url'], sizes, simple)

	xbmc.log('Upload: %0.2f M%s/s' % (ulspeed / 1000 / 1000 * units[1], units[0]))

	i = 2
	while ulspeed < 1:

		dp.update(65, '', '', '[COLOR ' + 'orange'
				  + ']Testing upload speed... [Attempt [/COLOR]'
				  + str(i) + ']')
		ulspeed = upload_speed(best['url'], sizes, simple)
		xbmc.log('Upload: %0.2f M%s/s' % (ulspeed / 1000 / 1000 * units[1], units[0]))
		i = i + 1
		if i == 6:
			return uploadfail

	line1 = line2
	line2 = '[COLOR %s]Testing upload speed:[/COLOR] [COLOR %s]%0.2f M%s/s[/COLOR]' % ('orange', 'yellow', ulspeed / 1000 / 1000 * units[1], units[0])
	line3 = '[COLOR %s]Getting results...[/COLOR]' % 'orange'
	dp.update(95, line1+'\n'+line2+'\n'+line3)

	if share:
		dlspeedk = int(round(dlspeed / 1000 * 8, 0))
		ping = int(round(best['latency'], 0))
		ulspeedk = int(round(ulspeed / 1000 * 8, 0))
		apiData = [
			'download={0}'.format(dlspeedk),
			'ping={0}'.format(ping),
			'upload={0}'.format(ulspeedk),
			'promo=',
			'startmode={0}'.format('pingselect'),
			'recommendedserverid={0}'.format(best['id']),
			'accuracy=1',
			'serverid={0}'.format(best['id']),
			'hash={0}'.format(hashlib.md5('{0}-{1}-{2}-{3}'.format(ping, ulspeedk, dlspeedk, '297aae72').encode('utf-8')).hexdigest())
			]

		headers = {'Referer': 'http://c.speedtest.net/flash/speedtest.swf'}
		request = build_request('http://www.speedtest.net/api/api.php',
								data='&'.join(apiData).encode(),
								headers=headers)
		f = catch_request(request)
		if f is False:
			xbmc.log('Could not submit results to speedtest.net: {0}'.format(e), level=xbmc.LOGDEBUG)
			sys.exit(1)
		response = f.read()
		code = f.code
		f.close()

		if int(code) != 200:
			xbmc.log('Could not submit results to speedtest.net', level=xbmc.LOGDEBUG)
			sys.exit(1)

		qsargs = parse_qs(response.decode())
		resultid = qsargs.get('resultid')
		if not resultid or len(resultid) != 1:
			xbmc.log('Could not submit results to speedtest.net', level=xbmc.LOGDEBUG)
			sys.exit(1)

		xbmc.log('Share results: {0}://www.speedtest.net/result/{1}.png'.format(scheme, resultid[0]), level=xbmc.LOGDEBUG)

		dp.close()

		curserver = '%(name)s [%(d)0.2f km]: %(latency)s ms' % best

		return ('{0}://www.speedtest.net/result/{1}.png'.format(scheme, resultid[0]), dlspeed / 1000 / 1000 * units[1],
				units[0], ulspeed / 1000 / 1000 * units[1], units[0], ping, curserver)


def net_info():
	import json
	from resources.libs.common import logging

	infoLabel = ['Network.IPAddress',
				 'Network.MacAddress']
	data = []
	x = 0
	for info in infoLabel:
		temp = tools.get_info_label(info)
		y = 0
		while temp == "Busy" and y < 10:
			temp = tools.get_info_label(info)
			y += 1
			xbmc.log("{0} sleep {1}".format(info, str(y)))
			xbmc.sleep(200)
		data.append(temp)
		x += 1
	try:
		url = 'http://extreme-ip-lookup.com/json/'
		req = Request(url)
		req.add_header('User-Agent',
					   'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3')
		response = urlopen(req)
		geo = json.load(response)
	except:
		url = 'http://ip-api.com/json'
		req = Request(url)
		req.add_header('User-Agent',
					   'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3')
		response = urlopen(req)
		geo = json.load(response)
	mac = data[1]
	inter_ip = data[0]
	ip = geo['query']
	isp = geo['org']
	city = geo['city']
	country = geo['country']
	state = geo['region']
	return mac, inter_ip, ip, city, state, country, isp


def get_ip():
	from resources.libs.common import tools

	site = 'http://whatismyipaddress.com/'
	response = tools.open_url(site)

	if not response:
		return 'Unknown', 'Unknown', 'Unknown'

	page = response.text.replace('\n', '').replace('\r', '')

	if 'Access Denied' not in page:
		ipmatch = re.compile('whatismyipaddress.com/ip/(.+?)"').findall(page)
		ipfinal = ipmatch[0] if (len(ipmatch) > 0) else 'Unknown'
		details = re.compile('"font-size:14px;">(.+?)</td>').findall(page)
		provider = details[0] if (len(details) > 0) else 'Unknown'
		location = details[1]+', '+details[2]+', '+details[3] if (len(details) > 2) else 'Unknown'
		return ipfinal, provider, location
	else:
		return 'Unknown', 'Unknown', 'Unknown'


def main():
	try:
		speedtest()
	except KeyboardInterrupt:
		xbmc.log('\nCancelling...', level=xbmc.LOGDEBUG)
		dp = xbmcgui.DialogProgress()
		dp.close()
		sys.exit()


if __name__ == '__main__':
	main()
