/****************************************************************************
    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  2 10:40:28 2003
****************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <sys/time.h>
#include <assert.h>
#include "tkgate.h"

int GlobalElementCount = 0;

void gate_setName(GCElement *g,const char *name,GModuleDef *mdef)
{
  char buf[1024];
  SHash *H = mdef ? mdef->gates : 0;

  if (name && g->ename && strcmp(name,g->ename) == 0)
    return;

  pickValidName(buf,name,"g",H);

  if (g->ename) {
    SHash_remove(H,g->ename);
    ob_free(g->ename);
  }
  ob_touch(g);
  g->ename = ob_strdup(buf);
  g->show_name = (name != 0);
  if (H)
    SHash_insert(H,g->ename,g);
}

void gate_add(GModuleDef *mdef,GCElement *g)
{
  if (!g->ename) {
    ob_touch(g);
    gate_setName(g,0,mdef);
    g->show_name = 0;
  }
}

void gate_remove(GModuleDef *mdef,GCElement *g)
{
  SHash_remove(mdef->gates,g->ename);
}

/* Creates a new gate */
GCElement *gate_new(int x,int y,int rot,int type)
{
  GCElement *g;
  int i,n;
  
#ifdef DEBUG
  printf("gate_new(%d %d %d  NULL)\n",x,y,type);
#endif
  g = (GCElement *) ob_malloc(sizeof(GCElement),"GCElement");
  ob_touch(g);
  g->typeinfo = gtype_get(type);
  g->tech = ob_strdup("default");
  g->xpos = x;
  g->ypos = y;
  g->top = 0;
  g->bottom = 0;
  g->selected = 0;
  g->orient = g->typeinfo->Flags.CanRot ? rot : 0;
  g->ename = 0;
  g->show_name = 0;
  g->anchored = 0;
  g->cust_delay = 0;
  g->cpath_cut = 0;
  g->enumb = GlobalElementCount++;
  for (i = 0;i < g->typeinfo->NumPads;i++)
    g->wires[i] = NULL;
  
  if (g->typeinfo->InitGate)
    (*g->typeinfo->InitGate)(g);

  n = 0;
  for (i = 0;g->typeinfo->delayNames[i];i++)n++;
  if (n == 0) 
    g->delays = 0;
  else {
    g->delays = (int*) ob_malloc(sizeof(int)*n,"int[]");
    for (i = 0;g->typeinfo->delayNames[i];i++)
      g->delays[i] = 1;
  }

  return g;
}

void gate_unattachwire(GWire *w,GModuleDef *env,int drawp)
{
  struct wire *wd;

  ob_touch(w);
  ob_touch(w->gate);

  wd = w->driver;
  if (env && drawp) wire_draw(wd->nodes);
  w->gate = NULL;
  if (w->name) ob_free(w->name);
  
  w->name = NULL;
  if (!wire_trash(w->nodes,env,drawp)) {
    w->invert = 0;
    if (env && drawp) wire_draw(wd->nodes);
  } else {
    if (env) {
      ob_touch(env);
      env->wires = wire_unlink(env->wires,w);
    }
    wire_free(w);
  }
}

void gate_unattachwirelist(GWire *w,GModuleDef *env,int drawp)
{
  GWire *wnext;

  for (;w;w = wnext) {
    wnext = w->next;
    gate_unattachwire(w,env,drawp);
  }
}

void gate_cutOffWires(GCElement *g,GModuleDef *M,int drawp)
{
  int i;

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

}

/*
  Delete a gate by unattaching all wires connected to it and removing it from
  the object list.  This function does not actually free the object.
  */
int gate_delete(GCElement *g,GModuleDef *env,int drawp)
{
  (*g->typeinfo->DeleteGate)(g,env,drawp);

  return 1;
}

GCElement *gate_hit_aux(GModuleDef *mdef,int x,int y,int threshDist)
{
  HashElem *gl;
  GCElement *hit = 0;

  for (gl = Hash_first(mdef->gates);gl;gl = Hash_next(mdef->gates,gl)) {
    GCElement *g = (GCElement*) HashElem_obj(gl);
    int dist;

    dist = (*g->typeinfo->HitDistance)(g,x,y);

    if  (dist < threshDist) {
      hit = g;
      threshDist = dist;
    }
  }

  return hit;
}

/* Tests the current position for a gate.  Returns the gate if it is found, NULL otherwise */
GCElement *gate_hit(GModuleDef *mdef,int x,int y)
{
  return gate_hit_aux(mdef,x,y,GATERANGE);
}

/*
  Gets rid of a gate.  This function needs to be modified to handle more
  recent gate types
*/
void gate_dispose(GCElement *g)
{
  if (g->ename)
    ob_free(g->ename);

  if (g->delays) ob_free(g->delays);
  ob_free(g);
}

/* Displays all the gates */
void gate_displayall(EditState *es,GModuleDef *M)
{
  HashElem *gl;
  GWireList *wl;

  for (gl = Hash_first(M->gates);gl;gl = Hash_next(M->gates,gl)) {
    int MinX,MinY,MaxX,MaxY;
    GCElement *g = (GCElement*) HashElem_obj(gl);

    gate_draw(g,GD_NOWIRE);
    gate_getbbx(g,&MinX,&MinY,&MaxX,&MaxY);
    if (MinX < XGate.ed->min_x) XGate.ed->min_x = MinX;
    if (MinY < XGate.ed->min_y) XGate.ed->min_y = MinY;
    if (MaxX > XGate.ed->max_x) XGate.ed->max_x = MaxX;
    if (MaxY > XGate.ed->max_y) XGate.ed->max_y = MaxY;
  }

  for (wl = M->wires;wl;wl = wl->cdr) {
    GWire *w = wl->car;
    if (w == w->driver)
      wire_draw(w->nodes);
  }
}

/* Draws a gate (Circuit element) and the wires connected to it. */
void gate_draw(GCElement *g,int md)
{
  if (g->typeinfo->DrawGate)
    (*g->typeinfo->DrawGate)(g,md);
  else
    Generic_Draw(g,md);
}

void gate_drawWires(GCElement *g,int md)
{
  GWire *w;
  int i;

  if (md != GD_NOWIRE) {
    for (i = 0;i < g->typeinfo->NumPads;i++)
      for (w = g->wires[i];w;w=w->next)
	if (w->nodes->out) {
	  wire_draw(w->nodes);
	} else {
	  if ((w->driver->gate != g) && !(w->driver->gate && md == GD_NOINWIRE))
	    wire_draw(w->driver->nodes);
	}
  }
}

void gate_drawgatename(GCElement *g,const char *name)
{
  int x,y; 
  int o;
 
  o = g->orient;
  x = g->xpos;
  y = g->ypos;

  if (g->selected)
    XSetFont(XGate.D,XGate.instGC,XGate.textbXF);
  else
    XSetFont(XGate.D,XGate.instGC,XGate.textXF);
  
  dce_DrawString(XGate.instGC,x + g->typeinfo->lpos[o].x,
		 y + g->typeinfo->lpos[o].y,
		 g->typeinfo->lpos[o].just,name);
}

/* Moves a gate and adjusts wires */
void gate_move(GCElement *g,int dx,int dy)
{
  if (XGate.circuit->es->env->is_lib) {
    message(0,msgLookup("err.libmode"));		/* "Can not edit library module." */
    return;
  }

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

  SetModified();

  (*g->typeinfo->MoveGate)(g,dx,dy);
}

void gate_moveTo(GCElement *g,int x,int y)
{
  gate_move(g,x-g->xpos,y-g->ypos);
}

/* Marks a gate */
void gate_mark(GCElement *g,int select)
{
  ob_touch(g);
  g->selected = select;
}

/*
  Gets the bounding box of a gate.
*/
void gate_getbbx(GCElement *g,int *minx,int *miny,int *maxx,int *maxy)
{
  (*g->typeinfo->GetExtents)(g,minx,miny,maxx,maxy,0);
}

void displayRepCount(int x,int y,int num)
{
  char ns[30];

  Icon_draw(XGate.D,XGate.W,XGate.toolGC,ctow_x(x),ctow_y(y),REPCOUNTMARK);

  sprintf(ns,"%d",num);
  dce_DrawString(XGate.toolGC,x+10,y,BetweenTopAndBottom|AtLeft,ns);
}

void gate_doReplication(EditState *es)
{
  struct celemnt *g;
  int sx,sy,ex,ey,dx,dy,h,w,n,bd,num;
  
  g = XGate.circuit->select;
  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));

  (*g->typeinfo->GetExtents)(g,&sx,&sy,&ex,&ey,&bd);

  w = ex-sx + bd;
  h = ey-sy + bd;

  
  sx = g->xpos;
  sy = g->ypos;
  ex = XGate.ed->tx;
  ey = XGate.ed->ty;
  
  if (sx == ex) {
    dx = 0;
    dy = (sy > ey) ? -h : h;
    n = abs((sy-ey)/dy);
  } else if (sy == ey) {
    dx = (sx > ex) ? -w : w;
    dy = 0;
    n = abs((sx-ex)/dx);
  } else if (abs(10*(sy-ey)/(sx-ex)) > (10*h)/w) {
    dy = (sy > ey) ? -h : h;
    dx = (dy*(sx-ex))/(sy-ey);
    n = abs((sy-ey)/dy);
  } else {
    dx = (sx > ex) ?-w : w;
    dy = (dx*(sy-ey))/(sx-ex);
    n = abs((sx-ex)/dx);
  }
  
  sx += dx;
  sy += dy;
  num = 2;
  while (n--) {
    GCElement *ng;

    displayRepCount(sx,sy,num);
    ng = (*g->typeinfo->ReplicateGate)(es->env,g,sx,sy,0);
    gate_draw(ng,0);
    sx += dx;
    sy += dy;
    num++;
  }
}

void gate_setinvertwirelist(GWire *nw,GWire *w)
{
  for (;nw && w;w = w->next,nw = nw->next) {
    ob_touch(nw);
    nw->invert = w->invert;
  }
}

void gate_hashrepline(EditState *es,int x,int y)
{
  GCElement *g;
  int sx,sy,ex,ey,dx,dy,bd,h,w,n,num;
  
  g = XGate.circuit->select;

  (*g->typeinfo->GetExtents)(g,&sx,&sy,&ex,&ey,&bd);

  w = ex-sx + bd;
  h = ey-sy + bd;
  
  sx = g->xpos;
  sy = g->ypos;
  ex = x;
  ey = y;
  
  if (sx == ex) {
    dx = 0;
    dy = (sy > ey) ? -h : h;
    n = abs((sy-ey)/dy);
  } else if (sy == ey) {
    dx = (sx > ex) ? -w : w;
    dy = 0;
    n = abs((sx-ex)/dx);
  } else if (abs(10*(sy-ey)/(sx-ex)) > (10*h)/w) {
    dy = (sy > ey) ? -h : h;
    dx = (dy*(sx-ex))/(sy-ey);
    n = abs((sy-ey)/dy);
  } else {
    dx = (sx > ex) ? -w : w;
    dy = (dx*(sy-ey))/(sx-ex);
    n = abs((sx-ex)/dx);
  }
  
  sx += dx;
  sy += dy;
  num = 2;
  while (n--) {
    displayRepCount(sx,sy,num);
    sx += dx;
    sy += dy;
    num++;
  }
}

void stringout(char *s)
{
  printf("stringout '%s'\n",s);
}
 
char *GGateInfo_getName(GGateInfo *GI,int p)
{
  char buf[STRMAX],*T;
  strcpy(buf,GI->vnames);

  for (T = strtok(buf,":");T;T = strtok(0,":")) {
    if (p-- <= 0) break;
  }
  return T;
}

int GGateInfo_getPadNum(GGateInfo *gi,const char *pname)
{
  char buf[STRMAX];
  int p;

  if (sscanf(pname,"%[A-Za-z]",buf) != 1)
    return -1;

  for (p = 0;p < gi->NumPads;p++)
    if (strcmp(gi->Pad[p].Name,buf) == 0)
      return p;

  return -1;
}

/*
   Transmute a gate from one type to another.

   Warning: Only certain types of transmutations can be
   performed.
*/
void gate_transmute(GCElement *g,GGateInfo *ngi)
{
  GGateInfo *ogi = g->typeinfo;
  int oldOrient,rot,i;

  assert(ngi->NumPads == ogi->NumPads);

  ob_touch(g);

  oldOrient = g->orient;
  rot = ogi->Pad[0].Loc[oldOrient].dir;
  for (i = 0;i < 4;i++) 
    if (ngi->Pad[0].Loc[i].dir == rot) {
      g->orient = i;
      break;
    }
  assert(i != 4);

  for (i = 0;i < ngi->NumPads;i++) {
    GWire *w;
    struct locate *oL = &ogi->Pad[i].Loc[oldOrient];
    struct locate *nL = &ngi->Pad[i].Loc[g->orient];

    for (w = g->wires[i];w;w = w->next) {
      ob_touch(w->nodes);
      w->nodes->x += nL->x1 - oL->x1;
      w->nodes->y += nL->y1 - oL->y1;
    }
  }

  g->typeinfo = ngi;
}
