/* Copyright (c) 2015 EPFL-BBP, All rights reserved.                             
                                                                                 
THIS SOFTWARE IS PROVIDED BY THE BLUE BRAIN PROJECT ``AS IS''                    
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,            
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR           
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE BLUE BRAIN PROJECT                 
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR           
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF             
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR                  
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,            
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE             
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN           
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                                    
                                                                                 
This work is licensed under a 
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
To view a copy of this license, visit 
http://creativecommons.org/licenses/by-nc-sa/4.0/legalcode or send a letter to 
Creative Commons, 
171 Second Street, Suite 300, 
San Francisco, California, 94105, USA.                 
*/      

/*                                                                               
 * @file synapses.hoc                                                          
 * @brief Template that store the synapses                                
 * @author Werner Van Geit @ BBP                                                 
 * @date 2015                                                                    
*/        

begintemplate synapses_8ef1aa6602                

public load_synapses, synapse_list, netcon_list, netstim_list, \
        pre_mtypes_bitmap, n_of_pre_mtypes, id_mtype_map, pre_mtypes, \
        active_pre_mtypes, update_synapses, pre_mtype_netconlists, \
        pre_mtype_freqs,  pre_mtype_netstimlists, reset_synapses, \
        weights, delays, synconf_list

objref synapse_list, netcon_list, netstim_list, pre_mtypes_bitmap, \
        id_mtype_map, pre_mtypes, active_pre_mtypes, pre_mtype_netconlists, \
        pre_cell_ids, pre_mtype_freqs, pre_mtype_netstimlists, rng_list, \
        pre_mtype_weightlists, pre_mtype_synapselists, \
        were_active_pre_mtypes, pre_mtypes_excinh, weights, delays, \
        synconf_list, stringtools, tmp_synapse, this

/** Constructor */                                                          
proc init() { local mtype_id localobj mtype_map_file, mtype_name_string
    strdef line, mtype_name

    // Number of m-types
    n_of_mtypes = 55

    synapse_list = new List()
    rng_list = new List()
    netcon_list = new List()
    netstim_list = new List()
    weights = new Vector()
    delays = new Vector()
    pre_mtype_netconlists = new List()
    pre_mtype_netstimlists = new List()
    pre_mtype_weightlists = new List()
    pre_mtype_synapselists = new List()
    pre_mtypes_bitmap = new Vector(n_of_mtypes, 0)
    pre_mtypes_excinh = new Vector(n_of_mtypes, 1)
    active_pre_mtypes = new Vector(n_of_mtypes, 0)
    were_active_pre_mtypes = new Vector(n_of_mtypes, 0)
    pre_mtype_freqs = new Vector(n_of_mtypes, 10.0)

    pre_mtypes = new Vector()
    pre_cell_ids = new Vector()

    for i=0, n_of_mtypes-1 {
        pre_mtype_netconlists.append(new List())
        pre_mtype_netstimlists.append(new List())
        pre_mtype_synapselists.append(new List())
        pre_mtype_weightlists.append(new Vector())
    }

    id_mtype_map = new List()

    // Read the mapping between m-type names and m-type IDs
    mtype_map_file = new File("synapses/mtype_map.tsv")
    mtype_map_file.ropen()
    for i=0, n_of_mtypes-1 {
        mtype_map_file.gets(line)
        sscanf(line, "%d\t%s", &mtype_id, mtype_name)
        mtype_name_string = new String(mtype_name)
        id_mtype_map.append(mtype_name_string)
    }
    mtype_map_file.close()

    stringtools = new StringFunctions()                                              
}

/** Update the active synapses in the GUI
                                                                                 
    Arguments:                                                                   
        synapse_plot: synapse plot object               
*/                                                          
proc update_synapses() { localobj pre_mtype_netcons, pre_mtype_netstims, \
                            pre_mtype_weights, pre_mtype_synapses, synapse_plot
    synapse_plot = $o1

    for i=0, n_of_mtypes-1 {
        pre_mtype_netcons = pre_mtype_netconlists.o(i)
        pre_mtype_weights = pre_mtype_weightlists.o(i)
        pre_mtype_synapses = pre_mtype_synapselists.o(i)

        // Use different color for exc / inh synapses 
        if (pre_mtypes_excinh.x[i] == 0) {
            // Red, excitatory
            syn_color = 2
        } else {
            // Yellow, inhibitory
            syn_color = 5
        }

        // Loop over the synapses. 
        // enable and plot active synapses, 
        // disable and remove inactive ones
        // only do something if status of a synapse changed since last update
        for j=0, pre_mtype_netcons.count()-1 {
            if (active_pre_mtypes.x[i] == 1 && \
                    were_active_pre_mtypes.x[i] == 0) {
                // Set weight to correct value
                pre_mtype_netcons.o(j).weight = pre_mtype_weights.x[j]
                // Draw the synapses
                synapse_plot.point_mark(pre_mtype_synapses.o(j), \
                                        syn_color, 4, 6)
            } else if (active_pre_mtypes.x[i] == 0 && \
                    were_active_pre_mtypes.x[i] == 1) {
                // Disable synapse by setting weight to 0
                pre_mtype_netcons.o(j).weight = 0.0
                // Remove synapse from plot
                synapse_plot.point_mark_remove(pre_mtype_synapses.o(j))
            }
        }

        // Update the list were_active_pre_mtypes, which is the list of 
        // mtypes that are active after this update
        if (active_pre_mtypes.x[i] == 1 && were_active_pre_mtypes.x[i] == 0) {
            were_active_pre_mtypes.x[i] = 1
        } else if (active_pre_mtypes.x[i] == 0 && \
                    were_active_pre_mtypes.x[i] == 1) {
            were_active_pre_mtypes.x[i] = 0
        }

        // Update the netstim frequencies
        pre_mtype_netstims = pre_mtype_netstimlists.o(i)
        for j=0, pre_mtype_netstims.count()-1 {
            pre_mtype_netstims.o(j).interval = 1000.0 / pre_mtype_freqs.x[i]
        }
    }

}

/** Disable all the synapses */
proc reset_synapses() {
    for i=0, n_of_mtypes-1 {
        were_active_pre_mtypes.x[i] = 0
    }
}



/** Load the synconf file
    Arguments:                                                                   
        nsynapses: number of synapses in cell
*/                                                          
proc load_synconf() { localobj synconf_file, synconf_gids
    strdef synconf_string
    nsynapses = $1

    synconf_list = new List()
    for isynapse=0, nsynapses-1 {
        synconf_list.append(new List())
    }

    synconf_file = new File("synapses/synconf.txt")                                                        
    {synconf_file.ropen()}
    
    synconf_gids = new Vector()                                                      
                                                                                                                                                                 
    while (synconf_file.gets(synconf_string) > 0) {                                  
        stringtools.left(synconf_string, stringtools.len(synconf_string)-1)          
        synconf_gids.scantil(synconf_file, -1e15)                                    
        for i=0, synconf_gids.size()-1 { 
            synconf_list.o(synconf_gids.x[i]).append(new String(synconf_string))
        }                                                 
    }                                                     
}

/** Load all the synapses
                                                                                 
    Arguments:                                                                   
        cell_ref: reference to the cell object               
*/                                                          

proc load_synapses() { local isynapse, nsynapses, ncols, synapse_id, \
                            pre_cell_id, seg_x, synapse_type, \
                            dep, fac, use, tau_d, delay, weight, \
                            base_seed, gid, netstim_id \
                        localobj synapse_data, synapse_file, cell_ref, \
                            synapse, section, rng, netcon, netstim

    printf("Starting to add synapses\n")

    strdef sectionlist_name, synapse_type_name, synconf_string, head_string, \
        tail_string

    cell_ref = $o1

    // Load the file that contains all the information about the synapses
    synapse_file = new File("synapses/synapses.tsv")
    {synapse_file.ropen()}

    // Data structure to store the data in synapses.tsv
    synapse_data = new Matrix()
    synapse_data.scanf(synapse_file)
    
    synapse_file.close()

    nsynapses = synapse_data.nrow
    ncols = synapse_data.ncol

    // There is only one cell in this simulation, so let's give it gid 1
    gid = 4432

    // Base seed for the rngs
    base_seed = 0

    // Load list of hoc commands that have to be execute on every synapse
    // to set certain parameters that are not specified in synapse.tsv
    load_synconf(nsynapses)

    for isynapse=0, nsynapses-1 {
        // Read the synapse parameters from the matrix
        synapse_id = synapse_data.x[isynapse][0]
        pre_cell_id = synapse_data.x[isynapse][1]
        pre_mtype = synapse_data.x[isynapse][2]
        sectionlist_id = synapse_data.x[isynapse][3]
        sectionlist_index = synapse_data.x[isynapse][4]
        seg_x = synapse_data.x[isynapse][5]
        synapse_type = synapse_data.x[isynapse][6]
        dep = synapse_data.x[isynapse][7] 
        fac = synapse_data.x[isynapse][8] 
        use = synapse_data.x[isynapse][9] 
        tau_d = synapse_data.x[isynapse][10] 
        delay = synapse_data.x[isynapse][11] 
        weight = synapse_data.x[isynapse][12] 

        // Create sectionref to the section the synapse will be placed on
        if ( sectionlist_id == 0 ) {
            cell_ref.soma[sectionlist_index] section = new SectionRef()        
            sectionlist_name = "somatic" 
        } else if ( sectionlist_id == 1 ) {
            cell_ref.dend[sectionlist_index] section = new SectionRef()       
            sectionlist_name = "basal" 
        } else if ( sectionlist_id == 2 ) {
            cell_ref.apic[sectionlist_index] section = new SectionRef()        
            sectionlist_name = "apical" 
        } else if ( sectionlist_id == 3 ) {
            cell_ref.axon[sectionlist_index] section = new SectionRef()        
            sectionlist_name = "axonal" 
        } else {                                                                
            printf("Sectionlist_id %d not support\n", sectionlist_id)           
            exit(1)                                                             
        }

        // If synapse_type < 100 the synapse is inhibitory, otherwise 
        // excitatory
        if ( synapse_type < 100 ) {
            synapse_type_name = "inhibitory"
            section.sec synapse = new ProbGABAAB_EMS(seg_x)
            synapse.tau_d_GABAA  = tau_d
            rng = new Random()                                                      
            rng.MCellRan4( isynapse*100000+100, gid+250+base_seed )                
            rng.lognormal(0.2, 0.1)                                                 
            synapse.tau_r_GABAA = rng.repick()
            pre_mtypes_excinh.x[pre_mtype] = 1                      
        } else {
            synapse_type_name = "excitatory"
            section.sec synapse = new ProbAMPANMDA_EMS(seg_x)
            synapse.tau_d_AMPA = tau_d
            pre_mtypes_excinh.x[pre_mtype] = 0                      
        }

        synapse.Use = abs( use )                                                  
        synapse.Dep = abs( dep )                                                  
        synapse.Fac = abs( fac )   

        // Execute all the extra synaptic configuration lines from synconf.txt
        tmp_synapse = synapse
        for isynconf=0,synconf_list.o(isynapse).count()-1 {
            synconf_string = synconf_list.o(isynapse).o(isynconf).s
            // Replacing all occurrences of %s with the temporary synapse name
            while( stringtools.substr( synconf_string, "%s" ) != -1 ) {
                stringtools.head(synconf_string, "%s", head_string)
                stringtools.tail(synconf_string, "%s", synconf_string)
                sprint(synconf_string, "%s%s%s", head_string, "tmp_synapse", synconf_string)
            }
            // Add {} around the string
            sprint(synconf_string, "{%s}", synconf_string)
            // Execute the statement 
            execute1(synconf_string, this)
        }

        // Create the random number generator for the synapse
        rng = new Random()                                                          
        rng.MCellRan4( isynapse*100000+100, gid+250+base_seed )                    
        rng.uniform(0,1)                                                            
        synapse.setRNG( rng )                                                       
        rng_list.append(rng)        
                                             
        synapse_list.append(synapse)

        // Check if there is already a netstim (spike generator) 
        // for the presynaptic cell
        // If it exists, use that one
        // Otherwise create a new one
        if (pre_cell_ids.contains(pre_cell_id)) {
            netstim_id = pre_cell_ids.indwhere("==", pre_cell_id)
            netstim = netstim_list.o(netstim_id)
        } else {
            section.sec netstim = new NetStim(seg_x)
            netstim.start = 0
            netstim.interval = 100
            netstim.number = 1e20
            netstim.noise = 1
            netstim_list.append(netstim)
            pre_mtype_netstimlists.o(pre_mtype).append(netstim)
            pre_cell_ids.append(pre_cell_id)
        }

        // Create a connection between the netstim and the synapse
        netcon = new NetCon(netstim, synapse)
        netcon.delay = delay
        netcon.weight = 0.0 
        netcon_list.append(netcon)  
        
        // Save all the objects we made to make them persistent        
        pre_mtype_netconlists.o(pre_mtype).append(netcon) 
        pre_mtype_weightlists.o(pre_mtype).append(weight) 
        weights.append(weight) 
        delays.append(delay) 
        pre_mtype_synapselists.o(pre_mtype).append(synapse) 
        pre_mtypes_bitmap.x[pre_mtype] = 1
        
        printf("Added %s synapse %d originating from cell %d of m-type %s on %s section %d(%f) and dep %f\n", \
            synapse_type_name, synapse_id, pre_cell_id, id_mtype_map.o(pre_mtype).s, sectionlist_name, \
            sectionlist_index, seg_x, dep)
 
    }    

    // Make a list of all the m-types presynaptic to this cell, used by the GUI
    for i=0, n_of_mtypes-1 {
        if (pre_mtypes_bitmap.x[i] == 1) {
            pre_mtypes.append(i)
        }
    } 

}

endtemplate synapses_8ef1aa6602