/****************************************************************************
    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.
****************************************************************************/
/*
  Functions for creating wires and adding wire to circuit elements are defined here.
      */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "tkgate.h"

/*#define DEBUG*/

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

struct wirenode *wire_nodehit();
struct wirenode *wire_splitnode();
struct wire *wire_endhit();
struct wire *wire_cut_y_in(),*wire_cut_x_in(),*wire_cut_y_out(),*wire_cut_x_out();

/* Counts the number of wires on a terminal */
int wire_count(GWire *w)
{
  if (!w)
    return 0;
  else
    return 1 + wire_count(w->next);
}


GWireList *wire_link(GWireList *wl,GWire *w)
{
  GWireList *new_wl;

  new_wl = (GWireList *) ob_malloc(sizeof(GWireList),"GWireList");
  ob_touch(new_wl);
  new_wl->cdr = wl;
  new_wl->car = w;
  return new_wl;
}

/* Unlinks a wire from the wire list */
GWireList *wire_unlink(GWireList *wl,GWire *w)
{
  GWireList *rest;

  if (wl == NULL)
    return NULL;

  if (wl->car != w) {
    ob_touch(wl);
    wl->cdr = wire_unlink(wl->cdr,w);
    return wl;
  } else {
    rest = wl->cdr;
    ob_free(wl);

    return rest;
  }
}

/* Creates a new endpoint of a wire */
GWire *wire_newend(GModuleDef *M,GNet *net,int doNodes)
{
  GWire *w;

  w = (GWire *) ob_malloc(sizeof(GWire),"GWire");
  ob_touch(w);

  w->net = 0;
  w->name = NULL;
  w->xanchor = 0;
  w->yanchor = 0;
  w->orient = 0; 
  w->wtype = 0;
  w->PadNum = 0;
  w->WireDir = 0;
  w->invert = 0;
  w->gate = NULL;
  w->nodes = NULL;
  w->driver = NULL;
  w->next = NULL;
  w->nidx = 0;
  w->cpath = 0;
  w->offset.num = w->offset.den = 0;

  if (net)
    wire_setNet(w,net);

  if (doNodes) {
    w->nodes = wire_newnode();
    ob_touch(w->nodes);
    w->nodes->end = w;
  }

  if (M) {
    ob_touch(M);
    M->wires = wire_link(M->wires,w);
  }

  return w;
}

/* Creates a new wire node (Should be in nodes) */
GWireNode *wire_newnode()
{
  GWireNode *n;

  n = (GWireNode *) ob_malloc(sizeof(GWireNode),"GWireNode");
  ob_touch(n);

  n->out = NULL;
  n->in = NULL;
  n->end = NULL;
  n->stype = UNSELECTED;
  return n;
}

/*
   Set/reset the net of a wire.  Update reference counts,
   and free net if this is the last reference to the old
   net of w.
*/
void wire_setNet(GWire *w,GNet *net)
{
  ob_touch(w);				/* JPH 121603 */
  if (net) {
    ob_touch(net);			/* JPH 121603 */
    net->refs++;
  }
  if (w->net) {
    ob_touch(w->net);			/* JPH 121603 */
    if (--w->net->refs <= 0)
      net_delete(w->net);
  }
  w->net = net;
}

/*
  Returns a new wire segment on an existing net.  The endpoints are returned
  in e1 and e2.
*/
int wire_newNetSegment(GModuleDef *mdef,GNet *net,GWire **e1,GWire **e2)
{
  GWire *end1,*end2;			/* end1 is output end, end2 is input end */

  end1 = wire_newend(mdef,net,1);
  end2 = wire_newend(mdef,net,1);

  ob_touch(end1);
  ob_touch(end2);
  ob_touch(end1->nodes);
  ob_touch(end2->nodes);
  end1->nodes->out = end2->nodes;
  end2->nodes->in = end1->nodes;

  end1->driver = end1;
  end1->wtype = DRIVER;
  end2->driver = end1;
  end2->wtype = DRIVEE;

  if (e1) *e1 = end1;
  if (e2) *e2 = end2;

  return 0;
}

/*
  Returns a new wire segment on a new net.  The endpoints are returned
  in e1 and e2.
*/
int wire_new(GModuleDef *mdef,GWire **e1,GWire **e2)
{
  GNet *net;

  net = net_new(0,mdef);		/* Create a whole new net */
  wire_newNetSegment(mdef,net,e1,e2);

  return 0;
}

void wire_free(GWire *w)
{
  if (w->net) net_decref(w->net);
  ob_free(w);
}

void wirenode_free(GWireNode *n)
{
  ob_free(n);
}


/*
   Redistributes wires on a multi-input or -ouput terminal when new ones are added
*/
#if 0
void wire_setPadSpacing(GCElement *g,int p)
{
  int n,i,dy,dx,ox,oy;
  GWire *q;
  GWire *w = g->wires[p];
  int dir = g->typeinfo->Pad[p].Dir;
  int x1 = g->xpos + g->typeinfo->Pad[p].Loc[g->orient].x1;
  int y1 = g->ypos + g->typeinfo->Pad[p].Loc[g->orient].y1;
  int x2 = g->xpos + g->typeinfo->Pad[p].Loc[g->orient].x2;
  int y2 = g->ypos + g->typeinfo->Pad[p].Loc[g->orient].y2;

  n = wire_count(w) + 1;
  dx = (1000*(x2 - x1)) / n;
  dy = (1000*(y2 - y1)) / n;
  i = 1;
  for (q = w;q;q = q->next) {
    /*   if (p != w)
	 wire_draw(p->driver->nodes); */
    ox = q->nodes->x;
    oy = q->nodes->y;
    ob_touch(q->nodes);
    q->nodes->x = x1 + (dx * i)/1000;
    q->nodes->y = y1 + (dy * i)/1000;
    i++;
    if (dir != IN)
      wire_makestraitaux(q->nodes->out,q->wtype,ox,oy,q->nodes->x,q->nodes->y);
    else
      wire_makestraitaux(q->nodes->in,q->wtype,ox,oy,q->nodes->x,q->nodes->y);
    /* wire_draw(p->driver->nodes); */
  }
}
#endif

void wire_setPadSpacing(GCElement *g,int p)
{
  int n,i,dy,dx;
  GWire *q;
  GWire *w = g->wires[p];
  int x1 = g->xpos + g->typeinfo->Pad[p].Loc[g->orient].x1;
  int y1 = g->ypos + g->typeinfo->Pad[p].Loc[g->orient].y1;
  int x2 = g->xpos + g->typeinfo->Pad[p].Loc[g->orient].x2;
  int y2 = g->ypos + g->typeinfo->Pad[p].Loc[g->orient].y2;

  n = wire_count(w) + 1;
  dx = (1000*(x2 - x1)) / n;
  dy = (1000*(y2 - y1)) / n;
  for (i = 1, q = w;q;i++, q = q->next) {
    int x = x1 + (dx * i)/1000;
    int y = y1 + (dy * i)/1000;
    wire_move(q->nodes,x-q->nodes->x,y-q->nodes->y,FULL);
  }
}

/*
Places a wires in the appropriate positions relative to the gates's position.
*/
void wire_placement(int p,GCElement *g,GWire *w,int inout)
{
  ob_touch(w);
  w->orient = g->typeinfo->Pad[p].Loc[g->orient].dir;
  if (inout == IN) {
    ob_touch(w->nodes->in);
    w->nodes->in->x = w->nodes->x;
    w->nodes->in->y = w->nodes->y;
    switch (w->orient) { /* Danger */
    case D_RIGHT :
      w->nodes->in->x += 10;
      break;
    case D_UP :
      w->nodes->in->y -= 10;
      break;
    case D_LEFT :
      w->nodes->in->x -= 10;
      break;
    case D_DOWN :
      w->nodes->in->y += 10;
      break;
    }
  } else {
    ob_touch(w->nodes->out);
    w->nodes->out->x = w->nodes->x;
    w->nodes->out->y = w->nodes->y;
    switch (w->orient) {
    case D_RIGHT :
      w->nodes->out->x += 10;
      break;
    case D_UP :
      w->nodes->out->y -= 10;
      break;
    case D_LEFT :
      w->nodes->out->x -= 10;
      break;
    case D_DOWN :
      w->nodes->out->y += 10;
      break;
    }
  }
#ifdef DEBUGADD
  printf("end place\n");
#endif
}

/*
   Append wire to end of list
*/
GWire *wire_append(GWire *wl,GWire *w)
{
  GWire *l;

  if (!wl) return w;

  for (l = wl;l->next;l = l->next);
  ob_touch(l);
  l->next = w;

  return wl;
}


/* Does the actual add for wire_add */
void wire_gateadd(GCElement *g,GModuleDef *mdef,GWire *e1,GWire *e2,int p,int dir,int invertp)
{
  GWire *w;

#ifdef DEBUGADD
  printf("wire_gate_add(%p)\n",p);
#endif
  net_setSize(e1->net,g->typeinfo->Pad[p].Size);
  net_setSize(e2->net,g->typeinfo->Pad[p].Size);

  switch (dir) {
  case IN :
    w = e2;
    ob_touch(w);
    w->gate = g;
    ob_touch(g);
    g->wires[p] = wire_append(g->wires[p],w);
    wire_placement(p,g,w,IN);
    wire_setPadSpacing(g,p);
    break;
  case TRI :
  case OUT :
    w = e1;
    ob_touch(w);
    w->gate = g;
    w->invert = invertp;
    ob_touch(g);
    g->wires[p] = wire_append(g->wires[p],w);
    wire_placement(p,g,w,OUT);
    wire_setPadSpacing(g,p);
    break;
  default :
    printf("UNKNOWN pin type\n");
    abort();
    break;
  }
}

/* Adds a wire to a gate */
int wire_addToGate(GCElement *g,int p,char *n,GModuleDef *mdef,int invertp)
{
  GWire *e1,*e2;

  wire_new(mdef,&e1,&e2);
  wire_gateadd(g,mdef,e1,e2,p,g->typeinfo->Pad[p].Dir,invertp);
  wire_finalizeNet(e1);

  return 0;
}

GWire *wire_addend(GWire *wl,GWire *w)
{
  GWire *twl;

  if (!wl) {
    ob_touch(w);
    w->next = NULL;
    return w;
  }
  
  for (twl = wl;wl->next;wl = wl->next);
  ob_touch(wl);
  ob_touch(w);
  wl->next = w;
  w->next = NULL;
  return twl;
}

GWire *wire_blockadd(GCElement *g,char *n,int p,int pn,GModuleDef *env,int ws)
{
  GWire *w,*e1,*e2;
  
  wire_new(env,&e1,&e2);
  net_setSize(e1->net,ws);
  net_setSize(e2->net,ws);
  w = (g->typeinfo->Pad[p].Dir == OUT) ? e1 : e2;

  ob_touch(w);
  w->gate = g;
  ob_touch(g);
  g->wires[p] = wire_addend(g->wires[p],w);
  w->name = ob_strdup(n);
  w->orient = g->typeinfo->Pad[p].Loc->dir;

  ob_touch(w->nodes);

  switch (p) {
  case BLOCK_TIN :
    ob_touch(w->nodes->in);
    w->nodes->y = g->ypos - 1;
    w->nodes->in->y = g->ypos - 11;
    break;
  case BLOCK_TOUT :
    ob_touch(w->nodes->out);
    w->nodes->y = g->ypos - 1;
    w->nodes->out->y = g->ypos - 11;
    break;
  case BLOCK_BIN :
    ob_touch(w->nodes->in);
    w->nodes->y = g->ypos + g->u.block.gheight + 1;
    w->nodes->in->y = g->ypos + g->u.block.gheight + 11;
    break;
  case BLOCK_BOUT :
    ob_touch(w->nodes->out);
    w->nodes->y = g->ypos + g->u.block.gheight + 1;
    w->nodes->out->y = g->ypos + g->u.block.gheight + 11;
    break;
  case BLOCK_LIN :
    ob_touch(w->nodes->in);
    w->nodes->x = g->xpos - 1;
    w->nodes->in->x = g->xpos - 11;
    break;
  case BLOCK_LOUT :
    ob_touch(w->nodes->out);
    w->nodes->x = g->xpos - 1;
    w->nodes->out->x = g->xpos - 11;
    break;
  case BLOCK_RIN :
    ob_touch(w->nodes->in);
    w->nodes->x = g->xpos + g->u.block.gwidth + 1;
    w->nodes->in->x = g->xpos +g->u.block.gwidth + 11;
    break;
  case BLOCK_ROUT :
    ob_touch(w->nodes->out);
    w->nodes->x = g->xpos + g->u.block.gwidth + 1;
    w->nodes->out->x = g->xpos + g->u.block.gwidth + 11;
    break;
  }
  return w;
}

/* Creates initial wires */
void createwire(GModuleDef *e,GCElement *g,int p,int invertp)
{
  wire_addToGate(g,p,NULL,e,invertp);
}

