/****************************************************************************
    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:27:35 2004
****************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include "tkgate.h"

GModuleDef *new_GModuleDef(const char *name,int isTop)
{
  GModuleDef *M;

  M = (GModuleDef*) ob_malloc(sizeof(GModuleDef),"GModuleDef");
  M->Name = ob_strdup(name);
  M->is_top = isTop;
  M->is_lib = 0;
  M->prot = 0;
  M->used = 0;
  M->nokill = 0;
  M->wires = NULL;
  M->blockdescript = 0;
  M->gates = new_SHash();
  M->nets = new_SHash();

  return M;
}

void delete_GModuleDef(GModuleDef *M)
{
  env_clear(M);
  ob_free(M->Name);
  delete_SHash(M->gates);
  delete_SHash(M->nets);
  ob_free(M);
}

GNet *GModuleDef_findNet(GModuleDef *M,const char *name)
{
  return (GNet*) SHash_find(M->nets,name);
}

GCElement *GModuleDef_findGate(GModuleDef *M,const char *name)
{
  return (GCElement*) SHash_find(M->gates,name);
}

static GWireNode *wirenode_replicate(GWireNode *n,int dx,int dy)
{
  GWireNode *nn;

  if (!n) return 0;
  nn = wire_newnode();

  ob_touch(nn);

  nn->x = n->x + dx;
  nn->y = n->y + dy;

  nn->out = wirenode_replicate(n->out,dx,dy);
  if (nn->out) nn->out->in = nn;

  return nn;
}

static GWireNode *replicate_wirenodes(GWireNode *sn,int dx,int dy)
{
  GWireNode *cn;

  if (!sn) return 0;

  cn = wire_newnode();
  ob_touch(cn);
  cn->x = sn->x + dx;
  cn->y = sn->y + dy;
  cn->out = replicate_wirenodes(sn->out,dx,dy);
  if (cn->out) {
    ob_touch(cn->out);
    cn->out->in = cn;
  }

  return cn;
}

static void copyWireProps(GWire *nw,GWire *w)
{
  ob_touch(nw);
  nw->orient = w->orient;
  nw->nidx = w->nidx;
  nw->xanchor = w->xanchor;
  nw->yanchor = w->yanchor;
}

static GWire *replicate_wire(GWire *w,GModuleDef *M,int dx,int dy)
{
  GWire *nw,*nw2,*ow;
  GWireNode *n;

  ow = wire_other(w);

  nw = wire_newend(M,0,0);
  nw2 = wire_newend(M,0,0);

  ob_touch(nw);
  ob_touch(nw2);

  n = replicate_wirenodes(w->driver->nodes,dx,dy);

  nw->driver = nw;
  nw2->driver = nw;
  nw->wtype = DRIVER;
  nw2->wtype = DRIVEE;

  nw->nodes = n;
  while (n->out) n = n->out;
  nw2->nodes = n;

  nw->nodes->end = nw;
  nw2->nodes->end = nw2;

  if (w->driver != w) {
    GWire *xw = nw;
    nw = nw2;
    nw2 = xw;
  }

  copyWireProps(nw,w);
  copyWireProps(nw2,ow);

  return nw;
}

static GWire *replicate_attachment(GWire *w,GCElement *ng,NHash *whash)
{
  GWire *nw;

  if (!w) return 0;

  nw = (GWire*) NHash_find(whash,(nkey_t)w);

  ob_touch(nw);

  if (!nw) {
    logError(ERL_WARN,"Could not replicate attachment.");
    return 0;
  }

  nw->next = replicate_attachment(w->next,ng,whash);

  nw->offset.num = w->offset.num;
  nw->offset.den = w->offset.den;
  nw->WireDir = w->WireDir;
  nw->PadNum = w->PadNum;
  nw->invert = w->invert;
  nw->gate = ng;
  if (w->name) nw->name = ob_strdup(w->name);

  return nw;
}

/*
 * Paste circuit in S into D.  Generally, S is a cut buffer and D is the
 * current module (or vice versa).  The coordinates of the circuit in S
 * is adjusted by the offset (dx, dy) as it is pasted.  If selOnly is
 * set, then only seleced circuit elements are copied.  If selDst is
 * set, then the pasted circuit elements are left as the current selection.
 */
void GModuleDef_copyInto(GModuleDef *D,GModuleDef *S,int dx,int dy,int selOnly,int selDst)
{
  HashElem *E;
  NHash *ghash = new_NHash();		/* Map from source gates to destination gates */
  NHash *whash = new_NHash();		/* Map from source wires to destination wires */
  NHash *nhash = new_NHash();		/* Map from source nets to destination nets */
  NHash *rhash = new_NHash();		/* Map from destination nets to destination root wire */
  GWire *w;
  int i;

  /*
    Create maps for gates, wires and nets
   */
  for (E = Hash_first(S->gates);E;E = Hash_next(S->gates,E)) {
    GCElement *g,*ng;

    g = (GCElement*) HashElem_obj(E);
    if (selOnly && !g->selected)
      continue;

    ng = (*g->typeinfo->ReplicateGate)(D,g,g->xpos+dx,g->ypos+dy,REP_NOWIRES);
    NHash_insert(ghash,(nkey_t)g,ng);

    ob_touch(ng);

    if (selDst) ng->selected = 1;

    for (i = 0;i < g->typeinfo->NumPads;i++) {
      for (w = g->wires[i];w;w = w->next) {
	if (!NHash_find(whash,(nkey_t)w)) {
	  GWire *ow = wire_other(w);
	  GWire *nw = replicate_wire(w,D,dx,dy);
	  GWire *onw = wire_other(nw);

	  NHash_insert(whash,(nkey_t)w,nw);
	  NHash_insert(whash,(nkey_t)ow,onw);
	}

	if (!NHash_find(nhash,(nkey_t)w->net)) {
	  GNet *n = w->net;
	  GNet *nn = net_new(n->signame,D);

	  ob_touch(nn);

	  nn->driver = (GWire*) NHash_find(whash,(nkey_t)w);
	  nn->nbits = n->nbits;
	  nn->show_name = n->show_name;

	  NHash_insert(nhash,(nkey_t)w->net,nn);
	}
      }
    }
  }

  /*
   * Set nets of copied wires
   */
  for (E = Hash_first(whash);E;E = Hash_next(whash,E)) {
    GWire *sw = (GWire*) NHashElem_key(E);
    GWire *dw = (GWire*) HashElem_obj(E);
    GNet *net = (GNet*) NHash_find(nhash,(nkey_t)sw->net);
    wire_setNet(dw,net);
  }

  /*
   * Attach wires to copied gates
   */
  for (E = Hash_first(ghash);E;E = Hash_next(ghash,E)) {
    GCElement *g = (GCElement*) NHashElem_key(E);
    GCElement *ng = (GCElement*) HashElem_obj(E);
    int i;

    ob_touch(ng);
    for (i = 0;i < g->typeinfo->NumPads;i++)
      ng->wires[i] = replicate_attachment(g->wires[i],ng,whash);
  }


  /*
   * Set i/o gates and finalize nets
   */
  for (E = Hash_first(nhash);E;E = Hash_next(nhash,E)) {
    GNet *n = (GNet*) NHashElem_key(E);
    GNet *nn = (GNet*) HashElem_obj(E);

    if (n->ionet)  {
      ob_touch(nn);
      nn->ionet = (GCElement*) NHash_find(ghash,(nkey_t)n->ionet);
    }

    wire_finalizeNet(nn->driver);
  }

  /*
   * Check for partitioned nets and make the partitions separate nets
   * if any are found.
   *
   */
  for (E = Hash_first(whash);E;E = Hash_next(whash,E)) {
    GWire *dw = (GWire*) HashElem_obj(E);
    GNet *net = dw->net;
    GWire *rw = (GWire*) NHash_find(rhash,(nkey_t)net);
    GWire *dw_r;

    if (!rw) {
      dw_r = rw = wire_sigroot(dw);
      NHash_insert(rhash,(nkey_t)net,rw);
    } else
      dw_r = wire_sigroot(dw);
#if 0
    printf("PCHECK: %s",net->signame);
    printf(" dw:{%d}(%x)",dw->nidx,dw);
    printf(" dw_r:{%d}(%x)",dw_r->nidx,dw_r);
    printf(" rw:{%d}(%x)",rw->nidx,rw);
    printf(" nd:{%d}(%x)",net->driver->nidx,net->driver);
    printf("\n");
#endif
    if (rw != dw_r) {
      GNet *nn = net_new(net->signame,D);
#if 0
      printf("  nn:%s net:%s\n",nn->signame,net->signame);
#endif
      ob_touch(nn);
      nn->driver = dw_r;
      nn->nbits = net->nbits;
      nn->show_name = net->show_name;

      wire_setNet(dw_r,nn);

      wire_finalizeNet(dw_r);
      wire_finalizeNet(net->driver);
    }
  }

  delete_NHash(rhash);
  delete_NHash(nhash);
  delete_NHash(whash);
  delete_NHash(ghash);
}

void GModuleDef_getBBX(GModuleDef *M,int *minX,int *maxX,int *minY,int *maxY)
{
  HashElem *E;
  GWireList *wl;
  int is_first = 1;

  *minX = *maxX = *minY = *maxY = 0;

  for (E = Hash_first(M->gates);E;E = Hash_next(M->gates,E)) {
    int g_minX,g_minY,g_maxX,g_maxY;

    GCElement *g = (GCElement*) HashElem_obj(E);

    gate_getbbx(g,&g_minX,&g_minY,&g_maxX,&g_maxY);
    if (is_first || g_minX < *minX) *minX = g_minX;
    if (is_first || g_minY < *minY) *minY = g_minY;
    if (is_first || g_maxX > *maxX) *maxX = g_maxX;
    if (is_first || g_maxY > *maxY) *maxY = g_maxY;
    is_first = 0;
  }

  for (wl = M->wires;wl;wl = wl->cdr) {
    GWire *w = wl->car;
    GWireNode *n;

    if (!w->nodes->out) continue;

    for (n = w->nodes;n;n = n->out) {
      if (n->x < *minX) *minX = n->x;
      if (n->y < *minY) *minY = n->y;
      if (n->x > *maxX) *maxX = n->x;
      if (n->y > *maxY) *maxY = n->y;
    }
  }
}
