/****************************************************************************
    Copyright (C) 1987-2005 by Jeffery P. Hansen

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    Last edit by hansen on Tue Jun  8 11:25:36 2004
****************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <signal.h>
#include <assert.h>
#include "tkgate.h"

/*
   Returns the log 2 of n rounded up.  That is:

	log2(1) = 1
	log2(2) = 1
	log2(3) = 2
	log2(4) = 2
	log2(5) = 3

    The number of select lines on an n way multiplexor is then log2(n).
*/
int required_bits(int n)
{
  int i,b;

  for (i = b = 0;n; i++) {
    b += (n & 1);
    n >>= 1;
  }

  return i - 1 + (b > 1);
}

unsigned htoi(const char *s)
{
  unsigned v;

/*
    for (v = 0;*s;s++)
	v = v*16 + (isdigit(*s) ? (*s - '0') : (islower(*s) ? *s - 'a' + 10 : *s - 'A' + 10));
*/

  if (sscanf(s,"%x",&v) != 1) v = 0;

  return v;
}

const char *article(const char *m)
{
  if (strchr("aeiouxAEIOUX",*m))
    return "an ";
  else
    return "a ";
}

/*
 * Generate pointless messages if enabled.
 */
void badermessage(int t,const char *m)
{
  extern int baderp,startrekp,batp;

  /*
   * Be mean to Miles Bader.
   */
  if (baderp) {
    switch (t) {
    case 1 :
      switch (random() & 63) {
      case 0 :
	message(1,msgLookup("miles.msg1"),article(m),m);
	return;
      case 1 :
	message(1,msgLookup("miles.msg2"),article(m),m);
	return;
      case 2 :
	message(1,msgLookup("miles.msg3"),article(m),m);
	return;
      case 3 :
	message(1,msgLookup("miles.msg4"),article(m),m);
	return;
      case 4 :
	message(1,msgLookup("miles.msg5"),article(m),m);
	return;
      case 5 :
	message(1,msgLookup("miles.msg6"),article(m),m);
	return;
      case 6 :
	message(1,msgLookup("miles.msg7"));
	return;
      case 7 :
	message(1,msgLookup("miles.msg8a"));
	message(1,msgLookup("miles.msg8b"));
	return;
      default :
	break;
      }
      break;
    case 2 :
      break;
    }
  }

  /*
   * In bat mode liven things up a little
   */
  if (batp) {
    switch (t) {
    case 1 :
      switch (random() & 255) {
      case 0 :
	message(1,"** BIFF **");
	return;
      case 1 :
	message(1,"** POW **");
	return;
      case 2 :
	message(1,"** CRASH **");
	return;
      case 3 :
	message(1,"** OOOF **");
	return;
      case 4 :
	message(1,"** WHAM **");
	return;
      case 5 :
	message(1,"** CRUNCH **");
	return;
      case 6 :
	message(1,"** BANG **");
	return;
      case 7 :
	message(1,"** SOCK **");
	return;
      default :
	break;
      }
      break;
    case 2 :
      break;
    }
  }
  if (startrekp) {
    switch (t) {
    case 1 :
      switch (random() & 255) {
      case 0 :
	message(1,"Beam me up Scotty!");
	return;
      case 1 :
	message(1,"Engage!");
	return;
      case 2 :
	message(1,"He's dead Jim!");
	return;
      case 3 :
	message(1,"Earl Grey Tea Hot!");
	return;
      case 4 :
	message(1,"Every sixteenth hour, I turn into a liquid!");
	return;
      case 5 :
	message(1,"Ugly bags of mostly water.");
	return;
      case 6 :
	message(1,"Microbrain! Growl for me, let me know you still care!");
	return;
      case 7 :
	message(1,"I'm a doctor, not an engineer!");
	return;
      default :
	break;
      }
      break;
    case 2 :
      break;
    }
  }
}

int posongate(GWire *w,GCElement *g,int *p,int *n)
{
  int i;

  for (i = 0;i < g->typeinfo->NumPads;i++)
    if ((*n = findwirepos(w,g->wires[i]))) {
      *p = i;
      return 0;
    }

  return -1;
}

/* Searches an i/o wire list for the number of the target wire */
int findwirepos(GWire *w,GWire *l)
{
  int p;

  if (!l)
    return 0;
  else {
    assert(l != l->next); 
    if (w == l)
      return 1;
    else
      if ((p = findwirepos(w,l->next)) != 0)
	return p + 1;
      else
	return 0;
  }
}

int GetPadPosition(GCElement *g,const char *name)
{
  int i;

  for (i = 0;i < g->typeinfo->NumPads;i++)
    if (!strcmp(g->typeinfo->Pad[i].Name,name))
      return i;
  return -1;
}

void undoSignals()
{
#ifdef SIGABRT
  signal(SIGABRT,SIG_DFL);
#endif
  signal(SIGHUP,SIG_DFL);
  signal(SIGQUIT,SIG_DFL);
  signal(SIGILL,SIG_DFL);
  signal(SIGTRAP,SIG_DFL);
  signal(SIGFPE,SIG_DFL);
  signal(SIGBUS,SIG_DFL);
  signal(SIGSEGV,SIG_DFL);
#ifdef SIGEMT
  signal(SIGEMT,SIG_DFL);
#endif
#ifdef SIGSYS
  signal(SIGSYS,SIG_DFL);
#endif
}

/*****************************************************************************
 * Called when something has gone wrong internally and we can not be sure of
 * maintining integrety of the circuit data.
 *****************************************************************************/
void panicSave(int s)
{
  static int in_panic_save = 0;


  ob_mode(0);			/* Turn of undo/redo object management */


  /*
   * If we get a panic while saving a panic file we will just have to give up.
   */
  if (in_panic_save) {
    printf("**** Signal %d received during panic save.  Sorry...\n",s);
    undoSignals();
    abort();
  }
  in_panic_save = 1;


  /*
   * Print signal number if panic was caused by a signal.
   */
  if (s) {
    printf("**** Trapped on signal %d\n",s);
  }

  /*
   * Tell the user the bad news.
   */
  if (baderp) {
    printf("Look what you did *now*, Miles!\n");
    printf("Even though this is your fault, I'll try to save in 'PANIC.v' anyway.\n");
  } else if (startrekp) {
    printf("Warp core breach in progress!  All hands abandon ship!\n");
    printf("Launching escape pod 'PANIC.v'.\n");
  } else {
    printf("TkGate has encountered an unrecoverable error and must exit.  Before doing this\n");
    printf("we will make one last attempt to save your circuit in a panic file.  If you have\n");
    printf("checkpointing enabled, you can also try using your checkpoint file as a backup.\n\n");
    printf("Attempting to save circuit in file 'PANIC.v'...\n");
  }

  VerilogWriteModules("PANIC.v",0);

  if (baderp) {
    printf("Looks like I bailed you out once again Miles.\n");
  } else if (startrekp) {
    printf("Escape pod is away.\n");
  } else {
    printf("Panic save completed.\n");
  }

  undoSignals();
  abort();
}

/*
 * Open a file in the context of the current circuit.
 * If the first character of 'name' is '/', then the
 * file is opened "as is".  Otherwise the following
 * path is searched:
 *
 *    current directory
 *    same direcory as current file
 *    user's home directory (value of HOME)
 */
FILE *openInPath(const char *name)
{
  char buf[STRMAX];
  FILE *f;
  char *r;
  const char *p;


  if (*name == '/') return fopen(name,"r");

  if ((p = Tcl_GetVar(XGate.tcl,"tkg_currentFile",TCL_GLOBAL_ONLY))) {
    strcpy(buf,p);
    if ((r = strrchr(buf,'/'))) {
      if (r != buf) {
	strcpy(r+1,name);
	if ((f = fopen(buf,"r"))) return f;
      }
    }
  }

  if ((r = getenv("HOME"))) {
    strcpy(buf,r);
    strcat(buf,"/");
    strcat(buf,name);
    if ((f = fopen(buf,"r"))) return f;
  }

  return 0;
}

int badaddr(void *S)
{
  return ((size_t)S & 0x1) != 0;
}

void PickInterval(int *R,int *I)
{
  int p,k,d;

  k = *R/10;
  d = 1;
  for (p = 0;k > 10;p++) {
    d *= 10;
    k /= 10;
  }
  *I = k * d;
}

/*
 * Lookup a message from the appropriate locality file.
 */
char *msgLookup(const char *tag)
{
  char *s = (char*) SHash_find(message_table,tag);

  if (!s) {
    char buf[1024];
    sprintf(buf,"<no-msg: %s>",tag);
    s = ob_strdup(buf);
    SHash_insert(message_table,tag,s);
  }

  return s;
}

void strreplace(char *s,const char *old_str,const char *new_str,int gbl)
{
  char *b = s;
  int ol = strlen(old_str);
  int nl = strlen(new_str);
  int sl = strlen(s);

  for (;*s;s++) {
    if ( (sl-(s-b)) < ol) break;		/* Not enough chars left */

    if (strncmp(s,old_str,ol) == 0) {
      memmove(s+nl,s+ol,(sl-(s-b)));
      memmove(s,new_str,nl);
      s += nl;
      sl += nl-ol;
      if (!gbl) return;
    }
  }
  b[sl] = 0;
}

int trimName(char *buf)
{
  char *p;
  char *q;
  int did_trim = 0;

  if (isdigit((int)*buf)) {
    memmove(buf+1,buf,strlen(buf)+1);
    *buf = 'D';
    did_trim = 1;
  }

  p = q = buf;

  for (;*q;q++) {
    if (isalpha((int)*q) || isdigit((int)*q) || *q == '_')
      *p++ = *q;
    else if (*q == ' ') { 
      if (p == buf)
	*p++ = 'S';
      else
	*p++ = '_';
    } else
      did_trim = 1;
  }
  *p = 0;

  return did_trim;
}

/*
 * Check 'name' to see if it is a valid name.  If 'H' is non-null, then the
 * name may not appear as a key in the hash table.  Returns 0 if the name
 * is valid.
 */
int checkValidName(const char *name,SHash *H)
{
  const char *p = name;

  if (!isalpha(*p) && *p != '_')
    return -1;

  for (p++;*p;p++) {
    if (!isalpha(*p) && !isdigit(*p) && *p != '_')
      return -1;
  }

  if (H && SHash_find(H,name))
    return -1;

  return 0;
}

/*
 * Pick a valid name for an object based on a possible suggested name 'name',
 * a base name 'base' for objects of a that type, and a hash table 'H' for objects
 * of that type.  One or more of 'name', 'base' and 'H' may be null.  The generated
 * name will be placed in 'buf'.  If 'name' is a valid name that does not appear in the
 * hash table 'H', 'name' will become the selected name.
 */
void pickValidName(char *buf,const char *name,const char *base,SHash *H)
{
  char *p;
  int reason = 0;

  if (!base || !*base)
    base = "n";

  if (name) {
    strcpy(buf,name);

    if (trimName(buf)) {
      reason = 1;
      if (!*buf) {
	if (base)
	  strcpy(buf,base);
	else
	  strcpy(buf,"name");
      }
    }

    if (!*buf)
      sprintf(buf,"%s0",base);

    if (H && SHash_find(H,buf))
      reason = 2;
    else if (gateinfo_lookup(buf))
      reason = 3;
    else if (ycIsKW(buf))
      reason = 4;
    else {
      if (reason)
	message(0,msgLookup("err.badnetname"));		/* "Illegal characters in identifier deleted." */
      return;
    }
  } else
    sprintf(buf,"%s0",base);

  p = buf+strlen(buf);

  if ((H && SHash_find(H,buf)) || gateinfo_lookup(buf) || ycIsKW(buf)) {
    char *p;
    int n;

    p = buf + strlen(buf);
    while (p > buf && isdigit((int)p[-1])) p--;

    n = 0;
    if (*p) sscanf(p,"%d",&n);

    do {
      sprintf(p,"%d",n++);
    } while ((H && SHash_find(H,buf)) || gateinfo_lookup(buf) || ycIsKW(buf));
  }

  switch (reason) {
  case 1 :
    message(0,msgLookup("err.netbcrename"),buf);	/* "Identifier renamed to '%s' because of illegal characters." */
    break;
  case 2 :
    message(0,msgLookup("err.netconfnet"),buf);
    break;
  case 3 :
    message(0,msgLookup("err.netconfgat"));
    break;
  case 4 :
    message(0,msgLookup("err.netconfkw"));
    break;
  }
}
