/**********
Copyright 1990 Regents of the University of California.  All rights reserved.
Author: 1985 Thomas L. Quarles, 1991 David A. Gates
Modified: 2001 Paolo Nenzi (Cider Integration)
$Id: inpgmod.c,v 1.18 2010/07/17 20:48:28 rlar Exp $
**********/

#include "ngspice.h"
#include "inpdefs.h"
#include "ifsim.h"
#include "cpstd.h"
#include "fteext.h"
#include "inp.h"
#include <errno.h>

#ifdef CIDER
/* begin Cider Integration */
#include "numcards.h"
#include "carddefs.h"
#include "numgen.h"
#include "suffix.h"

extern IFcardInfo *INPcardTab[];
extern int INPnumCards;
#define E_MISSING	-1
#define E_AMBIGUOUS	-2

static int INPparseNumMod( CKTcircuit* ckt, INPmodel *model, INPtables *tab, char **errMessage );
static int INPfindCard( char *name, IFcardInfo *table[], int numCards );
static int INPfindParm( char *name, IFparm *table, int numParms );

/* end Cider Integration */
#endif /* CIDER */

extern INPmodel *modtab;

/*
  code moved from INPgetMod
 */
static int
create_model( CKTcircuit* ckt, INPmodel* modtmp, INPtables* tab )
{
  IFvalue* val;
  char*    err = NULL;
  char*    line;
  char*    parm;
  char*    temp;
  int      error = 0;
  int      j;
  char *endptr; double dval;

  /* not already defined, so create & give parameters */
  error = (*(ft_sim->newModel))(ckt, (modtmp)->INPmodType, &((modtmp)->INPmodfast), (modtmp)->INPmodName);

  if (error) return error;

  /* parameter isolation, identification, binding */

#ifdef CIDER
  /* begin cider integration */
  /* Handle Numerical Models Differently */
  if ( ((modtmp)->INPmodType == INPtypelook("NUMD")) ||
       ((modtmp)->INPmodType == INPtypelook("NBJT")) ||
       ((modtmp)->INPmodType == INPtypelook("NUMD2")) ||
       ((modtmp)->INPmodType == INPtypelook("NBJT2")) ||
       ((modtmp)->INPmodType == INPtypelook("NUMOS")) ) {
    error = INPparseNumMod( ckt, modtmp, tab, &err );
    if (error) return error;
  } else {     
    /* It's an analytical model */
#endif /* CIDER */

    line = ((modtmp)->INPmodLine)->line;

#ifdef TRACE
    /* SDB debug statement */
    printf("In INPgetMod, inserting new model into table.  line = %s . . . \n", line);
#endif

    INPgetTok(&line, &parm, 1);	/* throw away '.model' */
    tfree(parm);
    INPgetTok(&line, &parm, 1);	/* throw away 'modname' */
    tfree(parm);
    while (*line != 0) {
      INPgetTok(&line, &parm, 1);
      if (!*parm)
	continue;
      
      for (j = 0; j < (* (*(ft_sim->devices)[(modtmp)->INPmodType]).numModelParms); j++) {
	
	if (strcmp(parm, "txl") == 0) {
	  if (strcmp("cpl", ((*(ft_sim->devices) [ (modtmp)->INPmodType ]).modelParms[j].keyword)) == 0) {
	    strcpy(parm, "cpl");
	  }
	}
	
	if (strcmp(parm,((*(ft_sim->devices)[(modtmp)->INPmodType]).modelParms[j].keyword)) == 0) {
	  
	  val = INPgetValue(ckt, &line, ((* (ft_sim->devices)[(modtmp)->INPmodType]).modelParms[j].dataType), tab);
	  
	  error = (*(ft_sim->setModelParm)) (ckt, ((modtmp)->INPmodfast),
					     (* (ft_sim->devices)[(modtmp)->INPmodType]).modelParms[j].id,
					     val, (IFvalue *) NULL);
	  if (error)
	    return error;
	  break;
	}
      } /* end for(j = 0 . . .*/
      
      if (strcmp(parm, "level") == 0) {
	/* just grab the level number and throw away */
	/* since we already have that info from pass1 */
	val = INPgetValue(ckt, &line, IF_REAL, tab);
      } else if (j >=
		 (*
		  (*(ft_sim->devices)
		   [(modtmp)->INPmodType]).numModelParms)) {

	/* want only the parameter names in output - not the values */
        errno = 0;    /* To distinguish success/failure after call */
        dval = strtod(parm, &endptr);
        /* Check for various possible errors */
        if ((errno == ERANGE && dval == HUGE_VAL) || errno != 0) {
            perror("strtod");
            exit(EXIT_FAILURE);
        }
        if (endptr == parm) { /* it was no number - it is really a string */
          temp = (char *) MALLOC((40 + strlen(parm)) * sizeof(char));
          (void) sprintf(temp,
                       "unrecognized parameter (%s) - ignored", parm);
          err = INPerrCat(err, temp);
        }
      }
      FREE(parm);
    }
#ifdef CIDER
  } /* analytical vs. numerical model parsing */
#endif
  (modtmp)->INPmodUsed = 1;
  (modtmp)->INPmodLine->error = err;

  return 0;
}

static bool
parse_line( char* line, char* tokens[], int num_tokens, double values[], bool found[] )
{
  char*     token;
  int       get_index = -1;
  int       i;
  bool      flag = TRUE;
  int       error;

  for ( i = 0; i < num_tokens; i++ )
    found[i] = FALSE;

  while( *line != '\0' ) {
    
    if ( get_index != -1 ) {
      values[get_index] = INPevaluate( &line, &error, 1 );
      found[get_index]  = TRUE;
      get_index         = -1;
      continue;
    } else {
      INPgetNetTok( &line, &token, 1 );
    }

    for ( i = 0; i < num_tokens; i++ )
      if ( strcmp( tokens[i], token ) == 0 ) get_index = i;
  }

  for ( i = 0; i < num_tokens; i++ )
    if ( found[i] == FALSE ) {
      flag = FALSE;
      break;
    }
  return flag;
}

static bool
is_equal( double result, double expectedResult )
{
  if (fabs(result - expectedResult) < 1e-15) return TRUE;
  else                                       return FALSE;
}

static bool
in_range( double value, double min, double max )
{
  if ( (is_equal( value, min ) == TRUE) || (is_equal( value, max ) == TRUE) ||
       (min < value && value < max) ) return TRUE;
  else                                return FALSE;
}

char*
INPgetModBin( CKTcircuit* ckt, char* name, INPmodel** model, INPtables* tab, char* line )
{
  INPmodel*    modtmp;
  double       l, w, lmin, lmax, wmin, wmax;
  double       parse_values[4];
  bool         parse_found[4];
  static char* instance_tokens[] = { "l", "w" };
  static char* model_tokens[]    = { "lmin", "lmax", "wmin", "wmax" };
  int          error;
  double       scale;

  if ( !cp_getvar( "scale", CP_REAL, (double*) &scale ) ) scale = 1;

  *model = NULL;

  if ( parse_line( line, instance_tokens, 2, parse_values, parse_found ) != TRUE )
    return NULL;
 
  l = parse_values[0]*scale;
  w = parse_values[1]*scale;

  for ( modtmp = modtab; modtmp != (INPmodel*)NULL; modtmp = modtmp->INPnextModel ) {
    if ( modtmp->INPmodType != INPtypelook( "BSIM3" ) && modtmp->INPmodType != INPtypelook( "BSIM3v32" ) &&
	 modtmp->INPmodType != INPtypelook( "BSIM4" ) && modtmp->INPmodType != INPtypelook( "BSIM4v2" ) &&
	 modtmp->INPmodType != INPtypelook( "BSIM4v3" ) && modtmp->INPmodType != INPtypelook( "BSIM4v4" ) && 
	 modtmp->INPmodType != INPtypelook( "BSIM4v5" ) )
      continue;

    if ( parse_line( modtmp->INPmodLine->line, model_tokens, 4, parse_values, parse_found ) != TRUE )
      continue;

    lmin = parse_values[0]; lmax = parse_values[1];
    wmin = parse_values[2]; wmax = parse_values[3];

    if ( strncmp( modtmp->INPmodName, name, strlen( name ) ) == 0 && 
	 in_range( l, lmin, lmax ) && in_range( w, wmin, wmax ) ) {
      if ( !modtmp->INPmodUsed ) {
	error = create_model( ckt, modtmp, tab );
	if ( error ) return NULL;
      }
      *model = modtmp;
      return NULL;
    }
  }
  return NULL;
}

char *INPgetMod(CKTcircuit *ckt, char *name, INPmodel ** model, INPtables * tab)
{
  INPmodel *modtmp;
  char *err = NULL;
  int error;

#ifdef TRACE
  /* SDB debug statement */
  printf("In INPgetMod, examining model %s . . . \n", name);
#endif

  for (modtmp = modtab; modtmp != (INPmodel *) NULL; modtmp = ((modtmp)->INPnextModel)) {

#ifdef TRACE
    /* SDB debug statement */
    printf("In INPgetMod, comparing %s against stored model %s . . . \n", name, (modtmp)->INPmodName);
#endif

    if (strcmp((modtmp)->INPmodName, name) == 0) {
      /* found the model in question - now instantiate if necessary */
      /* and return an appropriate pointer to it */

      if (modtmp->INPmodType < 0) {    /* First check for illegal model type */
        /* illegal device type, so can't handle */
        *model = (INPmodel *) NULL;
        err = (char *) MALLOC((35 + strlen(name)) * sizeof(char));
        (void) sprintf(err,"Unknown device type for model %s \n", name);

#ifdef TRACE
        /* SDB debug statement */
        printf("In INPgetMod, illegal device type for model %s . . . \n", name);
#endif

        return (err);
      }  /* end of checking for illegal model */

      if (!((modtmp)->INPmodUsed)) {   /* Check if model is already defined */
        error = create_model( ckt, modtmp, tab );
        if ( error ) return INPerror(error);
      }
      *model = modtmp;
      return ((char *) NULL);
    }
  }
  /* didn't find model - ERROR  - return model */
  *model = (INPmodel *) NULL;
  err = (char *) MALLOC((60 + strlen(name)) * sizeof(char));
  (void) sprintf(err, "Unable to find definition of model %s - default assumed \n", name);

#ifdef TRACE
  /* SDB debug statement */
  printf("In INPgetMod, didn't find model for %s, using default . . . \n", name);
#endif

  return (err);
}

#ifdef CIDER
/*
 * Parse a numerical model by running through the list of original
 * input cards which make up the model
 * Given:
 * 1. First card looks like: .model modname modtype <level=val>
 * 2. Other cards look like: +<whitespace>? where ? tells us what
 * to do with the next card:
 *    '#$*' = comment card
 *    '+'   = continue previous card
 *    other = new card
 */
static int
INPparseNumMod( CKTcircuit* ckt, INPmodel *model, INPtables *tab, char **errMessage )
{
    card *txtCard;	/* Text description of a card */
    GENcard *tmpCard;	/* Processed description of a card */
    IFcardInfo *info;	/* Info about the type of card located */
    char *line;
    char *cardName = NULL;	/* name of a card */
    char *parm;		/* name of a parameter */
    int cardType;	/* type/index for the current card */
    int cardNum = 0;	/* number of this card in the overall line */
    int lastType = E_MISSING;	/* type of previous card */
    char *err = NULL, *tmp;    /* Strings for error messages */
    IFvalue *value;
    int error, idx, invert;

    /* Chase down to the top of the list of actual cards */
    txtCard = model->INPmodLine->actualLine;

    /* Skip the first card if it exists since there's nothing interesting */
    /* txtCard will be empty if the numerical model is empty */
    if (txtCard) txtCard = txtCard->nextcard;

    /* Now parse each remaining card */
    while (txtCard) {
	line = txtCard->line;
	cardType = E_MISSING;
	cardNum++;

	/* Skip the initial '+' and any whitespace. */
	line++;
	while (*line == ' ' || *line == '\t')
	    line++;

	switch (*line) {
	case '*':
	case '$':
	case '#':
	case '\0':
	case '\n':
	    /* comment or empty cards */
	    lastType = E_MISSING;
	    break;
        case '+':
	    /* continuation card */
	    if (lastType >= 0) {
	        cardType = lastType;
                while (*line == '+') line++;	/* Skip leading '+'s */
	    } else {
	        tmp = (char *)tmalloc((55)*sizeof(char));
		(void) sprintf(tmp,
		    "Error on card %d : illegal continuation \'+\' - ignored",
		    cardNum);
		err = INPerrCat(err,tmp);
		lastType = E_MISSING;
	        break;
	    }
	    /* FALL THRU when continuing a card */
        default:
	    if (cardType == E_MISSING) {
	        /* new command card */
		if (cardName) FREE(cardName);	/* get rid of old card name */
	        INPgetTok(&line,&cardName,1);	/* get new card name */
		if (*cardName) { 		/* Found a name? */
		    cardType = INPfindCard(cardName,INPcardTab,INPnumCards);
		    if (cardType >= 0) {
		        /* Add card structure to model */
			info = INPcardTab[cardType];
			error = (*(info->newCard))( (void **)&tmpCard,
			        model->INPmodfast );
			if (error) return(error);
		    /* Handle parameter-less cards */
		    } else if (cinprefix( cardName, "title", 3 ) ) {
		        /* Do nothing */
		    } else if (cinprefix( cardName, "comment", 3 ) ) {
		        /* Do nothing */
		    } else if (cinprefix( cardName, "end", 3 ) ) {
			/* Terminate parsing */
			txtCard = ((card *) 0);
			cardType = E_MISSING;
		    } else {
		        /* Error */
		        tmp =(char *)tmalloc((55+strlen(cardName))*sizeof(char));
			(void) sprintf(tmp,
			    "Error on card %d : unrecognized name (%s) - ignored",
			    cardNum, cardName );
			err = INPerrCat(err,tmp);
		    }
		}
	    }
	    if (cardType >= 0) { /* parse the rest of this line */
		while (*line) {
		    /* Strip leading carat from booleans */
		    if (*line == '^') {
		      invert = TRUE;
		      line++;	/* Skip the '^' */
		    } else {
		      invert = FALSE;
		    }
		    INPgetTok(&line,&parm,1);
		    if (!*parm)
		      break;
		    idx = INPfindParm(parm, info->cardParms, info->numParms);
		    if (idx == E_MISSING) {
			/* parm not found */
                        tmp = (char *)tmalloc((60+strlen(parm)) * sizeof(char));
                        (void)sprintf(tmp,
                            "Error on card %d : unrecognized parameter (%s) - ignored",
			    cardNum, parm);
                        err = INPerrCat(err, tmp);
		    } else if (idx == E_AMBIGUOUS) {
			/* parm ambiguous */
                        tmp = (char *)tmalloc((58+strlen(parm)) * sizeof(char));
                        (void)sprintf(tmp,
                            "Error on card %d : ambiguous parameter (%s) - ignored",
			    cardNum, parm);
                        err = INPerrCat(err, tmp);
		    } else {
		        value = INPgetValue( ckt, &line,
			    ((info->cardParms)[idx]).dataType, tab );
			if (invert) { /* invert if it's a boolean entry */
			  if (((((info->cardParms)[idx]).dataType)&IF_VARTYPES)
			      == IF_FLAG) {
			    value->iValue = 0;
			  } else {
                            tmp =(char *)tmalloc((63+strlen(parm))*sizeof(char));
                            (void)sprintf(tmp,
                              "Error on card %d : non-boolean parameter (%s) - \'^\' ignored",
			      cardNum, parm);
                            err = INPerrCat(err, tmp);
			  }
			}
                        error = (*(info->setCardParm))(
			    ((info->cardParms)[idx]).id, value, tmpCard );
			if (error) return(error);
		    }
		    FREE(parm);
		}
	    }
	    lastType = cardType;
	    break;
	}
	if (txtCard) txtCard = txtCard->nextcard;
    }
    *errMessage = err;
    return( 0 );
}

/*
 * Locate the best match to a card name in an IFcardInfo table
 */
static int
INPfindCard( char *name, IFcardInfo *table[], int numCards )
{
    int test;
    int match, bestMatch;
    int best;
    int length;

    length = strlen(name);

    /* compare all the names in the card table to this name */
    best = E_MISSING;
    bestMatch = 0;
    for ( test = 0; test < numCards; test++ ) {
        match = cimatch( name, table[test]->name );
        if ((match == bestMatch ) && (match > 0)){
	    best = E_AMBIGUOUS;
        } else if ((match > bestMatch) && (match == length)) {
	    best = test;
	    bestMatch = match;
        }
    }
    return(best);
}

/*
 * Locate the best match to a parameter name in an IFparm table
 */
static int
INPfindParm( char *name, IFparm *table, int numParms )
{
    int test, best;
    int match, bestMatch;
    int id, bestId;
    int length;

    length = strlen(name);

    /* compare all the names in the parameter table to this name */
    best = E_MISSING;
    bestId = -1;
    bestMatch = 0;
    for ( test = 0; test < numParms; test++ ) {
        match = cimatch( name, table[test].keyword );
	if ( (match == length) && (match == strlen(table[test].keyword)) ) {
	    /* exact match */
	    best = test;
	    /* all done */
	    break;
	}
	id = table[test].id;
        if ((match == bestMatch) && (match > 0) && (id != bestId)) {
	    best = E_AMBIGUOUS;
        } else if ((match > bestMatch) && (match == length)) {
	    bestMatch = match;
	    bestId = id;
	    best = test;
        }
    }
    return(best);
}

#endif /* CIDER */
