import msg

import sys
import random

from msg import *
from sg_script_api import *


PORT_PUSH = 0
PORT_POLL = 1
MAX_PORT = PORT_POLL+1

HOST_TYPE = 0
RANDOM_SELECT_TYPE = 1
BEST_NEIGHBOR_TYPE = 2

# timeout for polls
RESPONSE_TIMEOUT = 100

StopSim = False
EndSim = False

QUERIES = 0
RTT = 1
RES_COUNT = 2
RESPONSE_FAILED = 3

graph = None

DefaultSimulationDuration = 100

# test resources every 5 seconds
ResourcesCicleTime = 5

globalGraphCurve = None
globalGraphCurve = None

colors = {'black': 0x000000, 'red': 0xff0000, 'darkRed': 0x800000, 'green': 0x00ff00, 
	'darkGreen': 0x008000, 'blue': 0x0000ff, 'darkBlue': 0x000080, 'cyan': 0x00ffff, 
	'darkCyan': 0x008080, 'magenta': 0xff00ff, 'darkMagenta': 0x800080, 'yellow': 0xffff00, 
	'darkYellow': 0x808000, 'gray': 0xa0a0a4, 'darkGray': 0x808080, 'lightGray': 0xc0c0c0}

random.seed()

def config(argv):
	global EndSim
	simTime = -1
	
	for arg in argv:
		argList = arg.split()

		if len(argList) != 0:
			cmd = argList[0].upper()

			if cmd == 'DURATION':
				if len(argList) != 2:
					print 'config: Too few arguments for Duration: ', arg
				else:
					simTime = float(argList[1])
	
	if simTime == -1:
		print 'config: Duration not set. Using default duration :', DefaultSimulationDuration
		simTime = DefaultSimulationDuration
	
	print 'config: simulation duration ', simTime
	
	g = PyMsgGlobal.instance()
	g.currentProcessSleep(simTime)
	#print 'config: Simulation finished ', g.getClock()
	EndSim = True

	return

def mergeResources(resources, remoteResources):
	g = PyMsgGlobal.instance()
	hostName = g.getCurrentHost().getName()

	for r in remoteResources:
		updateRes = False
		if r != hostName:
			if resources.has_key(r):
				# the one that last more is kept
				if (resources[r][0] + resources[r][1]) < (remoteResources[r][0] + remoteResources[r][1]):
					updateRes = True
			else:
				updateRes = True
				
		if updateRes:
			resources[r] = remoteResources[r]
			#print 'Resource updated ', r
	return

def updateFail(g, fails, hostFailing):
	t = g.getClock()
	
	hostFailing[0] = False
		
	for f in fails:
		if t > f[0] and (f[1] == -1 or f[1] > t):
			hostFailing[0] = True
	return


def updateResourceExpiration(script_api, g, hostName, resources, curve, setGlobalGraph):
	global global_res_value

	# verify expiration and all that staff
	toDelete = []
	
	
	for r in resources:
		if r != hostName:
			if (g.getClock() - resources[r][1]) > resources[r][0]:
				#print '[', g.getClock(), '] ',hostName, ': Resources from ', r, ' expired: Expiration ', resources[r][0], ' Time ', resources[r][1]
				toDelete.append(r)

	for r in toDelete:
		del resources[r]

	
	#print '[', g.getClock(), '] ',hostName, ': Resources ', resources
	value = int(float(len(resources)) / float(g.getHostCount()) * float(100))

	global_res_value[hostName] = value

	# only when the dict is complete
	global_value = 0
	if len(global_res_value) == g.getHostCount() and setGlobalGraph:
		host_res_count = 0
		for r_value in global_res_value:
			global_value = global_value + global_res_value[r_value]
		globalGraphCurve.addPoint(g.getClock(), global_value)

	script_api.setComponentRedLevel(hostName, value)
	curve.addPoint(g.getClock(), value)
	return

def updatePendingResponses(g, pendingResponses, neighbors):
	t = g.getClock()
	
	for p in pendingResponses:
		pendRespHost = pendingResponses[p]
		pendRespHostCopy = pendRespHost.copy()
		
		for c in pendRespHostCopy:
			if t - c > RESPONSE_TIMEOUT:
				pendRespHost.remove(c)
				neighbors[p][RESPONSE_FAILED] = neighbors[p][RESPONSE_FAILED] + 1
	return

def hostResponseValue(respInfo):
	if respInfo[QUERIES] == 0:
		return 0
	# value = (resCount * 2 - rtt) * 1, 1.1, 1.2, ... 1 + (responses - 1) * 0.1
	#return (-respInfo[RTT])
	#return (respInfo[RES_COUNT] * 3)
	return (respInfo[RES_COUNT] * 5 - respInfo[RTT] - respInfo[RESPONSE_FAILED] * 20)


# this process 
def mdsLoop(argv):
	global StopSim
	global EndSim

	g = PyMsgGlobal.instance()
	script_api = SimgridSimulationInterface.instance()
	hostName = g.getCurrentHost().getName()
	resources = g.getCurrentHost().getData()

	#resources = g.getCurrentHost().getData()
	args = g.getCurrentProcess().getData()

	pushs = args[0]
	polls = args[1]
	expiration = args[2]
	neighbors = args[3]
	neighborsUnknown = args[4]
	pendingResponses = args[5]
	hostFailing = args[6]

	
	for i in range(0, g.getHostCount()):
		curHostName = g.getHostByIndex(i).getName()
		if curHostName != hostName:
			neighborsUnknown.append(curHostName)
			
	allHosts = neighborsUnknown[:]
	#print 'MDS Loop Started in host ', hostName, ' Expiration: ', expiration, ' Pushs ', pushs, ' Polls ', polls

	while StopSim == False and EndSim == False:
		g.currentProcessSleep(ResourcesCicleTime)

		if not hostFailing[0]:
			for push in pushs:
				push[3] = push[3] - ResourcesCicleTime
				
				if push[3] <= 0:
					if push[0] == RANDOM_SELECT_TYPE or push[0] == BEST_NEIGHBOR_TYPE:
						host = g.getHostByIndex(random.randint(0, g.getHostCount()-1))
						#print '[', g.getClock(), '] ', hostName, ': Push task to ' , host.getName(), ' (random)'
						
						resCopy = resources.copy()
						resCopy[hostName] = (expiration, g.getClock())
						task = PyMsgTask('Push', 100, len(resCopy) * 1024, (resCopy, g.getClock()))
						host.putTask(task, PORT_PUSH)
						#print '[', g.getClock(), '] ',hostName, ': Push task to ', host.getName(), ' sent'
						
					elif push[0] == HOST_TYPE:
						#print '[', g.getClock(), '] ',hostName, ': Push task to ', push[1].getName()
						# aprox data size: 1k per resource
						resCopy = resources.copy()
						resCopy[hostName] = (expiration, g.getClock())
						task = PyMsgTask('Push', 100, len(resCopy) * 1024, (resCopy, g.getClock()))
						push[1].putTask(task, PORT_PUSH)
						#print '[', g.getClock(), '] ',hostName, ': Push task to ', push[1].getName(), ' sent'
	
					push[3] = push[2] + push[3]
			
			for poll in polls:
				poll[3] = poll[3] - ResourcesCicleTime
				
				if poll[3] <= 0:
					if poll[0] == RANDOM_SELECT_TYPE:
						host = g.getHostByIndex(random.randint(0, g.getHostCount()-1))
						#print '[', g.getClock(), '] ', hostName, ': Poll task to ', host.getName(), ' (random)'
						task = PyMsgTask('Poll', 100, 100, (g.getCurrentHost(), g.getClock()))
						host.putTask(task, PORT_POLL)
						#print '[', g.getClock(), '] ',hostName, ': Poll task to ', host.getName(), ' sent'
						
					elif poll[0] == BEST_NEIGHBOR_TYPE:
						# la probabilidad de que se elija un host al azar tiene que ver con este factor len(neighbors) / g.getHostCount(). Si
						# el numero random esta dentro del coeficiente, se elige el mejor host conocido. Si no uno al azar.
						# nunca es 1 porque neighbors no tiene a si mismo por lo tanto siempre puede pedir informacion a un host al azar. Si hay
						# hosts que no estan en la lista de neighbors, se elige uno de esos. Si hay informacion de todos o sea que 
						# len(neighbors) = g.getHostCount()-1 se elige uno al azar del total.
						c = float(len(neighbors)) / float(g.getHostCount())
						r = random.random()
						#print r, c
						# random select a unknown host 
						if r > c:
							if len(neighborsUnknown) > 0:
								#host = g.getHostByIndex(random.randint(0, g.getHostCount()-1))
	
								h = random.randint(0, len(neighborsUnknown)-1)
								neighbors[neighborsUnknown[h]] = [0, float(0), float(0), 0]
								host = g.getHostByName(neighborsUnknown[h])
								del neighborsUnknown[h]
								#print '[', g.getClock(), '] ', hostName, ': Poll task to ', host.getName(), ' (BEST NEIGHBOR: Random)'
							else:
								h = random.randint(0, len(allHosts)-1)
								host = g.getHostByName(allHosts[h])
								#print '[', g.getClock(), '] ', hostName, ': Poll task to ', host.getName(), ' (BEST NEIGHBOR: Random)'
								
						else:
							#host = g.getHostByIndex(random.randint(0, g.getHostCount()-1))
							#idx = random.randint(0, len(neighbors)-1)
							#
							#for n in neighbors:
							#	if idx == 0:
							#		break
							#	idx = idx - 1
							#
							#host = g.getHostByName(n)
							best = None
							for n in neighbors:
								if best == None:
									best = n
								elif hostResponseValue(neighbors[best]) < hostResponseValue(neighbors[n]):
									best = n
									
								#elif hostResponseValue(neighbors[best]) < hostResponseValue(neighbors[n]):
								#	if neighbors[best][RES_COUNT] == 0 or neighbors[n][RES_COUNT] != 0:
								#		best = n
	
								#elif neighbors[best][RTT] > neighbors[n][RTT]:
								#	best = n
								#elif (neighbors[best][RES_COUNT] * 5 - neighbors[best][RTT]) < (neighbors[n][RES_COUNT] * 5 - neighbors[n][RTT]):
								#	best = n
							host = g.getHostByName(best)
							#print '[', g.getClock(), '] ', hostName, ': Poll task to ', host.getName()
	
						curTime = g.getClock()
						
						#print neighbors
						if pendingResponses.has_key(host.getName()):
							pendingResponses[host.getName()].add(curTime)
						else:
							pendingResponses[host.getName()] = set([curTime])
						
						task = PyMsgTask('Poll', 100, 100, (g.getCurrentHost(), curTime))
						host.putTask(task, PORT_POLL)
						#print '[', g.getClock(), '] ',hostName, ': Poll task to ', host.getName(), ' sent'
						
					elif poll[0] == HOST_TYPE:
						#print '[', g.getClock(), '] ',hostName, ': Poll task to ', poll[1].getName()
						task = PyMsgTask('Poll', 100, 100, (g.getCurrentHost(), g.getClock()))
						poll[1].putTask(task, PORT_POLL)
						#print '[', g.getClock(), '] ',hostName, ': Poll task to ', poll[1].getName(), ' sent'
	
					poll[3] = poll[2] + poll[3]
	return

# here all the resource polls arrive: this host must respond with its resources
def mdsResourcePollServer(args):
	global StopSim
	global EndSim

	g = PyMsgGlobal.instance()
	script_api = SimgridSimulationInterface.instance()

	hostName = g.getCurrentHost().getName()

	resources = g.getCurrentHost().getData()
	
	args = g.getCurrentProcess().getData()
	expiration = args[0]
	hostFailing = args[1]

	#print 'MDS Poll Server Started in host ', hostName
	
	task = g.getTaskFromCurrentHost(PORT_POLL)

	while task != None and StopSim == False and EndSim == False:		
		if not hostFailing[0]:
			if task.getName() == 'Poll':
				task.execute()
				resCopy = resources.copy()
				resCopy[hostName] = (expiration, g.getClock())
				pollRespTask = PyMsgTask('PollResponse', 100, len(resCopy) * 1024, (resCopy, task.getData()[1]))
				#print '[', g.getClock(), '] ',hostName, ': Put PollResponse Task to ', task.getData()[0].getName()
				task.getData()[0].putTask(pollRespTask, PORT_PUSH)
				#print '[', g.getClock(), '] ',hostName, ': Put PollResponse Task Returned'
		
		task = g.getTaskFromCurrentHost(PORT_POLL)

	#print '[', g.getClock(), '] ',hostName, ': MDS Poll Server Exit'
	return

# here all the resource pushs arrive: can be a push from another node or a PollResponse
def mdsResourcePushServer(argv):
	global StopSim
	global EndSim

	g = PyMsgGlobal.instance()
	script_api = SimgridSimulationInterface.instance()

	hostName = g.getCurrentHost().getName()

	resources = g.getCurrentHost().getData()

	args = g.getCurrentProcess().getData()
	neighbors = args[0]
	pendingResponses = args[1]
	hostFailing = args[2]

	#print 'MDS Push Server Started in host ', hostName
	
	task = g.getTaskFromCurrentHost(PORT_PUSH)

	while task != None and StopSim == False and EndSim == False:
		if not hostFailing[0]:
			if task.getName() == 'Push' or task.getName() == 'PollResponse':
				#print '[', g.getClock(), '] ',hostName, ': ', task.getName(), ' Resources arrived from ', task.getSource().getName()
				remoteResources = task.getData()[0]
				t = task.getData()[1]
				
				# simulate transfer time, etc.
				task.execute()
	
				src = task.getSource().getName()
				
				if task.getName() == 'PollResponse':
					curtime = g.getClock()
					
					# this value will contain how useful are the resources sent by source
					resValue = 0
					
					for r in remoteResources:
						if r != hostName:
							content = remoteResources[r]
							# remainTime = expiration - (currentTime - resourceTime)
							# valueResource = remainTime / expiration
							resValue = resValue + (content[0] - (curtime - content[1])) / content[0]
						
					#print neighbors
					if src in neighbors:
						newResValue = (neighbors[src][RES_COUNT]*neighbors[src][QUERIES] + resValue) / (neighbors[src][QUERIES]+1)
						neighbors[src] = [neighbors[src][QUERIES]+1, (neighbors[src][RTT]*neighbors[src][QUERIES]+(g.getClock()-t))/(neighbors[src][QUERIES]+1), newResValue, neighbors[src][RESPONSE_FAILED]]
						#neighbors[src] = [neighbors[src][QUERIES]+1, (neighbors[src][RTT]*neighbors[src][QUERIES]+(g.getClock()-t))/(neighbors[src][QUERIES]+1), resValue, neighbors[src][RESPONSE_FAILED]]
						#neighbors[src] = [neighbors[src][QUERIES]+1, (neighbors[src][RTT]*neighbors[src][QUERIES]+(g.getClock()-t))/(neighbors[src][QUERIES]+1), (neighbors[src][RES_COUNT]*neighbors[src][QUERIES]+len(remoteResources))/(neighbors[src][QUERIES]+1)]
					else:
						neighbors[src] = [1, g.getClock() - t, resValue, 0]
						
					if src in pendingResponses:
						pendRespHost = pendingResponses[src]
						pendRespHostCopy = pendRespHost.copy()
						
						for p in pendRespHostCopy:
							pendRespHost.remove(p)
						
				
				#print '[', g.getClock(), '] ',hostName, ': ', task.getName(), ' Resources arrived from ', task.getSource().getName(), ' took ', g.getClock() - t
				#print '[', g.getClock(), '] ',hostName, ': Remote resources ', remoteResources
				
				mergeResources(resources, remoteResources)

				#print '[', g.getClock(), '] ',hostName, ': Merged resources ', resources
					
		task = g.getTaskFromCurrentHost(PORT_PUSH)
		#updateResourceExpiration(script_api, g, hostName, resources)
		
	#print '[', g.getClock(), '] ', hostName, ' MDS Server Exit '
	return

HostCount = 0

# is used to know when update the globalGraphCurve because updateResourceExpiration is called from all hosts
# and we need 1 entry for each battery of calls that happen every ResourcesCicleTime
host_res_count = 0

def mds(argv):
	global StopSim
	global EndSim
	global graph
	global HostCount
	global host_res_count

	printGlobalGraph = False
	
	g = PyMsgGlobal.instance()
	script_api = SimgridSimulationInterface.instance()

	hostName = g.getCurrentHost().getName()

	HostCount = HostCount + 1
	
	#greyLevel = 255 * HostCount / g.getHostCount()
	color = colors.values()[(HostCount-1) % len(colors)]
	
	curve = graph.createCurve()
	# select a color from the list
	curve.setColor(color >> 16, (color >> 8) & 0xff, color & 0xff, 255)
	#curve.setColor(greyLevel, greyLevel, greyLevel, 255)
	curve.setText(hostName)
	curve.setWidth(2)
	
	curve.addPoint(0, 0)
	
	expiration = -1
	resources = {}
	
	g.getCurrentHost().setData(resources)
	
	hostCount = g.getHostCount()

	polls = []
	pushs = []
	fails = []
	# this list contains a single value that indicates if the host is failing right now. It's a list so it can be shared by 
	# reference through different places
	hostFailing = [False]
	neighbors = dict()
	neighborsUnknown = []
	pendingResponses = dict()

	#print 'MDS Service Started in host ', hostName

	for arg in argv:
		argList = arg.split()
		
		if len(argList) != 0:
			cmd = argList[0].upper()
			
			if cmd == 'EXPIRATION':
				expiration = float(argList[1])
			elif cmd == 'POLL':
				if len(argList) != 3:
					print 'mds: Too few arguments for Poll: ', arg
				elif argList[1].upper() == 'RANDOM-SELECT':
					polls.append([RANDOM_SELECT_TYPE, None, float(argList[2]), float(argList[2])])
				elif argList[1].upper() == 'BEST-NEIGHBOR':
					polls.append([BEST_NEIGHBOR_TYPE, None, float(argList[2]), float(argList[2])])
				else:
					host = g.getHostByName(argList[1])
					if host == None:
						print 'mds: Cannot find host ', argList[1], ' Curve: ', arg
					else:
						polls.append([HOST_TYPE, host, float(argList[2]), float(argList[2])])
			elif cmd == 'PUSH':
				if len(argList) != 3:
					print 'mds: Too few arguments for Push: ', arg
				elif argList[1].upper() == 'RANDOM-SELECT':
					pushs.append([RANDOM_SELECT_TYPE, None, float(argList[2]), float(argList[2])])
				elif argList[1].upper() == 'BEST-NEIGHBOR':
					pushs.append([BEST_NEIGHBOR_TYPE, None, float(argList[2]), float(argList[2])])
				else:
					host = g.getHostByName(argList[1])
					if host == None:
						print 'mds: Cannot find host ', argList[1], ' Curve: ', arg
					else:
						#print 'argList ', argList[2], ' ', float(argList[2])
						pushs.append([HOST_TYPE, host, float(argList[2]), float(argList[2])])
			elif cmd == 'FAIL':
				if len(argList) < 2:
					print 'mds: Too few arguments for Fail: ', arg
				else:
					# to the end
					failTo = float(-1)
					failFrom = float(argList[1])
					
					if len(argList) == 3:
						failTo = float(argList[2])
					
					fails.append([failFrom, failTo])

	#print 'Process ', g.getCurrentProcess().getName()
	updateResourceExpiration(script_api, g, hostName, resources, curve, False)

	# create MDS Resource Server process
	p1 = PyMsgProcess('mdsResourcePollServer', 'MDS Poll Server', (expiration, hostFailing), g.getCurrentHost())

	# create MDS Resource Server process
	p2 = PyMsgProcess('mdsResourcePushServer', 'MDS Push Server', (neighbors, pendingResponses, hostFailing), g.getCurrentHost())

	p3 = PyMsgProcess('mdsLoop', 'MDS Loop', (pushs, polls, expiration, neighbors, neighborsUnknown, pendingResponses, hostFailing), g.getCurrentHost())

	# resources of this host are in the first position	
	resources[hostName] = (expiration, 0)
	
	#print 'Host ', hostName, '\nExpiration ', expiration, '\n', 'Polls ', polls, '\nPushs ', pushs
	
	script_api.setComponentRedLevel(hostName, 100 - len(resources) / g.getHostCount() * 100)

	while StopSim == False and EndSim == False:
		# sleep 5 seconds
		g.currentProcessSleep(ResourcesCicleTime)
		
		host_res_count = host_res_count + 1
		if host_res_count == hostCount:
			printGlobalGraph = True
			host_res_count = 0
		else:
			printGlobalGraph = False
		
		updateFail(g, fails, hostFailing)
		updateResourceExpiration(script_api, g, hostName, resources, curve, printGlobalGraph)
		updatePendingResponses(g, pendingResponses, neighbors)
	return
	

def stop():
	global StopSim
	#print 'Stoping Simulation'
	StopSim = True
	#print 'Stoping Simulation Finished'
	return

def main(platFile, deployFile):
	global StopSim
	global EndSim
	global graph
	global HostCount
	global global_res_value
	global globalGraphCurve
	
	HostCount = 0
	StopSim = False
	EndSim = False
	slaves = []
	
	# this var contain how up-to-date are the resources of each host
	global_res_value = dict()

	script_api = SimgridSimulationInterface.instance()

	graph = script_api.createGraphic()
	graph.setTitle("Resources Information")
	graph.setXText("Time")
	graph.setYText("Resources Info (%)")

	globalGraph = script_api.createGraphic()
	globalGraph.setTitle("Global Resources Information")
	globalGraph.setXText("Time")
	globalGraph.setYText("Resources Info")
	
	globalGraphCurve = globalGraph.createCurve()
	globalGraphCurve.setWidth(2)
	globalGraphCurve.addPoint(0, 0)

	g = msg.PyMsgGlobal.instance()
	g.globalInit(['testMsg'])
	g.setChannelNumber(MAX_PORT)
	g.setPajeOutput("msg_test.trace")
	g.createEnvironment(platFile)
	
	g.functionRegister("mds", mds)
	g.functionRegister("config", config)
	g.functionRegister("mdsResourcePushServer", mdsResourcePushServer)
	g.functionRegister("mdsResourcePollServer", mdsResourcePollServer)
	g.functionRegister("mdsLoop", mdsLoop)
	
	g.launchApplication(deployFile)
	res = g.main()
	g.destroy()


#g = msg.PyMsgGlobal.instance()
#g.functionRegister("master", master)
#g.functionRegister("slave", slave)
#g.functionRegister("forwarder", forwarder)


#g.run(main, "platform_file.xml")

#def main(deployFile):
#	g = msg.PyMsgGlobal.instance()
#	g.globalInit(['testMsg'])
#	g.setChannelNumber(1)
#	g.setPajeOutput("msg_test.trace")
#	g.createEnvironment("platform_file.xml")
#	
#	g.functionRegister("master", master)
#	g.functionRegister("slave", slave)
#	g.functionRegister("forwarder", forwarder)
#	g.launchApplication(deployFile)
#	
#	res = g.main()
#	
#	print 'Simulation time ', str(g.getClock());
