/****************************************************************************
    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 Thu Sep  2 11:04:53 2004
****************************************************************************/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "tkgate.h"


#define NUMSQRS 10
#define NUMSTEPS 15

#define on_edge(x,y,bx1,by1,bx2,by2) (bx1 == bx2 ? \
   ((abs((bx1)-(x)) < 10) && ((y) > (by1)) && ((y) < (by2))) : \
   ((abs((by1)-(y)) < 10) && ((x) > (bx1)) && ((x) < (bx2))))

struct locate block_right_loc[]  = {{0,0,0,0,D_RIGHT}};
struct locate block_top_loc[]    = {{0,0,0,0,D_UP}};
struct locate block_left_loc[]   = {{0,0,0,0,D_LEFT}};
struct locate block_bottom_loc[] = {{0,0,0,0,D_DOWN}};

extern int baderp;
extern int startrekp;
extern int GlobalElementCount;

GCElement *Block_Make(EditState **es,GModuleDef *env,int GType,
		      int x,int y,int r,const char *Name,int noWire,const char **options,int nOptions);
int Block_HitDistance(GCElement *g,int x,int y);
void Block_Init(GCElement *g);
void Block_Move(GCElement *g,int x,int y);
void Block_SimHitFunc(EditState*,GCElement*);

void Block_Delete(GCElement *g,GModuleDef *env,int drawp);
void Block_Draw(GCElement *g,int md);
void Block_GetExtents(GCElement *g,int *minx,int *miny,int *maxx,int *maxy,int *bd);
GCElement *Block_Replicate(GModuleDef *,GCElement *g,int x,int y,unsigned flags);
void Block_EditProps(GCElement *g,int isLoadDialog);
void Block_VerSave(FILE*,GCElement*);
int Block_HitDistance(GCElement*g,int,int);
void Block_SimStateFunc(GSimModule *ss,GCElement *g);
void Block_AddInput(EditState *es,GCElement *g);
void Block_AddOutput(EditState *es,GCElement *g);
void Block_AddTri(EditState *es,GCElement *g);
void Block_ChangePin(EditState *es,GCElement *g);
void Block_PSWrite(FILE *f,GModLayout*,GCElement *g);

/*
    Table for IO direction marks for pins on logic blocks.
*/
int iodmark[4][4];	/* [orient][iodir] */
/*
     ANY TRI IN  OUT
     <>  <>  <   >	Right
     ^v  ^v  v   ^      Up
     <>  <>  >   <	Left
     ^v  ^v  v   ^	Down
*/


GGateInfo gate_block_info = {
  BLOCK,
  "Module",
  "block",0x0,
  "barf",0,

  {{"B",	{"gmmod",2},		{"gmmodinst",0,0,100},	"gat_make block"},
   {0}},

  0,

  12,{{"Ti",IN,0,0,block_top_loc},
	{"Li",IN,0,0,block_left_loc},
	{"Bi",IN,0,0,block_bottom_loc},
	{"Ri",IN,0,0,block_right_loc},
	{"To",OUT,0,0,block_top_loc},
	{"Lo",OUT,0,0,block_left_loc},
	{"Bo",OUT,0,0,block_bottom_loc},
	{"Ro",OUT,0,0,block_right_loc},
	{"Tt",TRI,0,0,block_top_loc},
	{"Lt",TRI,0,0,block_left_loc},
	{"Bt",TRI,0,0,block_bottom_loc},
	{"Rt",TRI,0,0,block_right_loc}},
  {{0,0,CT},{0,0,CT},{0,0,CT},{0,0,CT}},
  {0,1,0,0,1},

  {0},
  
  Block_Make,
  Block_Init,
  Block_Delete,
  Block_GetExtents,
  Block_HitDistance,
  Block_Draw,
  Block_Move,
  Block_Replicate,
  Block_AddInput,
  Block_AddOutput,
  Block_AddTri,
  Block_ChangePin,
  Nop_SimStateFunc,
  Block_SimHitFunc,
  Block_PSWrite,
  Block_EditProps,
  Block_VerSave
};

/*
   Replicate the wire list of a block
*/
static GWire *block_repwirelist(GModuleDef *M,GCElement *g,GWire *w,int nx,int ny,int x,int y)
{
  GWire *nw;
  GWire *cw;
  GWire *oew;

  nw = NULL;
  for (;w;w = w->next) {
    if (M) {
      GWire *w1,*w2;

      wire_new(M,&w1,&w2);
      cw = w->nodes->in ? w2 : w1;
      oew = (!w->nodes->in) ? w2 : w1;
    } else {
      GWire *w1,*w2;

      wire_new(NULL,&w1,&w2);
      cw = w->nodes->in ? w2 : w1;
      oew = (!w->nodes->in) ? w2 : w1;
    }
    ob_touch(cw);
    ob_touch(cw->nodes);
    ob_touch(cw->net);
    ob_touch(oew);

    cw->orient = w->orient;
    cw->gate = g;

    cw->net->dtype = w->net->dtype;
    net_setSize(cw->net,w->net->nbits);

    cw->offset = w->offset;
    oew->wtype = cw->wtype = w->wtype;
    cw->nodes->x = w->nodes->x + nx - x;
    cw->nodes->y = w->nodes->y + ny - y;
    cw->next = nw;
    nw = cw;
    cw->name = ob_strdup(w->name);

    block_canonicalizewire(g,cw);

    cw = w->nodes->out ? cw->nodes->out->end : cw->nodes->in->end;
    ob_touch(cw->nodes);

    if (cw->nodes->x == nw->nodes->x)
      cw->nodes->y = nw->nodes->y > cw->nodes->y ? nw->nodes->y - 10 : nw->nodes->y + 10;
    else
      cw->nodes->x = nw->nodes->x > cw->nodes->x ? nw->nodes->x - 10 : nw->nodes->x + 10;

    wire_finalizeNet(cw);
  }
  return nw;
}

static void block_replicatewires(GCElement *ng,GCElement *g,GModuleDef *M)
{
  int i;

  ob_touch(ng);
  for (i = 0;i < g->typeinfo->NumPads;i++)
    ng->wires[i] =  block_repwirelist(M,ng,g->wires[i],
				      ng->xpos,ng->ypos,g->xpos,g->ypos);
}

/*
Creates a duplicated copy of block g.  (Assuming g is a block)
*/
GCElement *Block_Replicate(GModuleDef *M,GCElement *g,int x,int y,unsigned flags)
{
  GCElement *ng;

  ng = gate_new(x,y,g->orient,g->typeinfo->Code);
  if (M)
    gate_add(M,ng);

  ob_touch(ng);
  ng->u.block.gheight = g->u.block.gheight;
  ng->u.block.gwidth = g->u.block.gwidth;
  if (g->ename)
    gate_setName(ng,g->ename,M);
  ng->u.block.BlockFunction = ob_strdup(g->u.block.BlockFunction);
  ng->enumb = GlobalElementCount++;
  ng->show_name = g->show_name;

  if (!(flags & REP_NOWIRES))
    block_replicatewires(ng,g,M);

  return ng;
}

GCElement *Block_Make(EditState **es,GModuleDef *env,int GType,
		      int x,int y,int r,const char *Name,int noWire,const char **options,int nOptions)
{
  GCElement *g,*copy;
  const char *Func,*Size,*Interf;
  int W,H;

  if (!(g = Generic_Make(es,env,GType,x,y,r,Name,noWire,options,nOptions)))
    return NULL;

  Func = seekOption("-func",options,nOptions);
  Size = seekOption("-size",options,nOptions);
  Interf = seekOption("-interface",options,nOptions);

  ob_touch(g);
  
  if (es) {
    g->selected = 1;
    g->top = 0;
    g->bottom = 0;
    g->right = 0;
    g->left = 0;
  }
  
  if (Func) {
    g->u.block.BlockFunction = ob_strdup(Func);

    if (Interf && *Interf == '1') {
      if ((copy = env_blockdescript(g))) {
	g->u.block.gwidth = copy->u.block.gwidth;
	g->u.block.gheight = copy->u.block.gheight;
      }

      g->xpos -= g->u.block.gwidth/2;
      g->ypos -= g->u.block.gheight/2;

      if (copy) {
	block_replicatewires(g,copy,(*es)->env);
      }
    }

  } else if (es) {
    GGateInfo *gi = g->typeinfo;
    int ok;
    const char *temp;

    g->u.block.BlockFunction = ob_strdup("new_block");
    gate_draw(g,GD_NORMAL);

    Tcl_SetVar(XGate.tcl,"edgat_newBlock","1",TCL_GLOBAL_ONLY);
    (*gi->EditProps)(g,1);
    DoTcl("tkg_editGate Module");
    if ((temp = Tcl_GetVar(XGate.tcl,"edgat_ok",TCL_GLOBAL_ONLY)) && sscanf(temp,"%d",&ok) == 1 && ok)
      (*gi->EditProps)(g,0);
    else {
      gate_delete(g,(*es)->env,1);
      return 0;
    }
    Tcl_SetVar(XGate.tcl,"edgat_newBlock","0",TCL_GLOBAL_ONLY);
      
    env_checkname(g);
    gate_draw(g,GD_NORMAL);
    if ((copy = env_blockdescript(g))) {
      if (!Size) {
	g->u.block.gwidth = copy->u.block.gwidth;
	g->u.block.gheight = copy->u.block.gheight;
	block_replicatewires(g,copy,(*es)->env);
      }
    }
  } else {
    g->u.block.BlockFunction = 0;
  }

  if (Size && sscanf(Size,"%dx%d",&W,&H) == 2) {
    g->u.block.gwidth = W;
    g->u.block.gheight = H;
  }

 
  return g;
}

GWire *block_hitPort(GCElement *g,int x,int y)
{
  GWire *best_w = 0;
  int best_dist = MAXPORTRANGE+1;
  int i;

  for (i = 0;i < g->typeinfo->NumPads;i++) {
    GWire *w;

    for (w = g->wires[i];w;w = w->next) {
      int d = distance(x,y,w->nodes->x,w->nodes->y);

      if (d < best_dist) {
	best_dist = d;
	best_w = w;
      }
    }
  }

  if (best_w) {
    /*
     * If a corner is closer than a port, don't select the port.
     */
    if (distance(g->xpos                    ,g->ypos                     ,x,y) < best_dist) best_w = 0;
    if (distance(g->xpos + g->u.block.gwidth,g->ypos                     ,x,y) < best_dist) best_w = 0;
    if (distance(g->xpos                    ,g->ypos + g->u.block.gheight,x,y) < best_dist) best_w = 0;
    if (distance(g->xpos + g->u.block.gwidth,g->ypos + g->u.block.gheight,x,y) < best_dist) best_w = 0;
  }

  return best_w;
}

int Block_HitDistance(GCElement *g,int x,int y)
{
  GWire *w;

  ob_touch(g);
  g->left = 0;
  g->right = 0;
  g->top = 0;
  g->bottom = 0;

  w = block_hitPort(g,x,y); 
  if (w && !XGate.circuit->wsel) {
    XGate.circuit->wsel = w; 
    return GATERANGE-2;
  }

  if ((x > g->xpos - 10) &&
      (x < g->xpos + g->u.block.gwidth + 10) &&
      (y > g->ypos - 10) &&
      (y < g->ypos + g->u.block.gheight + 10)) {
    if (x < g->xpos + 10) {
      g->left = 1;
    }
    if (x > g->xpos + g->u.block.gwidth - 10) {
      g->right = 1;
    }
    if (y < g->ypos + 10) {
      g->top = 1;
    }
    if (y > g->ypos + g->u.block.gheight - 10) {
      g->bottom = 1;
    }
    return GATERANGE-1;		/* Hit on smaller objects if overlaping */
  } else
    return NOHIT;		/* Out of range */
}


void Block_Init(GCElement *g)
{
  ob_touch(g);
  g->u.block.gwidth = MINSIZE;
  g->u.block.gheight = MINSIZE;
}

void GetPinIOMark(GWire *w,int d,int iod,int iodset[4][4],int *x,int *y,int *arrow)
{
  *x = w->nodes->x;
  *y = w->nodes->y;
  *arrow = iodset[d][iod];
  switch (d) {
  case D_UP :
    *x += 4;
    *y -= 5;
    break;
  case D_DOWN :
    *x += 4;
    *y += 5;
    break;
  case D_LEFT :
    *x -= 5;
    *y += 4;
    break;
  case D_RIGHT :
    *x += 5;
    *y += 4;
    break;
  }
}

void DrawPinIOMark(GWire *w,int d,int iod)
{
  int x,y;
  int arrow;

  GetPinIOMark(w,d,iod,iodmark,&x,&y,&arrow);
  Icon_draw(XGate.D,XGate.W,XGate.modportGC,ctow_x(x),ctow_y(y),arrow);
}

/*
    Draw the names and I/O direction arrows for one 'pad' of
    a logic block.
*/
void block_drawnamelist(struct wire *w,int s,int d,int iod)
{
  int p,x,y;
  Tk_Font F;

#if 0
  printf("block_drawnamelist(%s, %d, %d, %d)\n",w ? w->name : "(nil)",s,d,iod);
#endif

  if (s) {
    XSetFont(XGate.D,XGate.modportGC, XGate.stextbXF);
    F = XGate.stextbF;
  } else {
    XSetFont(XGate.D,XGate.modportGC, XGate.stextXF);
    F = XGate.stextF;
  }


  for (;w;w = w->next) {
    if (w->name) {
      block_getwirexy(w,d,&x,&y,&p);
      RelPosDrawString(XGate.W,F,XGate.modportGC,x,y,w->name,p);
    }
    DrawPinIOMark(w,d,iod);
  }
}

void Block_Delete(GCElement *g,GModuleDef *env,int drawp)
{
  Generic_Delete(g,env,drawp);
}

void Block_GetExtents(GCElement *g,int *minx,int *miny,int *maxx,int *maxy,int *bd)
{
  *minx = g->xpos;
  *miny = g->ypos;
  *maxx = g->xpos + g->u.block.gwidth;
  *maxy = g->ypos + g->u.block.gheight;

  if (bd) *bd = 30;
}

/* Draw a logic block */
void Block_Draw(GCElement *g,int md)
{
  int i;
  
  ZDrawRectangle(XGate.D,XGate.W,XGate.moduleGC,
		 ctow_x(g->xpos),ctow_y(g->ypos),
		 g->u.block.gwidth,g->u.block.gheight);

  for (i = 0;i < g->typeinfo->NumPads;i++) {
    GWire *w = g->wires[i];

    if (md != GD_NOWIRE)
      wire_drawlist(w,(md == GD_NOINWIRE) ? g : 0);
    block_drawnamelist(g->wires[i],g->selected,
		       g->typeinfo->Pad[i].Loc[0].dir,
		       g->typeinfo->Pad[i].Dir);
  }
  
  if (g->selected) {
    ZDrawRectangle(XGate.D,XGate.W,XGate.moduleGC,
		   ctow_x(g->xpos+1),ctow_y(g->ypos+1),
		   g->u.block.gwidth-2,g->u.block.gheight-2);
  }

  
  if (g->ename && g->show_name) {
    char B[STRMAX];

    if (g->selected)
      XSetFont(XGate.D,XGate.moduleGC, XGate.stextbXF);
    else
      XSetFont(XGate.D,XGate.moduleGC, XGate.stextXF);

    sprintf(B,"(%s)",g->ename);
    dce_DrawString(XGate.moduleGC,g->xpos + (g->u.block.gwidth / 2),
		   g->ypos + (g->u.block.gheight / 2)+fontheight(XGate.stextF),
		   BetweenLeftAndRight | BetweenTopAndBottom,B);
  }
  if (g->u.block.BlockFunction) {
    if (g->selected)
      XSetFont(XGate.D,XGate.moduleGC, XGate.textbXF);
    else
      XSetFont(XGate.D,XGate.moduleGC, XGate.textXF);


    dce_DrawString(XGate.moduleGC,g->xpos + (g->u.block.gwidth / 2),
		   g->ypos + (g->u.block.gheight / 2),
		   BetweenLeftAndRight | BetweenTopAndBottom,
		   g->u.block.BlockFunction);
  }
}

/* Move a block */
void Block_Move(GCElement *g,int x,int y)
{
  int i;

  if (XGate.circuit->wsel) {
    int pad = block_getPad(g,XGate.circuit->wsel);

    switch (pad) {
    case BLOCK_TIN :
    case BLOCK_TOUT :
    case BLOCK_TTRI :
    case BLOCK_BIN :
    case BLOCK_BOUT :
    case BLOCK_BTRI :
      if (XGate.circuit->wsel->nodes->x + x < g->xpos + 3)
	x = g->xpos + 3 - XGate.circuit->wsel->nodes->x;
      if (XGate.circuit->wsel->nodes->x + x > g->xpos + g->u.block.gwidth - 3)
	x = g->xpos + g->u.block.gwidth - 3 - XGate.circuit->wsel->nodes->x;
      y = 0;
      break;
    case BLOCK_LIN :
    case BLOCK_LOUT :
    case BLOCK_LTRI :
    case BLOCK_RIN :
    case BLOCK_ROUT :
    case BLOCK_RTRI :
      if (XGate.circuit->wsel->nodes->y + y < g->ypos + 3)
	y = g->ypos + 3 - XGate.circuit->wsel->nodes->y;
      if (XGate.circuit->wsel->nodes->y + y > g->ypos + g->u.block.gheight - 3)
	y = g->ypos + g->u.block.gheight - 3 - XGate.circuit->wsel->nodes->y;
      x = 0;
      break;
    default :
      return;
      break;
    }
    wire_move(XGate.circuit->wsel->nodes,x,y,FULL);
    block_setWireEnd(g,XGate.circuit->wsel,pad);
    return;
  }

  ob_touch(g);

  if (g->top) {
    if (g->u.block.gheight - y < MINSIZE)
      y = g->u.block.gheight - MINSIZE;
    g->u.block.gheight -= y;
    g->ypos += y;
    for (i = 0;i < g->typeinfo->NumPads;i++) {
      if (g->typeinfo->Pad[i].Loc->dir == D_LEFT ||
	  g->typeinfo->Pad[i].Loc->dir == D_RIGHT)
	block_scalewirelist(g->wires[i],g,1);
      if (g->typeinfo->Pad[i].Loc->dir == D_UP)
	block_movewirelist(g->wires[i],0,y);
    }

  } else if (g->bottom) {
    if (g->u.block.gheight + y < MINSIZE)
      y = MINSIZE - g->u.block.gheight;
    g->u.block.gheight += y;
    
    for (i = 0;i < g->typeinfo->NumPads;i++) {
      if (g->typeinfo->Pad[i].Loc->dir == D_LEFT ||
	  g->typeinfo->Pad[i].Loc->dir == D_RIGHT)
	block_scalewirelist(g->wires[i],g,1);
      if (g->typeinfo->Pad[i].Loc->dir == D_DOWN)
	block_movewirelist(g->wires[i],0,y);
    }
  }
  
  if (g->left) {
    if (g->u.block.gwidth - x < MINSIZE)
      x = g->u.block.gwidth - MINSIZE;
    g->u.block.gwidth -= x;
    g->xpos += x;

    for (i = 0;i < g->typeinfo->NumPads;i++) {
      if (g->typeinfo->Pad[i].Loc->dir == D_UP ||
	  g->typeinfo->Pad[i].Loc->dir == D_DOWN)
	block_scalewirelist(g->wires[i],g,0);
      if (g->typeinfo->Pad[i].Loc->dir == D_LEFT)
	block_movewirelist(g->wires[i],x,0);
    }

  } else if (g->right) {
    if (g->u.block.gwidth + x < MINSIZE)
      x = MINSIZE - g->u.block.gwidth;
    g->u.block.gwidth += x;
    
    for (i = 0;i < g->typeinfo->NumPads;i++) {
      if (g->typeinfo->Pad[i].Loc->dir == D_UP ||
	  g->typeinfo->Pad[i].Loc->dir == D_DOWN)
	block_scalewirelist(g->wires[i],g,0);
      if (g->typeinfo->Pad[i].Loc->dir == D_RIGHT)
	block_movewirelist(g->wires[i],x,0);
    }
  } else if  (!g->top && !g->bottom) {
    for (i = 0;i < g->typeinfo->NumPads;i++)
      block_movewirelist(g->wires[i],x,y);
    g->xpos += x;
    g->ypos += y;
  }
}

void Block_AddInput(EditState *es,GCElement *g)
{
  message(1,msgLookup("err.oldportact"));
}

void Block_AddTri(EditState *es,GCElement *g)
{
  message(1,msgLookup("err.oldportact"));
}

void Block_AddOutput(EditState *es,GCElement *g)
{
  message(1,msgLookup("err.oldportact"));
}

void Block_ChangePin(EditState *es,GCElement *g)
{
  message(1,msgLookup("err.oldportact"));
}

void Block_PSDrawWireName(FILE *f,GWire *w,int Dir,int IODir)
{
  if (w->name) {
    int x,y,p;
    int arrow;

    block_getwirexy(w,Dir,&x,&y,&p);
    PSDrawText(f,x,y,8,w->name,p);
    GetPinIOMark(w,Dir,IODir,iodmark,&x,&y,&arrow);
    switch (IODir) {
    case IN :
      fprintf(f,"%d %d %d arrow\n",x,y,(Dir+2)%4);
      break;
    case OUT :
      fprintf(f,"%d %d %d arrow\n",x,y,Dir);
      break;
    case TRI :
    case ANY :
    default:
      fprintf(f,"%d %d %d tri\n",x,y,Dir%2);
      break;
    }
  }
}

void Block_PSWrite(FILE *f,GModLayout *L,GCElement *g)
{
  int i;
  struct wire *w;
  
  fprintf(f,"%d %d %d %d box\n",g->xpos,g->ypos,
	  g->u.block.gwidth,g->u.block.gheight);
  
  for (i = 0;i < g->typeinfo->NumPads;i++)
    for (w = g->wires[i];w;w = w->next)
      Block_PSDrawWireName(f,w,g->typeinfo->Pad[i].Loc[0].dir,
			   g->typeinfo->Pad[i].Dir);
  
  if (g->ename && g->show_name) {
    char B[STRMAX];
    int h;

    if (XGate.tcl)
      h = fontheight(XGate.stextF);
    else
      h = 8;

    sprintf(B,"(%s)",g->ename);
    PSDrawText(f,g->xpos + (g->u.block.gwidth / 2),
	       g->ypos + (g->u.block.gheight / 2)+h,8,
	       B,BetweenLeftAndRight | BetweenTopAndBottom);
  }
  if (g->u.block.BlockFunction) {
    PSDrawText(f,g->xpos + (g->u.block.gwidth / 2),
	       g->ypos + (g->u.block.gheight / 2),10,
	       g->u.block.BlockFunction,
	       BetweenLeftAndRight | BetweenTopAndBottom);
		
  }
}

void Block_EditProps(GCElement *g,int isLoadDialog)
{
  Tcl_Interp *tcl = XGate.tcl;

  if (isLoadDialog) {
    Tcl_SetVar(XGate.tcl,"edgat_blockFunc",
	       g->u.block.BlockFunction,TCL_GLOBAL_ONLY);
    Generic_EditProps(g,isLoadDialog);
  } else {
    char buf[STRMAX];
    const char *p;

    Generic_EditProps(g,isLoadDialog);
    if ((p = Tcl_GetVar(tcl,"edgat_blockFunc",TCL_GLOBAL_ONLY)) && *p) {
      ob_touch(g);
      ob_free(g->u.block.BlockFunction);
      pickValidName(buf,p,"func",0);
      g->u.block.BlockFunction = ob_strdup(buf);
    }
  }
}

void Block_VerSave(FILE *f,GCElement *g)
{
  GGateInfo *gi = g->typeinfo;
  GWire *w;
  int i,j;
  int first_pin;
  static char dirchar[] = "?=><";

  fprintf(f,"  %s %s",g->u.block.BlockFunction,g->ename);

  first_pin = 1;
  fprintf(f," (");
  for (i = 0;i < gi->NumPads;i++) {
    for (w = g->wires[i], j=0;w;w = w->next, j++) {
      if (first_pin)
	first_pin = 0;
      else
	fprintf(f,", ");
      fprintf(f,".%s(%s)",w->name,w->net->signame);
    }
  }
  fprintf(f,");");

  fprintf(f,"   //: @(%d, %d) /sz:(%d, %d)",g->xpos,g->ypos,g->u.block.gwidth,g->u.block.gheight);

  if (!g->show_name)
    fprintf(f," /sn:%d",g->show_name);
  if (g->anchored)
    fprintf(f," /anc:1");

  fprintf(f," /p:[");
  for (i = 0;i < gi->NumPads;i++) {
    for (w = g->wires[i], j=0;w;w = w->next, j++) {
      fprintf(f," %s%d%c%d",
	      gi->Pad[i].Name,j,
	      dirchar[gi->Pad[i].Dir],
	      w->nidx);
    } 
  }
  fprintf(f," ]");

  fprintf(f,"\n");
}

void block_scalewirelist(GWire *w,GCElement *g,int isY)
{
  for (;w;w = w->next) {
    int dx,dy;

    if (isY) {
      dx = 0;
      dy = (g->u.block.gheight*w->offset.num)/w->offset.den+g->ypos-w->nodes->y;
    } else {
      dx = (g->u.block.gwidth*w->offset.num)/w->offset.den+g->xpos-w->nodes->x;
      dy = 0;
    }
/*    printf("scalewirelist %d %d\n",dx,dy);*/
    wire_move(w->nodes,dx,dy,FULL);
  }
}


void block_movewirelist(GWire *w,int dx,int dy)
{
  for (;w;w = w->next) {
    GWire *ow = wire_other(w);
    GCElement *g = w->gate;

    if (ow->gate && ow->gate == g) {
      if (w->driver == w) {
	GWireNode *n;
      
	for (n = w->driver->nodes;n;n = n->out) {
	  ob_touch(n);
	  n->x += dx;
	  n->y += dy;
	}
      }
    } else
      wire_move(w->nodes,dx,dy,FULL);
  }
}

int block_getPortDir(GCElement *g,GWire *w)
{
  int i;

  for (i = 0;i < g->typeinfo->NumPads;i++) {
    GWire *bw;

    for (bw = g->wires[i];bw;bw = bw->next)
      if (bw == w)
	return g->typeinfo->Pad[i].Dir;
  }
  return 0;
}

int block_getPad(GCElement *g,GWire *w)
{
  int i;

  for (i = 0;i < g->typeinfo->NumPads;i++) {
    GWire *bw;

    for (bw = g->wires[i];bw;bw = bw->next)
      if (bw == w)
	return i;
  }
  return -1;
}

int block_setPortName(GCElement *g,GWire *w,EditState *es)
{
  static char Buf[STRMAX];
  int x,y;
  int dir;
  int nbits;
  const char *sdir = "";
  const char *sigName;
  const char *portName;
  int ok;

  dir = block_getPortDir(w->gate,w);
  switch (dir) {
  case IN : sdir = "in"; break;
  case OUT : sdir = "out"; break;
  case TRI : sdir = "inout"; break;
  }

  Tcl_SetVar(XGate.tcl,"edport_sig",w->net->signame,TCL_GLOBAL_ONLY);
  if (w->name)
    Tcl_SetVar(XGate.tcl,"edport_port",w->name,TCL_GLOBAL_ONLY);
  else
    Tcl_SetVar(XGate.tcl,"edport_port","port_name",TCL_GLOBAL_ONLY);
  Tcl_SetVar(XGate.tcl,"edport_type",sdir,TCL_GLOBAL_ONLY);
  sprintf(Buf,"%d",w->net->nbits);
  Tcl_SetVar(XGate.tcl,"edport_bits",Buf,TCL_GLOBAL_ONLY);

  x = w->nodes->x;
  y = w->nodes->y;

  DoTcl("edgatPortEdit 0 [offsetgeometry . %d %d]",ctow_x(x+125),ctow_y(y+50));

  if (strcmp(XGate.tcl->result,"1") == 0) {
    const char *numBits;
    int new_dir;

    sigName = Tcl_GetVar(XGate.tcl,"edport_sig",TCL_GLOBAL_ONLY);
    portName = Tcl_GetVar(XGate.tcl,"edport_port",TCL_GLOBAL_ONLY);
    numBits = Tcl_GetVar(XGate.tcl,"edport_bits",TCL_GLOBAL_ONLY);
    sdir = Tcl_GetVar(XGate.tcl,"edport_type",TCL_GLOBAL_ONLY);

    if (!sigName || sscanf(numBits,"%d",&nbits) != 1)
      nbits = w->net->nbits;

    if (strcmp(sdir,"in") == 0)
      new_dir = IN;
    else if (strcmp(sdir,"out") == 0)
      new_dir = OUT;
    else if (strcmp(sdir,"inout") == 0)
      new_dir = TRI;
    else
      new_dir = dir;

    /*
     * Change direction 1 or 2 times to make direction match.
     */
    if (new_dir != dir) {
      block_changedir(w->nodes,es);
      dir = block_getPortDir(w->gate,w);
      if (dir != new_dir) {
	block_changedir(w->nodes,es);
	dir = block_getPortDir(w->gate,w);
      }
    }

    ok = 1;
  } else {
    sigName = w->net->signame;
    portName = w->name ? w->name : "new_port";
    nbits = w->net->nbits;

    ok = 0;
  }

  Block_Draw(w->gate,0);
  if (strcmp(w->net->signame,sigName) != 0) net_rename(w->net,sigName,w->net->show_name);

  pickValidName(Buf,portName,"port",0);
  ob_touch(w);
  w->name = ob_strdup(Buf);
  net_setSize(w->net,nbits);
  Block_Draw(w->gate,0);

  return ok;
}

void block_getwirexy(GWire *w,int d,int *x,int *y,int *p)
{
  int h;

  if (XGate.tcl)
    h = fontheight(XGate.stextF);
  else
    h = 8;

  switch (d) {
  case D_UP :
    *x = w->nodes->x;
    *y = w->nodes->y + (7*h)/8;
    *p = BetweenLeftAndRight | BetweenTopAndBottom;
    break;
  case D_LEFT :
    *x = w->nodes->x + 4;
    *y = w->nodes->y + 3;
    *p = AtLeft | BetweenTopAndBottom;
    break;
  case D_DOWN :
    *x = w->nodes->x;
    *y = w->nodes->y - (2*h)/3;
    *p = BetweenLeftAndRight | BetweenTopAndBottom;
    break;
  case D_RIGHT :
    *x = w->nodes->x - 4;
    *y = w->nodes->y + 3;
    *p = AtRight | BetweenTopAndBottom;
    break;
  }
}

void block_free(GCElement *g)
{
  int i;

  ob_touch(g);
  for (i = 0;i < g->typeinfo->NumPads;i++) {
    gate_unattachwirelist(g->wires[i],NULL,1);
    g->wires[i] = NULL;
  }
  ob_free(g);
}

/* Used by box exploder */
static void propogate(int ux[NUMSQRS],int uy[NUMSQRS],int lx[NUMSQRS],int ly[NUMSQRS],int n)
{
  int i;

  for (;n;n--)
    for (i = NUMSQRS-1;i;i--) {
      ux[i-1] = ux[i];
      uy[i-1] = uy[i];
      lx[i-1] = lx[i];
      ly[i-1] = ly[i];
    }
}

/* Do explosion look on box */
void block_explode(GCElement *g)
{
  int ux[NUMSQRS],uy[NUMSQRS],lx[NUMSQRS],ly[NUMSQRS],n;

  ux[NUMSQRS-1] = ctow_x(g->xpos);
  uy[NUMSQRS-1] = ctow_y(g->ypos);
  lx[NUMSQRS-1] = ux[NUMSQRS-1] + g->u.block.gwidth;
  ly[NUMSQRS-1] = uy[NUMSQRS-1] + g->u.block.gheight;
  propogate(ux,uy,lx,ly,NUMSQRS-1);
  for (n = NUMSTEPS;n;n--) {
    XClearArea(XGate.D,XGate.W,ux[0],uy[0],lx[0]-ux[0],ly[0]-uy[0],0);
    propogate(ux,uy,lx,ly,1);
    ux[NUMSQRS-1] -= ux[NUMSQRS-1] / n;
    uy[NUMSQRS-1] -= uy[NUMSQRS-1] / n;
    lx[NUMSQRS-1] += (XGate.width - lx[NUMSQRS-1]) / n;
    ly[NUMSQRS-1] += (XGate.height - ly[NUMSQRS-1]) / n;
    box(wtoc_x(ux[NUMSQRS-1]),wtoc_y(uy[NUMSQRS-1]),
	lx[NUMSQRS-1]-ux[NUMSQRS-1],ly[NUMSQRS-1]-uy[NUMSQRS-1]);
    XFlush(XGate.D);
    usleep(1);
  }
  FlagRedraw();
}

void block_setWireEnd(GCElement *g,GWire *w,int pad)
{
  GWireNode *n = w->nodes;

  ob_touch(w);
  switch (pad) {
  case BLOCK_TIN :
  case BLOCK_TOUT :
  case BLOCK_TTRI :
    w->offset.num = n->x - g->xpos;
    w->offset.den = g->u.block.gwidth;
    w->orient = RIGHT;
    break;
  case BLOCK_LIN :
  case BLOCK_LOUT :
  case BLOCK_LTRI :
    w->offset.num = n->y - g->ypos;
    w->offset.den = g->u.block.gheight;
    w->orient = DOWN;
    break;
  case BLOCK_BIN :
  case BLOCK_BOUT :
  case BLOCK_BTRI :
    w->offset.num = n->x - g->xpos;
    w->offset.den = g->u.block.gwidth;
    w->orient = LEFT;
    break;
  case BLOCK_RIN :
  case BLOCK_ROUT :
  case BLOCK_RTRI :
    w->offset.num = n->y - g->ypos;
    w->offset.den = g->u.block.gheight;
    w->orient = UP;
    break;
  default :
    logError(ERL_ERROR,"Illegal pad number for wire on block.");
  }
}

void addbendnode(GWireNode *n)
{
  GWireNode *nn;

  nn = wire_newnode();
  ob_touch(nn);
  ob_touch(n);

  nn->x = n->x;
  nn->y = n->y;
  if (n->in) {
    ob_touch(nn->in);

    nn->in = n->in;
    nn->in->out = nn;
    n->in = nn;
    nn->out = n;
  } else {
    ob_touch(nn->out);

    nn->out = n->out;
    nn->out->in = nn;
    n->out = nn;
    nn->in = n;
  }
}

int quadrent(GCElement *g,GWireNode *n)
{
  if ((n->y - g->ypos)*g->u.block.gwidth > (n->x - g->xpos)*g->u.block.gheight) {
    if ((g->ypos+g->u.block.gheight-n->y)*g->u.block.gwidth >
	(n->x - g->xpos)*g->u.block.gheight) {
      return D_LEFT;
    } else {
      return D_DOWN;
    }
  } else {
    if ((g->ypos+g->u.block.gheight-n->y)*g->u.block.gwidth >
	(n->x - g->xpos)*g->u.block.gheight) {
      return D_UP;
    } else {
      return D_RIGHT;
    }
  }
}

int block_connect(GCElement *g,GWireNode *n,int Dir)
{
  int p,q;
  
  ob_touch(n->end);
  n->end->gate = g;
  
  switch (p = quadrent(g,n)) {
  case D_UP :
    q = BLOCK_TIN;
    break;
  case D_DOWN :
    q = BLOCK_BIN;
    break;
  case D_LEFT :
    q = BLOCK_LIN;
    break;
  case D_RIGHT :
    q = BLOCK_RIN;
    break;
  default :
    return -1;
  }
  
  switch (Dir) {
  case IN :
    break;
  case OUT :
    q += 4;
    break;
  case TRI :
    q += 8;
    break;
  }

  ob_touch(g);
  g->wires[q] = block_addwire(n->end,g->wires[q]);
  block_setWireEnd(g,n->end,q);
  
  switch (p) {
  case D_UP :
    wire_move(n,0,g->ypos - 1 - n->y,n->stype);
    if (!(wireorient(n,0) & 1))
      addbendnode(n);
    break;
  case D_LEFT :
    wire_move(n,g->xpos - 1 - n->x,0,n->stype);
    if (wireorient(n,0) & 1)
      addbendnode(n);
    break;
  case D_DOWN :
    wire_move(n,0,(g->ypos + g->u.block.gheight+1) - n->y,n->stype);
    if (!(wireorient(n,0) & 1))
      addbendnode(n);
    break;
  case D_RIGHT :
    wire_move(n,(1 + g->xpos + g->u.block.gwidth) - n->x,0,n->stype);
    if (wireorient(n,0) & 1)
      addbendnode(n);
    break;
  default :
    return -1;
  }

  wire_finalizeNet(n->end);

  return p;
}

GWire *block_addwire(GWire *w,GWire *wl)
{
  ob_touch(w);
  w->next = wl;
  return w;
}

/*
 * Primitive function to disconnect wire
 */
void block_deletewire(GWire *w)
{
  int n,p;
  struct celemnt *g;
  static int xdel[] = {3,0,-3,0};
  static int ydel[] = {0,-3,0,3};
  
  posongate(w,w->gate,&p,&n);
  g = w->gate;

  ob_touch(g);
  ob_touch(w);
  ob_touch(w->nodes);

  gate_draw(g,GD_NOWIRE);
  ob_free(w->name);
  w->name = NULL;
  w->gate = NULL;
  
  g->wires[p] = wire_unattach(w,g->wires[p]);
  w->nodes->y += ydel[g->typeinfo->Pad[p].Loc->dir];
  w->nodes->x += xdel[g->typeinfo->Pad[p].Loc->dir];
  
  gate_draw(g,GD_NOWIRE);
}

/*
 * Disconnect wire from block and do any necessary cleanup
 */
void block_cutoffwire(GWire *w,EditState *es)
{
  net_draw(w->net);
  block_deletewire(w);
  wire_nuke(w,0,es->env);
}


void block_newout(EditState *es)
{
  int p;
  GWire *w;
  
  if (!block_edgehit(XGate.circuit->select,XGate.ed->tx,XGate.ed->ty)) return;
  if (XGate.circuit->select && !(XGate.circuit->select->typeinfo->Code == BLOCK)) return;
  
  wire_new(es->env,0,&w);
  
  p = block_attach(es->env,XGate.circuit->select,w,w->driver,
		   XGate.ed->tx,XGate.ed->ty,NULL,OUT);
  
  if (p >= 0) {
    wire_draw(w->driver->nodes);
    DrawPinIOMark(w,p,OUT);
    if (!block_setPortName(XGate.circuit->select,w,es))
      block_cutoffwire(w,es);
  }
  
  SetModified();
}

void block_newin(EditState *es)
{
  GWire *w;
  int p;

  if (!block_edgehit(XGate.circuit->select,XGate.ed->tx,XGate.ed->ty)) return;
  if (XGate.circuit->select && !(XGate.circuit->select->typeinfo->Code == BLOCK)) return;
  
  wire_new(es->env,0,&w);
  
  p = block_attach(es->env,XGate.circuit->select,w,w->driver,XGate.ed->tx,XGate.ed->ty,NULL,IN);
  
  if (p >= 0) {
    wire_draw(w->driver->nodes);
    DrawPinIOMark(w,p,IN);
    if (!block_setPortName(XGate.circuit->select,w,es))
      block_cutoffwire(w,es);
  }
  SetModified();
}

void block_newtri(EditState *es)
{
  GWire *w;
  int p;
  
  if (!block_edgehit(XGate.circuit->select,XGate.ed->tx,XGate.ed->ty)) return;
  if (XGate.circuit->select && !(XGate.circuit->select->typeinfo->Code == BLOCK)) return;
  
  wire_new(es->env,0,&w);
  
  p = block_attach(es->env,XGate.circuit->select,w,w->driver,XGate.ed->tx,XGate.ed->ty,NULL,TRI);
  
  if (p >= 0) {
    wire_draw(w->driver->nodes);
    DrawPinIOMark(w,p,TRI);
    if (!block_setPortName(XGate.circuit->select,w,es))
      block_cutoffwire(w,es);
  }
  SetModified();
}

void block_changedir(GWireNode *n,EditState *es)
{
  GCElement *g;
  int p,x;
  
  if (!n || !n->end || !n->end->gate) return;
  
  if (n->end->gate->typeinfo->Code != BLOCK) {
    message(1,msgLookup("err.nopin"));			/* Can't change selected pin. */
    return;
  }
  g = n->end->gate;

  ob_touch(g);
  
  posongate(n->end,g,&p,&x);
  
  DrawPinIOMark(n->end,g->typeinfo->Pad[p].Loc[0].dir,
		g->typeinfo->Pad[p].Dir);
  g->wires[p] = wire_unattach(n->end,g->wires[p]);
  
  p = (p + 4) % g->typeinfo->NumPads;
  
  DrawPinIOMark(n->end,g->typeinfo->Pad[p].Loc[0].dir,
		g->typeinfo->Pad[p].Dir);
  g->wires[p] = block_addwire(n->end,g->wires[p]);
  
  SetModified();
}

int block_attach(GModuleDef *env,GCElement *g,GWire *w1,GWire *w2,
	     int x,int y,const char *name,int Dir)
{
  int p;

  ob_touch(w1);
  ob_touch(w1->nodes);
  ob_touch(w2->nodes);

  w1->nodes->x = x;
  w1->nodes->y = y;
  w1->gate = g;

  p = -1;
  switch (quadrent(g,w1->nodes)) {
  case D_UP :
    w1->nodes->y = g->ypos - 1;
    w2->nodes->y = w1->nodes->y - 10;
    w1->nodes->x = w2->nodes->x = x;
    break;
  case D_LEFT :
    w1->nodes->x = g->xpos-1;
    w2->nodes->x = w1->nodes->x - 10;
    w1->nodes->y = w2->nodes->y = y;
    break;
  case D_DOWN :
    w1->nodes->y = g->ypos + g->u.block.gheight + 1;
    w2->nodes->y = w1->nodes->y + 10;
    w1->nodes->x = w2->nodes->x = x;
    break;
  case D_RIGHT :
    w1->nodes->x = g->xpos + g->u.block.gwidth + 1;
    w2->nodes->x = w1->nodes->x + 10;
    w1->nodes->y = w2->nodes->y = y;
    break;
  default :
    return -1;
  }
  p = block_connect(g,w1->nodes,Dir);
  
  if (name && w1) w1->name = ob_strdup(name);
  
  return p;
}

int block_edgehit(GCElement *g,int x,int y)
{
  int bx,by,bw,bh;
  int e1,e2,e3,e4;

  bx = g->xpos;
  by = g->ypos;
  bw = g->u.block.gwidth;
  bh = g->u.block.gheight;

/*
Bogonic rt compiler can't handle this as a single expression (or even 2 expressions).
*/
  e1 = on_edge(x,y,bx+2,by,bx+bw-2,by);
  e2 = on_edge(x,y,bx,by+2,bx,by+bh-2);
  e3 = on_edge(x,y,bx+bw,by+2,bx+bw,by+bh-2);
  e4 = on_edge(x,y,bx+2,by+bh,bx+bw-2,by+bh);
  return e1 || e2 || e3 || e4;
}

void block_canonicalizewire(GCElement *g,GWire *w)
{
  struct wirenode *n;

  n = w->nodes->out ? w->nodes->out : w->nodes->in;

  ob_touch(n);
  ob_touch(w->nodes);

  switch (w->orient) {
  case 0 :
    w->nodes->x = g->xpos + g->u.block.gwidth + 1;
    n->x = w->nodes->x + 10;
    n->y = w->nodes->y;
    break;
  case 1 :
    w->nodes->y = g->ypos - 1;
    n->y = w->nodes->y - 10;
    n->x = w->nodes->x;
    break;
  case 2 :
    w->nodes->x = g->xpos - 1;
    n->x = w->nodes->x - 10;
    n->y = w->nodes->y;
    break;
  case 3 :
    w->nodes->y = g->ypos + g->u.block.gheight + 1;
    n->y = w->nodes->y + 10;
    n->x = w->nodes->x;
    break;
  }
}

char *block_reportname(GCElement *g)
{
  static char B[STRMAX];

  if (g->ename)
    sprintf(B,"block %s",g->ename);
  else
    sprintf(B,"unnamed %s block",g->u.block.BlockFunction);
  return B;
}

void init_block()
{
  Pixmap P;
  int id;

  P = Pixmap_registerFromFile("iodarrow","iodarrow.b");

  id = Icon_register(P,14,0,7,5,3,2);	/* <> arrow */
  iodmark[0][0] = iodmark[0][1] = iodmark[2][0] = iodmark[2][1] = id;

  id = Icon_register(P,14,6,5,7,2,3);	/* v^ arrow */
  iodmark[1][0] = iodmark[1][1] = iodmark[3][0] = iodmark[3][1] = id;

  id = Icon_register(P,0,0,7,5,2,3);   /* < arrow */
  iodmark[0][2] = iodmark[2][3] = id;

  id = Icon_register(P,6,8,7,5,3,2);   /* > arrow */
  iodmark[0][3] = iodmark[2][2] = id;

  id = Icon_register(P,8,0,5,7,2,3);   /* v arrow */
  iodmark[1][2] = iodmark[3][3] = id;

  id = Icon_register(P,0,6,5,7,2,3);   /* ^ arrow */
  iodmark[1][3] = iodmark[3][2] = id;

  RegisterGate(&gate_block_info);
}

void Block_SimHitFunc(EditState *es,GCElement *g)
{
  selectGate(es,XGate.ed->tx,XGate.ed->ty);
}
