/***************************************************************************
 *   Copyright (C) 2004, 2005 Thomas Nagy                                  *
 *   tnagy2^8@yahoo.fr                                                     *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License version 2        *
 *   as published by the Free Software Foundation (see COPYING)            *
 *                                                                         *
 *   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.                          *
 ***************************************************************************/

#include <math.h>
#include <qpainter.h>
#include <qcolor.h>
#include <kdebug.h>
#include "DCanvasItem.h"
#include "DCanvasLink.h"
#include "settings.h"

static const double _C_ = 8;

DCanvasLinkEllipse::DCanvasLinkEllipse(QCanvas* canvas) : QCanvasEllipse(canvas)
{
	setSize(1, 1);
	show();
	move( -30, -30 );
}

DCanvasLink::DCanvasLink(QCanvas* canvas, DCanvasItem* orig) : QCanvasPolygon(canvas)
{
	m_orig = orig;
	m_dest = NULL;
	m_candraw = false;
	m_ellipse = new DCanvasLinkEllipse(canvas);

	move(0,0);
	m_pa.putPoints(0, 4, 0,0,  0,0,  0,0,  0,0 );

	m_splineDown.putPoints(0, 4, 0,0,  0,0,  0,0,  0,0 );
	m_splineUp.putPoints(0, 4, 0,0,  0,0,  0,0,  0,0 );
	m_spline.putPoints(0, 4, 0,0,  0,0,  0,0,  0,0 );
	updateColor();
}

DCanvasLink::~DCanvasLink()
{
	delete m_ellipse;
	hide();
}

void DCanvasLink::setDest(DCanvasItem* dest)
{
	m_dest = dest;
}

void DCanvasLink::updateColor()
{
	QBrush brush;
	QPen pen;
	brush.setColor( m_orig->control()->m_col_link );
	pen.setColor( m_orig->control()->m_col_link );
	brush.setStyle( QBrush::SolidPattern );
	pen.setWidth( m_orig->control()->m_showDirectionSize );

	setBrush(brush);
	setPen(pen);
	m_ellipse->setBrush(brush);
	int s = m_orig->control()->m_showDirectionSize;
	m_ellipse->setSize(s+5, s+5);
}

void DCanvasLink::updatePos()
{
	invalidate();

	if (!m_dest)
	{
		setPosHide();
		return;
	}

	switch (m_orig->control()->m_linkstyle)
	{
		case Settings::EnumLinkStyle::spline:
			setPosWeb();
			break;

		case Settings::EnumLinkStyle::thickspline:
			setPosThickWeb();
			break;

		default:
			setPosLines();
	}
}

void DCanvasLink::setPosHide()
{
	//kdDebug()<<"setPosHide"<<endl;
	if (m_ellipse->x() > -120) m_ellipse->move(-130, -130);
	for (int i=0; i<4; i++) m_pa[i] = QPoint(0, 0);
	setPoints(m_pa);
	move(0, 0);
}

int DCanvasLink::IdOrig()
{
	return m_orig->Id();
}

int DCanvasLink::IdDest()
{
	return m_dest->Id();
}

void DCanvasLink::setPosLines()
{
	if (m_ellipse->x() > -120) m_ellipse->move(-130, -130);

	// join the middles of the two rectangles
	double x1 = m_orig->x() + m_orig->width()/2;
	double y1 = m_orig->y() + m_orig->height()/2;
	double x2 = m_dest->x() + m_dest->width()/2;
	double y2 = m_dest->y() + m_dest->height()/2;

	double X1 = x2 - x1;
	double Y1 = y2 - y1;

	if (QABS(X1) < 11 && QABS(Y1) < 11)
	{
		setPosHide();
		return;
	}

	double dist = 1;
	double coeff = Y1*Y1/(Y1*Y1+X1*X1);

	double deltax = dist * sqrt( coeff );
	double deltay = dist * sqrt( 1 - coeff );

	if (X1 != 0) if (Y1/X1 < 0) deltay *= -1;

	int s = m_orig->control()->m_showDirectionSize;

	m_pa[0] = QPoint((int) (x1 + deltax), (int) (y1 - deltay));
	m_pa[1] = QPoint((int) (x2 + s*deltax), (int) (y2 - s*deltay));
	m_pa[2] = QPoint((int) (x2 - s*deltax), (int) (y2 + s*deltay));
	m_pa[3] = QPoint((int) (x1 - deltax), (int) (y1 + deltay));

	setPoints(m_pa);
}

void DCanvasLink::drawShape(QPainter &p)
{
	if (!m_dest) return;

	QPen pen;
	switch (m_orig->control()->m_linkstyle)
	{
		case Settings::EnumLinkStyle::spline:
			if (m_candraw) p.drawPolyline( m_pa.cubicBezier() );
			else QCanvasPolygon::drawShape( p );
			break;

		case Settings::EnumLinkStyle::thickspline:
			if (m_candraw) p.drawPolygon( m_spline );
			else QCanvasPolygon::drawShape( p );
			break;

		default:
			QCanvasPolygon::drawShape( p );
	}
}

void DCanvasLink::setPosWeb()
{
	m_candraw = true;
	int diffx=0, diffy=0;

	if ( m_dest->x() > _C_ + m_orig->x() + m_orig->width() )
	{
		// parent is on the left
		diffx = ((int) m_dest->x()) - ( ((int) m_orig->x()) + m_orig->width() );

		m_pa[0]=QPoint( (int)(m_orig->x() + m_orig->width()), (int)(m_orig->y() + m_orig->height()/2));
		m_pa[1]=QPoint( (int)(m_dest->x() - (1*diffx)/3    ), (int)(m_orig->y() + m_orig->height()/2));
		m_pa[2]=QPoint( (int)(m_dest->x() - (2*diffx)/3    ), (int)(m_dest->y() + m_dest->height()/2));
		m_pa[3]=QPoint( (int)(m_dest->x()                  ), (int)(m_dest->y() + m_dest->height()/2));

		m_ellipse->move((int)(m_orig->x() + m_orig->width()), (int)(m_orig->y() + m_orig->height()/2));
	}
	else if ( m_dest->x() + m_dest->width() + _C_ < m_orig->x() )
	{
		// parent is on the right
		diffx = ((int) m_orig->x()) - ( ((int) m_dest->x()) + m_dest->width() );

		m_pa[0]=QPoint( (int)m_orig->x()                  , (int)(m_orig->y() + m_orig->height()/2));
		m_pa[1]=QPoint( (int)m_orig->x() - (2*diffx)/3    , (int)(m_orig->y() + m_orig->height()/2));
		m_pa[2]=QPoint( (int)m_orig->x() - (1*diffx)/3    , (int)(m_dest->y() + m_dest->height()/2));
		m_pa[3]=QPoint( (int)m_dest->x() + m_dest->width(), (int)(m_dest->y() + m_dest->height()/2));

		m_ellipse->move((int)m_orig->x(), (int)(m_orig->y() + m_orig->height()/2));
	}
	else if ( m_dest->y() + m_dest->height() + _C_ < m_orig->y() )
	{
		// parent is below
		diffy = ((int) m_orig->y()) - ((int) m_dest->y()) - m_dest->height();

		m_pa[0]=QPoint( (int)m_orig->x() + m_orig->width()/2, (int)(m_orig->y()));
		m_pa[1]=QPoint( (int)m_orig->x() + m_orig->width()/2, (int)(m_orig->y() - (2*diffy)/3));
		m_pa[2]=QPoint( (int)m_dest->x() + m_dest->width()/2, (int)(m_orig->y() - (1*diffy)/3));
		m_pa[3]=QPoint( (int)m_dest->x() + m_dest->width()/2, (int)(m_dest->y() + m_dest->height()));

		m_ellipse->move( (int)m_orig->x() + m_orig->width()/2, (int)(m_orig->y()));
	}
	else if ( m_dest->y() > m_orig->y() + m_orig->height() + _C_ )
	{
		// parent is above
		diffy = ((int) m_dest->y()) - ((int) m_orig->y()) - m_orig->height();

		m_pa[0]=QPoint( (int)m_orig->x() + m_orig->width()/2, (int)(m_orig->y() + m_orig->height()));
		m_pa[1]=QPoint( (int)m_orig->x() + m_orig->width()/2, (int)(m_dest->y() - (1*diffy)/3));
		m_pa[2]=QPoint( (int)m_dest->x() + m_dest->width()/2, (int)(m_dest->y() - (2*diffy)/3));
		m_pa[3]=QPoint( (int)m_dest->x() + m_dest->width()/2, (int)(m_dest->y()));

		m_ellipse->move( (int)m_orig->x() + m_orig->width()/2, (int)(m_orig->y() + m_orig->height()) );
	}
	else
	{
		// when we dont know how to link, use straight lines
		setPosLines();
		m_candraw = false;
		return;
	}

	QPointArray bez1 = m_pa.cubicBezier();
	QPointArray bez2 = m_pa.cubicBezier();

	if ( QABS(m_orig->x()-m_dest->x()) > QABS(m_orig->y()-m_dest->y()) )
	{
		bez1.translate(0,  9);
		bez2.translate(0, -9);
	}
	else
	{
		bez1.translate( 9, 0);
		bez2.translate(-9, 0);
	}

	QPointArray poly(bez1.count()+bez2.count());
	int n=0;
	for (unsigned int i=0; i<bez1.count(); i++)
	{
		poly[n++] = bez1.point(i);
	}
	for (unsigned int i=0; i<bez2.count(); i++)
	{
		poly[n++] = bez2.point(i);
	}
	setPoints( poly );
}

void DCanvasLink::setPosThickWeb()
{
	if (m_ellipse->x() > -120) m_ellipse->move(-130, -130);
	int s = m_orig->control()->m_showDirectionSize;

	m_candraw = true;
	int diffx=0, diffy=0;

	QPoint points_o[4];
	QPoint points_d[4];
	// left
	points_o[0] = QPoint( (int)(m_orig->x()), (int)(m_orig->y() + m_orig->height()/2) );
	// top
	points_o[1] = QPoint( (int)(m_orig->x() + m_orig->width()/2), (int)(m_orig->y()) );
	// right
	points_o[2] = QPoint( (int)(m_orig->x() + m_orig->width()), (int)(m_orig->y() + m_orig->height()/2) );
	// bottom
	points_o[3] = QPoint( (int)(m_orig->x() + m_orig->width()/2), (int)(m_orig->y() + m_orig->height()) );

	// left
	points_d[0] = QPoint( (int)(m_dest->x()), (int)(m_dest->y() + m_dest->height()/2) );
	// top
	points_d[1] = QPoint( (int)(m_dest->x() + m_dest->width()/2), (int)(m_dest->y()) );
	// right
	points_d[2] = QPoint( (int)(m_dest->x() + m_dest->width()), (int)(m_dest->y() + m_dest->height()/2) );
	// bottom
	points_d[3] = QPoint( (int)(m_dest->x() + m_dest->width()/2), (int)(m_dest->y() + m_dest->height()) );

	// figure out closest distance
	double shortest = 0;
	// compare each point from the first box to each point of the second box to see which pair is closer
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 4; j++) {
			double distance = sqrt(pow(points_o[i].x()-points_d[j].x(), 2) + pow(points_o[i].y()-points_d[j].y(), 2));
			if (shortest == 0 || distance < shortest) {
				shortest=distance;

				int x1 = (int)points_o[i].x();
				int y1 = (int)points_o[i].y();
				int x2 = (int)points_d[j].x();
				int y2 = (int)points_d[j].y();

				diffx = abs(x1 - x2);
				diffy = abs(y1 - y2);

				int xjump = 2 * diffx / 3;
				int yjump = 2 * diffy / 3;

				int xdir1 = 0;
				int ydir1 = 0;
				int xdir2 = 0;
				int ydir2 = 0;
				if (i == 0) {
					// left side
					xdir1 = -1;
				} else if (i == 1) {
					// top side
					ydir1 = -1;
				} else if (i == 2) {
					// right side
					xdir1 = 1;
				} else if (i == 3) {
					// bottom side
					ydir1 = 1;
				}

				if (j == 0) {
					// left side
					xdir2 = -1;
				} else if (j == 1) {
					// top side
					ydir2 = -1;
				} else if (j == 2) {
					// right side
					xdir2 = 1;
				} else if (j == 3) {
					// bottom side
					ydir2 = 1;
				}

				m_pa[0]=points_o[i];
				m_pa[1]=QPoint( x1 + xdir1*xjump , y1 + ydir1*yjump );
				m_pa[2]=QPoint( x2 + xdir2*xjump , y2 + ydir2*yjump );
				m_pa[3]=points_d[j];

				if (j == 0 || j == 2) {
					// left or right side
					m_splineUp[0] = m_pa[0];
					m_splineUp[1] = m_pa[1] + QPoint(0, s/2);
					m_splineUp[2] = m_pa[2] + QPoint(0, s);
					m_splineUp[3] = m_pa[3] + QPoint(0, s);

					m_splineDown[0] = m_pa[0];
					m_splineDown[1] = m_pa[1] - QPoint(0, s/2);
					m_splineDown[2] = m_pa[2] - QPoint(0, s);
					m_splineDown[3] = m_pa[3] - QPoint(0, s);
				} else {
					// top or bottom side
					m_splineUp[0] = m_pa[0];
					m_splineUp[1] = m_pa[1] + QPoint(s/2, 0);
					m_splineUp[2] = m_pa[2] + QPoint(s, 0);
					m_splineUp[3] = m_pa[3] + QPoint(s, 0);

					m_splineDown[0] = m_pa[0];
					m_splineDown[1] = m_pa[1] - QPoint(s/2, 0);
					m_splineDown[2] = m_pa[2] - QPoint(s, 0);
					m_splineDown[3] = m_pa[3] - QPoint(s, 0);
				}
			}
		}
	}

	QPointArray bez1 = m_pa.cubicBezier();
	QPointArray bez2 = m_pa.cubicBezier();

	if ( QABS(m_orig->x()-m_dest->x()) > QABS(m_orig->y()-m_dest->y()) )
	{
		bez1.translate(0,  9);
		bez2.translate(0, -9);
	}
	else
	{
		bez1.translate( 9, 0);
		bez2.translate(-9, 0);
	}

	QPointArray poly(bez1.count()+bez2.count());
	int n=0;
	for (unsigned int i=0; i<bez1.count(); i++)
	{
		poly[n++] = bez1.point(i);
	}
	for (unsigned int i=0; i<bez2.count(); i++)
	{
		poly[n++] = bez2.point(i);
	}
	setPoints( poly );

	bez1 = m_splineUp.cubicBezier();
	bez2 = m_splineDown.cubicBezier();
	m_spline.resize(bez1.count()+bez2.count());

	n=0;
	for (unsigned int i=0; i<bez1.count(); i++)
	{
		m_spline[n++] = bez1.point(i);
	}
	for (int i=bez2.count()-1; i>=0; i--)
	{
		m_spline[n++] = bez2.point(i);
	}
}
