import discord
import asyncio
import urllib.request				#importing modules used
import json
import re
import random

fp_osuapikey = open("apikey.txt", "r")
osuapikey = fp_osuapikey.read()					#reading personal osu apikey from apikey.txt
fp_osuapikey.close()

@asyncio.coroutine
def runComm(msg, client):

	if msg.content.startswith("="):			#Find whether the command was a dev command
		devrequest = False
		print("---COMMAND---\n{}: {}".format(msg.author.name, msg.content))
		yield from client.send_message(discord.Channel(id=136514512151904257), "**-----COMMAND----**\n*{}*: {}".format(msg.author.name, msg.content))
	elif msg.content.startswith("|") and msg.author.id == "111342297999802368":	#if author is Syrin
		devrequest = True
		print("---DEV_COMMAND---\n{}: {}".format(msg.author.name, msg.content))
		yield from client.send_message(discord.Channel(id=136514512151904257), "**-----DEVCOMMAND----**\n*{}*: {}".format(msg.author.name, msg.content))
	elif msg.content.find("osu.ppy.sh/") != -1:
		print("---URLREQUEST---\n{}: {}".format(msg.author.name, msg.content))
		yield from client.send_message(discord.Channel(id=136514512151904257), "**-----URLREQUEST----**\n*{}*: {}".format(msg.author.name, msg.content))
		yield from runUrl(msg, client)
	elif (msg.content.find("http://discord.gg/") != -1 or msg.content.find("https://discord.gg/") != -1) and msg.server is None:
		print("---INVITE_REQUEST---\n{}: {}".format(msg.author.name, msg.content))
		yield from joinInvite(msg, client)
		return
	else:
		return

	command = msg.content[1:].split(" ")[0]

	#Make check command cooldown function - would add user to a json file that would include { id : [command, time when available], etc... }

	#Make .json file for storing commands dict?
	commands = {	"help":helpComm,
					"source":sourceComm,
					"reload":reloadComm,	#dev
					"status":statusComm,	#dev
					"stats":statsComm,
					"addDev":addDevComm,	#dev
					"topranks":topranksComm,
					"updateserverlist":updateServers,	#dev
					"id":getIdComm,	#dev
					"quote":quoteComm,
					"addquote":addQuoteComm,
					"pp":ppComm		}

	if command not in commands:
		return

	yield from commands[command](msg, client, devrequest)
	return


#-----------------DEV_COMMANDS

@asyncio.coroutine
def addDevComm(msg, client, devrequest=False):
	if devrequest == True:
		#write new dev to json file
		return
	return


@asyncio.coroutine
def reloadComm(msg, client, devrequest=False):
	if devrequest == True:
		yield from client.send_message(msg.channel, "Reloading unichan.py...")
		yield from client.logout()
		exit("REBOOTING")
	else:
		return

@asyncio.coroutine
def statusComm(msg, client, devrequest=False):
	if devrequest == True:
		status = re.split(r"(status )", msg.content)
		yield from client.change_status(game=discord.Game(name=status[2]))

		yield from client.send_message(msg.channel, "Status changed to: {}".format(status[2]))
		print("UNICHAN: Status changed to {}".format(status[2]))
	return

@asyncio.coroutine
def updateServers(msg, client, devrequest=False):
	if devrequest == True:
		#iterate through connnected servers and write to json file
		with open("serverlist.json") as listfile:
			serverlist = json.load(listfile)
			for server in client.servers:
				serverlist[server.id] = server.name
			fp = open("serverlist.json", "w+")
			fp.write(json.dumps(serverlist, sort_keys=True, indent=4))
			fp.close()
		return
	return

@asyncio.coroutine
def getIdComm(msg, client, devrequest=False):
	if devrequest == True:
		yield from client.send_message(msg.channel, "**Channel id: **{}\n**Server id: **{}".format(msg.channel.id, msg.server.id))
		return
	return

@asyncio.coroutine
def addQuoteComm(msg, client, devrequest=False):
	if devrequest == False:
		quoteadder = msg.author.name
		quote = [msg.content[10:], quoteadder]
		#write new quote to json file
		with open("quotes.json") as jsonfile:
			jsondict = json.load(jsonfile)
			quoteindex = len(jsondict) + 1
			jsondict[str(quoteindex)] = quote
			fp = open("quotes.json", "w+")
			fp.write(json.dumps(jsondict, sort_keys=True, indent=4))
			fp.close()
		yield from client.send_message(msg.channel, "Quote #{} added by {}".format(quoteindex, quoteadder))
		return
	return


#-----------------COMMANDS

@asyncio.coroutine
def helpComm(msg, client, devrequest=False):
	yield from client.send_message(msg.channel, "Current commands include: \n**=stats**\t\t*Usage: =stats [user_name]*\n**=source**\n**=topranks**\t\t*Usage: =topranks <limit> [user_name]*\n**=quote**\t\t*Usage: =quote <quoteid>*")
	return


@asyncio.coroutine
def sourceComm(msg, client, devrequest=False):
	yield from client.send_message(msg.channel, "https://github.com/SpiderNight/Aeos/tree/master/UniChan")
	return

@asyncio.coroutine
def statsComm(msg, client, devrequest=False):
	if msg.content[1:].split(" ")[0] == "stats":
		clientquery = [msg.content[1:].split(" ")[0], msg.content[7:]]

		if "" in clientquery or len(clientquery) > 2:				#validation testing
			yield from client.send_message(msg.channel, "Invalid arguments {}".format(msg.author.mention))
			return
		else:
			valid = True

		clientid = clientquery[1]
	else:
		refurl = re.findall(r"osu\.ppy\.sh/u/.+", msg.content)

		clientid = re.split(r"(/u/)", refurl[0])[2]
		valid = True




	if valid != False:
		apiurl = "https://osu.ppy.sh/api/get_user?k={}&u={}".format(osuapikey, clientid)

		responce = urllib.request.urlopen(apiurl)				#retrieve the user data from the osu api
		content = responce.read()
		data = json.loads(content.decode("utf8"))

		if data == []:
			yield from client.send_message(msg.channel, "This user does not exist!")
		elif data[0]["playcount"] == None:
			yield from client.send_message(msg.channel, "This user has not played yet.")
		else:

			apiurl2 = "https://osu.ppy.sh/api/get_user_best?k={}&u={}&limit=1".format(osuapikey, clientid)

			responce2 = urllib.request.urlopen(apiurl2)					#retrieve the top play pp data from the osu api
			content2 = responce2.read()
			data2 = json.loads(content2.decode("utf8"))

			apiurl3 = "https://osu.ppy.sh/api/get_beatmaps?k={}&b={}".format(osuapikey, data2[0]["beatmap_id"])

			responce3 = urllib.request.urlopen(apiurl3)					#retrieve the top play map data from the osu api
			content3 = responce3.read()
			data3 = json.loads(content3.decode("utf8"))

			#-----------Mod calculations

			modsdict = {
			    ""          : 0,
			    "NF"        : 1,
			    "EZ"        : 2,
			    "//NoVideo" : 4,
			    "HD"        : 8,
			    "HR"       	: 16,
			    "SD"    	: 32,
			    "DT"     	: 64,
			    "RL"        : 128,			#bitwise mods dict
			    "HT"       	: 256,
			    "NC"      	: 512,
			    "FL"     	: 1024,
			    "Auto"      : 2048,
			    "SO"        : 4096,
			    "AP"       	: 8192,
			    "PF"        : 16384,
			}

			bitmods = int(data2[0]["enabled_mods"])
			usermods = []

			for option in modsdict:
				if modsdict[option] & bitmods != 0:
					usermods.append(option)

			if "NC" in usermods:
				usermods.remove("DT")

			#---------------Acc calculations
			#Accuracy = (Total points of hits / (Total number of hits * 300) * 100)
			#Total points of hits = (Number of 50s * 50 + Number of 100s * 100 + Number of 300s * 300)
			#Total number of hits = (Number of misses + Number of 50's + Number of 100's + Number of 300's)

			no300 = int(data2[0]["count300"])
			no100 = int(data2[0]["count100"])
			no50 = int(data2[0]["count50"])
			nomiss = int(data2[0]["countmiss"])

			totalhits = no300 + no100 + no50 + nomiss

			points = (no50 * 50) + (no100 * 100) + (no300 * 300)

			initacc = (points / (totalhits * 300)) * 100

			#---------------

			username =	data[0]["username"]
			level =		"{:0.1f}".format(float(data[0]["level"]))
			rank =		data[0]["pp_rank"]
			pp =		data[0]["pp_raw"]
			accuracy = 	"{:0.2f}%".format(float(data[0]["accuracy"]))
			playcount = data[0]["playcount"]

			if usermods != []:
				mods = "**{}**".format(", ".join(usermods))
			else:
				mods = ""

			acc = 		"{:0.2f}%".format(initacc)
			topplay = 	"{} - {} [{}] {} ({}) - {}pp".format(data3[0]["artist"], data3[0]["title"], data3[0]["version"], mods, acc, data2[0]["pp"])
			picurl = 	"https://a.ppy.sh/{}".format(data[0]["user_id"])

			newdata = "**------{}------**\n**Level:**               {}\n**Rank:**                #{} ({}pp)\n**Accuracy:**        {}\n**Play Count:**     {}\n\n**Top Play:**         {}\n{}".format(username.title(), level, rank, pp, accuracy, playcount, topplay, picurl)

			yield from client.send_message(msg.channel, newdata)				#send the result to the user
	return

@asyncio.coroutine
def topranksComm(msg, client, devrequest=False):
	query = msg.content[1:].split(" ")

	if len(query) < 2:
		yield from client.send_message(msg.channel, "Please specify a user {}".format(msg.author.mention))
		return
	else:
		clientid = query[1]

	if len(query) < 3:
		resultamount = 5
	else:
		resultamount = query[2]

	if int(resultamount) > 10:
		resultamount = 10

	apiurl = "https://osu.ppy.sh/api/get_user_best?k={}&u={}&limit={}".format(osuapikey, clientid, resultamount)

	responce = urllib.request.urlopen(apiurl)
	content = responce.read()
	data = json.loads(content.decode("utf8"))

	if data == []:
		yield from client.send_message(msg.channel, "This user does not exist {}".format(msg.author.mention))
		return

	rank = 0

	newstring = ""

	for topplay in data:
		apiurl2 = "https://osu.ppy.sh/api/get_beatmaps?k={}&b={}".format(osuapikey, topplay["beatmap_id"])
		
		responce2 = urllib.request.urlopen(apiurl2)
		content2 = responce2.read()
		data2 = json.loads(content2.decode("utf8"))

		#--------------Mod calculations

		modsdict = {
		    ""          : 0,
		    "NF"        : 1,
		    "EZ"        : 2,
		    "//NoVideo" : 4,
		    "HD"        : 8,
		    "HR"       	: 16,
		    "SD"    	: 32,
		    "DT"     	: 64,
		    "RL"        : 128,			#bitwise mods dict
		    "HT"       	: 256,
		    "NC"      	: 512,
		    "FL"     	: 1024,
		    "Auto"      : 2048,
		    "SO"        : 4096,
		    "AP"       	: 8192,
		    "PF"        : 16384,
		}

		bitmods = int(topplay["enabled_mods"])
		usermods = []

		for option in modsdict:
			if modsdict[option] & bitmods != 0:
				usermods.append(option)

		if "NC" in usermods:
			usermods.remove("DT")

		#---------------Acc calculations
		#Accuracy = (Total points of hits / (Total number of hits * 300) * 100)
		#Total points of hits = (Number of 50s * 50 + Number of 100s * 100 + Number of 300s * 300)
		#Total number of hits = (Number of misses + Number of 50's + Number of 100's + Number of 300's)

		no300 = int(topplay["count300"])
		no100 = int(topplay["count100"])
		no50 = int(topplay["count50"])
		nomiss = int(topplay["countmiss"])

		totalhits = no300 + no100 + no50 + nomiss

		points = (no50 * 50) + (no100 * 100) + (no300 * 300)

		initacc = (points / (totalhits * 300)) * 100

		#---------------

		rank += 1

		title = data2[0]["title"]
		if len(title) > 20:
			title = title[:18] + "..."

		artist = data2[0]["artist"]
		if len(artist) > 10:
			artist = artist[:10] + "..."

		diff = data2[0]["version"]

		pp = topplay["pp"]
		if usermods != []:
			mods = "**{}**".format(", ".join(usermods))
		else:
			mods = ""

		acc = "{:0.2f}%".format(initacc)

		newstring += "{}. **{}pp** - {} - {} [{}] {} ({})\n".format(str(rank), pp, artist, title, diff, mods, acc)
	finalstring = "**------{}'s Top Ranks------**\n".format(clientid.title()) + newstring[:-1]		#removing last \n because the extra line is unneeded
	yield from client.send_message(msg.channel, finalstring)
	return

@asyncio.coroutine
def quoteComm(msg, client, devrequest=False):
	if devrequest == False:
		if len(msg.content.split(" ")) > 2:
			yield from client.send_message(msg.channel, "Invalid arguments {}".format(msg.author.mention))
			return
		with open("quotes.json") as jsonfile:
			jsondict = json.load(jsonfile)
			if len(jsondict) <= 0:
				yield from client.send_message(msg.channel, "No quotes are available!")
				return
			if len(msg.content.split(" ")) == 2:
				quoteindex = msg.content.split(" ")[1]
			else:
				quoteindex = random.choice(list(jsondict.keys()))
			yield from client.send_message(msg.channel, "**Quote #{}:** {}".format(quoteindex ,jsondict[quoteindex][0]))
		return
	return

@asyncio.coroutine
def ppComm(msg, client, devrequest=False):
	if devrequest == False:
		user, *mods = msg.content.split()[1:]
		if mods == []:
			modstr = "NoMod"
		else:
			modstr = " ".join(mods).upper()

		if mods == []:
			mods.append("")


		resultamount = "100"

		#make api requests for top 100 plays

		apiurl = "https://osu.ppy.sh/api/get_user_best?k={}&u={}&limit={}".format(osuapikey, user, resultamount)

		responce = urllib.request.urlopen(apiurl)
		content = responce.read()
		data = json.loads(content.decode("utf8"))

		apiurl2 = "https://osu.ppy.sh/api/get_user?k={}&u={}".format(osuapikey, user)

		responce2 = urllib.request.urlopen(apiurl2)
		content2 = responce2.read()
		data2 = json.loads(content2.decode("utf8"))

		#define useful vars

		totalweightedpp = float(data2[0]["pp_raw"])

		rank = 0
		instrank = 0
		totalpp = 0
		modpp = 0

		newstring = ""

		#loop through top 100 and filter by mod

		for top_rank in data:
			modsdict = {
			    ""          : 0,
			    "NF"        : 1,
			    "EZ"        : 2,
			    "//NoVideo" : 4,
			    "HD"        : 8,
			    "HR"       	: 16,
			    "SD"    	: 32,
			    "DT"     	: 64,
			    "RL"        : 128,			#bitwise mods dict
			    "HT"       	: 256,
			    "NC"      	: 512,
			    "FL"     	: 1024,
			    "Auto"      : 2048,
			    "SO"        : 4096,
			    "AP"       	: 8192,
			    "PF"        : 16384,
			}

			bitmods = int(top_rank["enabled_mods"])
			usermods = []

			for option in modsdict:
				if modsdict[option] & bitmods != 0:
					usermods.append(option)			#look for plays that have the specified mods in them
				elif bitmods == 0:
					usermods.append("")


			for mod in mods:
				if mod.upper() in usermods:
					fits = True
				else:
					fits = False
					break


			totalpp += float(top_rank["pp"]) * (0.95 ** (rank+1-1))
			rank += 1

			if fits == True:
				instrank += 1
				modpp += float(top_rank["pp"]) * (0.95 ** (instrank-1))
				beatmap_id = top_rank["beatmap_id"]
				pp = top_rank["pp"]
				#newstring += "{}. {}: {}pp\n".format(instrank, beatmap_id, pp)			#Simulate rest of top 100 based on estimation (-2% each score starting at lowest)
			else:
				continue

		playsleft = 100 - instrank
		simulated_plays = 100 - instrank

		while playsleft > 0:
			playsleft -= 1
			instrank += 1
			pp = float(pp) * 0.996
			modpp += float(pp) * (0.95 ** (instrank-1))
			#newstring += "{}. {}: {:0.2f}pp\n".format(instrank, "simulated score", pp)

		bonus_pp = totalweightedpp - totalpp
		totalmodpp = modpp + bonus_pp
		newstring += "**------{}'s {} PP Stats------**\n**Predicted pp:** {:0.2f}\n**Actual pp:** {:0.2f}\n\n_Simulated Plays:_ {}".format(user.title(), modstr, totalmodpp, totalweightedpp, simulated_plays)
		yield from client.send_message(msg.channel, newstring)
	return

	#-----------------URL FUNCTIONS

@asyncio.coroutine
def bmlookupComm(msg, client, devrequest=False):
	if msg.content.find("osu.ppy.sh/s/") != -1:	#this is a specific beatmap
		refurl = re.findall(r"osu\.ppy\.sh/s/\d+", msg.content)
		beatmapid = re.split(r"(/s/)", refurl[0])[2]


		apiurl = "https://osu.ppy.sh/api/get_beatmaps?k={}&s={}".format(osuapikey, beatmapid)

		responce = urllib.request.urlopen(apiurl)
		content = responce.read()
		data = json.loads(content.decode("utf8"))

		difflist = []

		for diff in data:
			difflist.append((diff["version"], diff["difficultyrating"]))

		sorteddifflist = sorted(difflist, key=lambda tup: tup[1])

		beatmapsetname = "{} - {}".format(data[0]["artist"], data[0]["title"])
		creator = data[0]["creator"]
		
		diffs = ""

		for diff in sorteddifflist:
			diffs += "\n-{} | {:0.2f} stars".format(diff[0], float(diff[1]))

		newdata = "**_{}_**\n**Creator:** {}\n**Difficulties:**{}".format(beatmapsetname, creator, diffs)
		
		yield from client.send_message(msg.channel, newdata)


	else:										#this is a beatmapset
		refurl = re.findall(r"osu\.ppy\.sh/b/\d+", msg.content)
		beatmapid = re.split(r"(/b/)", refurl[0])[2]


		apiurl = "https://osu.ppy.sh/api/get_beatmaps?k={}&b={}".format(osuapikey, beatmapid)

		responce = urllib.request.urlopen(apiurl)
		content = responce.read()
		data = json.loads(content.decode("utf8"))

		beatmapname = "{} - {} [{}]".format(data[0]["artist"], data[0]["title"], data[0]["version"])
		creator = data[0]["creator"]
		bpm = data[0]["bpm"]
		diff = "{:0.2}".format(float(data[0]["difficultyrating"]))

		newdata = "**_{}_**\n**Creator:** {}\n**BPM: **{}\n**Difficulty: **{} stars".format(beatmapname, creator, bpm, diff)
		
		yield from client.send_message(msg.channel, newdata)
	return

@asyncio.coroutine
def runUrl(msg, client, devrequest=False):
	#Try and except
	try:
		urlrequest = re.findall(r"osu\.ppy\.sh/\w/", msg.content)[0]
	except:
		print("Url is not in supported list")
		return
	urllist = {	"osu.ppy.sh/u/":statsComm,
				"osu.ppy.sh/b/":bmlookupComm,
				"osu.ppy.sh/s/":bmlookupComm	}
	if urlrequest not in urllist:
		return
	else:
		yield from urllist[urlrequest](msg, client)
	return



@asyncio.coroutine
def joinInvite(msg, client, devrequest=False):
	invite = yield from client.get_invite(msg.content)
	serverid = invite.server.id
	servername = invite.server.name
	yield from client.send_message(discord.Channel(id=135809838155300865), "UniChan has detected an invite to {}\n\n\"{}\"".format(servername, msg.content))

	with open("serverlist.json") as jsonlist:
		serverdict = json.load(jsonlist)
		if serverid in serverdict:
			yield from client.send_message(msg.channel, "I am already in this server {}".format(msg.author.mention))
			return

#	if msg.author.id != "128878733074366464":
#		yield from client.send_message(discord.Channel(id=135494848605192202), msg.content)
#		yield from client.accept_invite(msg.content)

#	@client.event
#	@asyncio.coroutine
#	def on_server_join(server):
#		if msg.author.id == "126868315355348992":
#			greet_msg = "Hello {}. I'm UniChan.\nA bot created by Syrin to display osu user, beatmap, and top play stats as well as a few other things!\n\nI was invited to \"{}\" by my partner bot MaidChan".format(server.owner.name, server.name)
#		else: 
#			greet_msg = "Hello {}. I'm UniChan.\nA bot created by Syrin to display osu user, beatmap, and top play stats as well as a few other things!\n\nI was invited to \"{}\" by {}".format(server.owner.name, server.name, msg.author.name)
		
#		yield from client.send_message(server.owner, greet_msg)
#		yield from updateServers(msg, client, devrequest)
#		return
	return

