/****************************************************************************
    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 Dec 23 15:57:12 2003
****************************************************************************/
/*
    General purpose functions used by the editor.
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/time.h>
#include <string.h>
#include <assert.h>
#include "tkgate.h"

int stepsize;

int did_interface_resize = 0;

int debugmode = 0;

/* Draws a line */
void line(int x1,int y1,int x2,int y2)
{
  ZDrawLine(XGate.D,XGate.W,XGate.toolGC,ctow_x(x1), ctow_y(y1), ctow_x(x2), ctow_y(y2));
}

/* Displays, or erases a gate on the screen */
void mk_gate(int x,int y,GGateInfo *gi,int rot,int selected)
{
  int idx;

  idx = rot;
  if (selected) idx += 4;

  Icon_draw(XGate.D,XGate.W,XGate.instGC,ctow_x(x),ctow_y(y),gi->icon[idx]);
}

/*
  Draw the mark symbol
 */
void mark_draw()
{
  Icon_draw(XGate.D,XGate.W,XGate.toolGC,ctow_x(XGate.ed->mark_x),ctow_y(XGate.ed->mark_y),Mark);
}

/*
  Flush the mark state (as a result of screen being cleared)
 */
void mark_flush()
{
#if TOUCH_XGATE_ED
  ob_touch(XGate.ed);
#endif
  XGate.ed->mark_vis = 0;
}

/*
  Temporarily hide the mark symbol
 */
void mark_hide()
{
  if (XGate.ed->mark_vis)
    mark_draw();
  mark_flush();
}

/*
  Redraw mark is posted but not visible
 */
void mark_redraw()
{
  if (XGate.ed->mark_posted && !XGate.ed->mark_vis) {
    mark_draw();
#if TOUCH_XGATE_ED
    ob_touch(XGate.ed);
#endif
    XGate.ed->mark_vis = 1;
  }
}

/*
  Displays the mark on the screen
  */
void mark_post()
{
  if (XGate.circuit->mode != MODE_MOVE || XGate.circuit->select || XGate.circuit->mg_selection || 
      XGate.circuit->wnsel || XGate.circuit->es->isInterface)
    return;

  if (XGate.ed->mark_vis)
    mark_draw();

#if TOUCH_XGATE_ED
  ob_touch(XGate.ed);
#endif
  XGate.ed->mark_x = XGate.ed->tx;
  XGate.ed->mark_y = XGate.ed->ty;
  mark_draw();
  XGate.ed->mark_vis = 1;
  XGate.ed->mark_posted = 1;
}

/* 
   Removes the mark from the screen (standard)
 */
void mark_unpost()
{
  if (!XGate.ed->mark_vis) return;
  mark_draw();
#if TOUCH_XGATE_ED
  ob_touch(XGate.ed);
#endif
  XGate.ed->mark_vis = 0;
  XGate.ed->mark_posted = 0;
}

void selectGate(EditState *es,int tx,int ty)
{
  GModuleDef *env = es->env;
  GCElement *g;

  g = gate_hit(env,tx,ty);

  if (!(XGate.state & ControlMask))
    sel_clear(es);

  if (g) {
    int n;

    sel_appendGate(es,g);
    sel_finish(es);

    ob_touch(XGate.circuit);
    XGate.circuit->last = 0;
    n = sel_num(es);
    XGate.circuit->select = (n == 1) ? g : 0;
    if (n > 1) {
      ob_touch(XGate.circuit);
      XGate.circuit->mode = MODE_MOVESEL;
    }
  }
}

void unselectGate(EditState *es)
{
  if (XGate.circuit->select) {
    gate_draw(XGate.circuit->select,GD_NORMAL);
    gate_mark(XGate.circuit->select,0);
    gate_draw(XGate.circuit->select,GD_NORMAL);
  }
  ob_touch(XGate.circuit);
  XGate.circuit->last = NULL;
  XGate.circuit->select = NULL;
}

void DoSnap(GWire *w)
{
  wire_draw(w->driver->nodes);
  wire_snap(w->driver->nodes);
  wire_draw(w->driver->nodes);
}

void SetBatCursor()
{
  if (XGate.circuit->mode != MODE_MOVE) return;
  if (XGate.circuit->wnsel) return;

  switch (batc) {
  case 0 :
    wm_SetCursor(BATCURSOR1);
    break;
  case 1 :
    wm_SetCursor(BATCURSOR2);
    break;
  case 2 :
    wm_SetCursor(BATCURSOR3);
    break;
  case 3 :
    wm_SetCursor(BATCURSOR2);
    break;
  }
  batc = (batc + 1) % 4;
}

int OL_W,OL_H,OL_X,OL_Y;

static void blockoutline_draw(GCElement *g)
{
  ZDrawRectangle(XGate.D,XGate.W,XGate.toolGC, ctow_x(OL_X),ctow_y(OL_Y),OL_W,OL_H);
}

static void blockoutline_set(GCElement *g,int x,int y)
{
    OL_W = g->u.block.gwidth;
    OL_H = g->u.block.gheight;
    OL_X = g->xpos;
    OL_Y = g->ypos;
    blockoutline_draw(g);
}

static void blockoutline_unset(GCElement *g)
{
  blockoutline_draw(g);
}

static void blockoutline_move(GCElement *g,int dx,int dy)
{
  blockoutline_draw(g);

  if (g->top) {
    if (OL_H - dy < MINSIZE)
      dy = OL_H - MINSIZE;
    OL_H -= dy;
    OL_Y += dy;
  } else if (g->bottom) {
    if (OL_H + dy < MINSIZE)
      dy = MINSIZE - OL_H;
    OL_H += dy;
  }

  if (g->left) {
    if (OL_W - dx < MINSIZE)
      dx = OL_W - MINSIZE;
    OL_W -= dx;
    OL_X += dx;
  } else if (g->right) {
    if (OL_W + dx < MINSIZE)
      dx = MINSIZE - OL_W;
    OL_W += dx;

  } else if  (!g->top && !g->bottom) {
    OL_X += dx;
    OL_Y += dy;
  }

  blockoutline_draw(g);
}

void gate_moveObject(GCElement *g,int dx,int dy)
{
  if (g->anchored) {
    message(0,msgLookup("err.gatanchor"));		/* "Gate is anchored and can not be moved." */
    return;
  }

  if (!blockmovestyle||g->typeinfo->Code!=BLOCK) {
    gate_draw(g,GD_NORMAL);
    gate_move(g,dx,dy);
    gate_draw(g,GD_NORMAL);
  } else {
    blockoutline_move(g,dx,dy);
  }
}

void setrubberband(EditState *es)
{
#if TOUCH_XGATE_ED
  ob_touch(XGate.ed);
#endif
  XGate.ed->lx = XGate.ed->sx = XGate.ed->tx;
  XGate.ed->lx = XGate.ed->sy = XGate.ed->ty;

  ZDrawLine(XGate.D,XGate.W,XGate.toolGC,ctow_x(XGate.ed->sx),ctow_y(XGate.ed->sy),ctow_x(XGate.ed->tx),ctow_y(XGate.ed->ty));
}

void moverubberband(EditState *es)
{
  ZDrawLine(XGate.D,XGate.W,XGate.toolGC,ctow_x(XGate.ed->sx),ctow_y(XGate.ed->sy),ctow_x(XGate.ed->lx),ctow_y(XGate.ed->ly));
  ZDrawLine(XGate.D,XGate.W,XGate.toolGC,ctow_x(XGate.ed->sx),ctow_y(XGate.ed->sy),ctow_x(XGate.ed->tx),ctow_y(XGate.ed->ty));
}

void setRubberBox(EditState *es)
{
  int x = ctow_x(XGate.ed->sx);
  int y = ctow_y(XGate.ed->sy);
  int width = XGate.ed->tx-XGate.ed->sx;
  int height = XGate.ed->ty-XGate.ed->sy;

  if (width < 0) {
    width = -width;
    x = x - width;
  } 
  if (height < 0) {
    height = -height;
    y = y - height;
  } 

  ZDrawRectangle(XGate.D,XGate.W,XGate.toolGC,x,y,width,height);
}

void moveRubberBox(EditState *es)
{
  int x = ctow_x(XGate.ed->sx);
  int y = ctow_y(XGate.ed->sy);
  int lx = ctow_x(XGate.ed->sx);
  int ly = ctow_y(XGate.ed->sy);
  int width = XGate.ed->tx-XGate.ed->sx;
  int height = XGate.ed->ty-XGate.ed->sy;
  int lwidth = XGate.ed->lx-XGate.ed->sx;
  int lheight = XGate.ed->ly-XGate.ed->sy;

  if (width < 0) {
    width = -width;
    x = x - width;
  } 
  if (height < 0) {
    height = -height;
    y = y - height;
  } 
  if (lwidth < 0) {
    lwidth = -lwidth;
    lx = lx - lwidth;
  } 
  if (lheight < 0) {
    lheight = -lheight;
    ly = ly - lheight;
  } 

  ZDrawRectangle(XGate.D,XGate.W,XGate.toolGC,x,y,width,height);
  ZDrawRectangle(XGate.D,XGate.W,XGate.toolGC,lx,ly,lwidth,lheight);
}

void moveobject(EditState *es)
{
  if (XGate.circuit->es->env->is_lib) {
    message(0,msgLookup("err.libmode"));		/* "Can not edit library module." */
    return;
  }

  switch (XGate.circuit->mode) {
  case MODE_MOVENULL :
    break;
  case MODE_MOVESEL :
    ob_suggest_name("MultiMove");
    sel_draw(es);
    sel_move(es,XGate.ed->tx-XGate.ed->lx,XGate.ed->ty-XGate.ed->ly);
    sel_draw(es);
    break;
  case MODE_MOVE :
    mark_unpost();

    if (XGate.circuit->wnsel) {
      if (!XGate.circuit->wsel)
	printf("Huh? wsel is NULL\n");
      ob_suggest_name("MoveWire");
      wire_draw(XGate.circuit->wsel->nodes);
      wire_move(XGate.circuit->wnsel,XGate.ed->tx-XGate.ed->lx,XGate.ed->ty-XGate.ed->ly,XGate.circuit->wnsel->stype);
      wire_draw(XGate.circuit->wsel->nodes);
      SetModified();
    } else {
      if (XGate.circuit->select && XGate.circuit->select->typeinfo->Flags.special_move) {
	if (!XGate.circuit->select->anchored) {
	  sel_draw(es);
	  ob_suggest_name("MoveGate");
	  gate_move(XGate.circuit->select,XGate.ed->tx-XGate.ed->lx,XGate.ed->ty-XGate.ed->ly);
	  sel_draw(es);
	} else
	  message(0,msgLookup("err.gatanchor"));		/* "Gate is anchored and can not be moved." */

      } else if (XGate.circuit->mg_selection) {
	if (!XGate.circuit->mg_selection->s_hasAnchored) {
	  sel_draw(es);
	  ob_suggest_name("MoveGate");
	  sel_move(es,XGate.ed->tx-XGate.ed->lx,XGate.ed->ty-XGate.ed->ly);
	  sel_draw(es);
	} else
	  message(0,msgLookup("err.gatanchor"));		/* "Gate is anchored and can not be moved." */
      }
    }
    if (!XGate.circuit->select && !XGate.circuit->wnsel && !es->isInterface) {
      int dx = XGate.ed->tx-XGate.ed->sx;
      int dy = XGate.ed->ty-XGate.ed->sy;

      if (dx*dx+dy*dy > SELMODETHRESH) {
	ob_touch(XGate.circuit);
	XGate.circuit->mode = MODE_MAKESEL;
	wm_SetCursor(ARROWCURSOR);
	setRubberBox(es);
      }
    }

    mark_post();
    break;
  case MODE_MAKESEL :
    moveRubberBox(es);
    break;
  case MODE_REPLICATE :
    if (XGate.circuit->select) {
      gate_hashrepline(es,XGate.ed->lx,XGate.ed->ly);
      gate_hashrepline(es,XGate.ed->tx,XGate.ed->ty);
      moverubberband(es);
    }
    break;
  }
}

void DropWireOnGate(EditState *es,GCElement *g)
{
  GWire *w;
  int p;

  wire_draw(XGate.circuit->wsel->driver->nodes);
  w = XGate.circuit->wnsel->end;
  wire_deletezeronodes(XGate.circuit->wnsel->in ? w->driver->nodes : w->nodes);
  ob_touch(XGate.circuit);
  XGate.circuit->wnsel = w->nodes;
  
  switch (g->typeinfo->Code) {
  case JOINT :
    joint_connect(g,XGate.circuit->wnsel);
    wire_draw(XGate.circuit->wsel->driver->nodes);
    wire_finalizeNet(XGate.circuit->wsel);
    break;
  case BLOCK :
    p = block_connect(g,XGate.circuit->wnsel,IN);
    if (p >= 0) {
      wire_snap(XGate.circuit->wsel->driver->nodes);
      wire_draw(XGate.circuit->wsel->driver->nodes);
      DrawPinIOMark(XGate.circuit->wnsel->end,p,XGate.circuit->wnsel->in ? IN : OUT);
      block_setPortName(g,XGate.circuit->wnsel->end,es);
      wire_finalizeNet(XGate.circuit->wnsel->end);
    }
    break;
  default :
    wire_snap(w->driver->nodes);
    wire_finalizeNet(w);
    wire_draw(w->driver->nodes);
    break;
  }
}

void hit_choose(int x,int y,int gbias,GCElement **g,GWire **w,GWireNode **n)
{
  int dg, dw, dn;

  dg = dw = dn = NOHIT;

  if (g && *g) {
    int dx = (*g)->xpos - x;
    int dy = (*g)->ypos - y;
    dg = dx*dx+dy*dy - gbias;
  }

  if (w && *w) {
    int dx = (*w)->nodes->x - x;
    int dy = (*w)->nodes->y - y;
    dw = dx*dx+dy*dy;
  }
  
  if (n && *n) {
    GWire *e1,*e2;

    int dx = (*n)->x - x;
    int dy = (*n)->y - y;

    e1 = wirenode_driver(*n);
    e2 = wirenode_drivee(*n);

    if (g && (e1->gate == *g || e2->gate == *g))
      dn = NOHIT;
    else {
      switch ((*n)->stype) {
      case FULL :
	dn = dx*dx + dy*dy;
	break;
      case VERTICAL :
	dn = dx*dx;
	break;
      case HORIZONTAL :
	dn = dy*dy;
	break;
      default :
	logError(ERL_WARN,"Unknown stype %d in hit_all.",(*n)->stype);
	break;
      }
    }
  }

  if (dn <= dw && dn <= dg) {
    if (w) *w = 0;
    if (g) *g = 0;
  } else if (dw <= dn && dw <= dg) {
    if (n) *n = 0;
    if (g) *g = 0;
  } else {
    if (n) *n = 0;
    if (w) *w = 0;
  }
}

#if 1
void DropWire(EditState *es)
{
  GWire *csel = 0;
  GWireNode *nsel = 0;
  struct celemnt *g;
  GWire *kw1,*kw2;

  if (!XGate.circuit->wsel) {
    printf("Huh? wsel is NULL\n");
    return;
  }

  net_unselect(1);

  if (XGate.circuit->wnsel->end) {
    g = gate_hit(es->env,XGate.ed->tx,XGate.ed->ty);
    if (XGate.circuit->wnsel->stype == FULL) {
      nsel = wire_hit_other(XGate.circuit->wnsel->end,es->env->wires);
      csel = wire_endhit(XGate.circuit->wnsel->end,es->env->wires);
    }

    hit_choose(XGate.ed->tx,XGate.ed->ty,9,&g,&csel,&nsel);

    if (g) {
      DropWireOnGate(es,g);
    } else if (csel) {
      GWire *kw1 = wire_sigroot(XGate.circuit->wnsel->end);
      GWire *kw2 = wire_sigroot(csel);
      GWire *rw;

      if (!net_connectOK(kw1->net,kw2->net,0)) return;
      
      net_draw(kw1->net);
      net_draw(kw2->net);
      rw = wire_connect(es->env,XGate.circuit->wnsel->end,csel);
      net_draw(rw->net);
      ob_touch(XGate.circuit);	/* JPH 121703 */
      XGate.circuit->wsel = rw;
    } else if (nsel) {
      GCElement *tap_g;
      GNet *n1,*n2;

      kw1 = wire_sigroot(XGate.circuit->wnsel->end);
      kw2 = wire_sigroot(wirenode_driver(nsel));

      n1 = kw1->net;
      n2 = kw2->net;

      if (!net_connectOK(n1,n2,1)) return;
      if (nsel->stype != VERTICAL && nsel->stype != HORIZONTAL && n1->nbits != n2->nbits) {
	message(0,msgLookup("err.badconbitw"));	/* Connection refused because bit widths do not match. */
	return;
      }

      net_draw(n1);
      net_draw(n2);

      tap_g = join_wires(XGate.circuit->wnsel->end,nsel,es);

      wire_finalizeNet(XGate.circuit->wsel);

      if (n1->refs) { 
	wire_finalizeNet(n1->driver);
	net_draw(n1);
      }
      if (n2->refs) {
	wire_finalizeNet(n2->driver);
	net_draw(n2);
      }

      if (tap_g)
      	gate_draw(tap_g,GD_NOWIRE);
    }
  }

  if (!(XGate.state & Mod1Mask))
    DoSnap(XGate.circuit->wsel);
  wire_finalizeNet(XGate.circuit->wsel);

  if (XGate.circuit->mode == MODE_MOVE)
    net_select(XGate.circuit->wsel->net,1);
}
#endif

void dropobject(EditState *es)
{
  DoTcl("tkg_unpostNetDelay");

  switch (XGate.circuit->mode) {
  case MODE_MOVENULL :
    ob_touch(XGate.circuit);
    XGate.circuit->mode = MODE_MOVE;
    break;
  case MODE_CUT :
    if (startrekp)
      wm_SetCursor(TREKDELETEUP);
    else
      wm_SetCursor(OPENCUTTERS);
    break;
  case MODE_INVERT :
    wm_SetCursor(INVERTUPCURSOR);
    break;
  case MODE_MOVESEL :
    sel_dropFixup(es);
    break;
  case MODE_MOVE :
    if (XGate.circuit->es->env->is_lib) {
      message(0,msgLookup("err.libmode"));		/* "Can not edit library module." */
      if (batp) {
	SetBatCursor();
      } else
	wm_SetCursor(ARROWCURSOR);
      return;
    }

    if (XGate.circuit->wnsel) {
	DropWire(es);
    } else
      if (XGate.circuit->select) {
	if (blockmovestyle&&XGate.circuit->select->typeinfo->Code == BLOCK) {
	  blockoutline_unset(XGate.circuit->select);
	  if (!XGate.circuit->select->anchored) {
	    gate_draw(XGate.circuit->select,GD_NORMAL);
	    gate_move(XGate.circuit->select,XGate.ed->tx-XGate.ed->sx,XGate.ed->ty-XGate.ed->sy);
	    gate_draw(XGate.circuit->select,GD_NORMAL);
	  } else
	    message(0,msgLookup("err.gatanchor"));		/* "Gate is anchored and can not be moved." */
	}
	if (!(XGate.state & Mod1Mask))
	  wire_snapgate(XGate.circuit->select,1);
      }
    if (batp) {
      SetBatCursor();
    } else
      wm_SetCursor(ARROWCURSOR);

    mark_post();

    break;
  case MODE_DELETE :
    if (startrekp)
      wm_SetCursor(TREKDELETEUP);
    else
      wm_SetCursor(DELETEUPCURSOR);
    break;
  case MODE_SETSIZE :
    if (XGate.circuit->es->env->is_lib) {
      message(0,msgLookup("err.libmode"));		/* "Can not edit library module." */
      return;
    }
    ob_suggest_name("Resize");
    setwiresize(es);
    break;
  case MODE_REPLICATE :
    if (XGate.circuit->es->env->is_lib) {
      message(0,msgLookup("err.libmode"));		/* "Can not edit library module." */
      return;
    }
    ob_append_frame("Replicate");
    if (XGate.circuit->select)
      gate_doReplication(es);
    setEditMode(es,MODE_MOVE);
    ob_end_frame();
    break;
  case MODE_MAKESEL :
    if (XGate.circuit->es->env->is_lib) {
      message(0,msgLookup("err.libmode"));		/* "Can not edit library module." */
      return;
    }
    ob_touch(XGate.circuit);
    XGate.circuit->mode = MODE_MOVE;
    setRubberBox(es);
    if (sel_select(es))
      XGate.circuit->mode = MODE_MOVESEL;
    break;
  default :
    break;
  }
  sel_updateMenuState();
}

void mousedownmovement(EditState *es)
{
  moveobject(es);
}

void mouseuptransition(EditState *es)
{
  mark_unpost();

  dropobject(es);
  ob_touch(XGate.circuit);
  XGate.circuit->wnsel = NULL;
  XGate.circuit->wsel = NULL;

  if (es->isInterface && did_interface_resize) {
    modint_arrange(es);
    FlagRedraw();
  }
}


void mousedowntransition(EditState *es)
{
  ClearErrorMark();

  mark_unpost();
  selectobject(es);
  mark_post();
}

void unselectAll(EditState *es)
{
  ClearErrorMark();

  mark_unpost();

  if (XGate.circuit->select) {
    unselectGate(es);
  }
  sel_clear(es);
  net_unselect(1);
}

/* Sets initial wires for an object */
void initial_wires(GModuleDef *env,GCElement *select,int invertp)
{
  int i,j;

  for (i = 0;i < select->typeinfo->NumPads;i++)
    for (j = 0;j < select->typeinfo->Pad[i].Num;j++)
      createwire(env,select,i,select->typeinfo->Pad[i].Dir == OUT && invertp);
}

char *PrintNet(char *p,GNet *net)
{
  char *name;

  if (!net) {
    p += sprintf(p,"NetID:    *null*\n");
    return p;
  }

  name = net->signame ? net->signame : "*none*";
  p += sprintf(p,"NetID:   0x%p     Name: %s\n",(void*)net,name);
  p += sprintf(p,"  Bits: %d    Refs: %d",net->nbits,net->refs);
  if (net->wnum == 10000 || net->gnum == 10000)
    p += sprintf(p,"   Finalized: ok\n");
  else
    p += sprintf(p,"   Finalized: %d/%d\n",net->wnum,net->gnum);

  p += sprintf(p,"  Flags:");
  if (net->mark) p += sprintf(p," mark");
  if (net->ionet) p += sprintf(p," ionet");
  if (net->show_name) p += sprintf(p," show_name");
  p += sprintf(p,"\n");
  p += sprintf(p,"  Decoration: %d\n",net->decoration);

  return p;
}

char *PrintWireEnd(char *p,GWire *w)
{
  GCElement *g = w->gate;

  p += sprintf(p,"Port:    %s (0x%p) {%d}\n",w->name ? w->name : "*none*",(void*)w,w->nidx);

  if (!g)
    p += sprintf(p,"Gate:    *none*\n");
  else {
    
    if (!g->typeinfo)
      p += sprintf(p,"Gate:    typeinfo is null\n");
    else if (badaddr(g->typeinfo))
      p += sprintf(p,"Gate:    typeinfo is bad address\n");
    else if (!g->typeinfo->vnames)
      p += sprintf(p,"Gate:    typeinfo name is null\n");
    else {
      int pos,n;

      posongate(w,g,&pos,&n);
      p += sprintf(p,"Gate:    %s.%s[%d]\n",g->typeinfo->englishName,
	     g->typeinfo->Pad[pos].Name,n);
      if (g->typeinfo->Code == BLOCK)
	p += sprintf(p,"Offset:  %d/%d\n",w->offset.num,w->offset.den);
    }
  }

  p += sprintf(p,"Orient:  %d\n",w->orient);
  p += sprintf(p,"Wtype:   %d\n",w->wtype);

  if (w == (GWire*)wire_sigroot(w))
    p += sprintf(p,"DType:  root driver\n");
  else if (w == w->driver)
    p += sprintf(p,"DType:  segment driver\n");
  else
    p += sprintf(p,"DType:  segment drivee\n");

  return p;
}

char *PrintWireNode(char *p,GWireNode *n)
{
  p += sprintf(p," Pos: (%d, %d)  %s\n",n->x,n->y,n->end ? "*end*" : "");

  if (n->in && n->in->out != n)
    p += sprintf(p,"    n->in is wrong\n");
  if (n->out && n->out->in != n)
    p += sprintf(p,"    n->out is wrong\n");
  if (n->end && n->in && n->out)
    p += sprintf(p,"    n->end is wrong\n");

  return p;
}

/*
    Handle mouse down action while in debug mode (the wrench tool).
*/
void DoDebug(EditState *es)
{
  GWireNode *n;
  GWire *W = 0;
  GCElement *g;
  GGateInfo *gi;
  char msg[1024*10];
  char *p = msg;

  if ((g = gate_hit(es->env,XGate.ed->tx,XGate.ed->ty))) {
    GWire *w;
    int i;

    gi = g->typeinfo;

    p += sprintf(p,"Type: %s\n",gi->englishName);
    p += sprintf(p,"Name: %s\n",g->ename ? g->ename : "*none*");
    if (gi->Code == BLOCK) {
      p += sprintf(p,"Size: %dx%d\n",g->u.block.gwidth,g->u.block.gheight);
    }
    p += sprintf(p,"Pads:\n");
    for (i = 0;i < g->typeinfo->NumPads;i++) {
      p += sprintf(p,"  %s:",g->typeinfo->Pad[i].Name);
      for (w = g->wires[i];w;w = w->next) {
	p += sprintf(p," ");
	if (w->net->signame)
	  p += sprintf(p,"%s(0x%p) {%d}",w->net->signame,(void*)w->net,w->nidx);
	else
	  p += sprintf(p,"0x%p {%d}",(void*)w->net,w->nidx);
	if (w == (GWire*)wire_sigroot(w))
	  p += sprintf(p,"[r]");
	else if (w == w->driver)
	  p += sprintf(p,"[d]");
	else
	  p += sprintf(p,"[e]");
      }
      p += sprintf(p,"\n");
    }
  } else if ((n = wire_iohit(XGate.ed->tx,XGate.ed->ty,es->env->wires))) {
    GNet *net;

    while (n->in) n = n->in;

    net = n->end->net;
    p = PrintNet(p,net);
    p = PrintWireEnd(p,n->end);
    for (;n;n = n->out) {
      p = PrintWireNode(p,n);
      W = n->end;
    }
    p = PrintWireEnd(p,W);
    if (W->net != net) {
      p += sprintf(p,"####Warning other end on different net...\n");
      p = PrintNet(p,W->net);
    }
    p += sprintf(p,"\n");
  }

  DoTcl("tkg_tempMessage {%s}",msg);
}

void selectobject(EditState *es)
{
  GCElement *g = gate_hit(es->env,XGate.ed->tx,XGate.ed->ty);
  if (g && g->typeinfo->Code == COMMENT && !XGate.state && g->u.comment.doLink) {
    dohyperlink(g->u.comment.link);
    return;
  }

  switch (XGate.circuit->mode) {
  case MODE_MOVENULL :
    break;
  case MODE_MOVESEL :
    if (g && sel_isSelGate(g)) {
      break;
    }
    ob_touch(XGate.circuit);
    XGate.circuit->mode = MODE_MOVE;

    /* fall through */
  case MODE_MOVE :
    if (!(XGate.state & ControlMask))
      sel_clear(es);

#if TOUCH_XGATE_ED
    ob_touch(XGate.ed);
#endif
    XGate.ed->sx = XGate.ed->tx;
    XGate.ed->sy = XGate.ed->ty;
    if (!es->isInterface) {
      if ((XGate.circuit->wnsel = wire_hit(XGate.ed->tx,XGate.ed->ty,es->env->wires))) {
	if (((!XGate.circuit->wnsel->in) || (!XGate.circuit->wnsel->out)) && (XGate.circuit->wnsel->stype == FULL) ) {
	  batc = 0;
	  wm_SetCursor(IRON);
	}
	ob_touch(XGate.circuit);
	XGate.circuit->wsel = wirenode_driver(XGate.circuit->wnsel);
	unselectGate(es);
      } else if ((XGate.circuit->wnsel = wire_iohit(XGate.ed->tx,XGate.ed->ty,es->env->wires))) {
	if (XGate.circuit->es->env->is_lib) {
	  message(0,msgLookup("err.libmode"));				/* "Can not edit library module." */
	  ob_touch(XGate.circuit);
	  XGate.circuit->wsel = wirenode_driver(XGate.circuit->wnsel);
	  unselectGate(es);
	  net_select(XGate.circuit->wsel->net,1);
	  return;
	}

	if ((XGate.circuit->wnsel = wire_makecorner(XGate.circuit->wnsel,XGate.ed->tx,XGate.ed->ty))) {
	  XGate.circuit->wsel = wirenode_driver(XGate.circuit->wnsel);
	}
	unselectGate(es);
      }
      if (XGate.circuit->wnsel) {
	sel_clear(es);
      }
    }
    if (!XGate.circuit->wnsel) { 
      selectGate(es,XGate.ed->tx,XGate.ed->ty);
      if (XGate.circuit->select) {
	if (XGate.circuit->select->typeinfo->Code == BLOCK && blockmovestyle)
	  blockoutline_set(XGate.circuit->select,XGate.circuit->select->xpos,XGate.circuit->select->ypos);
      }
    }
    if ((!XGate.circuit->select) && (!XGate.circuit->mg_selection) && (!XGate.circuit->wnsel) && !XGate.circuit->es->isInterface) {
      batc = 0;
      switch (XGate.circuit->rot) {
      case ROT0 :
	wm_SetCursor(ARROW0);
	break;
      case ROT90 :
	wm_SetCursor(ARROW90);
	break;
      case ROT180 :
	wm_SetCursor(ARROW180);
	break;
      case ROT270 :
	wm_SetCursor(ARROW270);
	break;
      }
    }

    if (XGate.circuit->wsel) {
      net_select(XGate.circuit->wsel->net,1);
    } else {
      if (XGate.circuit->select && XGate.circuit->select->typeinfo->Code == JOINT) {
	GCElement *g = XGate.circuit->select;
	int i;
	for (i = 0;i < 4;i++)
	  if (g->wires[i]) {
	    net_select(g->wires[i]->net,1);
	    break;
	  }
      } else
	net_unselect(1);
    }

    break;
  case MODE_DEBUG :
    DoDebug(es);
    break;
  case MODE_REPLICATE :
    if (!modifyOK(es,0)) return;

    ob_append_frame("Replicate");
    selectGate(es,XGate.ed->tx,XGate.ed->ty);
    if (XGate.circuit->select) {
#if TOUCH_XGATE_ED
      ob_touch(XGate.ed);
#endif
      XGate.ed->tx = XGate.circuit->select->xpos;
      XGate.ed->ty = XGate.circuit->select->ypos;
      setrubberband(es);
    }
    ob_end_frame();
    break;
  case MODE_CUT :
    if (XGate.circuit->es->env->is_lib) {
      message(0,msgLookup("err.libmode"));				/* "Can not edit library module." */
      break;
    }
    if (startrekp)
      wm_SetCursor(TREKDELETEDN);
    else
      wm_SetCursor(CLOSEDCUTTERS);
    if ((XGate.circuit->wnsel = wire_iohit(XGate.ed->tx,XGate.ed->ty,es->env->wires))) {
      ob_suggest_name("Cut");
      SetModified();
      net_unselect(1);
      wire_cut(XGate.ed->tx,XGate.ed->ty,XGate.circuit->wnsel,es->env);
    }
    ob_touch(XGate.circuit);
    XGate.circuit->wsel = NULL;
    XGate.circuit->wnsel = NULL;
    break;
  case MODE_INVERT :
    wm_SetCursor(INVERTDNCURSOR);
    if (!modifyOK(es,0)) return;

    ob_touch(XGate.circuit);
    if ((XGate.circuit->wnsel = wire_iohit(XGate.ed->tx,XGate.ed->ty,es->env->wires)))
      if (XGate.circuit->wnsel->end && XGate.circuit->wnsel->end->gate &&
	  !XGate.circuit->wnsel->end->gate->typeinfo->Flags.NoInvert) {
	ob_suggest_name("Invert");
	wire_draw(XGate.circuit->wnsel->end->driver->nodes);
	ob_touch(XGate.circuit->wnsel->end);
	XGate.circuit->wnsel->end->invert = !XGate.circuit->wnsel->end->invert;
	wire_draw(XGate.circuit->wnsel->end->driver->nodes);
	SetModified();
      }
    XGate.circuit->wnsel = NULL;
    break;
  case MODE_DELETE :
    if (!modifyOK(es,0)) return;
    if (startrekp)
      wm_SetCursor(TREKDELETEDN);
    else
      wm_SetCursor(DELETEDNCURSOR);
    unselectGate(es);
    selectGate(es,XGate.ed->tx,XGate.ed->ty);
    if (XGate.circuit->select) {
      ob_suggest_name("Delete");
      sel_delete(es);
    }
    ob_touch(XGate.circuit);
    XGate.circuit->last = XGate.circuit->select = NULL;
    break;
  default :
    break;
  }

  if (XGate.circuit->wsel && tkgate_currentMode() == MM_ANALYZE)
    cpath_showNetDelay(XGate.circuit->wsel->net);
}

void setEditCursor(int mode)
{
  switch (mode) {
  case MODE_MOVE :
  default :
    if (batp) {
      SetBatCursor();
    } else
      wm_SetCursor(ARROWCURSOR);
    break;
  case MODE_INVERT :
    wm_SetCursor(INVERTUPCURSOR);
    break;
  case MODE_CUT :
    if (startrekp)
      wm_SetCursor(TREKDELETEUP);
    else
      wm_SetCursor(OPENCUTTERS);
    break;
  case MODE_DELETE :
    if (startrekp)
      wm_SetCursor(TREKDELETEUP);
    else
      wm_SetCursor(DELETEUPCURSOR);
    break;
  case MODE_DEBUG :
    wm_SetCursor(WRENCHCURSOR);
    break;
  case MODE_SETSIZE :
    wm_SetCursor(SIZECURSOR);
    break;
  case MODE_REPLICATE :
    wm_SetCursor(REPCURSOR);
    break;
  }

}

void setEditMode(EditState *es,int mode)
{
  int iomodep;
  static int lastmode = 0;

  ob_begin_frame("-Unselect");

  iomodep = 0;
  switch (XGate.circuit->mode) {
  case MODE_MOVENULL :
  case MODE_MOVE :
    lastmode = MODE_MOVE;
    break;
  case MODE_INVERT :
    lastmode = XGate.circuit->mode;
    break;
  case MODE_CUT :
    lastmode = XGate.circuit->mode;
    break;
  case MODE_DELETE :
    lastmode = XGate.circuit->mode;
    break;
  case MODE_SETSIZE :
    lastmode = XGate.circuit->mode;
    break;
  case MODE_DEBUG :
    lastmode = XGate.circuit->mode;
    break;
  default :
    break;
  }

  if (lastmode == MODE_MOVE && batp) lastmode = -1;

  batc = 0;
  ob_touch(XGate.circuit);
  XGate.circuit->mode = mode;
  switch (mode) {
  case MODE_MOVE :
    XGate.ed->mark_vis = 0;
    break;
  case MODE_INVERT :
    net_unselect(1);
    sel_clear(es);
    break;
  case MODE_CUT :
    net_unselect(1);
    sel_clear(es);
    break;
  case MODE_DELETE :
    net_unselect(1);
    sel_clear(es);
    break;
  case MODE_DEBUG :
    net_unselect(1);
    break;
  case MODE_SETSIZE :
    net_unselect(1);
    sel_clear(es);
    break;
  case MODE_REPLICATE :
    net_unselect(1);
    break;
  default :
    net_unselect(1);
    break;
  }

  setEditCursor(mode);

  if (!iomodep) {
    mark_unpost();
    unselectGate(es);
    ob_touch(XGate.circuit);
    XGate.circuit->wnsel = NULL;
    XGate.circuit->wsel = NULL;
  }
  ob_end_frame();
}

/*
      Write a message to the log file.
*/
void logError(int level,const char *fileName,int lineNum,const char *msg,...)
{
  FILE *f;
  struct passwd *pw;
  struct tm *tm;
  time_t clock[1];
  char buf[STRMAX],msgBuf[STRMAX],fname[STRMAX];
  char *lstr = 0;
  static char *mon[] = {
    "Jan","Feb","Mar","Apr","May","Jun",
    "Jul","Aug","Sep","Oct","Nov","Dec"
  };
  va_list ap;
  const char *home = 0;

  if (XGate.tcl)
    home = Tcl_GetVar(XGate.tcl,"tkg_gateHome",TCL_GLOBAL_ONLY);
  else
    home = "<tkgate-home>";

  va_start(ap,msg);
  vsprintf(msgBuf,msg,ap);
  va_end(ap);


  switch (level) {
  case 0 : lstr = "Warning: "; break;
  case 1 : lstr = "Error: "; break;
  case 2 : lstr = "FATAL: "; break;
  default: lstr = "Unknown: "; break;
  }

  pw = getpwuid(getuid());
  time(clock);
  tm = localtime(clock);
  sprintf(buf,"<%s> %s [%s] (%d-%s-%02d %d:%02d) %s (%s, line %d)"
	  ,TKGATE_VERSION
	  ,pw->pw_name,GetSysType()
	  ,tm->tm_mday,mon[tm->tm_mon],(tm->tm_year % 100),tm->tm_hour,tm->tm_min
	  ,msgBuf,fileName,lineNum);

  if (tkg_errorLogFile) {
    sprintf(fname,"%s/%s",home,tkg_errorLogFile);
    if ((f = fopen(fname,"a"))) {
      fprintf(f,"%s\n",buf);
      fclose(f);
    } else if ((f = fopen(tkg_errorLogFile,"a"))) {
      fprintf(f,"%s\n",buf);
      fclose(f);
    }
  }

  printf("tkgate: %s\n",buf);
  switch (level) {
  case 0 :
    if (baderp)
      printf("Oh look what you almost did, Miles!\n");
    else if (startrekp)
      printf("Captain!  Romulan ship decloaking dead ahead!\n");
    break;
  case 1 :
    if (baderp)
      printf("Oh look what you did, Miles!\n");
    else if (startrekp)
      printf("We have engaged the Borg!\n");
    break;
  case 2 :
    panicSave(0);
  }
}

void SyncModified()
{
  char *x = XGate.circuit->is_modified ? "1" : "0";

  Tcl_SetVar(XGate.tcl,"tkg_modifiedFlag",x,TCL_GLOBAL_ONLY);
}

/*
 * Mark the circuit as modified
 */
void SetModified()
{
  if (XGate.circuit->no_set_modify) return;

  search_clear(XGate.circuit->search);

  ob_touch(XGate.circuit);
  XGate.circuit->is_modified = 1;
  SyncModified();
}

/*
 * Mark the circuit as unmodified
 */
void ClearModified()
{
  ob_touch(XGate.circuit);
  XGate.circuit->is_modified = 0;
  SyncModified();
}

void dohyperlink(const char *s)
{
  const char *home    = Tcl_GetVar(XGate.tcl,"tkg_gateHome",TCL_GLOBAL_ONLY);
  const char *curfile = Tcl_GetVar(XGate.tcl,"tkg_currentFile",TCL_GLOBAL_ONLY);
  char buf[1024],cdir[1024];
  char *file = 0,*label = 0, *p;
  int isText = 0;
  int isVerilog = 0;
  char *edir = tkgate_exampledir;
  char *tdir = tkgate_tutorialdir;

  strcpy(buf,s);

  strcpy(cdir,curfile);
  p = strrchr(cdir,'/');
  if (p) *p = 0; else *cdir = 0;

  strreplace(buf,"@T",tdir,1);
  strreplace(buf,"@E",edir,1);
  strreplace(buf,"@H",home,1);
  strreplace(buf,"@C",cdir,1);
  strreplace(buf,"@",home,1);

  if (strncmp(buf,"file:",5) == 0) {
    file = buf+5;
  } else if (strncmp(buf,"tkgate:",7) == 0) {
    file = buf+7;
    isVerilog = 1; 
  } else if (strncmp(buf,"text:",5) == 0) {
    file = buf+5;
    isText = 1; 
  } else
    file = buf;

  label = strchr(file,'#');
  if (label) *label++ = 0;

  if (*file) {
    if (isVerilog)
      DoTcl("gat_load \"%s\"",file);
    else if (isText)
      DoTcl("viewFile \"%s\" \"%s\"",file,file);
    else if (isVerilogFile(file))
      DoTcl("gat_load \"%s\"",file);
    else
      DoTcl("viewFile \"%s\" \"%s\"",file,file);
  }

  if (label && *label) {
    if (editstate_checkPath(&XGate.circuit->es,label))
      editstate_setPath(&XGate.circuit->es,label);
    else
      message(0,msgLookup("err.badfind"),label);
  }

  ob_touch(XGate.circuit);
  XGate.circuit->mode = MODE_MOVENULL;
}

