/************************************************************************
 *
 * Copyright 2012 Jakob Leben (jakob.leben@gmail.com)
 *
 * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
 * All rights reserved.
 * Contact: Nokia Corporation (qt-info@nokia.com)
 *
 * This file is part of SuperCollider Qt GUI.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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 General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 ************************************************************************/

#include "stack_layout.hpp"

#include <QWidget>

namespace QtCollider {

StackLayout::StackLayout(): _index(-1), _mode(StackOne), _gotParent(false) {}

StackLayout::~StackLayout() { qDeleteAll(_list); }

int StackLayout::addWidget(QWidget* widget) { return insertWidget(_list.count(), widget); }

int StackLayout::insertWidget(int index, QWidget* widget) {
    addChildWidget(widget);
    index = qMin(index, _list.count());
    if (index < 0)
        index = _list.count();
    QWidgetItem* wi = new QWidgetItem(widget);
    _list.insert(index, wi);
    invalidate();
    if (_index < 0) {
        setCurrentIndex(index);
    } else {
        if (index <= _index)
            ++_index;
        if (_mode == StackOne)
            widget->hide();
        widget->lower();
    }
    return index;
}

QLayoutItem* StackLayout::itemAt(int index) const { return _list.value(index); }

QLayoutItem* StackLayout::takeAt(int index) {
    if (index < 0 || index >= _list.size())
        return 0;
    QLayoutItem* item = _list.takeAt(index);
    if (index == _index) {
        _index = -1;
        if (_list.count() > 0) {
            int newIndex = (index == _list.count()) ? index - 1 : index;
            setCurrentIndex(newIndex);
        }
    } else if (index < _index) {
        --_index;
    }
    // NOTE: Here, the Qt implementation hides item->widget() if it exists and is not being
    // deleted. We can't reproduce this, because we can't access private info on whether the
    // widget is being deleted.
    return item;
}

void StackLayout::setCurrentIndex(int index) {
    QWidget* prev = currentWidget();
    QWidget* next = widget(index);
    if (!next || next == prev)
        return;

    _index = index;

    if (!parent())
        return;

    bool reenableUpdates = false;
    QWidget* parent = parentWidget();

    if (parent && parent->updatesEnabled()) {
        reenableUpdates = true;
        parent->setUpdatesEnabled(false);
    }

    QWidget* fw = parent ? parent->window()->focusWidget() : 0;
    if (prev) {
        prev->clearFocus();
        if (_mode == StackOne)
            prev->hide();
    }

    next->raise();
    next->show();

    // try to move focus onto the incoming widget if focus
    // was somewhere on the outgoing widget.

    if (parent) {
        if (fw && (prev && prev->isAncestorOf(fw))) { // focus was on old page
            // look for the best focus widget we can find
            if (QWidget* nfw = next->focusWidget())
                nfw->setFocus();
            else {
                // second best: first child widget in the focus chain
                QWidget* i = fw;
                while ((i = i->nextInFocusChain()) != fw) {
                    if (((i->focusPolicy() & Qt::TabFocus) == Qt::TabFocus) && !i->focusProxy() && i->isVisibleTo(next)
                        && i->isEnabled() && next->isAncestorOf(i)) {
                        i->setFocus();
                        break;
                    }
                }
                // third best: incoming widget
                if (i == fw)
                    next->setFocus();
            }
        }
    }
    if (reenableUpdates)
        parent->setUpdatesEnabled(true);

    if (_mode == StackOne)
        // expandingDirections() might have changed, so invalidate():
        invalidate();
}

int StackLayout::currentIndex() const { return _index; }

void StackLayout::setCurrentWidget(QWidget* widget) {
    int index = indexOf(widget);
    if (index == -1) {
        qWarning("StackLayout::setCurrentWidget: Widget %p not contained in stack", widget);
        return;
    }
    setCurrentIndex(index);
}

QWidget* StackLayout::currentWidget() const { return _index >= 0 ? _list.at(_index)->widget() : 0; }

QWidget* StackLayout::widget(int index) const {
    if (index < 0 || index >= _list.size())
        return 0;
    return _list.at(index)->widget();
}

int StackLayout::count() const { return _list.size(); }

void StackLayout::addItem(QLayoutItem* item) {
    QWidget* widget = item->widget();
    if (widget) {
        addWidget(widget);
        delete item;
    } else {
        qWarning("StackLayout::addItem: Only widgets can be added");
    }
}

QSize StackLayout::sizeHint() const {
    QSize s(0, 0);

    switch (_mode) {
    case StackOne:
        if (_index >= 0)
            if (QWidget* w = _list.at(_index)->widget()) {
                if (w->sizePolicy().horizontalPolicy() != QSizePolicy::Ignored)
                    s.setWidth(w->sizeHint().width());
                if (w->sizePolicy().verticalPolicy() != QSizePolicy::Ignored)
                    s.setHeight(w->sizeHint().height());
            }
        break;

    case StackAll: {
        int n = _list.count();
        for (int i = 0; i < n; ++i)
            if (QWidget* w = _list.at(i)->widget()) {
                QSize ws(w->sizeHint());
                if (w->sizePolicy().horizontalPolicy() == QSizePolicy::Ignored)
                    ws.setWidth(0);
                if (w->sizePolicy().verticalPolicy() == QSizePolicy::Ignored)
                    ws.setHeight(0);
                s = s.expandedTo(ws);
            }
        break;
    }
    }

    return s;
}

static QSize smartMinSize(const QSize& sizeHint, const QSize& minSizeHint, const QSize& minSize, const QSize& maxSize,
                          const QSizePolicy& sizePolicy) {
    QSize s(0, 0);

    if (sizePolicy.horizontalPolicy() != QSizePolicy::Ignored) {
        if (sizePolicy.horizontalPolicy() & QSizePolicy::ShrinkFlag)
            s.setWidth(minSizeHint.width());
        else
            s.setWidth(qMax(sizeHint.width(), minSizeHint.width()));
    }

    if (sizePolicy.verticalPolicy() != QSizePolicy::Ignored) {
        if (sizePolicy.verticalPolicy() & QSizePolicy::ShrinkFlag) {
            s.setHeight(minSizeHint.height());
        } else {
            s.setHeight(qMax(sizeHint.height(), minSizeHint.height()));
        }
    }

    s = s.boundedTo(maxSize);
    if (minSize.width() > 0)
        s.setWidth(minSize.width());
    if (minSize.height() > 0)
        s.setHeight(minSize.height());

    return s.expandedTo(QSize(0, 0));
}

QSize StackLayout::minimumSize() const {
    QSize s(0, 0);

    switch (_mode) {
    case StackOne:
        if (_index >= 0)
            if (QWidget* w = _list.at(_index)->widget())
                s = smartMinSize(w->sizeHint(), w->minimumSizeHint(), w->minimumSize(), w->maximumSize(),
                                 w->sizePolicy());
        break;

    case StackAll: {
        int n = _list.count();
        for (int i = 0; i < n; ++i)
            if (QWidget* w = _list.at(i)->widget())
                s = s.expandedTo(smartMinSize(w->sizeHint(), w->minimumSizeHint(), w->minimumSize(), w->maximumSize(),
                                              w->sizePolicy()));
        break;
    }
    }

    return s;
}

Qt::Orientations StackLayout::expandingDirections() const {
    Qt::Orientations directions;

    switch (_mode) {
    case StackOne:
        directions = _index >= 0 ? _list.at(_index)->expandingDirections() : Qt::Orientations {};
        break;

    case StackAll: {
        Qt::Orientations directions {};
        int n = _list.count();
        for (int i = 0; i < n; ++i)
            directions |= _list.at(i)->expandingDirections();
        break;
    }
    }

    return directions;
}

void StackLayout::setGeometry(const QRect& rect) {
    switch (_mode) {
    case StackOne:
        if (QWidget* widget = currentWidget())
            widget->setGeometry(rect);
        break;
    case StackAll:
        if (const int n = _list.count())
            for (int i = 0; i < n; ++i)
                if (QWidget* widget = _list.at(i)->widget())
                    widget->setGeometry(rect);
        break;
    }
}

StackLayout::StackingMode StackLayout::stackingMode() const { return _mode; }

void StackLayout::setStackingMode(StackingMode stackingMode) {
    if (_mode == stackingMode)
        return;
    _mode = stackingMode;

    if (!parent())
        return;

    const int n = _list.count();
    if (n == 0)
        return;

    switch (_mode) {
    case StackOne: {
        const int idx = currentIndex();
        if (idx >= 0)
            for (int i = 0; i < n; ++i)
                if (QWidget* widget = _list.at(i)->widget())
                    widget->setVisible(i == idx);
        break;
    }
    case StackAll: { // Turn overlay on: Make sure all widgets are the same size
        QRect geometry;
        if (const QWidget* widget = currentWidget())
            geometry = widget->geometry();
        for (int i = 0; i < n; ++i)
            if (QWidget* widget = _list.at(i)->widget()) {
                if (!geometry.isNull())
                    widget->setGeometry(geometry);
                widget->setVisible(true);
            }
    } break;
    }

    invalidate();
}

void StackLayout::invalidate() {
    QWidget* pw = parentWidget();

    if (pw && !_gotParent) {
        _gotParent = true;

        const int n = _list.count();
        if (n == 0)
            return;

        QWidget* cw = currentWidget();

        switch (_mode) {
        case StackOne: {
            if (cw)
                for (int i = 0; i < n; ++i)
                    if (QWidget* widget = _list.at(i)->widget())
                        widget->setVisible(widget == cw);
            break;
        }
        case StackAll: {
            for (int i = 0; i < n; ++i)
                if (QWidget* widget = _list.at(i)->widget())
                    widget->setVisible(true);
            break;
        }
        }

        // NOTE: re-order the widgets after setting visibility, since the latter
        // may affect the order itself (at least on Mac OS X)
        if (cw) {
            for (int i = 0; i < n; ++i) {
                QWidget* w = _list.at(i)->widget();
                if (w && w != cw)
                    w->lower();
            }
        }
    }

    QLayout::invalidate();
}

} // namespace QtCollider
