/***********************************************************************************************
I.  LOAD LIBRARIES
***********************************************************************************************/
{load_file("nrngui.hoc")}				// Standard definitions - NEURON library file

{load_file("netparmpi.hoc")}			// Contains the template that defines the properties of
										//  the ParallelNetManager class, which is used to set
										//   up a network that runs on parallel processors
										
{load_file("./setupfiles/ranstream.hoc")}	// Contains the template that defines a RandomStream
											//  class used to produce random numbers
											// 	for the cell noise (what type of noise?)
											
{load_file("./setupfiles/CellCategoryInfo.hoc")}	// Contains the template that defines a 
													//  CellCategoryInfo class used to store
													// 	celltype-specific parameters

{load_file("./setupfiles/defaultvar.hoc")}	// Contains the proc definition for default_var proc
													//  that can't be changed at command line

{load_file("./setupfiles/parameters.hoc")}	// Loads in operational and model parameters that can
											//  be changed at command line											
{load_file("./setupfiles/set_other_parameters.hoc")}// Loads in operational and model parameters

/**********************/
default_var("BasedOnRunName","ReserveTrestles_01")		// Name of simulation run that the netclamp is based on
default_var("CellOI","axoaxoniccell")		// type of presynaptic cell
default_var("CellOItype",0)		// type of presynaptic cell
default_var("gid",11)		// gid of presynaptic cell

strdef cmdstr, cmd, RunName

sprint(RunName,"%s_%d", CellOI, gid)
/***********************************************************************************************
II. SET MODEL SIZE, CELL DEFINITIONS
***********************************************************************************************/
celsius=34

{load_file("./setupfiles/load_cell_category_info.hoc")}	// Reads the 'cells2include.hoc' file and
														//  loads the information into one
														//  'CellCategoryInfo' object for each cell
														//  type (bio or art cells?). Info includes
														//  number of cells, gid ranges, type name 

strdef tempFileStr
sprint(tempFileStr,"./cells/class_%s.hoc",cellType[CellOItype].technicalType)	// Concatenate the
																				//  path and file
print "loading ", tempFileStr																
load_file(tempFileStr)			// Load the file with the template that defines the classs

sprint(tempFileStr,"./cells/class_%s.hoc","ppspont")	// Concatenate the
print "loading ", 		tempFileStr																
load_file(tempFileStr)			// Load the file with the template that defines the class

totalCells = 1						// totalCells counts the number of 'real' cells
									
// Create numStims number of NetStims
strdef fn
objref f2
f2 = new File()
sprint(fn, "./netclamp/%s/%s_%d_NetStimConns.dat", BasedOnRunName, CellOI,  gid)
f2.ropen(fn)
numStims = f2.scanvar

ncell = 1 + numStims						// ncell counts all 'real' and 'artificial' cells
							
objref TMPcellType[numCellTypes+1]
								
for i=0,numCellTypes-1 {
	if (i==CellOItype) {
		cellType[CellOItype].numCells = 1	
	} else {
		cellType[i].numCells = 0
	}
	if (i>0) {
		cellType[i].updateGidRange(cellType[i-1].cellEndGid+1)	// Update the gid range for each
	} else {
		cellType[i].updateGidRange(0)	// Update the gid range for each
	}
	TMPcellType[i] = cellType[i]
}

TMPcellType[numCellTypes] = new CellCategoryInfo(numCellTypes)	// Make one object for each cell type to store cell type info
TMPcellType[numCellTypes].setCellTypeParams("PatternStim", "ppspont", 1, numStims, 0, 1)	// Set parameters
TMPcellType[numCellTypes].numCons = new Vector(numCellTypes,0)

numCellTypes = numCellTypes+1

objref cellType[numCellTypes]

for i=0,numCellTypes-1{
	cellType[i] = TMPcellType[i]
	print cellType[i].cellType_string, " ", cellType[i].numCells, "(|", cellType[i].cellStartGid, "| to |", cellType[i].cellEndGid, "|)"
}


objref TMPcellType
/***********************************************************************************************
III.SET UP PARALLEL CAPABILITY AND WRITE OUT RUN RECEIPT
***********************************************************************************************/
objref pnm, pc, nc, nil
proc parallelizer() {
	pnm = new ParallelNetManager(ncell)	// Set up a parallel net manager for all the cells
	pc = pnm.pc
	pnm.round_robin()					// Incorporate all processors - cells 0 through ncell-1
										//	are distributed throughout the hosts
										//	(cell 0 goes to host 0, cell 1 to host 1, etc)
}
parallelizer()


iterator pcitr() {local i2, startgid	// Create iterator for use as a standard 'for' loop
										//  throughout given # cells usage:
										//  for pcitr(&i1, &i2, &gid, it_start, it_end) {do stuff}
										//  it_start and it_end let you define range over
										//  which to iterate
										//  i1 is the index of the cell on the cell list for that host
										//  i2 is the index of that cell for that cell type on that host
	numcycles = int($4/pc.nhost)
	extra = $4%pc.nhost
	addcycle=0
	if (extra>pc.id) {addcycle=1}
	startgid=(numcycles+addcycle)*pc.nhost+pc.id
	i1 = numcycles+addcycle // the index into the cell # on this host.
	i2 = 0 // the index of the cell in that cell type's list on that host
	if (startgid<=$5) {
		for (i3=startgid; i3 <= $5; i3 += pc.nhost) {	// Just iterate through the cells on
														//  this host(this simple statement
														//  iterates through all the cells on
														//  this host and only these cells because 
														//  the roundrobin call made earlier dealt
														//  the cells among the processors in an
														//  orderly manner (like a deck of cards)
				$&1 = i1
				$&2 = i2
				$&3 = i3
				iterator_statement
				i1 += 1
				i2 += 1
		}
	}
}


// Create one Real Cell

objref cells, ransynlist, ranstimlist
cells = new List()						
ransynlist = new List()
ranstimlist = new List()

func xpos_algorithm() { // Arguments: 1-gid, 2-numCells, 3-startGid, 4-binNum, 5- binNum, 6-binSize; Return: x position of cell
	return 0
}
func ypos_algorithm() { // Arguments: gid, numCells, startGid, binNum, binNum, binSize; Return: y position of cell
	return 0
}
func zpos_algorithm() { // Arguments: 1-gid, 2-numCells, 3-startGid, 4-binNum, 5-binSize, cell layer Zo; Return: z position of cell
	return 0
}
													
{load_file("./setupfiles/create_cells_pos.hoc")}	// Creates each cell on its assigned host
													//  and sets its position using the algorithm
													//  defined above



objref cell
for r=cellType[numCellTypes-1].cellStartGid, cellType[numCellTypes-1].cellEndGid {
	cell = pc.gid2cell(r)
	cell.interval=1e9
	cell.start=-1
}

// Add a recording to its soma potential and somatic ion channel currents, as well as a few other locations

strdef nickname, secstr
nickname="soma"
myi_flag=1

strdef cmdstr
strdef mname, tmpstr
objref strobj, mt, cell
objref mechstring[9], mechlength

mechlength = new Vector(1)
strobj = new StringFunctions()

objref cell, mt
cell = pc.gid2cell(cellType[CellOItype].cellStartGid)
secstr="soma"

sprint(cmdstr,"objref mytrace%s", nickname)
{execute1(cmdstr)}

{sprint(cmdstr,"mytrace%s = new Vector(tstop/dt)", nickname)}
{execute1(cmdstr)}

//{sprint(cmdstr,"mytrace%s.record(&cell.%s.v(0.5))", nickname, secstr)}
{sprint(cmdstr,"mytrace%s.record(&%s.v(0.5))", nickname, cell.myroot)}
{execute1(cmdstr)	}	

/* Get mechanisms from soma section "myroot" recorded */

if (myi_flag==1) {
	mt = new MechanismType(0)
	objref mechstring[mt.count()]
	k = 0
	for i=0, mt.count()-1 {
		mt.select( i )
		mt.selected(mname)
		if( ismembrane(mname)) {
			if (strcmp(mname,"capacitance")!=0 && strcmp(mname,"morphology")!=0 && strcmp(mname,"iconc_Ca")!=0  && strcmp(mname,"iconcCa")!=0 && strcmp(mname,"ccanl")!=0 && strcmp(mname,"cad")!=0 && strcmp(mname,"pas")!=0 && strcmp(mname,"vmax")!=0 ) { //
				if (strobj.substr(mname,"_ion")==-1) {
					//printf("myi_%s \n", mname) 
					sprint(tmpstr, "myi_%s", mname) // "cell.soma.%s(0.5)", tmpstr
					mechstring[k] = new String()
					mechstring[k].s = tmpstr
					if (strcmp("myi_ch_Nav", mechstring[k].s)==0) {
					print nickname, ": ", tmpstr}
					{k = k+1}
				} 
			} 
		}
	}
	{mechlength.x[0] = k}
		if (strcmp("som", nickname)==0) {
	print "num mechs: ", k}

	for rt = 0, mechlength.x[0]-1 {
		sprint(cmdstr, "{objref %s_%s}", nickname, mechstring[rt].s)
		execute1(cmdstr)
		sprint(cmdstr, "{%s_%s = new Vector(%g)}", nickname, mechstring[rt].s, (tstop-tstart)/dt)
		execute1(cmdstr)
		sprint(cmdstr, "{%s_%s.record(&%s.%s(0.5))}", nickname, mechstring[rt].s, cell.myroot, mechstring[rt].s)
		execute1(cmdstr)
		if (strcmp("myi_ch_Nav", mechstring[rt].s)==0) {
		print "record: ", cmdstr}
	}
}



// Connect NetStims to all CellOI input synapses
{load_file("./setupfiles/nc_append_functions.hoc")}	// Defines nc_append and nc_appendo, which 
{load_file("./setupfiles/load_cell_conns.hoc")}	// Load in the cell connectivity info

pregid = 1

numConnTypes = f2.scanvar
postcellType = CellOItype

for r=0, numConnTypes-1 {
	precellType = f2.scanvar
	numSynTypes = f2.scanvar
	synWeight = cellType[precellType].wgtConns.x[postcellType]	
		
	for SynNumber = 0, numSynTypes-1 {
		print "pregid = ", pregid, ", precellType = ", precellType, ", synapse = ", SynNumber, ", weight = ", synWeight
		nc_append(pregid, cellType[postcellType].cellStartGid, precellType, SynNumber, synWeight + (pregid+1)*1000, 0.5)	// Make the connection
		pregid += 1
		cellType[postcellType].numCons.x[precellType] += 1
	}
}

f2.close()

// Read in firing times of all net stims
strdef fn
objref f2
objref pattern_, tvec_, idvec_
f2 = new File()
sprint(fn, "./netclamp/%s/%s_%d_NetStimTimes.dat", BasedOnRunName, CellOI,  gid)
f2.ropen(fn)
numspikes = f2.scanvar

pattern_ = new PatternStim()

tvec_ = new Vector(numspikes)
idvec_ = new Vector(numspikes)

for i=0, numspikes-1 {
	tvec_.x[i] = f2.scanvar // spike time in ms
	idvec_.x[i] = f2.scanvar // gid of NetStim to make fire
}
f2.close()

pattern_.fake_output = 1
pattern_.play(tvec_, idvec_)

// Run simulation & write out spike raster and recording results

proc init() { local dtsav, temp, secsav, secondordersav	// Redefine the proc that initializes the
														//  simulation (why?)

	dtsav = dt						// Save the desired dt value to reset it after temporarily
									//  changing dt to run a quick presimulation to allow the
									//  network to reach steady state before we start 'recording'
									
	secondordersav = secondorder	// Save the desired secondorder value to reset it after
									//  temporarily changing secondorder to run a quick presimulation
									//  to allow the network to reach steady state before we start
									//  'recording' its results

	finitialize(v_init)	// Call finitialize from within the custom init proc, just as the default
						//  init proc does. Note that finitialize will call the INITIAL block for
						//  all mechanisms and point processes inserted in the sections and set the
						//	initial voltage to v_init for all sections

	t = -200			// Set the start time for (pre) simulation; -500 ms to allow the network to
						// reach steady state before t = 0, when the real simulation begins
						
	dt= 10				// Set dt to a large value so that the presimulation runs quickly
	
	secondorder = 0		// Set secondorder to 0 to set the solver to the default fully implicit backward
						//  euler for numerical integration (see NEURON ref)
		
	temp= cvode.active()			// Check whether cvode, a type of solver (?) is on
	if (temp!=0) {cvode.active(0)}	// If cvode is on, turn it off while running the presimulation
															
	while(t<-100) { fadvance() if (PrintTerminal>1) {print t}}	// Run the presimulation from t = -500
															//  to t = -100 (why not 0?) to let the
															//  network and all its components reach
															//  steady state. Integrate all section
															//  equations over the interval dt,
															//  increment t by dt, and repeat until
															//  t at -100
															
	if (temp!=0) {cvode.active(1)}	// If cvode was on and then turned off, turn it back on now
	
	t = tstart 						// Set t to the start time of the simulation
	
	dt = dtsav						// Reset dt to the specified value for the simulation
	
	secondorder = secondordersav	// Reset secondorder to the specified value for the simulation
	
	if (cvode.active()){
		cvode.re_init()				// If cvode is active, initialize the integrator
	} else {
		fcurrent()					// If cvode is not active, make all assigned variables
									//	 (currents, conductances, etc) consistent with the
									//   values of the states
	}
}

simnum=0

strdef cmdstr
sprint(cmdstr,"mkdir ./netclamp/%s/%s", BasedOnRunName,RunName)
print cmdstr
system(cmdstr)

proc spikeout() {local i, rank  localobj f				// Write out a spike raster (cell, spike time)
	pc.barrier()									// Wait for all ranks to get to this point
	sprint(cmd,"./netclamp/%s/%s/spikeraster_%03.0f.dat", BasedOnRunName, RunName, simnum)
	f = new File(cmd)
	if (pc.id == 0) { 								// Write header to file 1 time only
		f.wopen()
		f.close()
	}
	
	for rank = 0, pc.nhost-1 {				// For each processor, allow processor to append to file the spike times of its cells
		if (rank == pc.id) {				// Ensure that each processor runs once
			f.aopen() 						// Open for appending to file
			for i=0, pnm.idvec.size-1 {
				f.printf("%.3f %d\n", pnm.spikevec.x[i], pnm.idvec.x[i])	// Print the spike time and spiking cell gid
			}
			f.close()
		}
		pc.barrier()
	}
}

// also write out vector recordings
objref f, strobj
strobj = new StringFunctions()
strdef outfile, beforestr, afterstr
objref tgt

proc printtrace() {
	{sprint(outfile, "./netclamp/%s/%s/%s__%d_%s_trace_%03.0f.dat", BasedOnRunName, RunName, CellOI, gid, secstr, simnum)}
	{f = new File(outfile)}
	{f.wopen()}
	{f.printf("t\tv")} // write header
	if (myi_flag==1) {
		
		mt = new MechanismType(0)
		objref mechstring[mt.count()]
		k = 0
		for i=0, mt.count()-1 {
			mt.select( i )
			mt.selected(mname)
			if( ismembrane(mname)) {
				if (strcmp(mname,"capacitance")!=0 && strcmp(mname,"morphology")!=0 && strcmp(mname,"iconc_Ca")!=0  && strcmp(mname,"iconcCa")!=0 && strcmp(mname,"ccanl")!=0 && strcmp(mname,"cad")!=0 && strcmp(mname,"pas")!=0 ) { //
					if (strobj.substr(mname,"_ion")==-1) {
						//printf("myi_%s \n", mname) 
						sprint(tmpstr, "myi_%s", mname) // "cell.soma.%s(0.5)", tmpstr
						mechstring[k] = new String()
						mechstring[k].s = tmpstr
			if (strcmp("myi_ch_Nav", mechstring[k].s)==0) {
						print nickname, ": ", tmpstr}
						{k = k+1}
					} 
				} 
			}
		}
		mechlength.x[0]=k
		
		
		for rt = 0, mechlength.x[0]-1 {
			sprint(cmdstr, "%s_%s", nickname, mechstring[rt].s)		
		if (strcmp("myi_ch_Nav", mechstring[rt].s)==0) {
			print "Nav nickname: ", nickname}
		if (strcmp("som", nickname)==0) {
			print "cmdstr: ", cmdstr, " declared: ", name_declared(cmdstr)		}
			if (name_declared(cmdstr)>0) {
				f.printf("\t%s", mechstring[rt].s)
			}
		}
	}
	{f.printf("\n")}

	for i=0, (tstop-tstart)/dt-1 {
		{sprint(cmdstr, "{f.printf(\"%%g\\t%%g\", i*dt, mytrace%s.x[i])}", nickname)}
		{execute1(cmdstr)}
		if (myi_flag==1) {
			for rt = 0, mechlength.x[0]-1 {
				sprint(cmdstr, "%s_%s", nickname, mechstring[rt].s)				
				if (name_declared(cmdstr)>0) {
					sprint(cmdstr, "{f.printf(\"\\t%%g\", %s_%s.x[i])}", nickname, mechstring[rt].s)
					execute1(cmdstr)
				}
			}
		}						
		{f.printf("\n")}
	}
	{f.close()}	
}

use_cache_efficient=1
get_spike_hist=0
use_bin_queue=0
use_spike_compression=0
if (use_spike_compression==1) {
	maxstepval = 2.5
} else {
	maxstepval = 0.5
}														
															
proc rrun(){									// Run the network simulation and write out the results

	pnm.want_all_spikes() 						// Record all spikes of all cells on this machine into the
												//  vectors pnm.spikevec (spiketimes) and pnm.idvec (gids)
												
	local_minimum_delay = pc.set_maxstep(maxstepval)	// Set every machine's max step size to minimum delay of
												//  all netcons created on pc using pc.gid_connect, but
												//  not larger than 10
												
	stdinit()									// Call the init fcn (which is redefined in this code) and
												//  then make other standard calls (to stop watch, etc)

	pc.psolve(tstop)							// Equivalent to calling cvode.solve(tstop) but for parallel NEURON;
												//  solve will be broken into steps determined by the result of
												//  set_maxstep

	spikeout()	// Write the spike times (in ms) and spiking cells (by gid) into a file called "spikeraster.dat"
	
	printtrace()
}


walltime = startsw()
strdef cmd, cmdo
newtstop = tstop
warningflag=0

{cvode.cache_efficient(use_cache_efficient)} // always double check that this addition does not affect the spikeraster (via pointers in mod files, etc)

if (use_bin_queue==1) {
	use_fixed_step_bin_queue = 1 // boolean
	use_self_queue = 0 // boolean - this one may not be helpful for me, i think it's best for large numbers of artificial cells that receive large numbers of inputs
	{mode = cvode.queue_mode(use_fixed_step_bin_queue, use_self_queue)}
}

if (maxstepval==1) {
	nspike = 3 // compress spiketimes or not
	gid_compress = 0 //only works if fewer than 256 cells on each proc
	{nspike = pc.spike_compress(nspike, gid_compress)}
}
celsius = 34

simnum=0
numsims=10
lowerbound=0 // 5
rrun()

strdef f2n, f3n
objref f2, f3
f2 = new File()
f3 = new File()
sprint(f2n, "./netclamp/%s/%s_%d_NetStimConns.dat", BasedOnRunName, CellOI,  gid)
sprint(f3n, "./netclamp/%s/%s_%d_key.dat", BasedOnRunName, CellOI,  gid)
f3.wopen(f3n)
for simnum=1, numsims {
	//nclist.remove_all
	pregid=1

	f2.ropen(f2n)
	numStims = f2.scanvar

	numConnTypes = f2.scanvar
	postcellType = CellOItype
	
	f3.printf("%d\t%f\n", simnum, 1 + (simnum-lowerbound)*.1)

	for r=0, numConnTypes-1 {
		precellType = f2.scanvar
		numSynTypes = f2.scanvar
		synWeight = cellType[precellType].wgtConns.x[postcellType]	
		
		if (strcmp(cellType[precellType].cellType_string,"ca3cell")==0 || strcmp(cellType[precellType].cellType_string,"eccell")==0) {
			synWeight = synWeight*(1 + (simnum-lowerbound)*.1)
		}
			
		for SynNumber = 0, numSynTypes-1 {		
			nclist.o(pregid-1).weight = synWeight
			print "pregid = ", pregid, ", precellType = ", precellType, ", synapse = ", SynNumber, ", weight = ", synWeight
			//nc_append(pregid, cellType[postcellType].cellStartGid, precellType, SynNumber, synWeight + (pregid+1)*1000, 0.5)	// Make the connection
			pregid += 1
			cellType[postcellType].numCons.x[precellType] += 1
		}
	}

	f2.close()

	rrun()	// Run the network simulation (in proc rrun)
}

f3.close()

///////////////

{pc.runworker()} 	// Everything after this line is executed only by the host node
					//  The NEURON website describes this as "The master host returns immediately. Worker hosts
					//  start an infinite loop of requesting tasks for execution." 
					
{pc.done()}			// Sends the quit message to the worker processors, which then quit NEURON

quit()	// Sends the quit message to the host processor, which then quits NEURON