/****************************************************************************
    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:29:30 2004
****************************************************************************/
/*
  Functions for manageing the multi-gate selection and cut buffer.

  We have separate handling for single-gate and multi-gate selection
  mainly due to historical baggage.  One day, I will merge them into
  a single selection


*/
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <pwd.h>
#include <sys/time.h>
#include <string.h>
#include <assert.h>
#include "tkgate.h"

GSelection *new_GSelection()
{
  GSelection *S = (GSelection*) ob_malloc(sizeof(GSelection),"GSelection");

  ob_touch(S);
  S->s_gates = new_SHash();
  S->s_wires = new_NHash();
  S->s_edgeWires = new_NHash();
  S->s_hasAnchored = 0;

  return S;
}

void delete_GSelection(GSelection *S)
{
  delete_SHash(S->s_gates);
  delete_NHash(S->s_wires);
  delete_NHash(S->s_edgeWires);
  ob_free(S);
}

GCutBuffer *new_GCutBuffer()
{
  GCutBuffer *C = (GCutBuffer*) ob_malloc(sizeof(GCutBuffer),"GCutBuffer");

  ob_touch(C);
  C->cb_minx = C->cb_maxx = C->cb_miny = C->cb_maxy = 0;
  C->cb_dx = C->cb_dy = 0;
  C->cb_buf = new_GModuleDef("cut-buffer",0);

  return C;
}

void GCutBuffer_computeBounds(GCutBuffer *C)
{
  HashElem *E;
  GWireList *wl;
  int did_first = 0;

  ob_touch(C);

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

    gate_getbbx(g,&MinX,&MinY,&MaxX,&MaxY);

    if (!did_first) {
      C->cb_minx = MinX;
      C->cb_maxx = MaxX;
      C->cb_miny = MinY;
      C->cb_maxy = MaxY;
    } else {
      if (MinX < C->cb_minx) C->cb_minx = MinX;
      if (MaxX > C->cb_maxx) C->cb_maxx = MaxX;
      if (MinY < C->cb_miny) C->cb_miny = MinY;
      if (MaxY > C->cb_maxy) C->cb_maxy = MaxY;
    }
    did_first = 1;
  }

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

    for (n = w->nodes;n;n = n->out) {
      int x = n->x;
      int y = n->y;

      if (x < C->cb_minx) C->cb_minx = x;
      if (x > C->cb_maxx) C->cb_maxx = x;
      if (y < C->cb_miny) C->cb_miny = y;
      if (y > C->cb_maxy) C->cb_maxy = y;
    }
  }

  C->cb_ctrx = (C->cb_minx + C->cb_maxx)/2;
  C->cb_ctry = (C->cb_miny + C->cb_maxy)/2;
}

void delete_GCutBuffer(GCutBuffer *C)
{
  delete_GModuleDef(C->cb_buf);
  ob_free(C);
}

void sel_updateMenuState()
{
  static char *states[] = {"disabled","normal"};
  int sel_ok = (XGate.circuit->select != 0) || (XGate.circuit->mg_selection != 0);
  int paste_ok = XGate.circuit->cut_buffer != 0;

  DoTcl(".mbar.edit.menu entryconfigure 4 -state %s",states[sel_ok]);
  DoTcl(".mbar.edit.menu entryconfigure 5 -state %s",states[sel_ok]);
  DoTcl(".mbar.edit.menu entryconfigure 6 -state %s",states[paste_ok]);
}

int sel_isSelGate(GCElement *g)
{
  if (!XGate.circuit->mg_selection)
    return 0;

  if (SHash_find(XGate.circuit->mg_selection->s_gates,g->ename))
    return 1;
  else
    return 0;
}


int sel_num(EditState *es)
{
  GSelection *S = XGate.circuit->mg_selection;

  if (!S) return 0;

  return Hash_numElems(S->s_gates);
}

static void sel_addGate(EditState *es,GCElement *g)
{
  GSelection *S = XGate.circuit->mg_selection;

  SHash_insert(S->s_gates,g->ename,g);

  ob_touch(S);
  ob_touch(g);

  if (g->anchored)
    S->s_hasAnchored = 1;

  gate_draw(g,GD_NOWIRE);
  g->selected = 1;
  gate_draw(g,GD_NOWIRE);
}

int sel_finish(EditState *es)
{
  GWireList *wl;
  GSelection *S = XGate.circuit->mg_selection;
  int n = Hash_numElems(S->s_gates);

  if (n == 0) {
    sel_updateMenuState();
    return 0;
  }

  ob_touch(XGate.circuit);

  if (n == 1) {
    GCElement *g = (GCElement*) HashElem_obj(Hash_first(XGate.circuit->mg_selection->s_gates));
    XGate.circuit->select = g;
  } else
    XGate.circuit->select = 0;

  for (wl = es->env->wires;wl;wl = wl->cdr) {
    GWire *w1 = wl->car;
    GWire *w2 = w1->driver;

    if (w1 == w2) continue;	/* Process each each wire only once */

    if (!w1->gate || !SHash_find(S->s_gates,w1->gate->ename)) 
      w1 = 0;

    if (!w2->gate || !SHash_find(S->s_gates,w2->gate->ename)) 
      w2 = 0;

    if (w1 && w2) {
      NHash_insert(S->s_wires,(nkey_t)w2,w2);
    } else if (w1) {
      NHash_insert(S->s_edgeWires,(nkey_t)w1,w1);
    } else if (w2) {
      NHash_insert(S->s_edgeWires,(nkey_t)w2,w2);
    }
  }

  sel_updateMenuState();
  return n;
}

void sel_clear(EditState *es)
{
  ob_touch(XGate.circuit);

  if (XGate.circuit->mg_selection) {
    HashElem *gl;
    SHash *H = XGate.circuit->mg_selection->s_gates;

    for (gl = Hash_first(H);gl;gl = Hash_next(H,gl)) {
      GCElement *g = (GCElement*) HashElem_obj(gl);
      ob_touch(g);
      gate_draw(g,GD_NOWIRE);
      g->selected = 0;
      gate_draw(g,GD_NOWIRE);
    }

    delete_GSelection(XGate.circuit->mg_selection);
    XGate.circuit->mg_selection = 0;
  }

  XGate.circuit->select = 0;

  sel_updateMenuState();
}

void sel_interfaceReset(EditState *es)
{
  if (XGate.circuit->mg_selection) {
    HashElem *E;
    SHash *H = XGate.circuit->mg_selection->s_gates;

    for (E = Hash_first(H);E;E = Hash_next(H,E)) {
      GCElement *g = (GCElement*) HashElem_obj(E);
      modint_reset(es,g);
    }

    sel_clear(es);
  }
}

int sel_select(EditState *es)
{
  int x = XGate.ed->sx;
  int y = XGate.ed->sy;
  int width = XGate.ed->tx-XGate.ed->sx;
  int height = XGate.ed->ty-XGate.ed->sy;
  HashElem *gl;

  ob_touch(XGate.circuit);

  if (!XGate.circuit->mg_selection)
    XGate.circuit->mg_selection = new_GSelection();
  else {
    NHash_flush(XGate.circuit->mg_selection->s_wires);
    NHash_flush(XGate.circuit->mg_selection->s_edgeWires);
  }

  if (width < 0) {
    width = -width;
    x = x - width;
  } 
  if (height < 0) {
    height = -height;
    y = y - height;
  } 

  for (gl = Hash_first(es->env->gates);gl;gl = Hash_next(es->env->gates,gl)) {
    GCElement *g = (GCElement*) HashElem_obj(gl);
    int gx,gy;

    gx = g->xpos;
    gy = g->ypos;
    if (g->typeinfo->Code == BLOCK) {
      gx += g->u.block.gwidth/2;
      gy += g->u.block.gheight/2;
    }

    if (gx >= x && gx <= x+width && gy >= y && gy <=y+height)
      sel_addGate(es,g);
  }

  return sel_finish(es);
}

void sel_appendGate(EditState *es,GCElement *g)
{
  ob_touch(XGate.circuit);

  if (!XGate.circuit->mg_selection)
    XGate.circuit->mg_selection = new_GSelection();

  NHash_flush(XGate.circuit->mg_selection->s_wires);
  NHash_flush(XGate.circuit->mg_selection->s_edgeWires);

  sel_addGate(es,g);
}

int sel_selectMarked(EditState *es)
{
  HashElem *gl;

  ob_touch(XGate.circuit);
  XGate.circuit->mg_selection = new_GSelection();

  for (gl = Hash_first(es->env->gates);gl;gl = Hash_next(es->env->gates,gl)) {
    GCElement *g = (GCElement*) HashElem_obj(gl);
    if (g->selected)
      sel_addGate(es,g);
  }
  return sel_finish(es);
}

int sel_selectAll(EditState *es)
{
  HashElem *gl;

  ob_touch(XGate.circuit);

  if (!XGate.circuit->mg_selection)
    XGate.circuit->mg_selection = new_GSelection();

  for (gl = Hash_first(es->env->gates);gl;gl = Hash_next(es->env->gates,gl)) {
    GCElement *g = (GCElement*) HashElem_obj(gl);
    sel_addGate(es,g);
  }

  return sel_finish(es);
}

void sel_move(EditState *es,int dx,int dy)
{
  HashElem *E;
  SHash *GH;
  NHash *WH,*EWH;

  if (!XGate.circuit->mg_selection) return;

  if (XGate.circuit->es->env->is_lib) {
    message(0,msgLookup("err.libmode"));		/* "Can not edit library module." */
    return;
  }
  if (XGate.circuit->mg_selection->s_hasAnchored) {
    message(0,msgLookup("err.gatanchor"));		/* Gate(s) are anchored and can not be moved. */
    return;
  }

  SetModified();
  ob_touch(XGate.circuit);

  GH = XGate.circuit->mg_selection->s_gates;
  WH = XGate.circuit->mg_selection->s_wires;
  EWH = XGate.circuit->mg_selection->s_edgeWires;

  for (E = Hash_first(GH);E;E = Hash_next(GH,E)) {
    GCElement *g = (GCElement*) HashElem_obj(E);

    ob_touch(g);
    g->xpos += dx;
    g->ypos += dy;

  }

  for (E = Hash_first(WH);E;E = Hash_next(WH,E)) {
    GWire *w = (GWire*) HashElem_obj(E);
    GWireNode *n;

    for (n = w->driver->nodes;n;n = n->out) {
      ob_touch(n);
      n->x += dx;
      n->y += dy;
    }
  }

  for (E = Hash_first(EWH);E;E = Hash_next(EWH,E)) {
    GWire *w = (GWire*) HashElem_obj(E);
    wire_move(w->nodes,dx,dy,FULL);
  }
}

void sel_draw(EditState *es)
{
  HashElem *E;
  SHash *GH;
  NHash *WH,*EWH;

  if (!XGate.circuit->mg_selection) return;

  GH = XGate.circuit->mg_selection->s_gates;
  WH = XGate.circuit->mg_selection->s_wires;
  EWH = XGate.circuit->mg_selection->s_edgeWires;

  for (E = Hash_first(GH);E;E = Hash_next(GH,E)) {
    GCElement *g = (GCElement*) HashElem_obj(E);
    gate_draw(g,GD_NOWIRE);
  }

  for (E = Hash_first(WH);E;E = Hash_next(WH,E)) {
    GWire *w = (GWire*) HashElem_obj(E);
    wire_draw(w->driver->nodes);
  }

  for (E = Hash_first(EWH);E;E = Hash_next(EWH,E)) {
    GWire *w = (GWire*) HashElem_obj(E);
    wire_draw(w->driver->nodes);
  }
}

void sel_dropFixup(EditState *es)
{
  HashElem *E;
  NHash *EWH;

  if (!XGate.circuit->mg_selection) return;

  EWH = XGate.circuit->mg_selection->s_edgeWires;

  for (E = Hash_first(EWH);E;E = Hash_next(EWH,E)) {
    GWire *w = (GWire*) HashElem_obj(E);

    wire_draw(w->driver->nodes);
    wire_snap(w->driver->nodes);
    wire_draw(w->driver->nodes);
  }
}

void sel_copy(EditState *es)
{
  if (XGate.circuit->cut_buffer) delete_GCutBuffer(XGate.circuit->cut_buffer);

  ob_touch(XGate.circuit);

  XGate.circuit->cut_buffer = new_GCutBuffer();

  GModuleDef_copyInto(XGate.circuit->cut_buffer->cb_buf,es->env,0,0,1,0);
  GCutBuffer_computeBounds(XGate.circuit->cut_buffer);

  ob_touch(XGate.circuit->cut_buffer);
  XGate.circuit->cut_buffer->cb_dx = 20;
  XGate.circuit->cut_buffer->cb_dy = 20;

  sel_updateMenuState();
}

void sel_kill(EditState *es)
{
  sel_copy(es);
  sel_delete(es);
  ob_touch(XGate.circuit->cut_buffer);
  XGate.circuit->cut_buffer->cb_dx = 0;
  XGate.circuit->cut_buffer->cb_dy = 0;
}

void sel_clearDelta()
{
  if (XGate.circuit->cut_buffer) {
    ob_touch(XGate.circuit->cut_buffer);
    XGate.circuit->cut_buffer->cb_dx = 0;
    XGate.circuit->cut_buffer->cb_dy = 0;
  }
}

void sel_yank(EditState *es)
{
  if (!XGate.circuit->cut_buffer) return;

  if (XGate.ed->mark_vis) {
    ob_touch(XGate.circuit->cut_buffer);
    XGate.circuit->cut_buffer->cb_dx = XGate.ed->tx - XGate.circuit->cut_buffer->cb_ctrx;
    XGate.circuit->cut_buffer->cb_dy = XGate.ed->ty - XGate.circuit->cut_buffer->cb_ctry;
    mark_unpost();
  }

  sel_clear(es);
  GModuleDef_copyInto(es->env,XGate.circuit->cut_buffer->cb_buf,
		      XGate.circuit->cut_buffer->cb_dx,XGate.circuit->cut_buffer->cb_dy,0,1);
  if (sel_selectMarked(es)) {
    ob_touch(XGate.circuit);
    XGate.circuit->mode = MODE_MOVESEL;
  }

  ob_touch(XGate.circuit->cut_buffer);
  XGate.circuit->cut_buffer->cb_dx += 20;
  XGate.circuit->cut_buffer->cb_dy += 20;

  SetModified();

  sel_updateMenuState();
}

void sel_delete(EditState *es)
{
  if (sel_num(es) == 0) return;

  SetModified();

  ob_touch(XGate.circuit);

  /*
   * A single gate was selected.
   */
  if (sel_num(es) == 1) {

    gate_delete(XGate.circuit->select,es->env,1);

    delete_GSelection(XGate.circuit->mg_selection);
    XGate.circuit->mg_selection = 0;
    XGate.circuit->last = XGate.circuit->select = 0;

    return;
  }

  if (XGate.circuit->mg_selection) {
    HashElem *gl;
    SHash *H = XGate.circuit->mg_selection->s_gates;
    NHash *delGates = new_NHash();

    for (gl = Hash_first(H);gl;gl = Hash_next(H,gl)) {
      GCElement *g = (GCElement*) HashElem_obj(gl);
      if (g->typeinfo->Code != JOINT)
	NHash_insert(delGates,(nkey_t)g,g);
    }

    for (gl = Hash_first(delGates);gl;gl = Hash_next(delGates,gl)) {
      GCElement *g = (GCElement*) HashElem_obj(gl);
      gate_delete(g,es->env,0);
    }

    delete_NHash(delGates);

    delete_GSelection(XGate.circuit->mg_selection);
    XGate.circuit->mg_selection = 0;
  }
  sel_updateMenuState();
  XGate.circuit->last = XGate.circuit->select = 0;

  FlagRedraw();
}

void sel_anchor(EditState *es,int isSet)
{
  HashElem *E;
  SHash *GH;

  if (!XGate.circuit->mg_selection) return;

  ob_touch(XGate.circuit->mg_selection);
  XGate.circuit->mg_selection->s_hasAnchored = (isSet != 0);

  GH = XGate.circuit->mg_selection->s_gates;
  for (E = Hash_first(GH);E;E = Hash_next(GH,E)) {
    GCElement *g = (GCElement*) HashElem_obj(E);
    ob_touch(g);
    g->anchored = (isSet != 0);
  }
}

void sel_setTech(EditState *es,const char *tech)
{
  HashElem *E;
  SHash *GH;

  if (!XGate.circuit->mg_selection) return;

  GH = XGate.circuit->mg_selection->s_gates;
  for (E = Hash_first(GH);E;E = Hash_next(GH,E)) {
    GCElement *g = (GCElement*) HashElem_obj(E);

    if (g->tech && g->typeinfo->num_delays > 0) {
      ob_free(g->tech);
      ob_touch(g);
      g->tech = ob_strdup(tech);
    }

  }
}


void sel_alignHorz(EditState *es)
{
  HashElem *E;
  SHash *GH;
  int y,n;
    
  if (!XGate.circuit->mg_selection) return;

  n = y = 0;
  GH = XGate.circuit->mg_selection->s_gates;
  for (E = Hash_first(GH);E;E = Hash_next(GH,E)) {
    GCElement *g = (GCElement*) HashElem_obj(E);
    y += g->ypos;
    n++;
  }
  if (n < 2) return;
  y /= n;

  n = 0;
  for (E = Hash_first(GH);E;E = Hash_next(GH,E)) {
    GCElement *g = (GCElement*) HashElem_obj(E);
    if (!g->anchored && y != g->ypos)
      gate_moveObject(g,0,y-g->ypos);
    if (g->anchored) n++;
  }
  if (n) message(0,msgLookup("err.gatanchor"));		/* Gate(s) are anchored and can not be moved. */
}

void sel_alignVert(EditState *es)
{
  HashElem *E;
  SHash *GH;
  int x,n;
    
  if (!XGate.circuit->mg_selection) return;

  n = x = 0;
  GH = XGate.circuit->mg_selection->s_gates;
  for (E = Hash_first(GH);E;E = Hash_next(GH,E)) {
    GCElement *g = (GCElement*) HashElem_obj(E);
    x += g->xpos;
    n++;
  }
  if (n < 2) return;
  x /= n;

  n = 0;
  for (E = Hash_first(GH);E;E = Hash_next(GH,E)) {
    GCElement *g = (GCElement*) HashElem_obj(E);
    if (!g->anchored && x != g->xpos)
      gate_moveObject(g,x-g->xpos,0);
    if (g->anchored) n++;
  }
  if (n) message(0,msgLookup("err.gatanchor"));		/* Gate(s) are anchored and can not be moved. */
}

