#include <math.h>

#include "linkablemapobj.h"
#include "branchobj.h"
#include "mapeditor.h"

#include "version.h"


/////////////////////////////////////////////////////////////////
// LinkableMapObj
/////////////////////////////////////////////////////////////////

LinkableMapObj::LinkableMapObj():MapObj()
{
  //  cout << "Const LinkableMapObj ()\n";
    init ();
}

LinkableMapObj::LinkableMapObj(QCanvas* c) :MapObj(c)
{
//    cout << "Const LinkableMapObj\n";
    init ();
}

LinkableMapObj::LinkableMapObj (LinkableMapObj* lmo) : MapObj (lmo->canvas)
{
    copy (lmo);
}

LinkableMapObj::~LinkableMapObj()
{
    delete (bottomline);
    delete (selbox);
	delete (frame);
	delLink();
}

void LinkableMapObj::delLink()
{
	switch (style)
	{
		case StyleLine:
			delete (l);
			break;
		case StyleParabel:
			segment.clear();
			break;
		case StylePolyLine:
			delete (p);
			delete (l);
			break;
		case StylePolyParabel:
			delete (p);
			segment.clear();
			break;
		default:
			break;
	}		
}

void LinkableMapObj::init ()
{
    depth=-1;	
	mapEditor=NULL;
    childObj=NULL;
    parObj=NULL;
    parObjTmpBuf=NULL;
    parPos=QPoint(0,0);
    childPos=QPoint(0,0);
	link2ParPos=false;
    l=NULL;
    orientation=OrientUndef;
    linkwidth=20;		
	thickness_start=8;
    style=StyleUndef;
	linkpos=LinkBottom;
    segment.setAutoDelete (TRUE);
    arcsegs=13;
	QPointArray pa(arcsegs*2+2);
    
    bottomline=new QCanvasLine(canvas);
    bottomline->setPen( QPen(linkcolor, 1) );
    bottomline->setZ(Z_LINK);
    bottomline->show();

    // Prepare showing the selection of a MapObj
    selbox = new QCanvasRectangle (canvas);
    selbox->setZ(Z_SELBOX);
    selbox->setBrush( QColor(255,255,0) );
    selbox->setPen( QPen(QColor(255,255,0) ));
    selbox->hide();
    selected=false;

	hideLinkUnselected=false;

	topPad=botPad=leftPad=rightPad=0;

	// initialize frame
	frame = new FrameObj (canvas);
	
	repositionRequest=false;

	// Rel Positions
	relPos=QPoint(0,0);
	useRelPos=false;
	useOrientation=true;
}

void LinkableMapObj::copy (LinkableMapObj* other)
{
    MapObj::copy(other);
	bboxTotal=other->bboxTotal;
    setLinkStyle(other->style);
    setLinkColor (other->linkcolor);
	relPos=other->relPos;
	useOrientation=other->useOrientation;

}

void LinkableMapObj::setChildObj(LinkableMapObj* o)
{
    childObj=o;
}

void LinkableMapObj::setParObj(LinkableMapObj* o)
{
    parObj=o;
	mapEditor=parObj->getMapEditor();
}

void LinkableMapObj::setParObjTmp(LinkableMapObj*,QPoint,int)
{
}

void LinkableMapObj::unsetParObjTmp()
{
}

bool LinkableMapObj::hasParObjTmp()
{
	if (parObjTmpBuf) return true;
	return false;
}

void LinkableMapObj::setUseRelPos (const bool &b)
{
	useRelPos=b;
}

void LinkableMapObj::setRelPos()
{
	if (parObj)
	{	
		relPos.setX (absPos.x() - parObj->getChildPos().x() );
		relPos.setY (absPos.y() - parObj->getChildPos().y() );

		parObj->calcBBoxSize();
		parObj->requestReposition();
	}	
}

void LinkableMapObj::setRelPos(const QPoint &p)
{
	relPos=p;
	if (parObj)
	{	parObj->calcBBoxSize();
		parObj->requestReposition();
	}	
}

int LinkableMapObj::getTopPad()
{
	return topPad;
}

int LinkableMapObj::getLeftPad()
{
	return leftPad;
}

int LinkableMapObj::getRightPad()
{
	return rightPad;
}

LinkStyle LinkableMapObj::getDefLinkStyle ()
{
	if (!mapEditor) return StyleUndef;

	LinkStyle ls=mapEditor->getLinkStyle();
	switch (ls)
	{
		case StyleLine: 
			return ls;
			break;
		case StyleParabel:
			return ls;
			break;
		case StylePolyLine:	
			if (depth>1)
				return StyleLine;
			else	
				return ls;
			break;
		case StylePolyParabel:	
			if (depth>1)
				return StyleParabel;
			else	
				return ls;
			break;
		default: 
			break;	
	}	
	return StyleUndef;
}

void LinkableMapObj::setLinkStyle(LinkStyle newstyle)
{
	//if (newstyle=style) return;
	delLink();
		
	style=newstyle;

    if (childObj!=NULL && parObj != NULL)
    {
		int i;
		QCanvasLine* cl;
		switch (style)
		{
			case StyleUndef:
				bottomline->hide();
				break;
			case StyleLine: 
				l = new QCanvasLine(canvas);
				l->setPen( QPen(linkcolor, 1) );
				l->setZ(Z_LINK);
				if (visible)
					l->show();
				else
					l->hide();
				break;
			case StyleParabel:
				for (i=0;i<arcsegs;i++)
				{
					cl = new QCanvasLine(canvas);
					cl->setPen( QPen(linkcolor, 1) );
					cl->setPoints( 0,0,i*10,100);
					cl->setZ(Z_LINK);
					if (visible)
						cl->show();
					else
						cl->hide();
					segment.append(cl);
				}
				pa0.resize (arcsegs+1);
				break;
			case StylePolyLine:	
				p = new QCanvasPolygon(canvas);
				p->setBrush( linkcolor );
				p->setZ(Z_LINK);
				if (visible)
					p->show();
				else
					p->hide();
				pa0.resize (3);
				// TODO a bit awkward: draw the lines additionally to polygon, to avoid
				// missing pixels, when polygon is extremly flat
				l = new QCanvasLine(canvas);
				l->setPen( QPen(linkcolor, 1) );
				l->setZ(Z_LINK);
				if (visible)
					l->show();
				else
					l->hide();
				break;
			case StylePolyParabel:	
				p = new QCanvasPolygon(canvas);
				p->setBrush( linkcolor );
				p->setZ(Z_LINK);
				if (visible)
					p->show();
				else
					p->hide();
				pa0.resize (arcsegs*2+2);
				pa1.resize (arcsegs+1);
				pa2.resize (arcsegs+1);

				// TODO a bit awkward: draw the lines additionally 
				// to polygon, to avoid missing pixels, 
				// if polygon is extremly flat
				for (i=0;i<arcsegs;i++)
				{
					cl = new QCanvasLine(canvas);
					cl->setPen( QPen(linkcolor, 1) );
					cl->setPoints( 0,0,i*10,100);
					cl->setZ(Z_LINK);
					if (visible)
						cl->show();
					else
						cl->hide();
					segment.append(cl);
				}
				break;
			default: 
				break;	
		}	
	} 
}

LinkStyle LinkableMapObj::getLinkStyle()
{
	return style;
}

void LinkableMapObj::setHideLinkUnselected(bool b)
{
	hideLinkUnselected=b;
	setVisibility (visible);
	updateLink();
}

bool LinkableMapObj::getHideLinkUnselected()
{
	return hideLinkUnselected;
}

void LinkableMapObj::setLinkPos(LinkPos lp)
{
	linkpos=lp;
}

LinkPos LinkableMapObj::getLinkPos()
{
	return linkpos;
}


void LinkableMapObj::setLinkColor()
{
	// Overloaded in BranchObj and childs
	// here only set default color
	if (mapEditor)
		setLinkColor (mapEditor->getDefLinkColor());
}

void LinkableMapObj::setLinkColor(QColor col)
{
	linkcolor=col;
    bottomline->setPen( QPen(linkcolor, 1) );
	QCanvasLine *cl;
	switch (style)
	{
		case StyleLine:
			l->setPen( QPen(col,1));
			break;	
		case StyleParabel:	
			for (cl=segment.first(); cl; cl=segment.next() )
				cl->setPen( QPen(col,1));
			break;
		case StylePolyLine:
			p->setBrush( QBrush(col));
			l->setPen( QPen(col,1));
			break;
		case StylePolyParabel:	
			p->setBrush( QBrush(col));
			for (cl=segment.first(); cl; cl=segment.next() )
				cl->setPen( QPen(col,1));
			break;
		default:
			break;
	} // switch (style)	
}

QColor LinkableMapObj::getLinkColor()
{
	return linkcolor;
}

FrameType LinkableMapObj::getFrameType()
{
	return frame->getFrameType();
}

void LinkableMapObj::setFrameType(const FrameType &t)
{
	frame->setFrameType(t);
	calcBBoxSize();
	positionBBox();
	requestReposition();
}

void LinkableMapObj::setFrameType(const QString &t)
{
	frame->setFrameType(t);
	calcBBoxSize();
	positionBBox();
	requestReposition();
}

void LinkableMapObj::setVisibility (bool v)
{
	QCanvasLine* cl;
	MapObj::setVisibility (v);
	bool visnow=visible;

	// We can hide the link, while object is not selected
	if (hideLinkUnselected && !selected)
		visnow=false;

	if (visnow) 
	{
		bottomline->show();
		switch (style)
		{
			case StyleLine:
				if (l) l->show();
				break;
			case StyleParabel:	
				for (cl=segment.first(); cl; cl=segment.next() )
					cl->show();
				break;	
			case StylePolyLine:
				if (p) p->show();
				if (l) l->show();
				break;
			case StylePolyParabel:	
				for (cl=segment.first(); cl; cl=segment.next() )
					cl->show();
				if (p) p->show();
				break;
			default:
				break;
		}
	} else 
	{
		bottomline->hide();
		switch (style)
		{
			case StyleLine:
				if (l) l->hide();
				break;
			case StyleParabel:	
				for (cl=segment.first(); cl; cl=segment.next() )
					cl->hide();
				break;	
			case StylePolyLine:
				if (p) p->hide();
				if (l) l->hide();
				break;
			case StylePolyParabel:	
				for (cl=segment.first(); cl; cl=segment.next() )
					cl->hide();
				if (p) p->hide();
				break;
			default:
				break;
		}
	}	
}

void LinkableMapObj::updateLink()
{
    // needs:
    //	childPos of parent
    //	orient   of parent
    //	style
    // 
    // sets:
    //	orientation
    //	childPos	(by calling setDockPos())
    //	parPos		(by calling setDockPos())
	//  bottomlineY
    //	drawing of the link itself

	// updateLink is called from move, but called from constructor we don't
	// have parents yet...
	if (style==StyleUndef) return;	

	if (frame->getFrameType() == NoFrame)
		linkpos=LinkBottom;
	else	
		linkpos=LinkMiddle;
	switch (linkpos)
	{
		case LinkMiddle:
			bottomlineY=bbox.top()+bbox.height() /2;	// draw link to middle (of frame)
			break;
		default :
			bottomlineY=bbox.bottom()-1;	// draw link to bottom of box
			break;
	}
	
    double p2x,p2y;								// Set P2 Before setting
	if (!link2ParPos)
	{
		p2x=QPoint( parObj->getChildPos() ).x();	// P1, we have to look at
		p2y=QPoint( parObj->getChildPos() ).y();	// orientation
	} else	
	{
		p2x=QPoint( parObj->getParPos() ).x();	
		p2y=QPoint( parObj->getParPos() ).y();
	} 

	LinkOrient orientOld=orientation;

    // Set orientation, first look for orientation of parent
    if (parObj->getOrientation() != OrientUndef ) 
		// use the orientation of the parent:
		orientation=parObj->getOrientation();
    else
    {
		// calc orientation depending on position rel to mapCenter
		if (absPos.x() < QPoint(parObj->getChildPos() ).x() )
			orientation=OrientLeftOfCenter; 
		else
			orientation=OrientRightOfCenter;
    }

	if ((orientation!=orientOld) && (orientOld!= OrientUndef))
	{
		// Orientation just changed. Reorient this subbranch, because move is called
		// before updateLink => Position is still the old one, which could lead to 
		// linking of subranch to itself => segfault
		//
		// Also possible: called in BranchObj::init(), then orientOld==OrientUndef,
		// no need to reposition now
		reposition();
	}
	
	setDockPos();

	double p1x=parPos.x();	// Link is drawn from P1 to P2
	double p1y=parPos.y();

	double vx=p2x - p1x;	// V=P2-P1
	double vy=p2y - p1y;

	// Draw the horizontal line below heading (from ChildPos to ParPos)
	bottomline->setPoints (qRound(childPos.x()),
		qRound(childPos.y()),
		qRound(p1x),
		qRound(p1y) );

	double a;	// angle
	if (vx > -0.000001 && vx < 0.000001)
		a=M_PI_2;
	else
		a=atan( vy / vx );
	// "turning point" for drawing polygonal links
	QPoint tp (-qRound(sin (a)*thickness_start), qRound(cos (a)*thickness_start));	
	
	QCanvasLine *cl;

	int i;

    // Draw the link
	switch (style)
	{
		case StyleLine:
			l->setPoints( qRound (parPos.x()),
				qRound(parPos.y()),
				qRound(p2x),
				qRound(p2y) );
			break;	
		case StyleParabel:	
			parabel (pa0, p1x,p1y,p2x,p2y);
			i=0;
			for (cl=segment.first(); cl; cl=segment.next() )
			{	
				cl->setPoints( pa0.point(i).x(), pa0.point(i).y(),pa0.point(i+1).x(),pa0.point(i+1).y());
				i++;
			}
			break;
		case StylePolyLine:
			pa0[0]=QPoint (qRound(p2x+tp.x()), qRound(p2y+tp.y()));
			pa0[1]=QPoint (qRound(p2x-tp.x()), qRound(p2y-tp.y()));
			pa0[2]=QPoint (qRound (parPos.x()), qRound(parPos.y()) );
			p->setPoints (pa0);
			// here too, draw line to avoid missing pixels
			l->setPoints( qRound (parPos.x()),
				qRound(parPos.y()),
				qRound(p2x),
				qRound(p2y) );
			break;
		case StylePolyParabel:	
			parabel (pa1, p1x,p1y,p2x+tp.x(),p2y+tp.y());
			parabel (pa2, p1x,p1y,p2x-tp.x(),p2y-tp.y());
			for (i=0;i<=arcsegs;i++)
			{
				// Combine the arrays to a single one
				pa0[i]=pa1[i];
				pa0[i+arcsegs+1]=pa2[arcsegs-i];
			}	
			p->setPoints (pa0);
			i=0;
			for (cl=segment.first(); cl; cl=segment.next() )
			{	
				cl->setPoints( pa1.point(i).x(), pa1.point(i).y(),pa1.point(i+1).x(),pa1.point(i+1).y());
				i++;
			}
			break;
		default:
			break;
	} // switch (style)	
}
	
LinkableMapObj* LinkableMapObj::getChildObj()
{
    return childObj;
}

LinkableMapObj* LinkableMapObj::getParObj()
{
    return parObj;
}

LinkableMapObj* LinkableMapObj::findObjBySelect (QString s)
{
	LinkableMapObj *lmo=this;
	QString part;
	QString typ;
	QString num;
	while (!s.isEmpty() )
	{
		part=s.section(",",0,0);
		typ=part.left (3);
		num=part.right(part.length() - 3);
		if (typ=="mc:")
		{
			if (depth>0)
				return false;	// in a subtree there is no center
			else
				break;
		} else
			if (typ=="bo:")
				lmo=((BranchObj*)(lmo))->getBranchNum (num.toUInt());
			else
				if (typ=="fi:")
					lmo=((BranchObj*)(lmo))->getFloatImageNum (num.toUInt());
		if (!lmo) break;
		
		if (s.contains(","))
			s=s.right(s.length() - part.length() -1 );
		else	
			break;
	}
	return lmo;
}

void LinkableMapObj::setDockPos()
{
	cout <<"LMO::setDockPos()\n";
}

QPoint LinkableMapObj::getChildPos()
{
    return childPos;
}

QPoint LinkableMapObj::getParPos()
{
    return parPos;
}

QPoint LinkableMapObj::getRelPos()
{
	return relPos;
/* FIXME not needed? relPos was moved in 1.7.10 from
   floatobj to linkablemapobj. Before we had:
	
	if (!parObj) return QPoint (0,0);
    return QPoint(
		absPos.x() - parObj->x(),
		absPos.y() - parObj->y()
	);
*/	
}


void LinkableMapObj::setUseOrientation (const bool &b)
{	
	if (useOrientation!=b)
	{
		useOrientation=b;
		requestReposition();
	}	
}

LinkOrient LinkableMapObj::getOrientation()
{
    return orientation;
}

int LinkableMapObj::getDepth()
{
    return depth;
}

void LinkableMapObj::setMapEditor (MapEditor *me)
{
	mapEditor=me;
}

MapEditor* LinkableMapObj::getMapEditor ()
{
	return mapEditor;
}

QPoint LinkableMapObj::getRandPos()
{
	// Choose a random position with given distance to parent:
	double a=rand()%360 * 2 * M_PI / 360;
    return QPoint ( (int)( + 150*cos (a)),
                    (int)( + 150*sin (a)));
}

void LinkableMapObj::alignRelativeTo (QPoint ref)
{
	// FIXME testing, seems not to be used right now...
	cout << "LMO::alignRelTo   ref="<<ref<<endl;
}

void LinkableMapObj::reposition()
{
	if (depth==0)
	{
		// only calculate the sizes once. If the deepest LMO changes its height,
		// all upper LMOs have to change, too.
		calcBBoxSizeWithChilds();

	    alignRelativeTo ( QPoint (absPos.x(),
							absPos.y()-(bboxTotal.height()-bbox.height())/2) );
	} else
	{
		// This is only important for moving branches:
		// For editing a branch it isn't called...
	    alignRelativeTo ( QPoint (absPos.x(),
							absPos.y()-(bboxTotal.height()-bbox.height())/2) );
	}
}

void LinkableMapObj::requestReposition()
{
	if (!repositionRequest)
	{
		// Pass on the request to parental objects, if this hasn't
		// been done yet
		repositionRequest=true;
		if (parObj) parObj->requestReposition();
	}
}

void LinkableMapObj::forceReposition()
{
	// Sometimes a reposition has to be done immediatly: For example
	// if the note editor flag changes, there is no user event in mapeditor
	// which could collect requests for a reposition.
	// Then we have to call forceReposition()
	// But no rule without exception: While loading a map or undoing it,
	// we want to block expensive repositioning, but just do it once at
	// the end, thus check first:

	if (mapEditor->isRepositionBlocked()) return;
	
	// Pass on the request to parental objects, if this hasn't been done yet
	
	if (parObj) 
		parObj->forceReposition(); 
	else 
		reposition(); 
}

bool LinkableMapObj::repositionRequested()
{
	return repositionRequest;
}


void LinkableMapObj::setSelBox()
{
    selbox->setX (clickBox.x() );
    selbox->setY (clickBox.y() );
    selbox->setSize (clickBox.width(), clickBox.height() );
}

void LinkableMapObj::select()
{
	setSelBox();
    selected=true;
    selbox->show();
// FIXME not needed?	
	setVisibility (visible);
}


void LinkableMapObj::unselect()
{
    selected=false;
    selbox->hide();
	// Maybe we have to hide the link:
	setVisibility (visible);
}

void LinkableMapObj::parabel (QPointArray &ya, double p1x, double p1y, double p2x, double p2y)

{
	double vx=p2x - p1x;	// V=P2-P1
	double vy=p2y - p1y;

	double dx;				// delta x during calculation of parabel
	
	double pnx;				// next point
	double pny;
	double m;

	if (vx > -0.0001 && vx < 0.0001)
		m=0;
	else	
		m=(vy / (vx*vx));
	dx=vx/(arcsegs);
	int i;
	ya.setPoint (0,QPoint (qRound(p1x),qRound(p1y)));
	for (i=1;i<=arcsegs;i++)
	{	
		pnx=p1x+dx;
		pny=m*(pnx-parPos.x())*(pnx-parPos.x())+parPos.y();
		ya.setPoint (i,QPoint (qRound(pnx),qRound(pny)));
		p1x=pnx;
		p1y=pny;
	}	
}

QString LinkableMapObj::getLinkAttr ()
{
	if (hideLinkUnselected)
		return attribut ("hideLink","true");
	else
		return attribut ("hideLink","false");
	
}
