"""\
Function definitions for making populations of neurons

"""
from __future__ import print_function, division
import numpy as np
import moose

from moose_nerp.prototypes import logutil, util
log = logutil.Logger()

def count_neurons(netparams):
    size=np.ones(len(netparams.grid),dtype=np.int)
    length=np.ones(len(netparams.grid),dtype=np.float)
    numneurons=1
    volume=1
    for i in range(len(netparams.grid)):
        if netparams.grid[i]['inc']>0:
            length[i]=netparams.grid[i]['xyzmax']-netparams.grid[i]['xyzmin']
            size[i]=np.int(np.ceil(length[i]/netparams.grid[i]['inc']))
        numneurons*=size[i]
        volume*=length[i]
    return size, numneurons, volume

def create_population(container, netparams, name_soma):
    netpath = container.path
    proto=[]
    neurXclass={}
    locationlist=[]
    #determine total number of neurons
    size,numneurons,vol=count_neurons(netparams)
    pop_percent=[]
    for neurtype in netparams.pop_dict.keys():
        if moose.exists(neurtype):
            proto.append(moose.element(neurtype))
            neurXclass[neurtype]=[]
            pop_percent.append(netparams.pop_dict[neurtype].percent)
    #create cumulative array of probabilities for selecting neuron type
    choicearray=np.cumsum(pop_percent)
    if choicearray[-1]<1.0:
        log.info("Warning!!!! fractional populations sum to {}",choicearray[-1])
    #array of random numbers that will be used to select neuron type
    rannum = np.random.uniform(0,choicearray[-1],numneurons)
    #Error check for last element in choicearray equal to 1.0
    log.info("numneurons= {} {} choicarray={}", size, numneurons, choicearray)
    log.debug("rannum={}", rannum)
    for i,xloc in enumerate(np.linspace(netparams.grid[0]['xyzmin'], netparams.grid[0]['xyzmax'], size[0])):
        for j,yloc in enumerate(np.linspace(netparams.grid[1]['xyzmin'], netparams.grid[1]['xyzmax'], size[1])):
            for k,zloc in enumerate(np.linspace(netparams.grid[2]['xyzmin'], netparams.grid[2]['xyzmax'], size[2])):
                #for each location in grid, assign neuron type, update soma location, add in spike generator
                neurnumber=i*size[2]*size[1]+j*size[2]+k
                neurtypenum=np.min(np.where(rannum[neurnumber]<choicearray))
                log.debug("i,j,k {} {} {} neurnumber {} type {}", i,j,k, neurnumber, neurtypenum)
                typename = proto[neurtypenum].name
                tag = '{}_{}'.format(typename, neurnumber)
                new_neuron=moose.copy(proto[neurtypenum],netpath, tag)
                neurXclass[typename].append(container.path + '/' + tag)
                #update all coordinates of the neuron - add same value to x,y,z,x0,y0,z0 of all compartments
                util.move_neuron(xloc,yloc,zloc,new_neuron.path)
                comp=moose.element(new_neuron.path + '/'+name_soma)
                log.debug("x,y,z={},{},{} for {}", comp.x, comp.y, comp.z, new_neuron.path)
                locationlist.append([new_neuron.name,comp.x,comp.y,comp.z])
                #spike generator - can this be done to the neuron prototype?
                spikegen = moose.SpikeGen(comp.path + '/spikegen')
                #should these be parameters in netparams?
                spikegen.threshold = 0.0
                spikegen.refractT=1e-3
                m = moose.connect(comp, 'VmOut', spikegen, 'Vm')
    #Create variability in neurons of network
    for neurtype in netparams.chanvar.keys():
        for chan,var in netparams.chanvar[neurtype].items():
            #single multiplier for Gbar for all the channels compartments
            if var>0 and len(neurXclass[neurtype]):
                log.info('adding variability to {} soma {}, variance: {}', neurtype,chan, var)
                GbarArray=abs(np.random.normal(1.0, var, len(neurXclass[neurtype])))
                for ii,neurname in enumerate(neurXclass[neurtype]):
                    soma_chan_path=neurname+'/'+name_soma+'/'+chan
                    if moose.exists(soma_chan_path):
                        chancomp=moose.element(soma_chan_path)
                        chancomp.Gbar=chancomp.Gbar*GbarArray[ii]
    #
    return {'location': locationlist,
            'pop':neurXclass}