/*                                                                               
Copyright (c) BBP/EPFL 2020-2022.
This work is licenced under Creative Common CC BY-NC-SA-4.0 (https://creativecommons.org/licenses/by-nc-sa/4.0/)
*/        

load_file("nrngui.hoc")
load_file("import3d.hoc")
load_file("{{cell_hoc_file}}")
{%- if add_synapses %}
load_file("{{syn_dir}}/{{syn_hoc_file}}")
{%- endif %}
//======================== constants ========================
celsius={{celsius}}
v_init={{v_init}}
dt={{dt}}

//=================== creating cell object ===========================
objref cell

proc create_cell() { localobj cellstring
    cellstring = new String()
    sprint(cellstring.s, "cell = new %s(%d, \"%s\")", "{{template_name}}", {{gid}}, "{{morph_path}}")
    execute(cellstring.s)
}
//==================== recording settings ==========================

/*
    Gives the min_distance and max_distance of the edges of the accessed section
    with respect to the specified origin (which should be set with distance() before using this function)

    Returns: min_max_distances (vector with [min_distance, max_distance])
*/
obfunc get_min_and_max_distance() { local start_distance, end_distance \
                                localobj min_max_distances

    min_max_distances = new Vector(2)
    
    start_distance = distance(1, 0.0)
    end_distance = distance(1, 1.0)

    if (start_distance > end_distance){
        min_max_distances.x[0] = end_distance
        min_max_distances.x[1] = start_distance
    } else {
        min_max_distances.x[0] = start_distance
        min_max_distances.x[1] = end_distance
    }

    return min_max_distances
}
/*
    $o1: iseclist
    $2: soma_distance

    Returns: reference to the section of the section list which is at given soma distance
*/
obfunc find_isec_at_soma_distance() { local soma_distance \
                                    localobj sref, iseclist, min_max_distances
    iseclist = $o1
    soma_distance = $2

    cell.soma[0] distance(0, 0.5)

    forsec iseclist {
        min_max_distances = get_min_and_max_distance()

        if (min_max_distances.x[0] <= soma_distance && soma_distance <= min_max_distances.x[1]){
            sref = new SectionRef()
        }
    }

    return sref
}

/*
    $o1: section reference
    $2: soma_distance

    Returns: location in given section that is at given soma distance
*/
func find_comp_x_at_soma_distance() { local soma_distance, comp_x \
                                    localobj sref, min_max_distances
    sref = $o1
    soma_distance = $2

    cell.soma[0] distance(0, 0.5)

    sref.sec {
        min_max_distances = get_min_and_max_distance()
    }
    comp_x = (soma_distance - min_max_distances.x[0]) / (min_max_distances.x[1] - min_max_distances.x[0])

    return comp_x
}

/*
    $1: apical_point_isec

    Returns: section list containing all section in apical branch
*/
obfunc get_apical_branch() { local apical_point_isec \
                            localobj apical_branch, current_section_ref, strobj
    apical_point_isec = $1
    apical_branch = new SectionList()
    strobj = new StringFunctions()

    cell.apic[apical_point_isec] current_section_ref = new SectionRef()

    while (current_section_ref.has_parent) {
        current_section_ref.sec {
            if (strobj.substr(secname(), "soma[0]") != -1) { // if section is soma
                break
            }

            current_section_ref.sec apical_branch.append()
        }

        // change current section to its parent
        current_section_ref.parent current_section_ref = new SectionRef()
    }

    return apical_branch
}


objref time, voltage {{extra_recordings_vars}}
proc create_recording() { local comp_x localobj secref, apical_branch // local vars needed depending on extra recordings
    voltage = new Vector()
    time = new Vector()

    time.record(&t, 0.1)
    voltage.record(&v(0.5), 0.1)

    {{extra_recordings}}

}

//==================== stimulus settings ===========================

objref holding_stimulus
{{initiate_step_stimuli}}
objref ramp_times
objref ramp_amps
objref ramp_stimulus

proc reset_stimuli () {
    holding_stimulus = new IClamp(0.5)
    holding_stimulus.dur = 0.0
    holding_stimulus.del = 0.0
    holding_stimulus.amp = 0.0
    cell.soma holding_stimulus

    ramp_times = new Vector()
    ramp_amps = new Vector()

    ramp_stimulus = new IClamp(0.5)
    ramp_stimulus.dur = 0.0
    cell.soma ramp_stimulus

    {{reset_step_stimuli}}
}

proc reset_cell(){
    {%- if add_synapses %}
    // Reset the synapses if there are any from previous protocols
    cell.synapses.reset_netcons()
    {%- endif %}
    reset_stimuli()
}

proc run_simulation() { local stim_number localobj fpath, timevoltage
    fpath = new String()

    {{run_simulation}}
}

//============================= simulation ================================

proc simulate() {
    cvode.active(0)

    print "Running simulation"

    run()
}

//============================= saving results ============================

proc write_float_output_file() {localobj fh, fpath
    fpath = $o1

    fh = new File()
    fh.wopen(fpath.s)
    fh.printf("%e ", $2)
    fh.close()

    print "Saved voltage trace to: ", fpath.s
}

proc write_output_file() { localobj timevoltage, fh, fpath
    fpath = $o1
    timevoltage = $o2

    fh = new File()
    fh.wopen(fpath.s)
    timevoltage.fprint(0, fh, "%e ")
    fh.close()

    print "Saved voltage trace to: ", fpath.s
}
