# Agent based model of lexicon convergence
import random

alphabet = ['p','t','k','b','d','g','i','a','u','o','s','n']

def initialize(numAgents, lexSize, wordLen, minConnections):
    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):
        agent[meaning] = {}
        agent[meaning][randomWord(wordLen)] = 1
    return agent

def randomWord(wordLen):
    word = ''
    while wordLen > 0:
        wordLen -= 1
        word += random.choice(alphabet)
    return word

def initNetwork(agents, minConnections):
    network = {}
    for srcAgent in range(len(agents)):
        numDests = random.randint(minConnections, len(agents))
        dests = random.sample(range(len(agents)), 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 updateAgents(agents, network):
    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 = pickone(srcAgent[meaning])

        #print '%s -> %s: "%s = %s"' % (srcIndex, destAgentIndex, meaning, word)
        # update destAgent's lexicon
        if word not in destAgent[meaning]:
            destAgent[meaning][word] = 0
        destAgent[meaning][word] += 1
    #print '\n'

def runSimulation(agents, network, timesteps, decay=1.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)
        for agent in agents:
            for meaning in agent:
                for word in agent[meaning]:
                    agent[meaning][word] /= 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(agent1, agent2):
    agent1Freq = {}
    agent2Freq = {}
    for meaning in agent1:
        total1 = float(sum(agent1[meaning].values()))
        agent1Freq[meaning] = dict([(word, count/total1) for \
                                    word,count in agent1[meaning].items()])
        total2 = float(sum(agent2[meaning].values()))
        agent2Freq[meaning] = dict([(word, count/total2) for \
                                    word,count in agent2[meaning].items()])
    agreements = {}
    for meaning in agent1:
        agreements[meaning] = 0.0
        for word in agent1[meaning]:
            if word in agent2[meaning]:
                agreements[meaning] += agent1Freq[meaning][word] * agent2Freq[meaning][word]

    #avgAgreement = sum(agreements.values()) / float(len(agreements))
    #return avgAgreement
    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])
    distance = 0.0
    for (agent1,agent2) in agentPairs:
        pairDistance = 0.0
        for meaning in agent1:
            agent1Total = float(sum(agent1[meaning].values()))
            agent2Total = float(sum(agent2[meaning].values()))
            for word in set(agent1[meaning].keys()) | set(agent2[meaning].keys()):
                if word in agent2[meaning] and word in agent1[meaning]:
                    pairDistance += abs((agent1[meaning][word]/agent1Total) - \
                                    (agent2[meaning][word]/agent2Total)) / 2.0
                elif word in agent1[meaning]:
                    pairDistance += (agent1[meaning][word]/agent1Total) / 2.0
                elif word in agent2[meaning]:
                    pairDistance += (agent2[meaning][word]/agent2Total) / 2.0
        pairDistance /= numMeanings
        distance += pairDistance
    distance /= len(agentPairs)
    return distance
        




        
