#include <../../nrnconf.h>
/* /local/src/master/nrn/src/nrnoc/extcell.c,v 1.4 1996/05/21 17:09:19 hines Exp */

#include	<stdio.h>
#include	<math.h>
#include	"section.h"
#include	"nrniv_mf.h"
#include	"hocassrt.h"

extern int cvode_active_;
extern int nrn_use_daspk_;

#if EXTRACELLULAR

/* the N index is a keyword in the following. See init.c for implementation.*/
static const char *mechanism[] = {
	"0",
	"extracellular",
	"xraxial[N]", "xg[N]", "xc[N]", "e_extracellular", 0,
#if I_MEMBRANE
	"i_membrane",
#endif
	0,
	"vext[N]", 0,
};
static HocParmLimits limits[] = {
	"xraxial", 1e-9, 1e15,
	"xg", 0., 1e15,
	"xc", 0., 1e15,
	0,0.,0.
};

static HocParmUnits units[] = {
	"xraxial", "MOhm/cm",
	"xg", "S/cm2",
	"xc", "uF/cm2",
	"e_extracellular", "mV",
	"vext", "mV",
	"i_membrane", "mA/cm2",
	0,0
};

static void extcell_alloc(Prop*);
static void extcell_init(NrnThread* nt, Memb_list* ml, int type);
#if 0
static void printnode(const char* s);
#endif

static int _ode_count(int type) {
/*	hoc_execerror("extracellular", "cannot be used with CVODE");*/
	/* but can be used with daspk. However everything is handled
	in analogy with intracellular nodes instead of in analogy with a
	membrane mechanism.
	Thus the count really comes from the size of sp13mat.
*/
	return 0;
}

void extracell_reg_(void) {
	int i;
	register_mech(mechanism, extcell_alloc, (Pvmi)0, (Pvmi)0, (Pvmi)0,
		extcell_init, -1, 1);
	i = nrn_get_mechtype(mechanism[1]);
	hoc_register_cvode(i, _ode_count, 0,0,0);
	hoc_register_limits(i, limits);
	hoc_register_units(i, units);
}

/* solving is done with sparse13 */

/* interface between hoc and extcell */
#define xraxial	pd	/* From Eion */
#define xg	(pd + (nlayer))
#define xc	(pd + 2*(nlayer))
#define e_extracellular	pd[3*(nlayer)]
#if I_MEMBRANE
#define i_membrane pd[1 + 3*(nlayer)]
#define sav_g	pd[2 + 3*(nlayer)]
#define sav_rhs	pd[3 + 3*(nlayer)]
#endif

/* based on update() in fadvance.c */
/* update has already been called so modify nd->v based on dvi
we only need to update extracellular
nodes and base the corresponding nd->v on dvm (dvm = dvi - dvx)
*/
void nrn_update_2d(NrnThread* nt)
{
	int i, cnt, il;
	extern int secondorder;
	Node *nd, **ndlist;
	Extnode *nde;
	double* pd;
	double cfac;
	Memb_list* ml = nt->_ecell_memb_list;
	if (!ml) { return; }
	cfac = .001 * nt->cj;
	cnt = ml->nodecount;
	ndlist = ml->nodelist;
	
	for (i=0; i < cnt; ++i) {
		nd = ndlist[i];
		nde = nd->extnode;
		for (il = 0; il < nlayer; ++il) {
			nde->v[il] += *nde->_rhs[il];
		}
		NODEV(nd) -= *nde->_rhs[0];
	}

#if I_MEMBRANE
	for (i=0; i < cnt; ++i) {
		pd = ml->data[i];
		nd = ndlist[i];
		NODERHS(nd) -= *nd->extnode->_rhs[0];
i_membrane = sav_g * (NODERHS(nd)) + sav_rhs;
#if 1
		/* i_membrane is a current density (mA/cm2). However
			it contains contributions from Non-ELECTRODE_CURRENT
			point processes. i_membrane(0) and i_membrane(1) will
			return the membrane current density at the points
			.5/nseg and 1-.5/nseg respectively. This can cause
			confusion if non-ELECTRODE_CURRENT point processes
			are located at these 0-area nodes since 1) not only
			is the true current density infinite, but 2) the
			correct absolute current is being computed here
			at the x=1 point but is not available, and 3) the
			correct absolute current at x=0 is not computed
			if the parent is a rootnode or there is no
			extracellular mechanism for the parent of this
			section. Thus, if non-ELECTRODE_CURRENT point processesm,
			eg synapses, are being used it is not a good idea to
			insert them at the points x=0 or x=1
		*/
#else
		i_membrane *= NODEAREA(nd);
		/* i_membrane is nA for every segment. This is different
			from all other continuous mechanism currents and
			same as PointProcess currents since it contains
			non-ELECTRODE_CURRENT point processes and may
			be non-zero for the zero area nodes.
		*/
#endif

	}	
#endif
}

static void extcell_alloc(Prop* p)
{
	double *pd;
	int i;

#if I_MEMBRANE
#define nparm (3*(nlayer) + 4)
#else
#define	nparm (3*(nlayer) + 1)
#endif
	pd = nrn_prop_data_alloc(EXTRACELL, nparm, p);
	p->param_size = nparm;

	for (i=0; i < nlayer; ++i) {
		xraxial[i]= 1.e9;
		xg[i] = 1.e9;
		xc[i] = 0.;
	}
	e_extracellular = 0.;
#if 0
	i_membrane = 0.;
	sav_g = 0.;
	sav_rhs = 0.;
#endif
	p->param = pd;
}

/*ARGSUSED*/
static void extcell_init(NrnThread* nt, Memb_list* ml, int type) {
	int ndcount = ml->nodecount;
	Node** ndlist = ml->nodelist;
	double** data = ml->data;
	int i, j;
	double* pd;
	if ((cvode_active_ > 0) && (nrn_use_daspk_ == 0)) {
hoc_execerror("Extracellular mechanism only works with fixed step methods and daspk", 0);
	}
	for (i=0; i < ndcount; ++i) {
		for (j = 0; j < nlayer; ++j) {
			ndlist[i]->extnode->v[j] = 0.;
		}
#if I_MEMBRANE
		pd = data[i];
		i_membrane = 0.;
#endif
	}
}

void extcell_node_create(Node* nd) {
	int i, j;
	Extnode *nde;
	Prop* p;
	/* may be a nnode increase so some may already be allocated */
	if (!nd->extnode) {
		nde = (Extnode *)emalloc(sizeof(Extnode));
		nd->extnode = nde;
		for (j=0; j < nlayer; ++j) {
			nde->v[j] = 0.;
		}
		nde->param = (double *)0;
		for (p = nd->prop; p; p = p->next) {
			if (p->type == EXTRACELL) {
				nde->param = p->param;
				break;
			}
		}
		assert(p && p->type == EXTRACELL);
	}
}

void nrn_extcell_update_param(void) {
	int i;
	NrnThread* nt;
	FOR_THREADS(nt) {
		Memb_list* ml = nt->_ecell_memb_list;
		if (ml) {
			int cnt = ml->nodecount;
			Node **ndlist = ml->nodelist;
			for (i=0; i < cnt; ++i) {
				Node* nd = ndlist[i];
				assert(nd->extnode);
				nd->extnode->param = ml->data[i];
			}
		}
	}
}

void extcell_2d_alloc(Section* sec)
{
	int i, j;
	Node *nd;
	Extnode *nde;
	Prop* p;
	/* may be a nnode increase so some may already be allocated */
	for (i = sec->nnode-1; i>=0; i--) {
		extcell_node_create(sec->pnode[i]);
	}
	/* if the rootnode is owned by this section then it gets extnode also*/
	if (!sec->parentsec && sec->parentnode) { /* last "if" clause unnecessary*/
		extcell_node_create(sec->parentnode);
	}
}

/* from treesetup.c */
void nrn_rhs_ext(NrnThread* _nt)
{
	int i, j, cnt;
	Node *nd, *pnd, **ndlist;
	double* pd;
	Extnode *nde, *pnde;
	Memb_list* ml = _nt->_ecell_memb_list;
	if (!ml) { return; }
	cnt = ml->nodecount;
	ndlist = ml->nodelist;
	
	/* nd rhs contains -membrane current + stim current */
	/* nde rhs contains stim current */
	for (i=0; i < cnt; ++i) {
		nd = ndlist[i];
		nde = nd->extnode;
		*nde->_rhs[0] -= NODERHS(nd);
#if I_MEMBRANE
		pd = ml->data[i];
		sav_rhs = *nde->_rhs[0];
		/* and for daspk this is the ionic current which can be
		   combined later with i_cap before return from solve. */
#endif
	}
	for (i=0; i < cnt; ++i) {
		nd = ndlist[i];
		nde = nd->extnode;
		pnd = _nt->_v_parent[nd->v_node_index];
		if (pnd) {
			pnde = pnd->extnode;
			pd = nde->param;
			/* axial contributions */
			if (pnde) { /* parent sec may not be extracellular */
				for (j=0; j < nlayer; ++j) {
					double dv = pnde->v[j] - nde->v[j];
					*nde->_rhs[j] -= nde->_b[j]*dv;
					*pnde->_rhs[j] += nde->_a[j]*dv;
					/* for the internal balance equation
					take care of vi = vm + vx */
					if (j == 0) {
						NODERHS(nd) -= NODEB(nd)*dv;
						NODERHS(pnd) += NODEA(nd)*dv;
					}
				}
			}else{ /* no extracellular in parent but still need
				to take care of vi = vm + vx in this node.
				Note that we are NOT grounding the parent
				side of xraxial so equivalent to sealed.
				Also note that when children of this node
				do not have extracellular, we deal with
				that, at end of this function. */
				double dv = - nde->v[0];
				NODERHS(nd) -= NODEB(nd)*dv;
				NODERHS(pnd) += NODEA(nd)*dv;
			}
	
			/* series resistance and battery to ground */
			/* between nlayer-1 and ground */
			j = nlayer-1;
			*nde->_rhs[j] -= xg[j] * (nde->v[j] - e_extracellular);
			for (--j; j >= 0; --j) { /* between j and j+1 layer */
				double x = xg[j]*(nde->v[j] - nde->v[j+1]);
				*nde->_rhs[j] -= x;
				*nde->_rhs[j+1] += x;
			}
		}
	}
	cnt = _nt->_ecell_child_cnt;
	for (i = 0; i < cnt; ++i) {
		double dv;
		nd = _nt->_ecell_children[i];
		pnd = _nt->_v_parent[nd->v_node_index];
		dv = pnd->extnode->v[0];
		NODERHS(nd) -= NODEB(nd)*dv;
		NODERHS(pnd) += NODEA(nd)*dv;
	}
}

void nrn_setup_ext(NrnThread* _nt)
{
	int i, j, cnt;
	Node *nd, *pnd, **ndlist;
	double* pd;
	double d, cfac, mfac;
	Extnode *nde, *pnde;
	Memb_list* ml = _nt->_ecell_memb_list;
	if (!ml) { return; }
/*printnode("begin setup");*/
	cnt = ml->nodecount;
	ndlist = ml->nodelist;
	cfac = .001 * _nt->cj;
	
	/* d contains all the membrane conductances (and capacitance) */
	/* i.e. (cm/dt + di/dvm - dis/dvi)*[dvi] and
		(dis/dvi)*[dvx] */
	for (i=0; i < cnt; ++i) {
		nd = ndlist[i];
		nde = nd->extnode;
		d = NODED(nd);
		/* nde->_d only has -ELECTRODE_CURRENT contribution */
		d = (*nde->_d[0] += NODED(nd));
		/* now d is only the membrane current contribution */
		/* i.e. d =  cm/dt + di/dvm */
		*nde->_x12[0] -= d;
		*nde->_x21[0] -= d;
#if I_MEMBRANE
		pd = ml->data[i];
		sav_g = d;
#endif
	}
	/* series resistance, capacitance, and axial terms. */
	for (i=0; i < cnt; ++i) {
		nd = ndlist[i];
		nde = nd->extnode;
		pnd = _nt->_v_parent[nd->v_node_index];
		if (pnd) {
			pd = nde->param;
			/* series resistance and capacitance to ground */
			j = 0;
			for (;; ) { /* between j and j+1 layer */
				mfac =  (xg[j] + xc[j]*cfac);
				*nde->_d[j] += mfac;
				++j;
				if (j == nlayer) {
					break;
				}
				*nde->_d[j] += mfac;
				*nde->_x12[j] -= mfac;
				*nde->_x21[j] -= mfac;
			}
			pnde = pnd->extnode;
			/* axial connections */
			if (pnde) { /* parent sec may not be extracellular */
				for (j=0; j < nlayer; ++j) {
					*nde->_d[j] -= nde->_b[j];
					*pnde->_d[j] -= nde->_a[j];;
					*nde->_a_matelm[j] += nde->_a[j];
					*nde->_b_matelm[j] += nde->_b[j];;
				}
			}
	
		}
	}
	/*printnode("end setup_lhs");*/
}

/* based on treeset.c */
void ext_con_coef(void)	/* setup a and b */
{
	int j,k;
	double dx, area;
	hoc_Item* qsec;
	Node *nd, **pnd;
	Extnode *nde;
	double *pd;
	
	/* temporarily store half segment resistances in rhs */
	ForAllSections(sec) /*{*/
	  if (sec->pnode[0]->extnode){
		dx = section_length(sec)/((double)(sec->nnode - 1));
		for (j = 0; j < sec->nnode-1; j++) {
			nde = sec->pnode[j]->extnode;
			pd = nde->param;
			for (k=0; k < nlayer; ++k) {
				*nde->_rhs[k] = 1e-4*xraxial[k]*(dx/2.); /*Megohms*/
			}
		}
		/* last segment has 0 length. */
		nde = sec->pnode[j]->extnode;
		pd = nde->param;
		for (k=0; k < nlayer; ++k) {
			*nde->_rhs[k] = 0.;
			xc[k] = 0.;
			xg[k] = 0.;
		}
		/* if owns a rootnode */
		if (!sec->parentsec) {
			nde = sec->parentnode->extnode;
			pd = nde->param;
			for (k=0; k < nlayer; ++k) {
				*nde->_rhs[k] = 0.;
				xc[k] = 0.;
				xg[k] = 0.;
			}
		}
			
	}}
	/* assume that if only one connection at x=1, then they butte
	together, if several connections at x=1
	then last point is at x=1, has 0 area and other points are at
	centers of nnode-1 segments.
	If interior connection then child half
	section connects straight to the point*/
	/* for the near future we always have a last node at x=1 with
	no properties */
	ForAllSections(sec) /*{*/
	  if(sec->pnode[0]->extnode){
		/* node half resistances in general get added to the
		node and to the node's "child node in the same section".
		child nodes in different sections don't involve parent
		node's resistance */
		nde = sec->pnode[0]->extnode;
		for (k=0; k < nlayer; ++k) {
			nde->_b[k] = *nde->_rhs[k];
		}
		for (j = 1; j < sec->nnode; j++) {
			nde = sec->pnode[j]->extnode;
			for (k=0; k < nlayer; ++k) {
				nde->_b[k] = *nde->_rhs[k] + *(sec->pnode[j-1]->extnode->_rhs[k]); /*megohms*/
			}
		}
	}}
	ForAllSections(sec) /*{*/
	  if(sec->pnode[0]->extnode){
		/* convert to siemens/cm^2 for all nodes except last
		and microsiemens for last.  This means that a*V = mamps/cm2
		and a*v in last node = nanoamps. Note that last node
		has no membrane properties and no area. It may perhaps recieve
		current stimulus later */
		/* first the effect of node on parent equation. Note That
		last nodes have area = 1.e2 in dimensionless units so that
		last nodes have units of microsiemens's */
		pnd = sec->pnode;
		nde = pnd[0]->extnode;
		area = NODEAREA(sec->parentnode);
		/* param[4] is rall_branch */
		for (k=0; k < nlayer; ++k) {
			nde->_a[k] = -1.e2*sec->prop->dparam[4].val/(nde->_b[k] * area);
		}
		for (j = 1; j < sec->nnode; j++) {
			nde = pnd[j]->extnode;
			area = NODEAREA(pnd[j - 1]);
			for (k=0; k < nlayer; ++k) {
				nde->_a[k] = -1.e2/(nde->_b[k] * area);
			}
		}
	}}
	/* now the effect of parent on node equation. */
	ForAllSections(sec) /*{*/
	  if(sec->pnode[0]->extnode){
		for (j=0; j < sec->nnode; j++) {
			nd = sec->pnode[j];
			nde = nd->extnode;
			for (k=0; k < nlayer; ++k) {
				nde->_b[k] = -1.e2/(nde->_b[k] * NODEAREA(nd));
			}
		}
	}}

}

#if 0
/* needs to be fixed to deal with rootnodes having this property */

static void printnode(const char* s) {
	int in, i, j, k;
	hoc_Item* qsec;
	Section* sec;
	Node* nd;
	Extnode* nde;	
	double *pd;
	NrnThread* _nt;
	FOR_THREADS(_nt) for (in=0; in < _nt->end; ++in) {
		nd = _nt->_v_node[in];
		if (nd->extnode) {
			sec = nd->sec;
			j = nd->sec_node_index_;
			nde = nd->extnode;
			pd = nde->param;
			for (k=0; k < nlayer; ++k) {
printf("%s %s nd%d layer%d v=%g rhs=%g:\n", s, secname(sec), j, k, nde->v[k], *nde->_rhs[k]);
printf("xraxial=%g xg=%g xc=%g e=%g\n", xraxial[k], xg[k], xc[k], e_extracellular);
			}
		}
	}
}
#if 0
static int cntndsave;
static Extnode* ndesave;

void save2mat(void) {
	int i, j, k, im, ipm;
	register Node *nd, *pnd;
	register Extnode *nde, *pnde;
	
	if (cntndsave < v_node_count) {
		if (ndesave) {
			free(ndesave);
		}
		cntndsave = v_node_count;
		ndesave = (Extnode*)ecalloc(cntndsave, sizeof(Extnode));
	}
	for (i=0; i < v_node_count; ++i) {
		nd = v_node[i];
		nde = nd->extnode;
		if (nde) {
			for (j=0; j < nlayer; ++j) {
				ndesave[i].v[j] = nde->v[j];
				ndesave[i].rhs[j] = nde->_rhs[j];
				for (k=0; k < 2*(nlayer)+1; ++k) {
					ndesave[i].m[j][k] = nde->_m[j][k];
				}
				if (!v_parent[i]->extnode && j > 0) {
					ndesave[i].m[j][0] = 0.;
					ndesave[i].m[j][2*(nlayer)] = 0.;
				}
			}
		}else{
			for (j=0; j < nlayer; ++j) {
				ndesave[i].m[j][nlayer] = 1;
			}
			ndesave[i].v[0] = nd->v;
			ndesave[i].m[0][nlayer] = NODED(nd);
			ndesave[i].rhs[0] = NODERHS(nd);
			ndesave[i].m[0][0] = NODEB(nd);
			ndesave[i].m[0][2*(nlayer)] = NODEA(nd);
		}			
	}
}

#if 0
#define DBG printf
#else
DBG() {}
#endif

void check2mat(void) {
	int i, j, k, im, ip;
	Node* nd;
	Extnode* nde;
	double sum;

	/* copy solved v to saved v */
	for (i=0; i < v_node_count; ++ i) {
		nd = v_node[i];
		nde = nd->extnode;
		if (nde) {
			for (j=0; j < (nlayer); ++j) {
				ndesave[i].v[j] = nde->_rhs[j];
			}
		}else{
			ndesave[i].v[0] = NODERHS(nd);
		}
	}

#if 0
	printf("mat\n");
	for (i=0; i < v_node_count; ++i) {
		printf("node %d\n", i);
		for (j=0; j < (nlayer); ++j) {
			printf("%d %d  ", j, i);
			for (k=0; k <= 2*(nlayer); ++k) {
				printf(" %-8.3g", ndesave[i].m[j][k]);
			}
			printf(" %g  %g\n", ndesave[i].v[j], ndesave[i].rhs[j]);
		}
	}
#endif

	/* rhs - M*V accomplished by subtracting every term from rhs */
	for (i=0; i < v_node_count; ++i) {
DBG("work on node %d\n", i);
		if (v_parent[i]) {
			ip = v_parent[i]->v_node_index;
			/* effect of parent on node */
DBG(" effect of parent %d on node %d\n", ip, i);
			for (j=0; j < nlayer; ++j) {
DBG("  work on layer %d\n", j);
				for (k=j; k < nlayer; ++k) {
DBG("   nde[%d].rhs[%d] -= nde[%d].v[%d]*nde[%d].m[%d][%d]\n", i,j,ip,k,i,j,k-j);
DBG("                        %g * %g\n",ndesave[ip].v[k],ndesave[i].m[j][k-j]);
ndesave[i].rhs[j] -= ndesave[ip].v[k]*ndesave[i].m[j][k-j];
				}
			}
			/* effect of node on parent */
DBG(" effect of node %d on parent %d\n", i, ip);
			for (j=0; j < nlayer; ++j) {
DBG("  work on layer %d\n", j);
				for (k=0; k <= j; ++k) {
DBG("   nde[%d].rhs[%d] -= nde[%d].v[%d]*nde[%d].m[%d][%d]\n", ip,j,i,k,i,j,2*(nlayer)-j+k);
DBG("                        %g * %g\n",ndesave[i].v[k],ndesave[i].m[j][2*(nlayer)-j+k]);
ndesave[ip].rhs[j] -= ndesave[i].v[k]*ndesave[i].m[j][2*(nlayer)-j+k];
				}
			}
		}
		/* effect of node on node */
DBG(" effect of node %d on node %d\n", i, i);
		for (j=0; j < nlayer; ++j) {
DBG("  work on layer %d\n", j);
			for (k=0; k < nlayer; ++k) {
DBG("   nde[%d].rhs[%d] -= nde[%d].v[%d]*nde[%d].m[%d][%d]\n", i,j,i,k,i,j,(nlayer)+k-j);
DBG("                        %g * %g\n",ndesave[i].v[k],ndesave[i].m[j][(nlayer)+k-j]);
ndesave[i].rhs[j] -= ndesave[i].v[k]*ndesave[i].m[j][(nlayer)+k-j];
			}
		}
	}

	for (i=0; i < v_node_count; ++i) {
		for (j=0; j < (nlayer); ++j) {
			if (fabs(ndesave[i].rhs[j]) > 1e-5) {
				printf("bad soln of eq %d,%d rhs-M*V=%g\n",
				 i,j, ndesave[i].rhs[j]);
			}
		}
	}
}
#endif
#endif

#endif /*EXTRACELLULAR*/
