/*
 *   Copyright (C) 2007 Ivan Cukic <ivan.cukic+kde@gmail.com>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License
 *   version 2, or (at your option) any later version, as published by the
 *   Free Software Foundation
 *
 *   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
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this program; if not, write to the
 *   Free Software Foundation, Inc.,
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "nodelayout.h"

#include <QPair>
#include <QMap>

#include <limits>

namespace Plasma
{
NodeLayout::NodeCoordinate::NodeCoordinate(qreal xRelative, qreal yRelative, qreal xAbsolute, qreal yAbsolute)
    : xr(xRelative), xa(xAbsolute), yr(yRelative), ya(yAbsolute) {}

NodeLayout::NodeCoordinate NodeLayout::NodeCoordinate::simple(qreal x, qreal y,
        CoordinateType xType, CoordinateType yType)
{
    NodeLayout::NodeCoordinate coo;
    switch (xType) {
    case Relative:
        coo.xr = x;
        coo.xa = 0;
        break;
    case Absolute:
        coo.xr = 0;
        coo.xa = x;
        break;
    case InnerRelative:
        coo.xr = x;
        coo.xa = std::numeric_limits<float>::infinity();
        break;
    }

    switch (yType) {
    case Relative:
        coo.yr = y;
        coo.ya = 0;
        break;
    case Absolute:
        coo.yr = 0;
        coo.ya = y;
        break;
    case InnerRelative:
        coo.yr = y;
        coo.ya = std::numeric_limits<float>::infinity();
        break;
    }
    return coo;
}

class NodeLayout::Private {
public:
    QMap <LayoutItem * , QPair < NodeCoordinate, NodeCoordinate > > items;
    //QRectF geometry;
    NodeLayout * parent;
    QSizeF sizeHint;

    Private(NodeLayout * parentLayout) {
        parent = parentLayout;
    }

    qreal calculateXPosition(const NodeCoordinate & coo, const QRectF & parentGeometry) const
    {
        return parentGeometry.left() + (coo.xr * parentGeometry.width())  + coo.xa;
    }

    qreal calculateYPosition(const NodeCoordinate & coo, const QRectF & parentGeometry) const
    {
        return parentGeometry.top() + (coo.yr * parentGeometry.height())  + coo.ya;
    }

    QPointF calculatePosition(const NodeCoordinate & coo, const QRectF & parentGeometry) const
    {
        Q_UNUSED( parentGeometry );

        return QPointF(
            calculateXPosition(coo, parent->geometry()),
            calculateYPosition(coo, parent->geometry())
        );
    }


    QRectF calculateRectangle(LayoutItem * item, QRectF geometry = QRectF()) const
    {
        if (geometry == QRectF()) {
            geometry = parent->geometry();
        }

        QRectF result;
        if (!item || !items.contains(item)) { 
            return QRectF();
        }

        result.setTopLeft(calculatePosition(items[item].first, geometry));

        if (items[item].second.xa != std::numeric_limits<float>::infinity()) {
            result.setRight(calculateXPosition(items[item].second, geometry));
        } else {
            result.setWidth(item->sizeHint().width());
            result.moveLeft(result.left() - items[item].second.xr * result.width());
        }

        if (items[item].second.ya != std::numeric_limits<float>::infinity()) {
            result.setBottom(calculateYPosition(items[item].second, geometry));
        } else {
            result.setHeight(item->sizeHint().height());
            result.moveTop(result.top() - items[item].second.yr * result.height());
        }

        return result;
    }

    void calculateSizeHint(LayoutItem * item = NULL) {
        if (item == NULL) {
            // Recalculate the sizeHint using all items
            sizeHint = QSizeF();
            foreach (LayoutItem * item, items.keys()) {
                if (item) {
                    calculateSizeHint(item);
                }
            }
        } else {
            // Calculate size hint for current item
            const QRectF scaled = calculateRectangle(item, QRectF(0, 0, 1, 1));

            // qMin(..., 1.0) so that for autosized elements we don't get smaller
            // size than the item's size itself. The sizeHint for NodeLayout can
            // not do anything smarter concerning the sizeHint when there are
            // autosized elements.

            qreal width  = item->sizeHint().width()  / qMin(scaled.width(), qreal(1.0));
            qreal height = item->sizeHint().height() / qMin(scaled.height(), qreal(1.0));

            if (width > sizeHint.width())   sizeHint.setWidth(width);
            if (height > sizeHint.height()) sizeHint.setHeight(height);
        }
    }

};


NodeLayout::NodeLayout(LayoutItem * parent) 
  : Layout(parent), d(new Private(this))
{
}

NodeLayout::~NodeLayout()
{
    releaseManagedItems();
    delete d;
}

Qt::Orientations NodeLayout::expandingDirections() const
{
    return Qt::Horizontal | Qt::Vertical;
}

void NodeLayout::relayout()
{
    foreach (LayoutItem * item, d->items.keys()) {
        if (item) {
            item->setGeometry(d->calculateRectangle(item));
        }
    }
}

void NodeLayout::releaseManagedItems()
{
    foreach (LayoutItem * item, d->items.keys()) {
        item->unsetManagingLayout(this);
    }
}

QSizeF NodeLayout::sizeHint() const
{
    return d->sizeHint;
}

void NodeLayout::addItem(LayoutItem * item)
{
    NodeLayout::addItem(item, NodeCoordinate());
}

void NodeLayout::addItem(LayoutItem * item, NodeCoordinate topLeft, NodeCoordinate bottomRight)
{
    if (!item) {
        return;
    }

    d->items[item] = QPair<NodeCoordinate, NodeCoordinate>(topLeft, bottomRight);
    item->setManagingLayout(this);
    d->calculateSizeHint(item);
}

void NodeLayout::addItem(LayoutItem * item, NodeCoordinate node, qreal xr, qreal yr)
{
    if (!item) {
        return;
    }

    d->items[item] = QPair<NodeCoordinate, NodeCoordinate>(node,
        NodeCoordinate::simple(xr, yr, NodeCoordinate::InnerRelative, NodeCoordinate::InnerRelative));
    item->setManagingLayout(this);
    d->calculateSizeHint(item);
}

void NodeLayout::removeItem(LayoutItem * item)
{
    if (!item) {
        return;
    }

    item->unsetManagingLayout(this);
    d->items.remove(item);
    d->calculateSizeHint();
}

int NodeLayout::count() const
{
    return d->items.count();
}

int NodeLayout::indexOf(LayoutItem * item) const
{
    if (!item) {
        return -1;
    }

    return d->items.keys().indexOf(item);
}

LayoutItem * NodeLayout::itemAt(int i) const
{
    if (i >= d->items.count()) {
        return 0;
    }

    return d->items.keys()[i];
}

LayoutItem * NodeLayout::takeAt(int i)
{
    if (i >= d->items.count()) {
        return 0;
    }

    LayoutItem * item = itemAt(i);
    removeItem(item);
    return item;
}

}