#####################
#Psuedo Code for Conversation Partners
#####################
import numpy as np
import random as random
import matplotlib.pyplot as plt
import pandas as pd

#############
#Terminiology
#############
#A juncture is when someone cedes the floor, and others look to determine gaze. A conversation is composed of these turn-taking junctures
#Many conversations can happen concurrently (as in (simulate_conversations))
#After individuals finish one conversation, they are randomly sorted into another. Each of these is a round. A round is how many times individuals finish one conversation and move on to the next


#########################
#Single Conversation Functions
#########################

#def one_conversation
	#Create list of who has spoken each juncture, then
	#Update speaker:
		#Each person gazes
		#Each calculates their probability of talking--not this juncture
		#Gazes per person is totaled and normalized to 1
		#New speaker is chosen probabilistically
	#Everyone updates their bias according to fancy bayesian rule.



def one_convo(junctures=10, participants=None):
	"""one conversation for n junctures with participants"""
	#Who spoke in this conversation?
	list_of_speakers=[]
	speaker=None #No one has said anything this juncture  yet.
	part_biases=[] #A list containing a list of partiicpant biases at each juncture.
	for i in range(junctures):
		#Get a baseline reading of current bias in that  of all participants--a list.
		#part_biases.append(bias_per_juncture(participants))
		#A speaker is chosen
		speaker=update_speaker(participants)#Each person updates their gaze, gazes are totaled, probabilistically determines next speaker
		#Update to new speaker
		list_of_speakers.append(speaker)
		#Everyone updates their gaze according to a bayesian rule
		for i in participants:
			i.update_bias()
	#print(len(participants))
	return list_of_speakers#, part_biases


def update_speaker(participants):
	"""let everyone choose gaze and update it; then calculate how frequently, in total, everyone was stared at. Then normalize it, and return the normalized probability of each speaker talking"""
	all_gazes=total_gazes(participants)
	#How frequently was each person stared at
	raw_frequencies=[[x,all_gazes.count(x)] for x in set(all_gazes)] #A list saying at x[0]=who was started at, and x[1] is how frequently x[0] was stared at
	total=sum([x[1] for x in raw_frequencies])
	#Make sure all participants gazed at someone
	assert total==len(participants)
	#List of the the individuals
	speakers=[i[0] for i in raw_frequencies]
	#List of normalized gazes per individual
	normed=[val[1]/total for val in raw_frequencies]
	return np.random.choice(speakers,p=normed)
	

def total_gazes(participants):
	"""create a summary of everyone's gaze patterns to determine next speaker"""
	all_gazes=[]
	for i in participants:
		#Everyone updates their gaze and returns who they are looking at.
		i.update_gaze(participants)
		all_gazes.append(i.output_gaze())
	return all_gazes # A list of who was gazed upon by each preson


######################
#Many Conversation and Plots Functions
######################

def simulate_conversations(junctures=10, n_per_convo=6,num_convos=15, participants=None):
	"""simulate num_convos conversations of junctures length with n_per_convo participants per conversation"""
	#Create random participants if not ported in.
	if participants==None:
		participants=[Agent() for i in range(n_per_convo*num_convos)]

	convo_stats=[] #Holds the statistics generated by each conversation in a list.
	#Divide into groups of size n_per_convo
	for i in range(0,num_convos*n_per_convo,n_per_convo):
		#print('Simulate one conversation--which participants are sorted into this conversation?')
		#print (i, i+n_per_convo)
		#Include all individuals in a size of n_per_convo, and have a conversation
		convo_stats.append(one_convo(junctures=junctures,participants=participants[i:i+n_per_convo]))
	return convo_stats, participants

def simulate_many_conversations(junctures=10, n_per_convo=6, num_convos=15, rounds=3):
	"""runs the simulate conversation method for rounds number of convesations"""
	repeated_conversations=[] #List of the statistics generated across conversations; each index is a list of lists containing who spoke when
	for i in range(rounds):
		#print("Round "+str(i))
		#Start the next conversation loop with shuffled participants
		if i==0: #First round
			stats, participants=simulate_conversations(junctures=junctures,n_per_convo=n_per_convo,num_convos=num_convos) #Start with a new population of participants
			#print('How many people total were in round %s?'%i)
			#print( len(participants))
		else: #All other rounds use same participants
			stats, participants=simulate_conversations(junctures=junctures,n_per_convo=n_per_convo,num_convos=num_convos, participants=participants) #Use the participants we're already working with.
			#print('How many people were in round %s?'%i)
			#print( len(participants))
		repeated_conversations.append(stats)
		#print(participants)
		#Shuffle them into a new order, so they go to new conversations
		random.shuffle(participants)
	create_speaker_plot(repeated_conversations,junctures,num_convos) #Create a plot of proportion black speaking at time t
	create_parts_plot(participants,junctures, rounds) #Create a plot of mean bias of all partcipants at any time t
	return repeated_conversations

def create_speaker_plot(data,num_junctures, num_convos):
	"""create plots demonstrating trends in the data based on who spoke and how bias changed"""

	###########################################
	#proportion of each race talking at any time
	###########################################

	#The data that we have currently is A list of list of agents
	#There are x repeated instances of y simulatenous conversations made up of z TT junctures
	#Index 0: What instance of conversation are we on (that is: how many conversations have each participant had)--returns a list of lists, where each item is a round of simultaneous conversations
	#Index 1: Within the instance, which particular simulatenous conversation are you interested in?--returns a list of agents in that single conversation
	#Index 2: Based on the particular conversation you're interested in, what juncture  are you interested in (that is, which TT juncture)--returns the agent who spoke that juncture
	black_speaking_t=[]
	white_speaking_t=[]
	for t in data: #Each round of converation
		b_total=[] #How many black speakers at each time point?
		w_total=[] #How many white speakers at each time point?
		for juncture in range(num_junctures): #This is the length of the first conversation, in junctures--just grab the first conversation that occured in that round of conversation, and see how long it is 
			speaker_at_juncture=[convo[juncture]for convo in t] #all the speaker Agents at any juncture during conversation round t
			speaker_black=[indiv.return_race()=='b' for indiv in speaker_at_juncture] #The race of those individuals
			#is_black=[indiv_race=='b' for indiv_race in speaker_race]#Wether that race is black
			black_speaking_t.append(sum(speaker_black)/num_convos) #How many black individuals spoke that juncture--add it to the list
	#Add a line between rounds
	#pd.Series(black_speaking_t).plot(kind='bar')
	plt.plot(black_speaking_t)
	for t in range(len(data)): #How many rounds of conversation were there?
			plt.axvline(num_junctures*(t+1)-1, linestyle='dashed',color='k') #Point a line at that end of that round
	plt.ylim(0,1)
	plt.xlim(xmin=-1)
	plt.ylabel('Proportion of Black Speakers')
	plt.xlabel('Time point')
	plt.title('Race of Speaker Over Time')
	plt.savefig('Proportion of Black Speakers Over Time')
		#black_speaking_t.append(b_total) #Each point on black speaking is a list of how many black people were spaeking at each juncture in that conversation round
	plt.close()

		#for i in range(len(t[0])): #The length of any conversation
			#b_total=sum([t[convo[i]].return_race()=='b' for convo in t])
def create_parts_plot(participants,n_junctures, rounds):
	###########################
	#Mean Individual biases across time
	###########################
	#At each juncture, read all participants bias levels.
	part_bias_logs=pd.DataFrame([part.return_bias_log() for part in participants])
	logs=part_bias_logs.describe().T
	#print(logs)
	logs['mean'].plot(yerr=logs['std'])
	for t in range(rounds): #How many rounds of conversation were there?
		plt.axvline(n_junctures*(t+1)-1, linestyle='dashed',color='k') #Point a line at that end of that round
	#part_bias_logs.describe()#['mean']#.plot(kind='line')
	plt.ylim(0,1)
	plt.xlim(xmin=-1)
	plt.ylabel('Mean Bias of All Participants (Bars are +1 SD)')
	plt.xlabel('Time point')
	plt.title('Variation of Bias of Participants Over Time')
	plt.savefig('Bias of Participants Over time')
	plt.close()


###################
#Individual class
###################

class Agent():

	def __init__(self, race=None, bias=None):
		"""creates an agent who has conversations with others, with ethnicity *race* and bias value *bias*)"""

		race_levels=['b','w']
		if race is not None:
			assert race in race_levels
			self.ethnicity=race
		else:
			self.ethnicity=random.choice(race_levels)
		if bias is not None:
			assert bias >=0 and bias <=1
			self.bias_level=bias #Let's call this a "pro white bias--higher means more pro white"
		else:
			self.bias_level=random.random()
		self.gaze=None
		self.bias_log=[] #Each agents bias at any point in the simulation

		#Later additions:
		#Volubility
		#topics
		#Spatiality

	def __repr__(self):
		"""represents the agent printed"""
		return 'An agent of race %s and bias level %s' %(self.ethnicity,self.bias_level)

	####
	#Methods for agents
	####

	def update_bias(self):
		self.bias_log.append(self.bias_level) #Note the current bias level for this juncture, before updating to a new one based on conversation
		#The race of the person currently gazing at.
		partner_race=self.output_gaze().return_race()
		new_level=int(partner_race=='w') #Returns 1 if white, 0 if black
		self.bias_level=(self.bias_level+new_level)/2
		

	def update_gaze(self, partners):
		"""compare partners (excluding self) in terms of bias, and gaze according to normalized bias. Currently just black and white"""
		raw_probabilities=[]
		for partner in partners:
			if partner==self:
				raw_probabilities.append(0) #Probability of gazing at self should be zero
			else:
				#Create raw probabilities based on partners race and current bias
				if partner.return_race()=='b':
					raw_probabilities.append(1.0-self.bias_level)
				elif partner.return_race()=='w':
					raw_probabilities.append(self.bias_level)
				else:
					print('Error! Not black or white partner')
					break
		#Normalize the probabilities
		total=sum(raw_probabilities)
		if total!=0: #Sometimes, thsi total is 0, which means there's no likelihood of talking to anyone--how is that possible?
			#List of normalized gazes per individual
			normalized_probabilities=[val/total for val in raw_probabilities]
			#Choose one based on normalized probabilitys
			self.gaze=np.random.choice(partners,p=normalized_probabilities)
		else:
			self.gaze=np.random.choice(partners)

	def return_race(self):
		return self.ethnicity

	def return_bias(self):
		return self.bias_level

	def output_gaze(self):
		return self.gaze

	def return_bias_log(self):
		return self.bias_log


#
stats=simulate_many_conversations(junctures=5,num_convos=10,rounds=10)

