#include <cstdlib>
#include <iostream>
#include <math.h>

#include "branchobj.h"
#include "linkablemapobj.h"
#include "vymmodel.h"

extern bool debug;

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

LinkableMapObj::LinkableMapObj(QGraphicsItem *parent, TreeItem *ti)
    : MapObj(parent, ti)
{
    // qDebug() << "Const LinkableMapObj this="<<this<<"  ti="<<ti<<"
    // treeItem="<<treeItem;
    parObj = (LinkableMapObj *)
        parent; // FIXME-4 try to get rid of parObj and use parentItem() instead
    init();
}

LinkableMapObj::~LinkableMapObj()
{
    // qDebug()<< "Destructor LMO  this="<<this<<" style="<<style<<" l="<<l<<"
    // p="<<p<<"  segment="<<segment.count();
    delLink();
}

void LinkableMapObj::init()
{
    parObjTmpBuf = NULL;
    tmpParent = false;
    parPos = QPointF(0, 0);
    childRefPos = QPointF(0, 0);
    floatRefPos = QPointF(0, 0);
    link2ParPos = false;
    l = NULL;
    p = NULL;
    orientation = UndefinedOrientation;
    linkwidth = 20;
    thickness_start = 8;
    style = UndefinedStyle;
    linkpos = Bottom;
    arcsegs = 13;

    // TODO instead of linkcolor pen.color() could be used	all around
    pen.setWidth(1);
    pen.setColor(linkcolor);
    pen.setCapStyle(Qt::RoundCap);

    useBottomline = false;
    bottomline = NULL;

    topPad = botPad = leftPad = rightPad = 0;

    repositionRequest = false;

    // Rel Positions
    relPos = QPointF(0, 0);
    useRelPos = false;
}

void LinkableMapObj::createBottomLine()
{
    bottomline = scene()->addLine(QLineF(1, 1, 1, 1), pen);
    bottomline->setZValue(dZ_LINK);
}

void LinkableMapObj::delLink()
{
    if (bottomline) {
        delete (bottomline);
        bottomline = NULL;
    }
    switch (style) {
    case Line:
        delete (l);
        break;
    case Parabel:
        while (!segment.isEmpty())
            delete segment.takeFirst();
        break;
    case PolyLine:
        delete (p);
        break;
    case PolyParabel:
        delete (p);
        break;
    default:
        break;
    }
}

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

void LinkableMapObj::setParObj(LinkableMapObj *o)
{
    parObj = o;
    setParentItem(parObj);
}

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

void LinkableMapObj::unsetParObjTmp() {}

bool LinkableMapObj::hasParObjTmp() { return tmpParent; }

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

bool LinkableMapObj::getUseRelPos() { return useRelPos; }

void LinkableMapObj::setRelPos()
{
    if (parObj)
        setRelPos(absPos - parObj->getChildRefPos());
    else
        qWarning() << "LMO::setRelPos parObj==0   this=" << this;
}

void LinkableMapObj::setRelPos(const QPointF &p)
{
    if (parObj) {
        relPos = p;
        useRelPos = true;
        setOrientation();
    }
    else
        qWarning() << "LMO::setRelPos (p)  parObj==0   this=" << this;
}

QPointF LinkableMapObj::getRelPos()
{
    if (!parObj) {
        qWarning() << "LMO::getRelPos parObj==0   this=" << this;
        return QPointF();
    }
    return relPos;
}

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

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

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

LinkableMapObj::Style LinkableMapObj::getDefLinkStyle(TreeItem *parent)
{
    VymModel *model = treeItem->getModel();
    if (!model) {
        qWarning("LMO::getDefLinkStyle   model=NULL");
        // return UndefinedStyle;
    }
    Style ls = model->getMapLinkStyle();
    int depth = 1 + parent->depth();
    if (depth == 0)
        return UndefinedStyle;
    switch (ls) {
    case Line:
        return ls;
        break;
    case Parabel:
        return ls;
        break;
    case PolyLine:
        if (depth > 1)
            return Line;
        else
            return ls;
        break;
    case PolyParabel:
        if (depth > 1)
            return Parabel;
        else
            return ls;
        break;
    default:
        break;
    }
    return UndefinedStyle;
}

void LinkableMapObj::setLinkStyle(Style newstyle)
{
    // qDebug()<<"LMO::setLinkStyle s="<<newstyle;	//FIXME-4 called very
    // often?!?! qDebug()<<"LMO::setLinkStyle s="<<newstyle<<" for "<<this<<"
    // "<<treeItem->getHeading()<<"  parObj="<<parObj;
    delLink();

    style = newstyle;

    QGraphicsLineItem *cl;
    switch (style) {
    case Line:
        l = scene()->addLine(QLineF(1, 1, 1, 1), pen);
        l->setZValue(dZ_LINK);
        if (visible)
            l->show();
        else
            l->hide();
        createBottomLine();
        break;
    case Parabel:
        for (int i = 0; i < arcsegs; i++) {
            cl = scene()->addLine(QLineF(i * 5, 0, i * 10, 100), pen);
            cl->setZValue(dZ_LINK);
            if (visible)
                cl->show();
            else
                cl->hide();
            segment.append(cl);
        }
        pa0.resize(arcsegs + 1);
        createBottomLine();
        break;
    case PolyLine:
        p = scene()->addPolygon(QPolygonF(), pen, linkcolor);
        p->setZValue(dZ_LINK);
        if (visible)
            p->show();
        else
            p->hide();
        pa0.resize(3);
        createBottomLine();
        break;
    case PolyParabel:
        p = scene()->addPolygon(QPolygonF(), pen, linkcolor);
        p->setZValue(dZ_LINK);
        if (visible)
            p->show();
        else
            p->hide();
        pa0.resize(arcsegs * 2 + 2);
        pa1.resize(arcsegs + 1);
        pa2.resize(arcsegs + 1);
        createBottomLine();
        break;
    default:
        break;
    }
}

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

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

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

void LinkableMapObj::setLinkColor()
{
    // Overloaded in BranchObj and children
    // here only set default color
    VymModel *model = treeItem->getModel();
    if (!model)
        return;

    if (model->getMapLinkColorHint() == HeadingColor) {
        if (treeItem->isBranchLikeType() )
            LinkableMapObj::setLinkColor(treeItem->getHeading().getColor());
        else
            // For images use color of parent heading
            LinkableMapObj::setLinkColor(treeItem->parent()->getHeading().getColor());
    } else
        LinkableMapObj::setLinkColor(model->getMapDefLinkColor());
}

void LinkableMapObj::setLinkColor(QColor col)
{
    linkcolor = col;
    pen.setColor(col);
    if (bottomline)
        bottomline->setPen(pen);
    switch (style) {
    case Line:
        l->setPen(pen);
        break;
    case Parabel:
        for (int i = 0; i < segment.size(); ++i)
            segment.at(i)->setPen(pen);
        break;
    case PolyLine:
        p->setBrush(QBrush(col));
        p->setPen(pen);
        break;
    case PolyParabel:
        p->setBrush(QBrush(col));
        p->setPen(pen);
        break;
    default:
        break;
    }
}

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

void LinkableMapObj::setVisibility(bool v)
{
    MapObj::setVisibility(v);
    updateVisibility();
}

void LinkableMapObj::setOrientation()
{
    if (!parObj) {
        orientation = UndefinedOrientation;
        return;
    }

    // calc orientation depending on position rel to parent
    if (useRelPos) {
        if (relPos.x() < 0)
            orientation = LeftOfCenter;
        else
            orientation = RightOfCenter;
    }
    else
        // use the orientation of the parent:
        orientation = parObj->getOrientation();
}

void LinkableMapObj::updateVisibility()
{
    bool visnow = visible;

    // Hide links of unselected objects (if wanted)
    if (((MapItem *)treeItem)->getHideLinkUnselected() &&
        !treeItem->getModel()->isSelected(treeItem))
        visnow = false;

    if (visnow) {
        if (bottomline) {
            if (useBottomline)
                bottomline->show();
            else
                bottomline->hide();
        }

        switch (style) {
        case Line:
            if (l)
                l->show();
            break;
        case Parabel:
            for (int i = 0; i < segment.size(); ++i)
                segment.at(i)->show();
            break;
        case PolyLine:
            if (p)
                p->show();
            else
                qDebug() << "LMO::updateVis p==0 (PolyLine)"; // FIXME-4
            break;
        case PolyParabel:
            if (p)
                p->show();
            else
                qDebug() << "LMO::updateVis p==0 (PolyParabel) "
                         << treeItem->getHeadingPlain(); // FIXME-4
            break;
        default:
            break;
        }
    }
    else {
        if (bottomline)
            bottomline->hide();
        switch (style) {
        case Line:
            if (l)
                l->hide();
            break;
        case Parabel:
            for (int i = 0; i < segment.size(); ++i)
                segment.at(i)->hide();
            break;
        case PolyLine:
            if (p)
                p->hide();
            break;
        case PolyParabel:
            if (p)
                p->hide();
            break;
        default:
            break;
        }
    }
}

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

    // updateLinkGeometry is called from move, but called from constructor we
    // don't have parents yet...

    if (style == UndefinedStyle) {
        setDockPos();
        return;
    }

    switch (linkpos) {
    case Middle:
        bottomlineY =
            bbox.top() + bbox.height() / 2; // draw link to middle (of frame)
        break;
    case Bottom:
        // bottomlineY = bbox.bottom()-1;  // draw link to bottom of box
        bottomlineY = bbox.bottom() - botPad;
        break;
    }

    double p2x, p2y; // Set P2 Before setting
    if (!link2ParPos) {
        p2x = QPointF(parObj->getChildRefPos()).x(); // P1, we have to look at
        p2y = QPointF(parObj->getChildRefPos()).y(); // orientation
    }
    else {
        p2x = QPointF(parObj->getParPos()).x();
        p2y = QPointF(parObj->getParPos()).y();
    }

    setOrientation();
    setDockPos(); // Call overloaded method

    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;

    int z;
    // Hack to z-move links to MapCenter (d==1) below MCOs frame (d==0)
    // //FIXME-4 no longer used?
    if (treeItem->depth() < 2)
        // z=(treeItem->depth() -2)*dZ_DEPTH + dZ_LINK;
        z = -dZ_LINK;
    else
        z = dZ_LINK;

    // qDebug()<<"LMO::updateGeo d="<<treeItem->depth()<<"  this="<<this<<"
    // "<<treeItem->getHeading();

    // Draw the horizontal line below heading (from childRefPos to ParPos)
    if (bottomline) {
        bottomline->setLine(QLineF(childRefPos.x(), childRefPos.y(), p1x, p1y));
        bottomline->setZValue(z);
    }

    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
    QPointF tp(-qRound(sin(a) * thickness_start),
               qRound(cos(a) * thickness_start));

    // Draw the link
    switch (style) {
    case Line:
        l->setLine(QLine(qRound(parPos.x()), qRound(parPos.y()), qRound(p2x),
                         qRound(p2y)));
        l->setZValue(z);
        break;
    case Parabel:
        parabel(pa0, p1x, p1y, p2x, p2y);
        for (int i = 0; i < segment.size(); ++i) {
            segment.at(i)->setLine(QLineF(pa0.at(i).x(), pa0.at(i).y(),
                                          pa0.at(i + 1).x(),
                                          pa0.at(i + 1).y()));
            segment.at(i)->setZValue(z);
        }
        break;
    case PolyLine:
        pa0.clear();
        pa0 << QPointF(qRound(p2x + tp.x()), qRound(p2y + tp.y()));
        pa0 << QPointF(qRound(p2x - tp.x()), qRound(p2y - tp.y()));
        pa0 << QPointF(qRound(parPos.x()), qRound(parPos.y()));
        p->setPolygon(QPolygonF(pa0));
        p->setZValue(z);
        break;
    case PolyParabel:
        parabel(pa1, p1x, p1y, p2x + tp.x(), p2y + tp.y());
        parabel(pa2, p1x, p1y, p2x - tp.x(), p2y - tp.y());
        pa0.clear();
        for (int i = 0; i <= arcsegs; i++)
            pa0 << QPointF(pa1.at(i));
        for (int i = 0; i <= arcsegs; i++)
            pa0 << QPointF(pa2.at(arcsegs - i));
        p->setPolygon(QPolygonF(pa0));
        p->setZValue(z);
        break;
    default:
        break;
    }
}

QPointF LinkableMapObj::getChildRefPos() { return childRefPos; }

QPointF LinkableMapObj::getFloatRefPos() { return floatRefPos; }

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

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

void LinkableMapObj::reposition() // virtual
{
}

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:

    VymModel *model = treeItem->getModel();
    if (model->isRepositionBlocked())
        return;

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

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

void LinkableMapObj::parabel(QPolygonF &ya, qreal p1x, qreal p1y, qreal p2x,
                             qreal p2y)

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

    qreal dx; // delta x during calculation of parabel

    qreal pnx; // next point
    qreal pny;
    qreal m;

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