/*****************************************************************

Copyright (c) 1996-2004 the kicker authors. See file AUTHORS.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

******************************************************************/

#include <assert.h>
#include <math.h>

#include <qapplication.h>
#include <kdebug.h>
#include <kglobal.h>

#include "containerarea.h"

#include "containerarealayout.h"


class ContainerAreaLayoutIterator : public QGLayoutIterator
{
    public:
        ContainerAreaLayoutIterator(ContainerAreaLayout::ItemList *l)
            : m_idx(0), m_list(l)
        {
        }

        QLayoutItem* current()
        {
            return m_idx < int(m_list->count()) ? m_list->at(m_idx)->item : 0;
        }

        QLayoutItem* next()
        {
            m_idx++;
            return current();
        }

        QLayoutItem* takeCurrent()
        {
            QLayoutItem* item = 0;
            ContainerAreaLayoutItem* b = m_list->take(m_idx);
            if (b)
            {
                item = b->item;
                b->item = 0;
                delete b;
            }
            return item;
        }

    private:
        int m_idx;
        ContainerAreaLayout::ItemList* m_list;
};


int ContainerAreaLayoutItem::heightForWidth(int w) const
{
    BaseContainer* container = dynamic_cast<BaseContainer*>(item->widget());
    if (container)
        return container->heightForWidth(w);
    else
        return item->sizeHint().height();
}

int ContainerAreaLayoutItem::widthForHeight(int h) const
{
    BaseContainer* container = dynamic_cast<BaseContainer*>(item->widget());
    if (container)
        return container->widthForHeight(h);
    else
        return item->sizeHint().width();
}

bool ContainerAreaLayoutItem::isStretch() const
{
    BaseContainer* container = dynamic_cast<BaseContainer*>(item->widget());
    return container ? container->isStretch() : false;
}

double ContainerAreaLayoutItem::freeSpaceRatio() const
{
    BaseContainer* container = dynamic_cast<BaseContainer*>(item->widget());
    if (container)
        return kClamp(container->freeSpace(), 0.0, 1.0);
    else
        return m_freeSpaceRatio;
}

void ContainerAreaLayoutItem::setFreeSpaceRatio(double ratio)
{
    BaseContainer* container = dynamic_cast<BaseContainer*>(item->widget());
    if (container)
        container->setFreeSpace(ratio);
    else
        m_freeSpaceRatio = ratio;
}

Qt::Orientation ContainerAreaLayoutItem::orientation() const
{
    return m_layout->orientation();
}

QRect ContainerAreaLayoutItem::geometryR() const
{
    return m_layout->transform(geometry());
}

void ContainerAreaLayoutItem::setGeometryR(const QRect& r)
{
    setGeometry(m_layout->transform(r));
}

int ContainerAreaLayoutItem::widthForHeightR(int h) const
{
    if (orientation() == Horizontal)
        return widthForHeight(h);
    else
        return heightForWidth(h);
}

int ContainerAreaLayoutItem::widthR() const
{
    if (orientation() == Horizontal)
        return geometry().width();
    else
        return geometry().height();
}

int ContainerAreaLayoutItem::heightR() const
{
    if (orientation() == Horizontal)
        return geometry().height();
    else
        return geometry().width();
}

int ContainerAreaLayoutItem::leftR() const
{
    if (orientation() == Horizontal)
    {
        if (QApplication::reverseLayout())
            return m_layout->geometry().right() - geometry().right();
        else
            return geometry().left();
    }
    else
    {
        return geometry().top();
    }
}

int ContainerAreaLayoutItem::rightR() const
{
    if (orientation() == Horizontal)
    {
        if (QApplication::reverseLayout())
            return m_layout->geometry().right() - geometry().left();
        else
            return geometry().right();
    }
    else
    {
        return geometry().bottom();
    }
}


ContainerAreaLayout::ContainerAreaLayout(QWidget* parent)
    : QLayout(parent),
      m_orientation(Horizontal),
      m_stretchEnabled(true)
{
}

void ContainerAreaLayout::addItem(QLayoutItem* item)
{
    m_items.append(new ContainerAreaLayoutItem(item, this));
}

void ContainerAreaLayout::insertIntoFreeSpace(QWidget* widget, QPoint insertionPoint)
{
    if (!widget)
    {
        return;
    }

    add(widget);
    ContainerAreaLayoutItem* item = m_items.last();

    if (!item)
    {
        // this should never happen as we just added the item above
        // but we do this to be safe.
        return;
    }

    ItemIterator current(m_items);
    if (!current.current())
    {
        // this shouldn't happen either, but again... we try and be safe
        return;
    }

    ItemIterator next(m_items);
    ++next;

    if (!next.current())
    {
        // first item in!
        item->setGeometryR(QRect(insertionPoint.x(), insertionPoint.y(), widget->width(), widget->height()));
        updateFreeSpaceValues();
        return;
    }

    int insPos = (orientation() == Horizontal) ? insertionPoint.x(): insertionPoint.y();

    for (; next.current(); ++current, ++next)
    {
        if (current.current() == item ||
            next.current() == item)
        {
            continue;
        }

        if (insPos == 0)
        {
            if (current.current()->rightR() + 3 <
                next.current()->leftR())
            {
                insPos = current.current()->rightR();
                break;
            }
        }
        else
        {
            if ((current.current()->rightR() < insPos) &&
                (next.current()->leftR() > insPos))
            {
                // Free space available at insertion point!
                if (insPos + item->widthR() > next.current()->leftR())
                {
                    // We have overlap on the right, move to the left
                    insPos = next.current()->leftR() - item->widthR();
                    if (insPos < current.current()->rightR())
                    {
                        // We have overlap on the left as well, move to the right
                        insPos = current.current()->rightR();
                        // We don't fit entirely, let updateFreeSpaceValues sort it out
                    }
                }
                break;
            }
            if ((next.current()->leftR() <= insPos) && 
                (next.current()->rightR() > insPos))
            {
                // Insert at the location of next
                insPos = next.current()->leftR();
                break;
            }
        }
    }

    QRect geom = item->geometryR();
    geom.moveLeft(insPos);
    item->setGeometryR(geom);
    widget->setGeometry(transform(geom)); // widget isn't shown, layout not active yet

    if (current.current() && next.current())
    {
        m_items.removeRef(item);
        m_items.insert(m_items.findRef(next), item);
    }

    updateFreeSpaceValues();
}

QSize ContainerAreaLayout::sizeHint() const
{
    const int size = KickerLib::sizeValue(KPanelExtension::SizeNormal);

    if (orientation() == Horizontal)
        return QSize(widthForHeight(size), size);
    else
        return QSize(size, heightForWidth(size));
}

QSize ContainerAreaLayout::minimumSize() const
{
    const int size = KickerLib::sizeValue(KPanelExtension::SizeTiny);

    if (orientation() == Horizontal)
        return QSize(widthForHeight(size), size);
    else
        return QSize(size, heightForWidth(size));
}

QLayoutIterator ContainerAreaLayout::iterator()
{
    return QLayoutIterator(new ContainerAreaLayoutIterator(&m_items));
}

void ContainerAreaLayout::setGeometry(const QRect& rect)
{
    //RESEARCH: when can we short curcuit this?
    //          maybe a dirty flag to be set when we have containers
    //          that needs laying out?

    QLayout::setGeometry(rect);

    float totalFreeSpace = kMax(0, widthR() - widthForHeightR(heightR()));
    int occupiedSpace = 0;

    ItemIterator it(m_items);
    while (it.current())
    {
        ContainerAreaLayoutItem* cur  = *it;
        ++it;
        ContainerAreaLayoutItem* next = *it;

        double fs = cur->freeSpaceRatio();
        double freeSpace = fs * totalFreeSpace;
        int pos = int(rint(freeSpace)) + occupiedSpace;

        int w = cur->widthForHeightR(heightR());
        occupiedSpace += w;
        if (m_stretchEnabled && cur->isStretch())
        {
            if (next)
            {
                double nfs = next->freeSpaceRatio();
                w += int((nfs - fs)*totalFreeSpace);
            }
            else
            {
                w = widthR() - pos;
            }
        }
        cur->setGeometryR(QRect(pos, 0, w, heightR()));
    }
}

int ContainerAreaLayout::widthForHeight(int h) const
{
    int width = 0;
    ItemIterator it(m_items);
    for (; it.current(); ++it)
    {
        width += kMax(0, it.current()->widthForHeight(h));
    }
    return width;
}

int ContainerAreaLayout::heightForWidth(int w) const
{
    int height = 0;
    ItemIterator it(m_items);
    for (; it.current(); ++it)
    {
        height += kMax(0, it.current()->heightForWidth(w));
    }
    return height;
}

void ContainerAreaLayout::setStretchEnabled(bool enable)
{
    if (m_stretchEnabled != enable)
    {
        m_stretchEnabled = enable;
        activate();
    }
}

void ContainerAreaLayout::updateContainerList()
{
    updateFreeSpaceValues();
}

void ContainerAreaLayout::updateFreeSpaceValues()
{
    int freeSpace =
        kMax(0, widthR() - widthForHeightR(heightR()));

    double fspace = 0;
    for (ItemIterator it(m_items); it.current(); ++it)
    {
        int distance = distanceToPreviousItem(it);
        if (distance < 0) distance = 0;
        fspace += distance;
        double ssf = ( freeSpace == 0 ? 0 : fspace/freeSpace );
        if (ssf > 1) ssf = 1;
        if (ssf < 0) ssf = 0;
        it.current()->setFreeSpaceRatio(ssf);
    }
}

int ContainerAreaLayout::distanceToPreviousItem(ItemIterator it) const
{
    assert(it.current());

    ContainerAreaLayoutItem* cur  = it.current();
    ContainerAreaLayoutItem* prev = --it;

    return prev ? cur->leftR() - prev->leftR() - prev->widthForHeightR(heightR()) :
                  cur->leftR() - leftR();
}

void ContainerAreaLayout::moveContainerSwitch(QWidget* container, int distance)
{
    const bool horizontal    = orientation() == Horizontal;
    const bool reverseLayout = QApplication::reverseLayout();

    if (horizontal && reverseLayout)
        distance = - distance;

    const bool forward = distance > 0;

    // Get the iterator 'it' pointing to 'moving'.
    ItemIterator it(m_items);
    while (it.current() && it.current()->item->widget() != container)
    {
        ++it;
    }

    ContainerAreaLayoutItem* moving = it.current();
    ContainerAreaLayoutItem* next   = forward ? ++it : --it;
    ContainerAreaLayoutItem* last   = moving;

    if (!moving)
    {
        return;
    }

    while (next)
    {
        // Calculate the position and width of the virtual container
        // containing 'moving' and 'next'.
        int tpos = forward ? next->leftR() - moving->widthR()
                           : next->leftR();
        int tsize = moving->widthR() + next->widthR();

        // Determine the middle of the containers.
        int tmiddle = tpos + tsize / 2;
        int movingMiddle = moving->leftR() + distance + moving->widthR() / 2;

        // Check if the middle of 'moving' has moved past the middle of the
        // virtual container.
        if (!forward && movingMiddle > tmiddle
          || forward && movingMiddle < tmiddle)
            break;

        // Move 'next' to the other side of 'moving'.
        QRect geom = next->geometryR();
        if (forward)
            geom.moveLeft(geom.left() - moving->widthR());
        else
            geom.moveLeft(geom.left() + moving->widthR());
        next->setGeometryR(geom);

        // Store 'next'. It may become null in the next iteration, but we
        // need its value later.
        last = next;
        next = forward ? ++it : --it;
    }

    int newPos = moving->leftR() + distance;
    if (last != moving)
    {
        // The case that moving has switched position with at least one other container.
        newPos = forward ? kMax(newPos, last->rightR() + 1)
                         : kMin(newPos, last->leftR() - moving->widthR());

        // Move 'moving' to its new position in the container list.
        m_items.removeRef(moving);
        m_items.insert(m_items.findRef(last) + (forward ? 1 : 0), moving);
    }
    else if (next)
    {
        // Make sure that the moving container will not overlap the next one.
        newPos = forward ? kMin(newPos, next->leftR() - moving->widthR())
                         : kMax(newPos, next->rightR() + 1);
    }

    // Move the container to its new position and prevent it from moving outside the panel.
    QRect geom = moving->geometryR();
    distance = kClamp(newPos, 0, widthR() - moving->widthR());
    geom.moveLeft(distance);
    moving->setGeometryR(geom);

    updateFreeSpaceValues();
}

int ContainerAreaLayout::moveContainerPush(QWidget* a, int distance)
{
    const bool horizontal    = orientation() == Horizontal;
    const bool reverseLayout = QApplication::reverseLayout();

    // Get the iterator 'it' pointing to the layoutitem representing 'a'.
    ItemIterator it(m_items);
    while (it.current() && it.current()->item->widget() != a)
        ++it;

    if (it.current())
    {
        if (horizontal && reverseLayout)
            distance = -distance;
        int retVal = moveContainerPushRecursive(it, distance);
        updateFreeSpaceValues();
        if (horizontal && reverseLayout)
            retVal = -retVal;
        return retVal;
    }
    else
    {
        return 0;
    }
}

int ContainerAreaLayout::moveContainerPushRecursive(ItemIterator it,
                                                    int distance)
{
    if (distance == 0)
        return 0;

    const bool forward = distance > 0;

    int available; // Space available for the container to move.
    int moved;     // The actual distance the container will move.
    ContainerAreaLayoutItem* cur  = it.current();
    ContainerAreaLayoutItem* next = forward ? ++it : --it;

    if (!next)
    {
        available = forward ? rightR() - cur->rightR()
                            : -cur->leftR();
    }
    else
    {
        available = forward ? next->leftR()  - cur->rightR() - 1
                            : next->rightR() - cur->leftR()  + 1;

        if (!forward && distance < available
          || forward && distance > available)
            available += moveContainerPushRecursive(it, distance - available);
    }
    moved = forward ? kMin(distance, available)
                    : kMax(distance, available);

    QRect geom = cur->geometryR();
    geom.moveLeft(geom.left() + moved);
    cur->setGeometryR(geom);

    return moved;
}

QRect ContainerAreaLayout::transform(const QRect& r) const
{
    if (orientation() == Horizontal)
    {
        if (QApplication::reverseLayout())
        {
            QRect t = r;
            t.moveLeft(geometry().right() - r.right());
            return t;
        }
        else
        {
            return r;
        }
    }
    else
    {
        return QRect(r.y(), r.x(), r.height(), r.width());
    }
}

int ContainerAreaLayout::widthForHeightR(int h) const
{
    if (orientation() == Horizontal)
        return widthForHeight(h);
    else
        return heightForWidth(h);
}

int ContainerAreaLayout::widthR() const
{
    if (orientation() == Horizontal)
        return geometry().width();
    else
        return geometry().height();
}

int ContainerAreaLayout::heightR() const
{
    if (orientation() == Horizontal)
        return geometry().height();
    else
        return geometry().width();
}

int ContainerAreaLayout::leftR() const
{
    if (orientation() == Horizontal)
        return geometry().left();
    else
        return geometry().top();
}

int ContainerAreaLayout::rightR() const
{
    if (orientation() == Horizontal)
        return geometry().right();
    else
        return geometry().bottom();
}

