# Agent based model of lexicon convergence
import random

ons = ['p','t','k','b','d','g','s','z','n','m']
nuc = ['i','u','e','o','a']
cod = ['n','m','s','z','']

def initialize(numAgents, lexSize, wordLen, minConnections):
    worLen = 3 * (wordLen /3) # Make it a multiple of 3
    agents = [initAgent(lexSize, wordLen) for i in range(numAgents)]
    network = initNetwork(agents, minConnections)
    return agents, network

def initAgent(lexSize, wordLen):
    agent = {}
    for meaning in range(lexSize):
        word = randomWord(wordLen)
        agent[meaning] = [dict() for w in range(wordLen)]
        for index in range(wordLen):
            agent[meaning][index][word[index]] = 1.0
    return agent

def randomWord(wordLen):
    word = []
    while wordLen > 0:
        wordLen -= 3
        word.append(random.choice(ons))
        word.append(random.choice(nuc))
        word.append(random.choice(cod))        
    return word

def initNetwork(agents, minConnections):
    network = {}
    for srcAgent in range(len(agents)):
        numDests = random.randint(minConnections, len(agents)-1)
        # preclude agents from having self-loops
        possibleDests = range(srcAgent) + range(srcAgent+1, len(agents))
        print possibleDests
        dests = random.sample(possibleDests, numDests)
        network[srcAgent] = dests
    return network

def pickone(cdict):
    tot = sum(cdict.values())
    n = random.uniform(0,tot)
    for k in cdict:
        n -= cdict[k]
        if n <= 0: return k

def pickWord(phoneDicts):
    word = []
    for phonemePos in phoneDicts:
        phoneme = pickone(phonemePos)
        word.append(phoneme)
    return ''.join(word)

def listen(listener, meaning, word):
    for (pos, phoneme) in enumerate(word):
        if phoneme not in listener[meaning][pos]:
            listener[meaning][pos][phoneme] = 0.0
        listener[meaning][pos][phoneme] += 1.0

def updateAgents(agents, network, noise):
    for (srcIndex, srcAgent) in enumerate(agents):
        # pick destination agent
        destAgentIndex = random.choice(network[srcIndex])
        destAgent = agents[destAgentIndex]
        
        # pick meaning and word that srcAgent says
        meaning = random.choice(srcAgent.keys())
        word = pickWord(srcAgent[meaning])

        # decide whether noise changes the word
        if random.random() < noise:
            word = randomWord(len(word))
            
        #print '%s -> %s: "%s = %s"' % (srcIndex, destAgentIndex, meaning, word)
        # update destAgent's lexicon
        listen(destAgent, meaning, word)

        # srcAgent also updates its own dictionary
        listen(srcAgent, meaning, word)
    #print '\n'

def decayAgents(agents, decay):
    for agent in agents:
        for meaning in agent:
            for phonePos in agent[meaning]:
                for phone in phonePos:
                    phonePos[phone] /= decay

def runSimulation(agents, network, timesteps, decay=1.0, noise=0.0):
    while timesteps > 0:
        timesteps -= 1
        #print 'Agreement: %s' % (pairAgreement(agents[0], agents[1]))
##        agreements = avgMeaningAgreements(agents)
##        for meaning in sorted(agreements.keys()):
##            print ('%s\t' % (agreements[meaning],)),
##        print
        updateAgents(agents, network, noise)
        decayAgents(agents, decay)
    
    print 'FINAL AGREEMENTS:'
    agreements = avgMeaningAgreements(agents)
    for meaning in sorted(agreements.keys()):
        print ('%s\t' % (agreements[meaning],)),
    print
    print 'FINAL PROB DISTANCE:'
    print probabilityDistance(agents)

header = \
"""
digraph agent_network {
	rankdir=LR;
	size="8,5"
	node [shape = circle];
%s
}
"""

def drawNetwork(network):
    graph = printNetwork(network)
    f = open('network.dot', 'w')
    f.write(graph)
    f.close()

def printNetwork(network):
    arcs = []
    for srcAgent, destAgents in network.items():
        for destAgent in destAgents:
            arcs.append('A_%s -> A_%s ;' % (srcAgent, destAgent))
    graph = header % '\n'.join(arcs)
    return graph

##def meaningAgreements_old(agent1, agent2):
##    agent1Freq = {}
##    agent2Freq = {}
##    for meaning in agent1:
##        agent1Freq[meaning] = []
##        agent2Freq[meaning] = []
##        for phonePos in agent1[meaning]:
##            total1 = float(sum(phonePos.values()))
##            agent1Freq[meaning][-1] = dict([(phone, count/total1) for \
##                                    phone,count in phonePos.items()])
##        for phonePos in agent2[meaning]:
##            total2 = float(sum(phonePos.values()))
##            agent2Freq[meaning][-1] = dict([(phone, count/total1) for \
##                                    phone,count in phonePos.items()])
##    agreements = {}
##    for meaning in agent1Freq:
##        agreements[meaning] = 0.0
##        for (phonePos, phoneFreq) in enumerate(agent1Freq[meaning]):
##            for phone, freq in phoneFreq:
##                if phone in agent2Freq[meaning][phonePos]:
##                    agreements[meaning] += freq * agent2Freq[meaning][phonePos][phone]
##
##    return agreements

def meaningAgreements(agent1,agent2):
    '''Returns likelihoods of using the same name for ideas.'''
    agreements = {}
    for meaning in agent1:
        pSamePron = 1.0
        for phonPos in range(len(agent1[meaning])):
            a1_pron = dict([(phon,cnt/sum(agent1[meaning][phonPos].values())) \
                            for (phon,cnt) in agent1[meaning][phonPos].items()])
            a2_pron = dict([(phon,cnt/sum(agent2[meaning][phonPos].values())) \
                            for (phon,cnt) in agent2[meaning][phonPos].items()])
            #Chance of same seg = sum of products of probs of same phones
            pSameSeg = 0.0
            for (sg,p1) in a1_pron.items():
                if sg in a2_pron: pSameSeg += p1*a2_pron[sg]
            pSamePron *= pSameSeg
        agreements[meaning] = pSamePron
    return agreements
            
def avgMeaningAgreements(agents):
    agreement = 0.0
    agentPairs = [(agents[idx], agents[idy]) for idx in range(len(agents)) \
                  for idy in range(idx+1, len(agents))]
    avgAgreements = dict([(meaning, 0.0) for meaning in agents[0]])
    for (agent1,agent2) in agentPairs:
        pairAgreements = meaningAgreements(agent1,agent2)
        for meaning, pairAgr in pairAgreements.items():
            avgAgreements[meaning] += (pairAgr / len(agentPairs))
    return avgAgreements

def probabilityDistance(agents):
    agentPairs = [(agents[idx], agents[idy]) for idx in range(len(agents)) \
              for idy in range(idx+1, len(agents))]
    numMeanings = len(agents[0])
    wordLen = len(agents[0][0])
    distance = 0.0
    for (agent1,agent2) in agentPairs:
        pairDistance = 0.0
        for meaning in agent1:
            for phonPos in range(len(agent1[meaning])):
                a1_pron = dict([(phon,cnt/sum(agent1[meaning][phonPos].values())) \
                                for (phon,cnt) in agent1[meaning][phonPos].items()])
                a2_pron = dict([(phon,cnt/sum(agent2[meaning][phonPos].values())) \
                                for (phon,cnt) in agent2[meaning][phonPos].items()])
                for sg in set(a1_pron.keys()) | set(a2_pron.keys()):
                    if sg in a1_pron:
                        if sg in a2_pron:
                            pairDistance += abs(a1_pron[sg] - a2_pron[sg])/2.0
                        else: pairDistance += a1_pron[sg]/2.0
                    else: pairDistance += a2_pron[sg]/2.0
        pairDistance /= (numMeanings*wordLen)
        distance += pairDistance
    distance /= len(agentPairs)
    return distance
        




        
