/**********
Copyright 1992 Regents of the University of California.  All rights reserved.
Author:	1987 Kartikeya Mayaram, U. C. Berkeley CAD Group
**********/

#include "ngspice/ngspice.h"
#include "ngspice/numglobs.h"
#include "ngspice/numenum.h"
#include "ngspice/onemesh.h"
#include "ngspice/onedev.h"
#include "ngspice/macros.h"
#include "onedext.h"
#include "oneddefs.h"

extern void CiderLoaded(int);

/* Forward Declarations */
/* static void ONEresetEvalFlag(ONEdevice *); sjb - not used */

void
ONEbuildMesh(ONEdevice *pDevice, ONEcoord *pCoord, ONEdomain *pDomain,
             ONEmaterial *pMaterial)
{
  int index, i;
  int elemType;
  double xPos;
  ONEcoord *pC;
  ONEnode *pNode;
  ONEdomain *pD;
  ONEelem *pElem;
  ONEmaterial *pM;
  int poiEqn, numEqn;
  ONEedge *pEdge;
  ONEnode **nodeArray=NULL;
  bool error = FALSE;


  /* generate the work array for setting up nodes and elements */
  XCALLOC(nodeArray, ONEnode *, 1 + pDevice->numNodes);

  for (pC = pCoord; pC != NULL; pC = pC->next) {
    xPos = pC->location;
    XCALLOC(pNode, ONEnode, 1);
    pNode->x = xPos;
    pNode->nodeI = pC->number;
    nodeArray[pNode->nodeI] = pNode;
  }

  /* mark the domain info on the nodes */
  if (pDomain == NULL) {
    fprintf(stderr, "Error: domains not defined for device\n");
    exit(-1);
  }
  for (pD = pDomain; pD != NULL; pD = pD->next) {
    for (pM = pMaterial; pM != NULL; pM = pM->next) {
      if (pD->material == pM->id) {
	break;
      }
    }
    elemType = pM->type;
    for (index = pD->ixLo; index <= pD->ixHi; index++) {
      pNode = nodeArray[index];
      if (!pNode->nodeType) {
	pNode->nodeType = elemType;
      }
    }
  }

  /*
   * check to see if a domain has been defined for all nodes. if not flag an
   * error message
   */
  for (index = 2; index < pDevice->numNodes; index++) {
    pNode = nodeArray[index];
    if (!pNode->nodeType) {
      fprintf(stderr, "Error: No domain defined for node %d\n", pNode->nodeI);
      error = TRUE;
    }
  }
  if (error) {
    /* nodes with undefined domains -- exit */
    exit(-1);
  }
  /* mark the first and last nodes to be contact nodes */
  nodeArray[1]->nodeType = CONTACT;
  nodeArray[pDevice->numNodes]->nodeType = CONTACT;


  /* generate the elements and the edges */
  for (index = 1; index < pDevice->numNodes; index++) {
    XCALLOC(pElem, ONEelem, 1);
    XCALLOC(pEdge, ONEedge, 1);
    pElem->pEdge = pEdge;
    pElem->pLeftNode = nodeArray[index];
    pElem->pRightNode = nodeArray[index + 1];
    pDevice->elemArray[index] = pElem;
  }

  /* now setup the nodes to which an element belongs */
  for (index = 1; index < pDevice->numNodes; index++) {
    pElem = pDevice->elemArray[index];
    pElem->pLeftNode->pRightElem = pElem;
    pElem->pRightNode->pLeftElem = pElem;
    if (index > 1) {
      pElem->pLeftElem = pDevice->elemArray[index - 1];
    }
    if (index < pDevice->numNodes - 1) {
      pElem->pRightElem = pDevice->elemArray[index + 1];
    }
  }

  /* mark the domain info on the elements */
  for (pD = pDomain; pD != NULL; pD = pD->next) {
    for (pM = pMaterial; pM != NULL; pM = pM->next) {
      if (pD->material == pM->id) {
	break;
      }
    }
    elemType = pM->type;
    for (index = pD->ixLo; index < pD->ixHi; index++) {
      pElem = pDevice->elemArray[index];
      pElem->domain = pD->id;
      pElem->elemType = elemType;
      pElem->matlInfo = pM;
    }
  }

  /* identify the interface nodes */
  for (index = 2; index < pDevice->numNodes; index++) {
    pNode = nodeArray[index];
    if (pNode->pLeftElem->elemType !=
	pNode->pRightElem->elemType) {
      /* an interface node */
      pNode->nodeType = INTERFACE;
    }
  }

  /* now mark the nodes to be evaluated */
  /* all interface nodes marked in silicon elements */

  for (index = 1; index < pDevice->numNodes; index++) {
    pElem = pDevice->elemArray[index];
    pElem->dx = pElem->pRightNode->x - pElem->pLeftNode->x;
    for (i = 0; i <= 1; i++) {
      pNode = pElem->pNodes[i];
      pElem->evalNodes[i] = FALSE;
      if (pElem->elemType == INSULATOR) {
	if ((!pNode->evaluated) &&
	    (pNode->nodeType != INTERFACE)) {
	  /* a non interface node in oxide domain */
	  pNode->evaluated = TRUE;
	  pElem->evalNodes[i] = TRUE;
	}
      }
      if (pElem->elemType == SEMICON) {
	if (!pNode->evaluated) {
	  pNode->evaluated = TRUE;
	  pElem->evalNodes[i] = TRUE;
	}
      }
    }
  }

  /* set up the equation number for the nodes */
  poiEqn = numEqn = 1;
  for (index = 1; index < pDevice->numNodes; index++) {
    pElem = pDevice->elemArray[index];
    for (i = 0; i <= 1; i++) {
      if (pElem->evalNodes[i]) {
	pNode = pElem->pNodes[i];
	if (pNode->nodeType != CONTACT) {
	  pNode->poiEqn = poiEqn++;

	  pNode->psiEqn = numEqn;
	  if (pElem->elemType == INSULATOR) {
	    numEqn += 1;	/* only poisson's equation */
	  } else {
	    pNode->nEqn = numEqn + 1;
	    pNode->pEqn = numEqn + 2;
	    numEqn += 3;
	  }
	} else {		/* this is a contact node */
	  pNode->poiEqn = 0;
	  pNode->psiEqn = 0;
	  pNode->nEqn = 0;
	  pNode->pEqn = 0;
	}
	/*
	 * fprintf(stdout,"NODE: %d %d\n",pNode->nodeI,pNode->poiEqn);
	 */
      }
    }
  }
  pDevice->dimEquil = poiEqn;
  pDevice->dimBias = numEqn;

  FREE(nodeArray);
  {
    CiderLoaded(1);
  }

  /* 
   * ONEprnMesh( pDevice );
   */
}

/*
 * We have a separate function for this, so that the setup routines can reset
 * the state pointers without rebuilding the entire mesh.
 */
void
ONEgetStatePointers(ONEdevice *pDevice, int *numStates)
{
  int eIndex, nIndex;
  ONEelem *pElem;
  ONEnode *pNode;


  for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) {
    pElem = pDevice->elemArray[eIndex];
    for (nIndex = 0; nIndex <= 1; nIndex++) {
      if (pElem->evalNodes[nIndex]) {
	pNode = pElem->pNodes[nIndex];
	pNode->nodeState = *numStates;
	*numStates += ONEnumNodeStates;
      }
    }
    pElem->pEdge->edgeState = *numStates;
    *numStates += ONEnumEdgeStates;
  }
}

/* adjust the base contact to be the position of maximum density */
/* index EB and BC are the indexes for the eb, bc junctions */
void
adjustBaseContact(ONEdevice *pDevice, int indexEB, int indexBC)
{
  int index, i, newBaseIndex, midPoint;
  double maxDensity;
  ONEnode *pNode;
  ONEelem *pElem;
  ONEnode *pBaseNode = pDevice->elemArray[pDevice->baseIndex]->pNodes[0];


  /* Initialize the base contact to be the center of the two junctions */
  /* This should take care of uniform dopings. */

  midPoint = (indexEB + indexBC) / 2;
  newBaseIndex = midPoint;

  if (pBaseNode->baseType == P_TYPE) {
    maxDensity = pDevice->elemArray[midPoint]->pNodes[0]->pConc;
    for (index = indexEB; index < indexBC; index++) {
      pElem = pDevice->elemArray[index];
      for (i = 0; i <= 1; i++) {
	pNode = pElem->pNodes[i];
	if (pNode->pConc > maxDensity) {
	  maxDensity = pNode->pConc;
	  newBaseIndex = index;
	}
      }
    }
  } else if (pBaseNode->baseType == N_TYPE) {
    maxDensity = pDevice->elemArray[midPoint]->pNodes[0]->nConc;
    for (index = indexEB; index < indexBC; index++) {
      pElem = pDevice->elemArray[index];
      for (i = 0; i <= 1; i++) {
	pNode = pElem->pNodes[i];
	if (pNode->nConc > maxDensity) {
	  maxDensity = pNode->nConc;
	  newBaseIndex = index;
	}
      }
    }
  } else {
    printf("adjustBaseContact: unknown base type %d\n", pBaseNode->baseType);
  }
  /* at the conclusion of this loop have the point of max density */
  if (pDevice->baseIndex != newBaseIndex) {
    /* so change the position */
    pNode = pDevice->elemArray[newBaseIndex]->pNodes[0];
    pNode->baseType = pBaseNode->baseType;
    pNode->vbe = pBaseNode->vbe;
    pBaseNode->baseType = FALSE;
    pBaseNode->vbe = 0.0;
    pDevice->baseIndex = newBaseIndex;
  }
}

void
NBJTjunctions(ONEdevice *pDevice, int *indexEB, int *indexBC)
{
  int index;
  double conc1, conc2;
  bool findFirstJunction = TRUE;
  bool notFound = TRUE;

  for (index = 1; (index < pDevice->numNodes) && (notFound); index++) {
    conc1 = pDevice->elemArray[index]->pNodes[0]->netConc;
    conc2 = pDevice->elemArray[index]->pNodes[1]->netConc;

    if ((conc1 * conc2 < 0.0) && (findFirstJunction)) {
      *indexEB = index;
      findFirstJunction = FALSE;
    } else if ((conc1 * conc2 < 0.0) && (!findFirstJunction)) {
      *indexBC = index;
      notFound = FALSE;
    }
  }

  if (notFound) {
    fprintf(stderr, "BJT: Device does not have two junctions!\n");
    exit(-1);
  }
}

void
ONEprnMesh(ONEdevice *pDevice)
{
  int eIndex, index;
  ONEelem *pElem;
  ONEnode *pNode;
  char *name;


  for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) {
    pElem = pDevice->elemArray[eIndex];
    fprintf(stderr, "elem %5d:\n", eIndex);
    for (index = 0; index <= 1; index++) {
      if (pElem->evalNodes[index]) {
	pNode = pElem->pNodes[index];
	switch (pNode->nodeType) {
	case SEMICON:
	  name = "semiconductor";
	  break;
	case INSULATOR:
	  name = "insulator";
	  break;
	case CONTACT:
	  name = "contact";
	  break;
	case SCHOTTKY:
	  name = "schottky";
	  break;
	case INTERFACE:
	  name = "interface";
	  break;
	default:
	  name = "unknown";
	  break;
	}
	
	
	 fprintf(stderr, "node %5d: %s %5d\n", index, name,
	    pNode->nodeI);
	 
      }
    }
  }
}

/* sjb - not used
static void
ONEresetEvalFlag(ONEdevice *pDevice)
{
  int index, eIndex;
  ONEelem *pElem;


  for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) {
    pElem = pDevice->elemArray[eIndex];
    for (index = 0; index <= 1; index++) {
      pElem->pNodes[index]->evaluated = FALSE;
    }
    pElem->pEdge->evaluated = FALSE;
  }
}
*/
