/*  Part of XPCE --- The SWI-Prolog GUI toolkit

    Author:        Jan Wielemaker and Anjo Anjewierden
    E-mail:        jan@swi.psy.uva.nl
    WWW:           http://www.swi.psy.uva.nl/projects/xpce/
    Copyright (c)  1985-2002, University of Amsterdam
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:

    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.

    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in
       the documentation and/or other materials provided with the
       distribution.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.
*/

#include <h/kernel.h>
#include <h/graphics.h>
#include <math.h>

forwards int	bestConnectionPoint(Device, Name, int, int,
				    Graphical, Handle *, int *, int *);
forwards int	relateConnection(Connection c, Graphical from, Graphical to);

static int	distanceLineToPoint_int(int x1, int y1, int x2, int y2,
					int px, int py);

#define getStartXLine(ln) (ln)->start_x
#define getStartYLine(ln) (ln)->start_y
#define getEndXLine(ln)   (ln)->end_x
#define getEndYLine(ln)   (ln)->end_y

static status
initialiseConnection(Connection c, Graphical from, Graphical to,
		     Link link, Name from_handle, Name to_handle)
{ static Link default_link = NIL;

  TRY(initialiseLine((Line) c, ZERO, ZERO, ZERO, ZERO, DEFAULT));

  if ( isDefault(link) )
  { if ( isNil(default_link) )
      default_link = globalObject(NAME_defaultLink, ClassLink, EAV);
    link = default_link;
  }

  copyLine((Line) c, link->line);

  assign(c, link,        link);
  assign(c, from_handle, isDefault(from_handle) ? (Name) NIL : from_handle);
  assign(c, to_handle,   isDefault(to_handle)   ? (Name) NIL : to_handle);
  assign(c, fixed_from,  isDefault(from_handle) ? OFF : ON);
  assign(c, fixed_to,    isDefault(to_handle)   ? OFF : ON);

  return relateConnection(c, from, to);
}


static status
unlinkConnection(Connection c)
{ if ( notNil(c->from) ) detachConnectionGraphical(c->from, c);
  if ( notNil(c->to) )   detachConnectionGraphical(c->to, c);

  return unlinkGraphical((Graphical) c);
}


static status
relateConnection(Connection c, Graphical from, Graphical to)
{ if ( from != c->from && notNil(c->from) )
  { detachConnectionGraphical(c->from, c);
    assign(c, from, NIL);
  }
  if ( to != c->to && notNil(c->to) )
  { detachConnectionGraphical(c->to, c);
    assign(c, to, NIL);
  }

  if ( notNil(from) )
  { attachConnectionGraphical(from, c);
    assign(c, from, from);
  }

  if ( notNil(to) )
  { attachConnectionGraphical(to, c);
    assign(c, to, to);
  }

  return updateDeviceConnection(c);
}


static status
updateLineConnection(Connection c, Int x1, Int y1, Int x2, Int y2)
{ if ( x1 != getStartXLine((Line) c) ||
       y1 != getStartYLine((Line) c) ||
       x2 != getEndXLine((Line) c) ||
       y2 != getEndYLine((Line) c) )
    send(c, NAME_points, x1, y1, x2, y2, EAV);

  succeed;
}


#define NO_POINTS	0
#define SAME_POINTS	1
#define POINTS_CHANGED	2

static status
getConnectionPointsConnection(Connection c, Graphical from, Graphical to,
			      int *x1, int *y1, int *x2, int *y2)
{ int cxfrom, cyfrom;
  Device dev = c->device;
  Handle hf = FAIL, ht = FAIL;
  int hf_ok = FALSE, ht_ok = FALSE;

  if ( notNil(c->from_handle) )
    hf = getHandleGraphical(from, c->from_handle);
  if ( notNil(c->to_handle) )
    ht = getHandleGraphical(to, c->to_handle);

  if ( c->fixed_from == ON && hf != FAIL )
  { *x1 = valInt(getXHandle(hf, from, dev));
    *y1 = valInt(getYHandle(hf, from, dev));
    hf_ok = TRUE;
  }

  if ( c->fixed_to == ON && ht != FAIL )
  { *x2 = valInt(getXHandle(ht, to, dev));
    *y2 = valInt(getYHandle(ht, to, dev));
    ht_ok = TRUE;
  }

  if ( hf_ok && ht_ok )			/* both fixed and available */
    return POINTS_CHANGED;
  if ( hf_ok && !ht_ok )		/* `to_handle' needs to be fixed */
  { if ( !bestConnectionPoint(dev, c->link->to, *x1, *y1, to, &ht, x2, y2) )
      return NO_POINTS;
    assign(c, to_handle, ht->name);
    return POINTS_CHANGED;
  }
  if ( !hf_ok && ht_ok )		/* `from_handle' needs to be fixed */
  { if ( !bestConnectionPoint(dev, c->link->from, *x2, *y2, from, &hf, x1, y1))
      return NO_POINTS;
    assign(c, from_handle, hf->name);
    return POINTS_CHANGED;
  }

  if ( hf != FAIL && ht != FAIL )
  { if ( getXHandle(hf, from, dev) == getStartXLine((Line) c) &&
	 getYHandle(hf, from, dev) == getStartYLine((Line) c) &&
	 getXHandle(ht, to,   dev) == getEndXLine((Line) c) &&
	 getYHandle(ht, to,   dev) == getEndYLine((Line) c) &&
	 hf->name == c->link->from &&
	 ht->name == c->link->to )
      return SAME_POINTS;
  }

  cxfrom = valInt(getAbsoluteXGraphical(from,dev)) + valInt(from->area->w)/2;
  cyfrom = valInt(getAbsoluteYGraphical(from,dev)) + valInt(from->area->h)/2;

  DEBUG(NAME_absolutePosition,
	Cprintf("getConnectionPointsConnection(): dev=%s\n", pp(dev)));

  TRY(bestConnectionPoint(dev, c->link->to, cxfrom, cyfrom, to, &ht, x2, y2));
  TRY(bestConnectionPoint(dev, c->link->from, *x2, *y2, from, &hf, x1, y1));
  TRY(bestConnectionPoint(dev, c->link->to, *x1, *y1, to, &ht, x2, y2));

  assign(c, from_handle, hf->name);
  assign(c, to_handle,   ht->name);

  return POINTS_CHANGED;
}


/* (JW)	Determine the best point to link up with a handle of specified
	type.  It assumes the connection line starts at (x, y).
 */

static int
bestConnectionPoint(Device dev, Name kind, int x, int y,
		    Graphical gr, Handle *hp, int *xp, int *yp)
{ int bestx = 0, besty = 0, bestd=10000000, bestdc=10000000;
  int cx, cy;
  int X, Y, D, DC;
  int found = FAIL;
  Cell cell;

  DEBUG(NAME_handle,
	Cprintf("bestConnectionPoint(%s, %s, %d, %d, %s) --> ",
		pp(dev), pp(kind), x, y, pp(gr)));

#define FindAHandle \
  { Handle h = cell->value; \
    if ( h->kind == kind ) \
    { if ( !found ) \
      { *hp = h; \
	found = SUCCEED; \
      } else \
	goto findbest; \
    } \
  }


  if ( notNil(gr->handles) )
  { for_cell(cell, gr->handles)
      FindAHandle;
  }
  if ( notNil(classOfObject(gr)->handles) )
  { for_cell(cell, classOfObject(gr)->handles)
      FindAHandle;
  }

  if ( found )
  { Int hx, hy;
    getXYHandle(*hp, gr, dev, &hx, &hy);

    *xp = valInt(hx);
    *yp = valInt(hy);

    DEBUG(NAME_handle, Cprintf("%s, %d, %d\n", pp((*hp)->name), *xp, *yp));
    succeed;
  }

findbest:

  cx = valInt(getAbsoluteXGraphical(gr, dev)) + valInt(gr->area->w)/2;
  cy = valInt(getAbsoluteYGraphical(gr, dev)) + valInt(gr->area->h)/2;

#define FindBestHandle \
  { Handle h = cell->value; \
    Int hx, hy; \
    double dx, dy; \
    if ( h->kind != kind ) \
      continue; \
    getXYHandle(h, gr, dev, &hx, &hy); \
    X = valInt(hx); Y = valInt(hy); \
    dx = (double)(x-X); dy = (double)(y-Y); \
    D = rdouble(sqrt(dx*dx+dy*dy)); \
    DC = distanceLineToPoint_int(x, y, X, Y, cx, cy); \
    if ((D + DC < bestd + bestdc) || found == FAIL) \
    { bestd = D; \
      bestdc = DC; \
      bestx = X; \
      besty = Y; \
      *hp = h; \
      found = SUCCEED; \
    } \
  }

  if ( notNil(gr->handles) )
  { for_cell(cell, gr->handles)
      FindBestHandle;
  }
  if ( notNil(classOfObject(gr)->handles) )
  { for_cell(cell, classOfObject(gr)->handles)
      FindBestHandle;
  }

  if ( found == FAIL )
  { DEBUG(NAME_handle, Cprintf("FAIL\n"));
    fail;
  }

  DEBUG(NAME_handle, Cprintf("%s, %d, %d\n", pp((*hp)->name), bestx, besty));

  *xp = bestx;
  *yp = besty;

  return found;
}


status
updateHideExposeConnection(Connection c)
{ Device ldev;

  if ( isNil(ldev = c->device) )
    succeed;

  if ( ldev == c->from->device && ldev == c->to->device )
  { if ( beforeChain(ldev->graphicals, c->from, c->to) )
      exposeGraphical(c, c->to);
    else
      exposeGraphical(c, c->from);
  } else
    exposeGraphical(c, DEFAULT);

  succeed;
}


static status
computeConnection(Connection c)
{ if ( notNil(c->request_compute) )
  { Graphical from = c->from;
    Graphical to = c->to;
    Device dev = c->device;

    if ( getIsDisplayedGraphical(from, dev) == ON &&
	 getIsDisplayedGraphical(to, dev) == ON )
    { int x1, y1, x2, y2;

      switch( getConnectionPointsConnection(c, from, to, &x1, &y1, &x2, &y2) )
      { case POINTS_CHANGED:
	  updateLineConnection(c, toInt(x1), toInt(y1), toInt(x2), toInt(y2));
	  /*FALLTHROUGH*/
	case SAME_POINTS:
	  computeLine((Line)c);
	  DisplayedGraphical(c, ON);
	  assign(c, request_compute, NIL);

	  succeed;
	case NO_POINTS:
	  break;
      }
    }

    assign(c, request_compute, NIL);
    return DisplayedGraphical(c, OFF);
  }

  succeed;
}


status
updateDeviceConnection(Connection c)
{ Graphical from = c->from;
  Graphical to = c->to;
  Device device;

  if ( isNil(from) || isNil(to) ||
       !(device = getCommonDeviceGraphical(c->from, c->to)) )
    return DeviceGraphical(c, NIL);

  DeviceGraphical(c, device);
  return requestComputeGraphical(c, DEFAULT);
}


static status
updateLinkAttributesConnection(Connection c)
{ Line proto = c->link->line;

  CHANGING_GRAPHICAL(c,
	assign(c, texture, proto->texture);
	assign(c, pen, proto->pen);
	setArrowsJoint((Joint) c, proto->first_arrow, proto->second_arrow);
	changedEntireImageGraphical(c));

  return requestComputeGraphical(c, DEFAULT);
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Connections cannot be moved themselves.  The only method understood by
a connection that changes it's start and end-position is ->points.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

static int in_points = 0;

static status
pointsConnection(Connection c, Int x1, Int y1, Int x2, Int y2)
{ in_points++;
  pointsLine((Line) c, x1, y1, x2, y2);
  in_points--;

  succeed;
}


static status
geometryConnection(Connection c, Int x, Int y, Int w, Int h)
{ if ( in_points > 0 )
    return geometryGraphical(c, x, y, w, h);

  succeed;
}


static Graphical
getOppositeConnection(Connection c, Graphical gr)
{ if ( c->to == gr )
    answer(c->from);
  if ( c->from == gr )
    answer(c->to);

  fail;
}


static status
eventConnection(Connection c, EventObj ev)
{ if ( eventGraphical(c, ev) )
    succeed;

  if ( c->active != OFF )
  { Chain recognisers;
    Cell cell;

    TRY( recognisers = getAllRecognisersGraphical(c->link->line, OFF) );

    for_cell(cell, recognisers)
      if ( qadSendv(cell->value, NAME_event, 1, (Any*)&ev) )
	succeed;
  }

  fail;
}


		 /*******************************
		 *	 CLASS DECLARATION	*
		 *******************************/

/* Type declarations */

static char *T_initialise[] =
        { "from=graphical", "to=graphical", "link=[link]", "handle_from=[name]*", "handle_to=[name]*" };
static char *T_points[] =
        { "start_x=[int]", "start_y=[int]", "end_x=[int]", "end_y=[int]" };
static char *T_geometry[] =
        { "x=[int]", "y=[int]", "width=[int]", "height=[int]" };
static char *T_relate[] =
        { "from=graphical*", "to=graphical*" };

/* Instance Variables */

static vardecl var_connection[] =
{ IV(NAME_link, "link", IV_GET,
     NAME_relation, "Generic definition of the link"),
  IV(NAME_from, "graphical*", IV_GET,
     NAME_relation, "Graphical at `from' side"),
  IV(NAME_to, "graphical*", IV_GET,
     NAME_relation, "Graphical at `to' side"),
  IV(NAME_fromHandle, "name*", IV_GET,
     NAME_relation, "Name of 1st handle link is connected to"),
  IV(NAME_toHandle, "name*", IV_GET,
     NAME_relation, "Name of 2nd handle link is connected to"),
  IV(NAME_fixedFrom, "bool", IV_GET,
     NAME_relation, "From side is fixed"),
  IV(NAME_fixedTo, "bool", IV_GET,
     NAME_relation, "To side is fixed")
};

/* Send Methods */

static senddecl send_connection[] =
{ SM(NAME_compute, 0, NULL, computeConnection,
     DEFAULT, "Recompute the line"),
  SM(NAME_geometry, 4, T_geometry, geometryConnection,
     DEFAULT, "Do nothing: constrained by connected graphicals"),
  SM(NAME_initialise, 5, T_initialise, initialiseConnection,
     DEFAULT, "Create from graphicals, link and handle names"),
  SM(NAME_points, 4, T_points, pointsConnection,
     DEFAULT, "Set X1, Y1, X2, Y2"),
  SM(NAME_unlink, 0, NULL, unlinkConnection,
     DEFAULT, "Detach from graphicals"),
  SM(NAME_relate, 2, T_relate, relateConnection,
     NAME_relation, "Specify the graphicals related"),
  SM(NAME_event, 1, "event", eventConnection,
     NAME_event, "Process an event"),
  SM(NAME_updateLinkAttributes, 0, NULL, updateLinkAttributesConnection,
     NAME_update, "Re-read the link properties")
};

/* Get Methods */

static getdecl get_connection[] =
{ GM(NAME_opposite, 1, "graphical", "graphical", getOppositeConnection,
     NAME_relation, "Other side of the connection"),
  GM(NAME_idealLength, 0, "0..", NULL, getFailObject,
     NAME_layout, "Ideal length for `graphical->layout'")
};

/* Resources */

#define rc_connection NULL
/*
static classvardecl rc_connection[] =
{
};
*/

/* Class Declaration */

static Name connection_termnames[] = { NAME_from, NAME_to, NAME_link, NAME_fromHandle, NAME_toHandle };

ClassDecl(connection_decls,
          var_connection, send_connection, get_connection, rc_connection,
          5, connection_termnames,
          "$Rev$");

status
makeClassConnection(Class class)
{ declareClass(class, &connection_decls);

  cloneStyleClass(class, NAME_relation);
  distanceLineToPoint_int(0, 0, 10, 10, 0, 10);	/* initialise */

  succeed;
}



#define STEP 20
#define ENTRIES 200
#define EPS ((float) (ENTRIES / STEP))
#define EMS (ENTRIES * STEP)

static int
distanceLineToPoint_int(int x1, int y1, int x2, int y2, int px, int py)
{ static int atable[ENTRIES+1];
  static int done = FALSE;
  int a, d;
  int dx = x2-x1;
  int dy = y2-y1;

  if ( y1 == y2 || abs(dy) * 16 < abs(dx) )
    return abs(y1 - py);
  if ( x1 == x2 || abs(dx) * 16 < abs(dy) )
    return abs(x1 - px);

  if ( !done )
  { int i;

    for(i=0; i <= ENTRIES; i++)
      atable[i] = rfloat((float) ENTRIES * sqrt(1.0 + (i/EPS)*(i/EPS)));

    done = TRUE;
  }

  a = (ENTRIES*(dy)) / dx;
  if ( a < -EMS )
  { a = -EMS;
  } else
  { if ( a > EMS )
      a = EMS;
  }

  d = (a*(px-x1) + ENTRIES*(y1-py)) / atable[abs(a)/STEP];

  return abs(d);
}
