#include <QDebug>

#include "branchobj.h"

#include "attributeitem.h"
#include "branchitem.h"
#include "geometry.h"
#include "mainwindow.h"
#include "mapeditor.h"
#include "misc.h"

extern FlagRowMaster *standardFlagsMaster;
extern FlagRowMaster *userFlagsMaster;
extern FlagRowMaster *systemFlagsMaster;
extern bool debug;
extern bool usingDarkTheme;

/////////////////////////////////////////////////////////////////
// BranchObj
/////////////////////////////////////////////////////////////////

BranchObj::BranchObj(QGraphicsItem *parent, TreeItem *ti)
    : OrnamentedObj(parent, ti)
{
    // qDebug ()<< "Const BranchObj  (s,ti) ti="<<ti;
    treeItem = ti;
    BranchItem *pi = (BranchItem *)(ti->parent());
    if (pi && pi != ti->getModel()->getRootItem())
        parObj = pi->getLMO();
    else
        parObj = NULL;
    init();
}

BranchObj::~BranchObj()
{
    // qDebug()<< "Destr BranchObj  of "<<this;

    // If I'm animated, I need to un-animate myself first
    if (anim.isAnimated()) {
        anim.setAnimated(false);
        VymModel *model = treeItem->getModel();
        model->stopAnimation(this);
    }

    clear();
}

void BranchObj::init()
{
    if (parObj)
        absPos = parObj->getChildRefPos();
}

void BranchObj::copy(BranchObj *other)
{
    OrnamentedObj::copy(other);

    setVisibility(other->visible);

    positionBBox();
}

void BranchObj::clear() {}

void BranchObj::setParObjTmp(LinkableMapObj *dst, QPointF m, int off)
{
    // Temporary link to dst
    // m is position of mouse pointer
    // offset 0: default 1: below dst   -1 above dst  (if possible)

    BranchItem *dsti = (BranchItem *)(dst->getTreeItem());

    BranchItem *pi = (BranchItem *)(dsti->parent());
    int pi_depth = pi->depth();
    BranchObj *bodst = (BranchObj *)dst;

    if (!tmpParent) {
        tmpParent = true;
        parObjTmpBuf = parObj;
    }

    if (pi_depth < 1)
        off = 0;
    if (off == 0)
        link2ParPos = false;
    else
        link2ParPos = true;
    parObj = bodst;

    setLinkStyle(dst->getDefLinkStyle(dsti));

    // Move temporary to new position at destination
    // Usually the positioning would be done by reposition(),
    // but then also the destination branch would "Jump" around...
    // Better just do it approximately
    if (dsti->depth() == 0) { // new parent is a mapcenter
        Vector v = (m - bodst->getChildRefPos());
        v.normalize();
        v.scale(150);
        move2RelPos(v.toQPointF());
    }
    else {
        qreal y;
        if (off == 0) {
            // Below is needed e.g. in a freshly loaded map,
            // bboxTotal seems not to be correct yet
            // relinking positions too far below then
            calcBBoxSizeWithChildren();

            // new parent is just a branch, link to it
            bodst->calcBBoxSizeWithChildren();
            QRectF t = bodst->getTotalBBox();
            if (dsti->getLastBranch())
                // Move below children of destination
                y = t.y() + t.height();
            else
                // Move left or right to destination
                y = t.y();
        }
        else {
            if (off < 0)
                // we want to link above dst
                y = bodst->y() - height() + 12;
            else
                // we want to link below dst
                // Bottom of sel should be 5 pixels above
                // the bottom of the branch _below_ the target:
                // Don't try to find that branch, guess 12 pixels
                y = bodst->getChildRefPos().y() - height() + 12;
        }
        if (bodst->getOrientation() == LinkableMapObj::LeftOfCenter)
            move(bodst->getChildRefPos().x() - linkwidth - bboxTotal.width(),
                 y);
        else
            move(bodst->getChildRefPos().x() + linkwidth, y);
    }

    // updateLinkGeometry is called implicitly in move
    requestReposition();
}

void BranchObj::unsetParObjTmp()
{
    if (tmpParent) {
        tmpParent = false;
        link2ParPos = false;
        parObj = parObjTmpBuf;
        parObjTmpBuf = NULL;
        setLinkStyle(getDefLinkStyle(treeItem->parent()));
        updateLinkGeometry();
    }
}

void BranchObj::setVisibility(bool v, int toDepth)
{
    BranchItem *bi = (BranchItem *)treeItem;
    if (bi->depth() <= toDepth) {
        frame->setVisibility(v);
        heading->setVisibility(v);
        systemFlagRowObj->setVisibility(v);
        standardFlagRowObj->setVisibility(v);
        LinkableMapObj::setVisibility(v);
        int i;
        for (i = 0; i < treeItem->imageCount(); ++i)
            treeItem->getImageObjNum(i)->setVisibility(v);
        for (i = 0; i < treeItem->xlinkCount(); ++i)
            treeItem->getXLinkObjNum(i)->setVisibility();

        // Only change children, if I am not scrolled
        if (!bi->isScrolled() && (bi->depth() < toDepth)) {
            // Now go recursivly through all children
            for (i = 0; i < treeItem->branchCount(); ++i)
                treeItem->getBranchObjNum(i)->setVisibility(v, toDepth);
        }
    }
}

void BranchObj::setVisibility(bool v) { setVisibility(v, MAX_DEPTH); }

void BranchObj::positionContents()
{
    OrnamentedObj::positionContents();
    updateLinkGeometry(); // required before positioning images
    for (int i = 0; i < treeItem->imageCount(); ++i)
        treeItem->getImageObjNum(i)->reposition();
}

void BranchObj::move(double x, double y) { OrnamentedObj::move(x, y); }

void BranchObj::move(QPointF p) { move(p.x(), p.y()); }

void BranchObj::moveBy(double x, double y)
{
    OrnamentedObj::moveBy(x, y);
    for (int i = 0; i < treeItem->branchCount(); ++i)
        treeItem->getBranchObjNum(i)->moveBy(x, y);
    positionBBox();
}

void BranchObj::moveBy(QPointF p) { moveBy(p.x(), p.y()); }

void BranchObj::positionBBox() // FIXME-3 consider dimensions of frame
                               // (thickness, geometry, padding...
{
    QPointF ap = getAbsPos();
    bbox.moveTopLeft(ap);
    positionContents(); // this positions FIOs

    // Update links to other branches
    XLinkObj *xlo;
    for (int i = 0; i < treeItem->xlinkCount(); ++i) {
        xlo = treeItem->getXLinkObjNum(i);
        if (xlo)
            xlo->updateXLink();
    }
}

void BranchObj::calcBBoxSize()
{
    QSizeF heading_r = heading->getSize();
    qreal heading_w = (qreal)heading_r.width();
    qreal heading_h = (qreal)heading_r.height();
    QSizeF sysflags_r = systemFlagRowObj->getSize();
    qreal sysflags_h = sysflags_r.height();
    qreal sysflags_w = sysflags_r.width();
    QSizeF stanflags_r = standardFlagRowObj->getSize();
    qreal stanflags_h = stanflags_r.height();
    qreal stanflags_w = stanflags_r.width();
    qreal w;
    qreal h;

    // set width to sum of all widths
    w = heading_w + sysflags_w + stanflags_w;

    // set height to maximum needed height
    h = max(sysflags_h, stanflags_h);
    h = max(h, heading_h);

    // Save the dimension of flags and heading
    ornamentsBBox.setSize(QSizeF(w, h));

    // clickBox includes Flags and Heading
    clickPoly = QPolygonF(ornamentsBBox);

    // Floatimages
    QPointF rp;

    topPad = botPad = leftPad = rightPad = 0;
    bool incV = ((BranchItem *)treeItem)->getIncludeImagesVer();
    bool incH = ((BranchItem *)treeItem)->getIncludeImagesHor();
    if (incH || incV) {
        FloatImageObj *fio;
        for (int i = 0; i < treeItem->imageCount(); ++i) {
            fio = treeItem->getImageObjNum(i);
            rp = fio->getRelPos();
            if (incV) {
                qreal y;
                if (rp.y() > 0) {
                    y = rp.y() + fio->height() / 2 - ornamentsBBox.height() / 2;
                    botPad = max(botPad, y);
                }
                else {
                    y = -rp.y() + fio->height() / 2 -
                        ornamentsBBox.height() / 2;
                    topPad = max(topPad, y);
                }
            }
            if (incH) {
                qreal x;
                if (rp.x() > 0) {
                    x = rp.x() + fio->width() / 2 - ornamentsBBox.width() / 2;
                    rightPad = max(rightPad, x);
                }
                else {
                    x = -rp.x() + fio->width() / 2 - ornamentsBBox.width() / 2;
                    leftPad = max(leftPad, x);
                }
            }
        }
        h += topPad + botPad;
        w += leftPad + rightPad;
    }

    // Frame thickness
    w += frame->getTotalPadding() * 2;
    h += frame->getTotalPadding() * 2;

    // Finally set size
    bbox.setSize(QSizeF(w, h));
    // if (debug) qDebug()<<"BO: calcBBox "<<treeItem->getHeading()<<"
    // bbox="<<bbox;
}

void BranchObj::setDockPos()
{
    floatRefPos = ornamentsBBox.center();

    if (treeItem->getType() == TreeItem::MapCenter) {
        // set childRefPos to middle of MapCenterObj
        QRectF r = clickPoly.boundingRect();
        childRefPos.setX(r.topLeft().x() + r.width() / 2);
        childRefPos.setY(r.topLeft().y() + r.height() / 2);
        parPos = childRefPos;
        for (int i = 0; i < treeItem->branchCount(); ++i)
            treeItem->getBranchObjNum(i)->updateLinkGeometry();
    }
    else {
        if (orientation == LinkableMapObj::LeftOfCenter) {
            // Left of center
            if (((BranchItem *)treeItem)->getFrameIncludeChildren()) {
                childRefPos = QPointF(ornamentsBBox.bottomLeft().x() - leftPad,
                                      bottomlineY);
                parPos = QPointF(bboxTotal.bottomRight().x() -
                                     frame->getPadding() / 2,
                                 bottomlineY);
            }
            else {
                childRefPos = QPointF(ornamentsBBox.bottomLeft().x() -
                                          frame->getPadding(),
                                      bottomlineY);
                parPos = QPointF(ornamentsBBox.bottomRight().x(), bottomlineY);
            }
        }
        else {
            // Right of center
            if (((BranchItem *)treeItem)->getFrameIncludeChildren()) {
                childRefPos = QPointF(
                    ornamentsBBox.bottomRight().x() + rightPad, bottomlineY);
                parPos = QPointF(bboxTotal.bottomLeft().x() +
                                     frame->getPadding() / 2,
                                 bottomlineY);
            }
            else {
                childRefPos = QPointF(ornamentsBBox.bottomRight().x() +
                                          frame->getPadding(),
                                      bottomlineY);
                parPos = QPointF(ornamentsBBox.bottomLeft().x(), bottomlineY);
            }
        }
    }
}

void BranchObj::updateVisuals()
{
    if (!treeItem) {
        qWarning("BranchObj::udpateHeading treeItem==NULL");
        return;
    }
    QString s = treeItem->getHeadingText();
    if (s != heading->text())
        heading->setText(s);

    // Update standard flags active in TreeItem
    QList<QUuid> TIactiveFlagUids = treeItem->activeFlagUids();
    standardFlagRowObj->updateActiveFlagObjs(
        TIactiveFlagUids, standardFlagsMaster, userFlagsMaster);

    // Add missing system flags active in TreeItem
    TIactiveFlagUids = treeItem->activeSystemFlagUids();
    systemFlagRowObj->updateActiveFlagObjs(TIactiveFlagUids, systemFlagsMaster);

    calcBBoxSize();
}

void BranchObj::setDefAttr(BranchModification mod, bool keepFrame)
{

    // Note: not needed in 3.x.0 versions, 
    // where MapDesign will be used
    QFont font = treeItem->getModel()->getMapDefaultFont();
    qreal fontsize = font.pointSizeF();
    switch (treeItem->depth()) {
    case 0:
        break;
    case 1:
        fontsize = fontsize - 2;
        break;
    case 2:
        fontsize = fontsize - 4;
        break;
    default:
        fontsize = fontsize - 6;
        break;
    }
    setLinkStyle(getDefLinkStyle(treeItem->parent()));
    setLinkColor();
    font.setPointSizeF(fontsize);
    heading->setFont(font);

    if (mod == NewBranch && !keepFrame) {
        if (treeItem->depth() == 0) {
            setFrameType(FrameObj::RoundedRectangle);
            setFrameBorderWidth(2);
            if (usingDarkTheme) {
                setFramePenColor(QColor(Qt::white));
                setFrameBrushColor(QColor(85, 85, 127));
                treeItem->setHeadingColor(QColor(Qt::white));
            } else {
                setFramePenColor(QColor(Qt::black));
                setFrameBrushColor(QColor(Qt::white));
            }
        } else
            setFrameType(FrameObj::NoFrame);
    }
    if (mod == NewBranch)
        setColor(treeItem->getHeadingColor());
    else {
        // Relinked mapcenters
        if (!keepFrame && getFrameType() != FrameObj::NoFrame)
            setFrameType(FrameObj::NoFrame);

        // Also set styles for children
        for (int i = 0; i < treeItem->branchCount(); ++i)
            treeItem->getBranchObjNum(i)->setDefAttr(MovedBranch, keepFrame);
    }
    calcBBoxSize();
}

void BranchObj::alignRelativeTo(QPointF ref, bool alignSelf)
{
    // Define some heights
    qreal th = bboxTotal.height();
    qreal ch = 0; // Sum of childrens heights
    for (int i = 0; i < treeItem->branchCount(); ++i)
        ch += treeItem->getBranchObjNum(i)->getTotalBBox().height();

    int depth = 0;
    BranchItem::LayoutHint layoutHint = BranchItem::AutoPositioning;
    if (parObj) {
        TreeItem *pi = parObj->getTreeItem();
        depth = 1 + pi->depth();
        layoutHint =
            ((BranchItem *)treeItem)->parentBranch()->getChildrenLayout();
    }

    // set useRelPos, depending on layout
    if (depth > 1) {
        if (layoutHint == BranchItem::FreePositioning) {
            if (!useRelPos) {
                useRelPos = true;
                // if we used relPos before, set known positions
                // "known" means any position != (0,0)
                if (relPos == QPointF(0, 0))
                    // use current position to get relPos()
                    setRelPos();
            }
        }
        else
            useRelPos = false;
    }

    // TODO testing
    /*
        if (debug)
        {
            QString o;
            switch (orientation)
            {
                case UndefinedOrientation: o = "UndefOrientation"; break;
                case LeftOfCenter: o = "LeftOfCenter"; break;
                case RightOfCenter: o = "RightOfCenter"; break;
            }

            QString h=QString (depth+1,' ');
            h += treeItem->getHeadingPlain();
            h += QString (25,' ');
            h.truncate (25);
            QPointF pp;
            if (parObj) pp = parObj->getChildRefPos();
            qDebug() << "BO::alignRelTo for "<<h
        //    qDebug() << "    d="<<depth;
        //    qDebug() <<"   ref="<<ref;
        //    qDebug() <<"    th="<<th;
        //    qDebug() <<"    ch="<<ch;
        //    if (ch < th) qDebug()<<"   ch<th !";
        //    qDebug() <<"  parO="<<parObj;
            //qDebug() <<   "  bbox.tL="<<bboxTotal.topLeft();
            << "  useRelPos=" << useRelPos
            << " layoutHint= " << layoutHint
        //    qDebug() <<"absPos="<<absPos
            << "  relPos="<<relPos
        //	<< "  parPos="<<pp
        //	<< "  bbox="<<bbox
            << "  orient="<<o<<" "<<orientation;
        //	<< "  alignSelf="<<alignSelf
        //	<< "  scrolled="<<((BranchItem*)treeItem)->isScrolled()
        //	<< "  pad="<<topPad<<","<<botPad<<","<<leftPad<<","<<rightPad
        //	<< "  hidden="<<hidden
        //	<< "  th="<<th
            ;
        }
       */

    setOrientation();

    // Align myself
    if (depth == 0)
        move(getAbsPos()); // Trigger update of frames etc.
    else if (depth == 1)
        move2RelPos(getRelPos());
    else if (depth > 1) {
        if (layoutHint == BranchItem::FreePositioning)
            move2RelPos(getRelPos());
        else {
            if (anim.isAnimated())
                move2RelPos(anim);
            else {
                if (alignSelf)
                    switch (orientation) {
                    case LinkableMapObj::LeftOfCenter:
                        move(ref.x() - bbox.width(),
                             ref.y() + (th - bbox.height()) / 2);
                        break;
                    case LinkableMapObj::RightOfCenter:
                        move(ref.x(), ref.y() + (th - bbox.height()) / 2);
                        break;
                    default:
                        qWarning("LMO::alignRelativeTo: oops, no orientation "
                                 "given for BO...");
                        break;
                    }
            }
        }
    }

    // Without ancestors I am done
    if (((BranchItem *)treeItem)->isScrolled())
        return;

    // Set reference point for alignment of children
    QPointF ref2;
    if (orientation == LinkableMapObj::LeftOfCenter)
        ref2.setX(childRefPos.x() - linkwidth);
    else
        ref2.setX(childRefPos.x() + linkwidth);

    if (depth == 1)
        ref2.setY(absPos.y() + (bbox.height() - ch) / 2);
    else {
        if (ch > th)
            ref2.setY(ref.y() + frame->getPadding());
        else
            // Parent is bigger than all of childs, center childs vertically
            ref2.setY(ref.y() + (th - ch) / 2);
    }

    // Align the branch children depending on reference point
    for (int i = 0; i < treeItem->branchCount(); ++i) {
        if (!treeItem->getBranchNum(i)->isHidden()) {
            treeItem->getBranchObjNum(i)->alignRelativeTo(ref2, true);

            // append next branch below current one
            ref2.setY(ref2.y() +
                      treeItem->getBranchObjNum(i)->getTotalBBox().height());
        }
    }
}

void BranchObj::reposition()
{
    /* TODO testing only
        if (debug)
        {
            if (!treeItem->getHeading().isEmpty())
                qDebug()<< "  BO::reposition  a) d="<<treeItem->depth()<<"
       "<<treeItem->getHeading(); else qDebug()<< "  BO::reposition  a)
       d="<<treeItem->depth()<<" ???";
        }
    */

    if (treeItem->depth() == 0)
        // only calculate the sizes once. If the deepest LMO
        // changes its height,
        // all upper LMOs have to change, too.
        calcBBoxSizeWithChildren();

    alignRelativeTo(QPointF(
        absPos.x(), absPos.y() - (bboxTotal.height() - bbox.height()) / 2));
}

void BranchObj::unsetAllRepositionRequests()
{
    repositionRequest = false;
    for (int i = 0; i < treeItem->branchCount(); ++i)
        treeItem->getBranchObjNum(i)->unsetAllRepositionRequests();
}

QRectF BranchObj::getTotalBBox() { return bboxTotal; }

ConvexPolygon BranchObj::getBoundingPolygon()
{
    if (treeItem->branchCount() == 0 || treeItem->depth() == 0) {
        return MapObj::getBoundingPolygon();
    }

    QPolygonF p;
    p << bboxTotal.topLeft();
    p << bboxTotal.topRight();
    p << bboxTotal.bottomRight();
    p << bboxTotal.bottomLeft();
    return p;
}

void BranchObj::calcBBoxSizeWithChildren()
{
    // if branch is scrolled, ignore children, but still consider floatimages
    BranchItem *bi = (BranchItem *)treeItem;
    if (bi->isScrolled()) {
        bboxTotal.setWidth(bbox.width());
        bboxTotal.setHeight(bbox.height());
        return;
    }

    if (bi->isHidden()) {
        bboxTotal.setWidth(0);
        bboxTotal.setHeight(0);
        return;
    }

    QRectF r(0, 0, 0, 0);
    QRectF br;

    // Now calculate
    // sum of heights
    // maximum of widths
    // minimum of y
    for (int i = 0; i < treeItem->branchCount(); i++) {
        if (!bi->getBranchNum(i)->isHidden()) {
            BranchObj *bo = bi->getBranchObjNum(i);
            bo->calcBBoxSizeWithChildren();
            br = bo->getTotalBBox();
            r.setWidth(max(br.width(), r.width()));
            r.setHeight(br.height() + r.height());
        }
    }

    // Add myself and also
    // add width of link to sum if necessary
    if (bi->branchCount() < 1)
        bboxTotal.setWidth(bbox.width() + r.width());
    else
        bboxTotal.setWidth(bbox.width() + r.width() + linkwidth);

    // bbox already contains frame->padding()*2
    bboxTotal.setHeight(
        max(r.height() + frame->getPadding() * 2, bbox.height()));
}

void BranchObj::setAnimation(const AnimPoint &ap) { anim = ap; }

void BranchObj::stopAnimation()
{
    anim.stop();
    if (useRelPos)
        setRelPos(anim);
    else
        move(anim);
}

bool BranchObj::animate()
{
    anim.animate();
    if (anim.isAnimated()) {
        if (useRelPos)
            setRelPos(anim);
        else
            move(anim);
        return true;
    }
    return false;
}
