/****************************************************************************
    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 Mon Feb  9 10:03:50 2004
****************************************************************************/
#include "tkgate.h"

#define CONCAT_IN 0
#define CONCAT_OUT 1

#define CONCAT_WIRESPACE	10
#define CONCAT_HOOK		5

void Concat_Draw(GCElement *g,int md);
void Concat_AddInput(EditState *es,GCElement*);
GCElement *Concat_Make(EditState **es,GModuleDef *env,int GType,
		       int x,int y,int r,const char *Name,int noWires,const char**,int);
void Concat_VerSave(FILE *f,GCElement *g);
void Concat_SetProp(GCElement *g,const char *prop,const void *value);

void Concat_GetExtents(GCElement *g,int *minx,int *miny,int *maxx,int *maxy,int *bd);
int Concat_HitDistance(GCElement *g,int x,int y);
void Concat_PSWrite(FILE *f,GModLayout*,GCElement *g);
GCElement *Concat_Replicate(GModuleDef *M,GCElement *g,int x,int y,unsigned flags);

static int xBitLab[] = {0,0,0,0};
static int yBitLab[] = {0,8,0,0};
static int jBitLab[] = {RJ,RJ,LJ,RJ};

struct locate concat_in_loc[] =	{
	{-5,-11,-5,11,D_LEFT},
	{-11,5,11,5,D_DOWN},
	{5,11,5,-11,D_RIGHT},
	{11,-5,-11,-5,D_UP}};

struct locate concat_out_loc[] = {
	{1,0,1,0,D_RIGHT},
	{0,-1,0,-1,D_UP},
	{-1,0,-1,0,D_LEFT},
	{0,1,0,1,D_DOWN}};

static char *psConcat[] = {
  "%",
  "% [() ...] l x y o concat",
  "%",
  "/psconcat {",
  "  dup 5 1 roll",
  "  startgate",
  "  dup 2 div -5 exch moveto",
  "  5 0 rlineto",
  "  dup neg 0 exch rlineto",
  "  -5 0 rlineto",
  "  stroke",
  "  8 rfont",
  "  exch -180 eq {",
  "    180 rotate",
  "    2 div neg 6 exch 12 add moveto",
  "    {",
  "      gsave show grestore",
  "      0 10 rmoveto",
  "    } forall",
  "  } {",
  "    2 div -6 exch 8 sub moveto",
  "    {",
  "      gsave dup stringwidth pop neg 0 rmoveto show grestore",
  "      0 -10 rmoveto",
  "    } forall",
  "  } ifelse",
  "  grestore",
  "} def",
  0
};

struct gateinfo gate_concat_info = {
  0,
  "Concatenation",
  "concat",0x0,
  "psconcat",psConcat,

  {{"w",	{"gmswitch",0},		{"gmmerge",0,0,500},	"gat_make concat"},
   {0}},

  0,

  2,{{"I",IN,1,2,concat_in_loc,1},{"Z",OUT,2,1,concat_out_loc,0}},
  {{5,5,LJ},{5,5,LJ},{5,5,LJ},{5,5,LJ}},
  {1,1,0,1},

  {0},
  
  Concat_Make,
  Generic_Init,
  Generic_Delete,
  Concat_GetExtents,
  Concat_HitDistance,
  Concat_Draw,
  Generic_Move,
  Concat_Replicate,
  Concat_AddInput,
  Concat_AddInput,
  Err_AddInOut,
  Err_ChangePin,
  Nop_SimStateFunc,
  Nop_SimHitFunc,
  Concat_PSWrite,
  Generic_EditProps,
  Concat_VerSave,
  Concat_SetProp
};

void Concat_adjustWires(GCElement *g)
{
  int N,L,i,wx,wy,dx,dy;
  GWire *w;
  int d = g->u.cat.direction ? -1 : 1;

  wx = wy = dx = dy = 0;

  N = wire_numOnPad(g->wires[CONCAT_IN]);
  L = CONCAT_WIRESPACE*(N+1);
  
  
  switch (g->orient) {
  case 0 :
    wx = g->xpos - CONCAT_HOOK;
    wy = g->ypos + d*L/2;
    dx = 0;
    dy = -d*CONCAT_WIRESPACE;
    break;
  case 1 :
    wx = g->xpos + d*L/2;
    wy = g->ypos + CONCAT_HOOK;
    dx = -d*CONCAT_WIRESPACE;
    dy = 0;
    break;
  case 2 :
    wx = g->xpos + CONCAT_HOOK;
    wy = g->ypos - d*L/2;
    dx = 0;
    dy = d*CONCAT_WIRESPACE;
    break;
  case 3 :
    wx = g->xpos - d*L/2;
    wy = g->ypos - CONCAT_HOOK;
    dx = d*CONCAT_WIRESPACE;
    dy = 0;
    break;
  }
  
  for (i = 0,w = g->wires[CONCAT_IN];w;i++, w = w->next) {
    wx += dx;
    wy += dy;
    wire_move(w->nodes,wx-w->nodes->x,wy-w->nodes->y,VERTICAL|HORIZONTAL);
  }
}

GCElement *Concat_Make(EditState **es,GModuleDef *env,int GType,
		       int x,int y,int r,const char *Name,int noWires,const char **options,int nOptions)
{
  GWire *w;
  GCElement *g;

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

  ob_touch(g);
  g->u.cat.direction = 0;

  if (es && (g->orient & 0x2)) g->u.cat.direction = 1;


  if (!noWires) {
    w = wire_drivee(g->wires[CONCAT_OUT]);
    net_setSize(w->net,2);


    Concat_adjustWires(g);
  }

  return g;
}

GCElement *Concat_Replicate(GModuleDef *M,GCElement *g,int x,int y,unsigned flags)
{
  GCElement *c_g = Generic_Replicate(M,g,x,y,flags);
  ob_touch(c_g);
  c_g->u.cat.direction = g->u.cat.direction;
  return c_g;
}

void Concat_GetExtents(GCElement *g,int *minx,int *miny,int *maxx,int *maxy,int *bd)
{
  int N = wire_numOnPad(g->wires[CONCAT_IN]);
  int L = (N > 0) ? (CONCAT_WIRESPACE*(N+1)) : 2*CONCAT_WIRESPACE;
  int gx = g->xpos;
  int gy = g->ypos;

  if (bd) *bd = 0;


  switch (g->orient) {
  case 0 :
  case 2 :
    *minx = gx - 5;
    *maxx = gx + 5;
    *miny = gy-L/2-5;
    *maxy = gy+L/2-5;
    break;
  case 1 :
  case 3 :
    *minx = gx-L/2-5;
    *maxx = gx+L/2-5;
    *miny = gy - 5;
    *maxy = gy + 5;
    break;
  }
}

int Concat_HitDistance(GCElement *g,int x,int y)
{
  int N = wire_numOnPad(g->wires[CONCAT_IN]);
  int L = (N > 0) ? (CONCAT_WIRESPACE*(N+1)) : 2*CONCAT_WIRESPACE;
  int gx = g->xpos;
  int gy = g->ypos;

  switch (g->orient) {
  case 0 :
  case 2 :
    if (y >= gy-L/2-5 && y <= gy+L/2+5) y = gy;
    break;
  case 1 :
  case 3 :
    if (x >= gx-L/2-5 && x <= gx+L/2+5) x = gx;
    break;
  }
  return distance(x,y,gx,gy);
}

void Concat_Draw(GCElement *g,int md)
{
  int N = wire_numOnPad(g->wires[CONCAT_IN]);
  int L;
  int x,y;
  GWire *w;
  int lsb;
  
  x = ctow_x(g->xpos);
  y = ctow_y(g->ypos);

  L = (N > 0) ? (CONCAT_WIRESPACE*(N+1)) : 2*CONCAT_WIRESPACE;

  switch (g->orient) {
  case 0 :
    ZDrawLine(XGate.D,XGate.W,XGate.instGC,x,y+L/2,x,y-L/2);
    ZDrawLine(XGate.D,XGate.W,XGate.instGC,x,y+L/2+1,x-CONCAT_HOOK,y+L/2+1);
    ZDrawLine(XGate.D,XGate.W,XGate.instGC,x,y-L/2-1,x-CONCAT_HOOK,y-L/2-1);
    if (g->selected) {
      ZDrawLine(XGate.D,XGate.W,XGate.instGC,x-1,y+L/2,x-1,y-L/2);
      ZDrawLine(XGate.D,XGate.W,XGate.instGC,x,y+L/2+2,x-CONCAT_HOOK,y+L/2+2);
      ZDrawLine(XGate.D,XGate.W,XGate.instGC,x,y-L/2-2,x-CONCAT_HOOK,y-L/2-2);
    }
    break;
  case 1 :
    ZDrawLine(XGate.D,XGate.W,XGate.instGC,x+L/2,y,x-L/2,y);
    ZDrawLine(XGate.D,XGate.W,XGate.instGC,x+L/2+1,y,x+L/2+1,y+CONCAT_HOOK);
    ZDrawLine(XGate.D,XGate.W,XGate.instGC,x-L/2-1,y,x-L/2-1,y+CONCAT_HOOK);
    if (g->selected) {
      ZDrawLine(XGate.D,XGate.W,XGate.instGC,x+L/2,y+1,x-L/2,y+1);
      ZDrawLine(XGate.D,XGate.W,XGate.instGC,x+L/2+2,y,x+L/2+2,y+CONCAT_HOOK);
      ZDrawLine(XGate.D,XGate.W,XGate.instGC,x-L/2-2,y,x-L/2-2,y+CONCAT_HOOK);
    }
    break;
  case 2 :
    ZDrawLine(XGate.D,XGate.W,XGate.instGC,x,y+L/2,x,y-L/2);
    ZDrawLine(XGate.D,XGate.W,XGate.instGC,x,y+L/2+1,x+CONCAT_HOOK,y+L/2+1);
    ZDrawLine(XGate.D,XGate.W,XGate.instGC,x,y-L/2-1,x+CONCAT_HOOK,y-L/2-1);
    if (g->selected) {
      ZDrawLine(XGate.D,XGate.W,XGate.instGC,x+1,y+L/2,x+1,y-L/2);
      ZDrawLine(XGate.D,XGate.W,XGate.instGC,x,y+L/2+2,x+CONCAT_HOOK,y+L/2+2);
      ZDrawLine(XGate.D,XGate.W,XGate.instGC,x,y-L/2-2,x+CONCAT_HOOK,y-L/2-2);
    }
    break;
  case 3 :
    ZDrawLine(XGate.D,XGate.W,XGate.instGC,x+L/2,y,x-L/2,y);
    ZDrawLine(XGate.D,XGate.W,XGate.instGC,x+L/2+1,y,x+L/2+1,y-CONCAT_HOOK);
    ZDrawLine(XGate.D,XGate.W,XGate.instGC,x-L/2-1,y,x-L/2-1,y-CONCAT_HOOK);
    if (g->selected) {
      ZDrawLine(XGate.D,XGate.W,XGate.instGC,x+L/2,y-1,x-L/2,y-1);
      ZDrawLine(XGate.D,XGate.W,XGate.instGC,x+L/2+2,y,x+L/2+2,y-CONCAT_HOOK);
      ZDrawLine(XGate.D,XGate.W,XGate.instGC,x-L/2-2,y,x-L/2-2,y-CONCAT_HOOK);
    }
    break;
  }

  gate_drawWires(g,md);

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

  lsb = 0;
  for (w = g->wires[CONCAT_IN];w;w = w->next) {
    char buf[STRMAX];

    if (w->net->nbits == 1)
      sprintf(buf,"%d",lsb);
    else
      sprintf(buf,"%d:%d",lsb+w->net->nbits-1,lsb);
    lsb += w->net->nbits;

    x = w->nodes->x + xBitLab[g->orient];
    y = w->nodes->y + yBitLab[g->orient];
	  
    dce_DrawString(XGate.instGC,x,y,jBitLab[g->orient],buf);
  }
}

void Concat_AddInput(EditState *es,GCElement *g)
{
  GWire *e1,*e2;
  int d = g->u.cat.direction ? -1 : 1;

  gate_draw(g,0);

  wire_new(es->env,&e1,&e2);

  ob_touch(g);
  ob_touch(e1->nodes);
  ob_touch(e2->nodes);

  e1->nodes->x = e2->nodes->x = 0;
  e1->nodes->y = e2->nodes->y = 0;

  g->wires[CONCAT_IN] = wire_append(g->wires[CONCAT_IN],e1);
  e1->gate = g;
  e1->orient = g->typeinfo->Pad[CONCAT_IN].Loc[g->orient].dir;

  switch (e1->orient) {
  case D_RIGHT :
    e2->nodes->x -= d*10;
    break;
  case D_UP :
    e2->nodes->y += d*10;
    break;
  case D_LEFT :
    e2->nodes->x -= d*10;
    break;
  case D_DOWN :
    e2->nodes->y += d*10;
    break;
  }

  wire_finalizeNet(e1);
  Concat_adjustWires(g);

  gate_draw(g,0);
}

void Concat_SetProp(GCElement *g,const char *prop,const void *value)
{
  if (strcmp(prop,"/dr") == 0) {
    int n = *((int*)value);
    ob_touch(g);
    g->u.cat.direction = n;
  }
}

void Concat_VerSave(FILE *f,GCElement *g)
{
  VerilogBasicGateCall(f,g);
  VerilogBasicGateParmList(f,g);
  VerilogBasicGateComment(f,g,1);
  fprintf(f," /dr:%d",g->u.cat.direction);
  fprintf(f,"\n");
}

void Concat_PSWrite(FILE *f,GModLayout *L,GCElement *g)
{
  int N = wire_numOnPad(g->wires[CONCAT_IN]);
  int len = (N > 0) ? (CONCAT_WIRESPACE*(N+1)) : 2*CONCAT_WIRESPACE;
  char buf[STRMAX];
  int *wire_nbits;
  int nwires = 0;
  int nbits = 0;
  int ncount;
  GWire *w;

  /*
  ** We need to collect bit-widths of wires into an array, since we don't
  ** know it which order we will be going through them.
  */
  for (w = g->wires[CONCAT_IN];w;w = w->next)
  {
    nbits += w->net->nbits;
    nwires++;
  }

  wire_nbits = malloc(nwires * sizeof(int));

  nwires = 0;
  for (w = g->wires[CONCAT_IN];w;w = w->next) {
    wire_nbits[nwires++] = w->net->nbits;
  }

  /*
  ** Now use this information to print the wires.
  */
  fprintf(f,"[\n");
  if (g->u.cat.direction) {
    int lsb = 0;
    for (ncount = 0; ncount < nwires; ncount++) {
      if (wire_nbits[ncount] == 1)
	sprintf(buf,"%d",lsb);
      else
	sprintf(buf,"%d:%d",lsb+wire_nbits[ncount]-1,lsb);
      fprintf(f,"  (%s)\n",buf);
      lsb += wire_nbits[ncount];
    }
  } else {
    int msb = nbits-1;
    int ncount = nwires;
    for (ncount = nwires - 1;ncount >= 0; ncount--) {
      if (wire_nbits[ncount] == 1)
	sprintf(buf,"%d",msb);
      else
	sprintf(buf,"%d:%d",msb,msb-wire_nbits[ncount]+1);
      fprintf(f,"  (%s)\n",buf);
      msb -= wire_nbits[ncount];
    }
  }

  fprintf(f,"] %d %d %d %d psconcat\n",len,g->xpos,g->ypos,-g->orient*90);
  free(wire_nbits);
}

void init_concat()
{
  RegisterGate(&gate_concat_info);
}
