/****************************************************************************
    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.
****************************************************************************/
/*
    One of four main modules pertaining to wires.  COntaines functions for moving
    and drawing wires.  Basic routines to maintain horizontal and vertical wires
    being moved are defined here.


*/
#include <stdlib.h>
#include <stdio.h>
#include "tkgate.h"

char ArrowIcons[] = {'m','}','M',']'}; 

#define gatenode(_n) ((_n)->end && (_n)->end->gate)

extern int floatjoints;
extern int expertuser;

/*#define DEBUG*/

#ifdef DEBUG
#define debugprint(m) printf(m)
#else
#define debugprint(m)
#endif

GWire *wire_cut_y_in(),*wire_cut_x_in(),*wire_cut_y_out(),*wire_cut_x_out();


/*
  Fixes bogosities in wires.  This shouldn't be necessary, but there are
  still some bugs that create bogus (diagonal) wires.
*/
void wire_repair(GWireNode *n)
{
  int o;

  if (!n->end) {
    logError(ERL_WARN,"Repairaing non-wireend in wire_repair.");
    return;
  }

  for (o = n->end->orient % 2;n->out;n = n->out, o = !o) {
    if ((n->x != n->out->x) && (n->y != n->out->y)) {
      if (n->out->end) {
	if (!n->in) {
	  logError(ERL_ERROR,"Bogonic wire in wire_repair.");
	  break;
	}
	if ((n->out->end->orient % 2) != o)  {
	  GWireNode *nn;

	  logError(ERL_ERROR,"Inserting node in wire_repair.");
	  nn = (GWireNode *) ob_malloc(sizeof(GWireNode),"GWireNode");

	  ob_touch(nn);
	  nn->out = n->out;
	  nn->in = n;
	  ob_touch(nn->out);
	  nn->out->in = nn;
	  ob_touch(n);
	  n->out = nn;
	  nn->end = NULL;
	  nn->y = nn->out->y;
	  nn->x = nn->out->x;
	} else
	  ob_touch(n);
	  if (o) {
	    logError(ERL_ERROR,"Fixing case 3 in wire_repair.");
	    n->x = n->out->x;
	  } else {
	    logError(ERL_ERROR,"Fixing case 4 in wire_repair.");
	    n->y = n->out->y;
	  }
      } else
	ob_touch(n->out);
	if (o) {
	  logError(ERL_ERROR,"Fixing case 1 in wire_repair.");
	  n->out->x = n->x;
	} else {
	  logError(ERL_ERROR,"Fixing case 2 in wire_repair.");
	  n->out->y = n->y;
	}
    }
  }
}

/*
   Get the position (x,y) and justification p for a signal name
   on wire w.
*/
void SignalNamePos(GWire *w,int *x,int *y,int *p,int *q)
{
  struct wirenode *n,*n2;

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

  *x = n->x + (n2->x - n->x)/3;
  *y = n->y + (n2->y - n->y)/3;

  if (n->x == n2->x) {
    *x += 5;
    if (p) *p = AtLeft|BetweenTopAndBottom;
  } else {
    *x -= 5;
    if (p) *p = BetweenLeftAndRight|AtBottom;
  }


  if (q) *q = (n->x == n2->x) ? (n->y <= n2->y) : 2 + (n->x <= n2->x);
}

void wire_drawlabel(int x,int y,int a,int nbits,char *s)
{
  if (s) {
    if (nbits > 1)
      dce_DrawString(XGate.busGC,x,y,a,s);
    else
      dce_DrawString(XGate.wireGC,x,y,a,s);
  }
}


/*
 * Draw the size of a wire.  It is assumed that (x1,y1) is the end nearest the gate,
 * and (x2,y2) is the next segment out.
 */
void wire_displaysize(int x1,int y1,int x2,int y2,int s)
{
  int x,y;
  char ns[30];
  int dx,dy;

  dx = (x2 - x1);
  dy = (y2 - y1);

  if (dx*dx+dy*dy < (15*15))
    return;

  x = x1 + 2*dx/3;
  y = y1 + 2*dy/3;

  sprintf(ns,"%d",s);

  if (x1 == x2) {
    Icon_draw(XGate.D,XGate.W,XGate.busGC,ctow_x(x+TKGATE_BUSW_VERT-1),ctow_y(y),SIZEHASH);
    dce_DrawString(XGate.busGC,x+8+TKGATE_BUSW_VERT,y,AtLeft|BetweenTopAndBottom,ns);
  } else {
    Icon_draw(XGate.D,XGate.W,XGate.busGC,ctow_x(x),ctow_y(y+TKGATE_BUSW_HORZ-1),SIZEHASH);
    dce_DrawString(XGate.busGC,x,y-3+TKGATE_BUSW_HORZ,BetweenLeftAndRight|AtBottom,ns);
  }
}

/*
    Draw end of wire
*/
void wire_drawend(GWireNode *n,GWireNode *n2,int s,short *x,short *y)
{
  static int xval[] = {0,0,-1,1};
  static int yval[] = {-1,1,0,0};
  int d,inv,wx,wy,wp;

  d = (n->x == n2->x) ? (n->y <= n2->y) : 2 + (n->x <= n2->x);
  inv = n->end->invert != 0;

  if (inv)
    Icon_draw(XGate.D,XGate.W,XGate.instGC,ctow_x(n->x + 2*xval[d]),ctow_y(n->y + 2*yval[d]),INVERTER);

  if (n->end && n->end->gate && n->end->gate->cpath_cut) {
    int x1,y1,x2,y2;
    int r;

    x1 = x2 = ctow_x(n->x + xval[d]*5);
    y1 = y2 = ctow_y(n->y + yval[d]*5);

    r = (d + 2) % 4;

    x1 += xval[r]*5;
    x2 -= xval[r]*5;
    y1 += yval[r]*5;
    y2 -= yval[r]*5;

    ZDrawLine(XGate.D, XGate.W, XGate.instGC, x1,y1, x2,y2);
  }

  /*
    Draw decoration at designated segment end.
   */
  if (n->end->nidx == n->end->net->decoration) {
    GNet *net = n->end->net;

    if (!net->ionet && net->show_name && net->signame) {
      SignalNamePos(n->end,&wx,&wy,&wp,0);
      wire_drawlabel(wx,wy,wp,net->nbits,net->signame);
    }

    if (n->end->net->nbits > 1)
      wire_displaysize(n->x,n->y,n2->x,n2->y,n->end->net->nbits);
  }

  *x += 5*xval[d]*inv;
  *y += 5*yval[d]*inv;
}

/*
  Tweek end point p (where q is the adjacent point) to compensate for
  X server differences.  It is assumed that p and q are non-heap
  objects and do not require being saved with ob_touch()
 */
static void tweekPoint(XPoint *p,XPoint *q)
{
  if (p->x == q->x) {
    if (p->y > q->y) {
      p->y += TKGATE_WIRETWEEK_BOTTOM;
    } else if (p->y < q->y) {
      p->y += TKGATE_WIRETWEEK_TOP;
    }
  } else if (p->y == q->y) {
    if (p->x > q->x) {
      p->x += TKGATE_WIRETWEEK_RIGHT;
    } else if (p->x < q->x) {
      p->x += TKGATE_WIRETWEEK_LEFT;
    }
  }
}

void wire_draw(GWireNode *n)
{
  GWire *w1,*w2;
  XPoint p[1024];
  int k;

  w1 = wirenode_driver(n);
  w2 = wire_other(w1);

  for (k = 0, n = w1->nodes;n;k++, n = n->out) {
    p[k].x = ctow_x(n->x);
    p[k].y = ctow_y(n->y);
  }

  wire_drawend(w1->nodes,w1->nodes->out,0,&p[0].x,&p[0].y);
  wire_drawend(w2->nodes,w2->nodes->in,0,&p[k-1].x,&p[k-1].y);

  if (w1->net->nbits > 1) {
    GC gc = (w1->net == XGate.circuit->nsel) ? XGate.selBusGC : XGate.busGC;

    tweekPoint(&p[0],&p[1]);
    tweekPoint(&p[k-1],&p[k-2]);

    ZDrawLines(XGate.D,XGate.W,gc,p,k,CoordModeOrigin);
  } else {
    GC gc = (w1->net == XGate.circuit->nsel) ? XGate.selWireGC : XGate.wireGC;

    if (w1->net == XGate.circuit->nsel) {
      tweekPoint(&p[0],&p[1]);
      tweekPoint(&p[k-1],&p[k-2]);
    }
    ZDrawLines(XGate.D,XGate.W,gc,p,k,CoordModeOrigin);
  }

  if (w1->cpath && XGate.cpath_flashState) {
    int i;

    for (i = 1;i < k;i++)
      cpath_draw(p[i-1].x,p[i-1].y,p[i].x,p[i].y);
  }
}

/*
  
*/
void wires_moveothersonjoint(GCElement *g,GWire *w,int dx,int dy)
{
  int i;
  
  mk_gate(g->xpos,g->ypos,g->typeinfo,0,0);
  ob_touch(g);
  g->xpos += dx;
  g->ypos += dy;
  mk_gate(g->xpos,g->ypos,g->typeinfo,0,0);
  for (i = 0;i < 4;i++)
    if (g->wires[i] && (g->wires[i] != w)) {
      wire_draw(g->wires[i]->driver->nodes);

      /*
       * The following three lines are a bogus hack.  I take no responsabilty.
       * They are the result of temporary insanity.
       */
      g->wires[i]->gate = NULL;
      wire_move(g->wires[i]->nodes,dx,dy,FULL);
      g->wires[i]->gate = g;

      wire_draw(g->wires[i]->driver->nodes);
    }
}


/* 
 * Straighten out nodes
 */
int wire_netstrait(GWireNode *n,int wt,int ox,int oy,int nx,int ny)
{
  int x,y;
  GWireNode *next;
  
  if (!n) return OK;
  
  next = (wt == DRIVER ? n->out : n->in);
  x = n->x;
  y = n->y;
  if (gatenode(n)) {
    if ((ox == nx) && (oy == ny)) return OK;
    if (next && (ox == nx) && next->y == n->y) return OK;
    if (next && (oy == ny) && next->x == n->x) return OK;

    return IMMOBILE;
  }

  ob_touch(n);
  
  switch (wire_netstrait(next,wt,x,y,x+nx-ox,y+ny-oy)) {
  case OK :
    n->x += nx - ox;
    n->y += ny - oy;
    if ((nx != ox) || (ny != oy)) n->mark = 1;
    return OK;
  case IMMOBILE :
    if ((next->end->orient % 2) == 0) {
      n->y = next->y;
      n->x += nx - ox;
      if (nx - ox) n->mark = 1;
    } else {
      n->x = next->x;
      n->y += ny - oy;
      if (ny - oy) n->mark = 1;
    }
    return OK;
  }
  logError(ERL_FATAL,"Unknown case in wire_newstrait.");
  return IMMOBILE;
}

/*
  Splits a node if we can't find a way around it
 */
GWireNode *wire_splitnode(GWireNode *n,int dir)
{
  GWireNode *newnode;

  newnode = (GWireNode *) ob_malloc(sizeof(GWireNode),"GWireNode");

  ob_touch(newnode);
  newnode->mark = 1;
  switch (dir) {
  case OUT_DIR :
    newnode->out = n->out;
    newnode->in = n;
    newnode->end = NULL;
    if (n->out) {
      ob_touch(n->out);
      n->out->in = newnode;
      newnode->x = (n->x + n->out->x) / 2;
      newnode->y = (n->y + n->out->y) / 2;
    } else {
      newnode->x = n->x;
      newnode->y = n->y;
    }
    ob_touch(n);
    n->out = newnode;
    break;
  case IN_DIR :
    newnode->in = n->in;
    newnode->out = n;
    newnode->end = NULL;
    if (n->in) {
      ob_touch(n->in);
      n->in->out = newnode;
      newnode->x = (n->x + n->in->x) / 2;
      newnode->y = (n->y + n->in->y) / 2;
    } else {
      newnode->x = n->x;
      newnode->y = n->y;
    }
    ob_touch(n);
    n->in = newnode;
    break;
  }
  return newnode;
}

/* Auxilliary function for makestrait */
void wire_makestraitaux(GWireNode *n,int wt,int ox,int oy,int nx,int ny)
{
  GWireNode *n1,*n2;
  
  if (!n) return;
  
  if (wire_netstrait(n,wt,ox,oy,nx,ny) == IMMOBILE) {
    if (wt == DRIVEE && (n->out->out || gatenode(n->out))) {
      n1 = wire_splitnode(n,OUT_DIR);
      n2 = wire_splitnode(n1,OUT_DIR);
      ob_touch(n1);
      ob_touch(n2);
      n2->x = n1->x = (n->x + ox) / 2;
      n2->y = n1->y = (n->y + oy) / 2;
    } else if (wt != DRIVEE && (n->in->in || gatenode(n->in))) {
      n1 = wire_splitnode(n,IN_DIR);
      n2 = wire_splitnode(n1,IN_DIR);
      ob_touch(n1);
      ob_touch(n2);
      n2->x = n1->x = (n->x + ox) / 2;
      n2->y = n1->y = (n->y + oy) / 2;
    } else {
      n2 = wire_splitnode(n,wt == DRIVEE ? OUT_DIR : IN_DIR);
      ob_touch(n2);
      if (n->x == ox)
	n2->y = oy;
      else
	n2->x = ox;
    }
    if (wire_netstrait(n2,wt,ox,oy,nx,ny) == IMMOBILE)
      fprintf(stderr,"Huh? split node didn't help\n");
  }
}

/* Takes a moved node and it's old position to figure out how to move the rest of the
   network to keep the rep. invarient */
void wire_makestrait(GWireNode *n,int ox,int oy)
{
  if (n->stype == FULL) {
    wire_makestraitaux(n->out,DRIVER,ox,oy,n->x,n->y);
    wire_makestraitaux(n->in,DRIVEE,ox,oy,n->x,n->y);
  } else {
    if (n->stype & HORIZONTAL) {
      ob_touch(n->out);
      n->out->x += n->x - ox;
      if (n->x - ox) {
	ob_touch(n);
	n->mark = 1;
      }
    }
    if (n->stype & VERTICAL) {
      ob_touch(n->out);
      n->out->y += n->y - oy;
      if (n->y - oy) {
	ob_touch(n);
	n->mark = 1;
      }
    }
  }
}

/* Moves a wire a certian amount, in a certain mode. */
void wire_move(GWireNode *n,int dx,int dy,int type)
{
  int x,y;

  ob_touch(n);
  n->stype = type;
  x = n->x;
  y = n->y;
 
  if ((type & VERTICAL)) n->y += dy;
  if ((type & HORIZONTAL)) n->x += dx;

  if (n->x == x && n->y == y) return;	/* Didn't move */
  if ((type & NOSTRAITEN)) return;

  wire_makestrait(n,x,y);
}


GWireNode *wire_hitanynode(int x,int y,GWireList *wires)
{
  struct wirenode *N,*Close;
  int CloseD,D;
  
  if (!wires) return NULL;
  
  CloseD = distance(x,y,wires->car->nodes->x,wires->car->nodes->y);
  Close = wires->car->nodes;
  
  for (;wires;wires = wires->cdr)
    if (wires->car->nodes->out)
      for (N = wires->car->nodes;N;N = N->out) {
	D = distance(x,y,N->x,N->y);
	if (D < CloseD) {
	  CloseD = D;
	  Close = N;
	}
      }
  
  if (CloseD < MAXWIRERANGE)
    return Close;
  else
    return NULL;
}

/* Test an individual wire's node list */
GWireNode *wire_nodehit(int x,int y,GWireNode *n,int includeend,int range,int corneronly)
{
  int testrange;
  GWireNode *test;
  
  if (!n)
    return NULL;
  
  if (includeend || !anchoredp(n))
    if ((testrange = distance(x,y,n->x,n->y))
	< (corneronly ? range : MAXWIRERANGE)) {
      if ((test = wire_nodehit(x,y,n->out,includeend,testrange,1)))
	return test;
      else {
	ob_touch(n);
	n->stype = FULL;
	return n;
      }
    }
  
  if ((n->out ? includeend || (!anchoredp(n) && !anchoredp(n->out)) : 0) &&
      !corneronly) {
    if ((n->y == n->out->y) &&
	(midpointp(x,n->x,n->out->x) && ((testrange = sqr(y - n->y)) < range))) {
      
      
      if ((test = wire_nodehit(x,y,n->out,includeend,testrange,0)))
	return test;
      else {
	ob_touch(n);
	n->stype = VERTICAL;
	return n;
      }
    }
    if ((n->x == n->out->x) &&
	(midpointp(y,n->y,n->out->y) && ((testrange = sqr(x - n->x)) < range))) {
      
      if ((test = wire_nodehit(x,y,n->out,includeend,testrange,0)))
	return test;
      else {
	ob_touch(n);
	n->stype = HORIZONTAL;
	return n;
      }
    }
  }
  return wire_nodehit(x,y,n->out,includeend,range,corneronly);
}

GWireNode *wire_hitall(int x,int y,GWireList *wires)
{
  GWireNode *test;

  if (!wires)
    return NULL;

  if ((wires->car->nodes->out ? (test = 
      wire_nodehit(x,y,wires->car->nodes,1,MAXWIRERANGE,0)):NULL))
    return test;
  else 
    return wire_hitall(x,y,wires->cdr);
}


/* Test to see if theres a wire here */
GWireNode *wire_hit(int x,int y,GWireList *wires)
{
  GWireNode *test;
  
  if (!wires)
    return NULL;
  
  if ((wires->car->nodes->out ? (test = 
      wire_nodehit(x,y,wires->car->nodes,0,MAXWIRERANGE,0)):NULL))
    return test;
  else 
    return wire_hit(x,y,wires->cdr);
}

/* Search for a wire other than 'w' */
GWireNode *wire_hit_other(GWire *w,GWireList *wires)
{
 GWireNode *test;

 if ((!wires) || (!w))
   return NULL;
 
 if ((wires->car->nodes->out && (wires->car->driver != w->driver)) ?
     (test = wire_nodehit(w->nodes->x,w->nodes->y,wires->car->nodes,
			 1,MAXWIRERANGE,0)):NULL)
   return test;
 else 
   return wire_hit_other(w,wires->cdr);
}

struct wirenode *wire_iohit(int x,int y,GWireList *wires)
{
  struct wirenode *test;
  
  if (!wires)
    return NULL;
  
#ifdef DEBUGHIT
  printf("Wire is: %d\n",wires->car->wtype);
#endif
  /* Range was 5*5+1 */
  if ((test = wire_nodehit(x,y,wires->car->nodes,1,MAXWIRERANGE,0)))
    /* Danger */
    return test;
  else 
    return wire_iohit(x,y,wires->cdr);
}

/* Test only at endpoints of a wire. */
GWire *wire_endhit(GWire *w,GWireList *wl)
{
  if (!wl)
    return NULL;
  if ((abs(wl->car->nodes->x - w->nodes->x) < 5) &&
      (abs(wl->car->nodes->y - w->nodes->y) < 5) &&
      (wl->car != w) && !anchoredp(wl->car->nodes))
    return wl->car;
  else
    return wire_endhit(w,wl->cdr);
}

GCElement *wire_drivinggate(GWire *w)
{
  if (!w)
    return NULL;
  else
    return w->driver->gate;
}

void wire_drawlist(GWire *w,GCElement *g)
{
  GCElement *dg;

  for (;w;w = w->next) {
    dg = wire_drivinggate(w);
    if ((!g) || (!dg) || g == dg)
      wire_draw(w->driver->nodes);
  }
}

/* Returns the driver of a wire network */
GWire *wirenode_driver(GWireNode *n)
{
  if (!n) 
    return NULL;
  if (!n->in)
    return  n->end;
  else
    return wirenode_driver(n->in);
}

/* Returns the drivee of a wire network */
GWire *wirenode_drivee(GWireNode *n)
{
  if (!n) 
    return NULL;
  if (!n->out) {
    return n->end;
  } else
    return wirenode_drivee(n->out);
}

GWire *wire_driver(GWire *w)
{
  return wirenode_driver(w->nodes);
}

GWire *wire_drivee(GWire *w)
{
  return wirenode_drivee(w->nodes);
}


GWire *wire_other(GWire *w)
{
  if (w->driver != w)
    return w->driver;
  else
    return wire_drivee(w);
}
