// $Id$
// class LineGraphEdge

// Copyright (C) 1995 Technische Universitaet Braunschweig, Germany.
// Written by Andreas Zeller <zeller@gnu.org>.
// 
// This file is part of DDD.
// 
// DDD 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 3 of the License, or (at your option) any later version.
// 
// DDD 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 DDD -- see the file COPYING.
// If not, see <http://www.gnu.org/licenses/>.
// 
// DDD is the data display debugger.
// For details, see the DDD World-Wide-Web page, 
// `http://www.gnu.org/software/ddd/',
// or send a mail to the DDD developers <ddd@gnu.org>.

char LineGraphEdge_rcsid[] = 
    "$Id$";

#include "LineGraphE.h"

#include "assert.h"

#include <math.h>
#include "pi.h"
#include "hypot.h"
#include <limits.h>
#include <string.h>
#include <stdlib.h>

#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Intrinsic.h>

#include "GraphNode.h"
#include "LineGESI.h"
#include "printBox.h"


DEFINE_TYPE_INFO_1(LineGraphEdge, GraphEdge)

// Find the points to draw line at

// Clip point P to side SIDE of region B.
void LineGraphEdge::moveToSide(const BoxRegion& b, int side, 
			       BoxPoint& p, const BoxPoint&)
{
    assert(side == North || side == South || side == East || side == West);

    p = b.origin();

    // Fetch points
    if (side & (North | South))
    {
	p[X] += b.space(X) / 2;
	if (side & South)
	    p[Y] += b.space(Y);
    }

    if (side & (East | West))
    {
	p[Y] += b.space(Y) / 2;
	if (side & East)
	    p[X] += b.space(X);
    }
}


// Clip point P to side SIDE of region B centered around C.
void LineGraphEdge::clipToSide(const BoxRegion& b, int side, 
			       BoxPoint& p, const BoxPoint& c)
{
    assert(side == North || side == South || side == East || side == West);

    BoxDimension d1, d2;

    if (side & (North | South))
	d1 = X, d2 = Y;
    else
	d1 = Y, d2 = X;

    int offset;
    if (side & (North | West))
	offset = -1;
    else
	offset = 1;

    if (c[d1] != p[d1] && c[d2] != p[d2])
	p[d1] += offset * (b.space(d2) / 2) * (c[d1] - p[d1]) 
	    / (c[d2] - p[d2]);
    p[d2] += offset * b.space(d2) / 2;
}


// Clip point P to side SIDE of region B centered around C.  Assume
// that B contains a circle.
void LineGraphEdge::clipToCircle(const BoxRegion& b, int /* side */, 
				 BoxPoint& p, const BoxPoint& c)
{
    // assert(side == North || side == South || side == East || side == West);

    double radius = max(b.space(X), b.space(Y)) / 2;
    if (radius > 0.0)
    {
	double hyp = hypot(c[X] - p[X], c[Y] - p[Y]);

	p[X] += BoxCoordinate((radius * (c[X] - p[X])) / hyp);
	p[Y] += BoxCoordinate((radius * (c[Y] - p[Y])) / hyp);
    }
}

// Find line from region B1 centered around C1 to region B2 centered
// around C2.  Resulting line shall be drawn from P1 to P2
void LineGraphEdge::findLine(const BoxPoint& c1, const BoxPoint& c2,
			     const BoxRegion& b1, const BoxRegion& b2, 
			     BoxPoint& p1, BoxPoint& p2, 
			     const GraphGC& gc)
{
    // allow all sizes to begin
    int side1 = North | South | East | West;
    int side2 = North | South | East | West;

    // exclude opposite side
    if (c2[X] > c1[X]) { side1 &= ~West; side2 &= ~East; }
    else               { side1 &= ~East; side2 &= ~West; }

    if (c2[Y] > c1[Y]) { side1 &= ~North; side2 &= ~South; }
    else               { side1 &= ~South; side2 &= ~North; }

    // find edge cutting the line between the two center points c1, c2
    BoxCoordinate dx = abs(c1[X] - c2[X]);
    BoxCoordinate dy = abs(c1[Y] - c2[Y]);

    if (b1.space(Y) * dx > b1.space(X) * dy)
	side1 &= ~(North | South);
    else
	side1 &= ~(East | West);

    if (b2.space(Y) * dx > b2.space(X) * dy)
	side2 &= ~(North | South);
    else
	side2 &= ~(East | West);

    p1 = c1;
    p2 = c2;

    // Select appropriate clipping procedure
    typedef void (*ClipProc)(const BoxRegion& b, int side,
			     BoxPoint& p, const BoxPoint& c);

    struct ClipMapRec {
	EdgeAttachMode mode;
	ClipProc       proc;
    };

    static const ClipMapRec clipMap[] = 
    {
	{Straight, LineGraphEdge::clipToSide},
	{Circle,   LineGraphEdge::clipToCircle},
	{Centered, LineGraphEdge::moveToSide},
	{Straight, 0}
    };

    for (int i = 0; clipMap[i].proc != 0; i++)
	if (gc.edgeAttachMode == clipMap[i].mode)
	{
	    clipMap[i].proc(b1, side1, p1, c2);
	    clipMap[i].proc(b2, side2, p2, c1);

	    return;
	}

    assert(0);
    ::abort();
}


// Draw

void LineGraphEdge::_draw(Widget w, 
			  const BoxRegion& exposed, 
			  const GraphGC& gc) const
{
    if (from() == to())
	drawSelf(w, exposed, gc);
    else
	drawLine(w, exposed, gc);
}

void LineGraphEdge::drawLine(Widget w, 
			     const BoxRegion& exposed, 
			     const GraphGC& gc) const
{
    // Get node starting points
    BoxPoint pos1     = from()->pos();
    BoxRegion region1 = from()->region(gc);
    if (from()->selected())
    {
	pos1             += gc.offsetIfSelected;
	region1.origin() += gc.offsetIfSelected;
    }

    BoxPoint pos2     = to()->pos();
    BoxRegion region2 = to()->region(gc);
    if (to()->selected())
    {
	pos2             += gc.offsetIfSelected;
	region2.origin() += gc.offsetIfSelected;
    }

    // If nodes overlap, don't draw the edge.
    if (region1 <= region2)
	return;

    // Get the line points
    BoxPoint l1, l2;
    findLine(pos1, pos2, region1, region2, l1, l2, gc);

    // If there is no edge (adjacent nodes), don't draw it.
    if (l1 == l2)
	return;

    XDrawLine(XtDisplay(w), XtWindow(w), gc.edgeGC,
	      l1[X], l1[Y], l2[X], l2[Y]);

    // Draw annotation
    BoxPoint anno_pos = annotationPosition(gc);
    if (annotation() != 0 && anno_pos.isValid())
    {
	annotation()->draw(w, anno_pos, exposed, gc);
    }

    // Get arrow angle
    double alpha = atan2(double(l1[Y] - l2[Y]), double(l1[X] - l2[X]));

    // Draw arrow head at L2
    drawArrowHead(w, exposed, gc, l2, alpha);
}


// Draw arrow head at POS
void LineGraphEdge::drawArrowHead(Widget w,
				  const BoxRegion& /* exposed */,
				  const GraphGC& gc,
				  const BoxPoint& pos,
				  double alpha) const
{
    if (!gc.drawArrowHeads || to()->isHint())
	return;

    const double offset = gc.arrowAngle * PI / 180;	// Angle
    const int length    = gc.arrowLength;		// Length

    // Get coordinates
    XPoint points[3];
    points[0].x = pos[X];
    points[0].y = pos[Y];
    points[1].x = short(pos[X] + length * cos(alpha + offset / 2));
    points[1].y = short(pos[Y] + length * sin(alpha + offset / 2));
    points[2].x = short(pos[X] + length * cos(alpha - offset / 2));
    points[2].y = short(pos[Y] + length * sin(alpha - offset / 2));

#if 0
	std::clog << "\nangle = " << (alpha / (PI * 2.0)) * 360.0  << "\n";
	for (int i = 0; i < 3; i++)
	    std::clog << "points[" << i << "] = "
		      << BoxPoint(points[i].x, points[i].y) << "\n";
#endif

    XFillPolygon(XtDisplay(w), XtWindow(w), gc.edgeGC, points,
		 XtNumber(points), Convex, CoordModeOrigin);
}


// Region occupied by edge
BoxRegion LineGraphEdge::region(const GraphGC& gc) const
{
    BoxRegion r;
    if (gc.drawAnnotations && annotation() != 0)
    {
	BoxPoint anno_pos = annotationPosition(gc);
	if (anno_pos.isValid())
	{
	    BoxRegion anno_region = annotation()->region(anno_pos, gc);
	    if (r.origin().isValid())
		r = r | anno_region;
	    else
		r = anno_region;
	}
    }

    if (from() == to())
    {
	BoxRegion region = from()->region(gc);
       	if (from()->selected())
	    region.origin() += gc.offsetIfSelected;

	LineGraphEdgeSelfInfo info(region, gc);

	BoxRegion self_region(info.arc_pos,
			      BoxSize(info.diameter, info.diameter));

	if (r.origin().isValid())
	    r = r | self_region;
	else
	    r = self_region;
    }

    return r;
}


// Draw self edge
void LineGraphEdge::drawSelf(Widget w,
			     const BoxRegion& exposed,
			     const GraphGC& gc) const
{
    assert(from() == to());

    // Get region
    BoxRegion region = from()->region(gc);
    if (from()->selected())
	region.origin() += gc.offsetIfSelected;

    LineGraphEdgeSelfInfo info(region, gc);

    XDrawArc(XtDisplay(w), XtWindow(w), gc.edgeGC, info.arc_pos[X],
	     info.arc_pos[Y], info.diameter, info.diameter,
	     info.arc_start * 64, info.arc_extend * 64);

    if (annotation() != 0)
    {
	// Draw annotation
	annotation()->draw(w, info.anno_pos, exposed, gc);
    }

    // Find arrow angle
    drawArrowHead(w, exposed, gc, info.arrow_pos, info.arrow_alpha);
}

void LineGraphEdge::printSelf(std::ostream& os, const GraphGC &gc) const
{
    assert(from() == to());

    // Get region
    BoxRegion region = from()->region(gc);
    if (from()->selected())
	region.origin() += gc.offsetIfSelected;

    LineGraphEdgeSelfInfo info(region, gc);

    if (gc.printGC->isPostScript())
    {
	int start = (720 - info.arc_start - info.arc_extend) % 360 ;
	int end   = (720 - info.arc_start) % 360 ;

	BoxCoordinate line_width = 1;

	// Draw arc
	os << start << " " << end << " " 
	   << info.radius << " " << info.radius << " "
	   << info.arc_center[X] << " " << info.arc_center[Y] << " " 
	   << line_width << " arc*\n";

	// Now draw the arrow head
	int angle = (720 - info.arrow_angle) % 360;

	os << gc.arrowAngle << " " << gc.arrowLength << " " << angle << " "
	   << info.arrow_pos[X] << " " << info.arrow_pos[Y] << " arrowhead*\n";
    }
    else if (gc.printGC->isFig())
    {
	BoxCoordinate line_width = 1;

	os << ARCARROWHEAD1 << line_width << ARCARROWHEAD2;
	switch (gc.selfEdgeDirection)
	{
	case Clockwise:
	    os << ARCCLOCKWISE;
	    break;
	case Counterclockwise:
	    os << ARCCOUNTERCLOCKWISE;
	    break;
	}
	os << ARCARROWHEAD3
	   << float(info.arc_center[X]) << " " 
	   << float(info.arc_center[Y]) << " ";
	for (int i = 0; i < 3; i++)
	    os << info.fig_pos[i][X] << " " << info.fig_pos[i][Y] << " ";
	os << ARCARROWHEAD4;
    }

    if (annotation() != 0)
    {
	// Print annotation
	annotation()->_print(os, info.anno_pos, gc);
    }
}

BoxPoint LineGraphEdge::annotationPosition(const GraphGC &gc) const
{
    if (from() == to())
    {
	BoxRegion region = from()->region(gc);
	if (from()->selected())
	    region.origin() += gc.offsetIfSelected;

	LineGraphEdgeSelfInfo info(region, gc);
	return info.anno_pos;
    }

    BoxPoint pos1     = from()->pos();
    BoxRegion region1 = from()->region(gc);

    BoxPoint pos2     = to()->pos();
    BoxRegion region2 = to()->region(gc);

    BoxPoint l1, l2;
    findLine(pos1, pos2, region1, region2, l1, l2, gc);

    if (from()->isHint() && to()->isHint())
    {
	// Between two hints -- don't draw anything
	return BoxPoint();
    }

    if (to()->isHint())
    {
	// Draw at hint position
	return to()->pos();
    }

    // Draw at mid-distance
    return l1 + (l2 - l1) / 2;
}

void LineGraphEdge::_print(std::ostream& os, const GraphGC &gc) const
{
    if (from() == to())
    {
	printSelf(os, gc);
	return;
    }

    GraphEdge::_print(os, gc);

    // Print annotation
    if (annotation() != 0)
    {
	BoxPoint anno_pos = annotationPosition(gc);
	if (anno_pos.isValid())
	{
	    annotation()->_print(os, anno_pos, gc);
	}
    }
}
