/*******************************************************************************
KWin - the KDE window manager
This file is part of the KDE project.

Copyright (C) 2009 Jorge Mata <matamax123@gmail.com>
Copyright (C) 2009 Lucas Murray <lmurray@undefinedfire.com>

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 "clientgroup.h"

#include "client.h"
#include "effects.h"

namespace KWin
{

ClientGroup::ClientGroup(Client *c)
    : clients_()
    , items_()
    , visible_(0)
    , minSize_(0, 0)
    , maxSize_(INT_MAX, INT_MAX)
{
    clients_.append(c);
    updateItems();
    updateMinMaxSize();
    Workspace::self()->addClientGroup(this);
    c->setClientShown(true);   // Ensure the client is visible
    c->triggerDecorationRepaint(); // TODO: Required? Maybe for creating new group?
}

ClientGroup::~ClientGroup()
{
    Workspace::self()->removeClientGroup(this);
}

void ClientGroup::add(Client* c, int before, bool becomeVisible)
{
    if (contains(c) || !c->workspace()->decorationSupportsClientGrouping())
        return;

    // Remove the Client->ClientGroup reference if the client is already in another group so we
    // don't change the geometry of other clients in their current group by accident. However
    // don't REMOVE them from the actual group until we are certain that the client will be moved.
    ClientGroup* oldGroup = NULL;
    if (c->clientGroup()) {
        oldGroup = c->clientGroup();
        c->setClientGroup(NULL);
    }

    // If it's not possible to have the same states then ungroup them, TODO: Check all states
    // We do this here as the ungroup code in updateStates() cannot be called until add() completes
    ShadeMode oldShadeMode = c->shadeMode();
    if (c->shadeMode() != clients_[visible_]->shadeMode())
        c->setShade(clients_[visible_]->shadeMode());
    if (c->shadeMode() != clients_[visible_]->shadeMode()) {
        if (oldGroup)   // Re-add to old group if required
            c->setClientGroup(oldGroup);
        // One need to trigger decoration repaint on the group to
        // make sure hover animations are properly reset.
        clients_[visible_]->triggerDecorationRepaint();
        return;
    }
    QRect oldGeom = c->geometry();
    if (c->geometry() != clients_[visible_]->geometry())
        c->setGeometry(clients_[visible_]->geometry());
    if (c->geometry() != clients_[visible_]->geometry()) {
        if (c->shadeMode() != oldShadeMode)
            c->setShade(oldShadeMode);    // Restore old shade mode
        if (oldGroup)   // Re-add to old group if required
            c->setClientGroup(oldGroup);
        clients_[visible_]->triggerDecorationRepaint();
        return;
    }
    if (c->desktop() != clients_[visible_]->desktop())
        c->setDesktop(clients_[visible_]->desktop());
    if (c->desktop() != clients_[visible_]->desktop()) {
        if (c->geometry() != oldGeom)
            c->setGeometry(oldGeom);   // Restore old geometry
        if (c->shadeMode() != oldShadeMode)
            c->setShade(oldShadeMode);    // Restore old shade mode
        if (oldGroup)   // Re-add to old group if required
            c->setClientGroup(oldGroup);
        clients_[visible_]->triggerDecorationRepaint();
        return;
    }

    // Tabbed windows MUST have a decoration
    if (c->noBorder())
        c->setNoBorder(false);
    if (clients_[visible_]->noBorder())
        clients_[visible_]->setNoBorder(false);

    // Re-add to old group if required for the effect hook
    if (oldGroup)
        c->setClientGroup(oldGroup);

    // Notify effects of merge
    if (effects != NULL)
        static_cast<EffectsHandlerImpl*>(effects)->slotClientGroupItemAdded(
            c->effectWindow(), clients_[visible_]->effectWindow());

    // Actually remove from old group if required and update
    if (c->clientGroup())
        c->clientGroup()->remove(c);
    c->setClientGroup(this);   // Let the client know which group it belongs to

    // Actually add to new group
    if (before >= 0) {
        if (visible_ >= before)
            visible_++;
        clients_.insert(before, c);
    } else
        clients_.append(c);
    if (!becomeVisible)   // Hide before adding
        c->setClientShown(false);
    updateItems();
    updateMinMaxSize();
    updateStates(clients_[visible_], c);

    if (becomeVisible)   // Set visible after settings geometry
        setVisible(c);

    // Activate the new visible window
    clients_[visible_]->setActive(true);
    //clients_[visible_]->takeFocus( Allowed );
    //clients_[visible_]->workspace()->raiseClient( clients_[visible_] );

    clients_[visible_]->triggerDecorationRepaint();
}

void ClientGroup::remove(int index, const QRect& newGeom, bool toNullGroup)
{
    remove(clients_[index], newGeom, toNullGroup);
}

void ClientGroup::remove(Client* c, const QRect& newGeom, bool toNullGroup)
{
    if (!c)
        return;
    if (clients_.count() < 2) {
        c->setClientGroup(NULL);
        Workspace::self()->removeClientGroup(this);   // Remove immediately
        delete this;
        return;
    }

    ClientList::const_iterator i;
    Client* newVisible = clients_[visible_];
    if (newVisible == c)
        newVisible = (visible_ != clients_.size() - 1) ? clients_[visible_ + 1] : clients_[visible_ - 1];

    // Notify effects of removal
    if (effects)
        static_cast<EffectsHandlerImpl*>(effects)->slotClientGroupItemRemoved(
            c->effectWindow(), newVisible->effectWindow());

    setVisible(newVisible);   // Display new window before removing old one
    clients_.removeAll(c);
    visible_ = indexOfClient(newVisible);   // Index may have changed
    updateItems();
    updateMinMaxSize();

    c->setClientGroup(toNullGroup ? NULL : new ClientGroup(c));
    if (newGeom.isValid()) {
        // HACK: if the group was maximized, one needs to make some checks on the future client maximize mode
        // because the transition from maximized to MaximizeRestore is not handled properly in setGeometry when
        // the new geometry size is unchanged.
        // since newGeom has the same size as the old client geometry, one just needs to check the topLeft position of newGeom
        // and compare that to the group maximize mode.
        // when the new mode is predicted to be MaximizeRestore, one must set it manually, in order to avoid decoration artifacts
        Client::MaximizeMode groupMaxMode(newVisible->maximizeMode());
        if (((groupMaxMode & Client::MaximizeHorizontal) && newGeom.left() != newVisible->geometry().left()) ||
                ((groupMaxMode & Client::MaximizeVertical) && newGeom.top() != newVisible->geometry().top()))
            c->maximize(Client::MaximizeRestore);
        c->setGeometry(newGeom);
    }
    newVisible->triggerDecorationRepaint();
}

void ClientGroup::removeAll()
{
    while (clients_.count() > 1)
        remove(clients_.at(1));
}

void ClientGroup::closeAll()
{
    Client* front;
    ClientList list(clients_);
    while (!list.isEmpty()) {
        front = list.front();
        list.pop_front();
        if (front != clients_[visible_])
            front->closeWindow();
    }
    clients_[visible_]->closeWindow();
}

void ClientGroup::move(int index, int before)
{
    move(clients_[index], (before >= 0 && before < clients_.size()) ? clients_[before] : NULL);
}

void ClientGroup::move(Client* c, Client* before)
{
    if (c == before)   // Impossible to do
        return;

    Client* wasVisible = clients_[visible_];
    clients_.removeAll(c);
    clients_.insert(before ? indexOfClient(before) : clients_.size(), c);
    visible_ = indexOfClient(wasVisible);
    updateItems();

    clients_[visible_]->triggerDecorationRepaint();
}

void ClientGroup::displayClientMenu(int index, const QPoint& pos)
{
    if (index == -1)
        index = visible_;
    displayClientMenu(clients_[index], pos);
}

void ClientGroup::displayClientMenu(Client* c, const QPoint& pos)
{
    c->workspace()->showWindowMenu(pos, c);
}

bool ClientGroup::containsActiveClient()
{
    return contains(Workspace::self()->activeClient());
}

void ClientGroup::setVisible(int index)
{
    setVisible(clients_[index]);
}

void ClientGroup::setVisible(Client* c)
{
    if (c == clients_[visible_] || !contains(c))
        return;

    // Notify effects of switch
    if (effects != NULL)
        static_cast<EffectsHandlerImpl*>(effects)->slotClientGroupItemSwitched(
            clients_[visible_]->effectWindow(), c->effectWindow());

    visible_ = indexOfClient(c);
    c->setClientShown(true);
    for (ClientList::const_iterator i = clients_.constBegin(); i != clients_.constEnd(); ++i)
        if ((*i) != c)
            (*i)->setClientShown(false);
}

void ClientGroup::updateStates(Client* main, Client* only)
{
    for (ClientList::const_iterator i = clients_.constBegin(); i != clients_.constEnd(); ++i)
        if ((*i) != main && (!only || (*i) == only)) {
            if ((*i)->isMinimized() != main->isMinimized()) {
                if (main->isMinimized())
                    (*i)->minimize(true);
                else
                    (*i)->unminimize(true);
            }
            if ((*i)->isShade() != main->isShade())
                (*i)->setShade(main->isShade() ? ShadeNormal : ShadeNone);
            if ((*i)->geometry() != main->geometry())
                (*i)->setGeometry(main->geometry());
            if ((*i)->desktop() != main->desktop())
                (*i)->setDesktop(main->desktop());
            if ((*i)->isOnAllDesktops() != main->isOnAllDesktops())
                (*i)->setOnAllDesktops(main->isOnAllDesktops());
            if ((*i)->activities() != main->activities())
                (*i)->setOnActivities(main->activities());
            if ((*i)->keepAbove() != main->keepAbove())
                (*i)->setKeepAbove(main->keepAbove());
            if ((*i)->keepBelow() != main->keepBelow())
                (*i)->setKeepBelow(main->keepBelow());

            // If it's not possible to have the same states then ungroup them, TODO: Check all states
            if ((*i)->geometry() != main->geometry())
                remove(*i);
            if ((*i)->desktop() != main->desktop())
                remove(*i);
        }
}

void ClientGroup::updateItems()
{
    items_.clear();
    for (ClientList::const_iterator i = clients_.constBegin(); i != clients_.constEnd(); ++i) {
        QIcon icon((*i)->icon());
        icon.addPixmap((*i)->miniIcon());
        items_.append(ClientGroupItem((*i)->caption(), icon));
    }
}

void ClientGroup::updateMinMaxSize()
{
    // Determine entire group's minimum and maximum sizes
    minSize_ = QSize(0, 0);
    maxSize_ = QSize(INT_MAX, INT_MAX);
    for (ClientList::const_iterator i = clients_.constBegin(); i != clients_.constEnd(); ++i) {
        if ((*i)->minSize().width() > minSize_.width())
            minSize_.setWidth((*i)->minSize().width());
        if ((*i)->minSize().height() > minSize_.height())
            minSize_.setHeight((*i)->minSize().height());
        if ((*i)->maxSize().width() < maxSize_.width())
            maxSize_.setWidth((*i)->maxSize().width());
        if ((*i)->maxSize().height() < maxSize_.height())
            maxSize_.setHeight((*i)->maxSize().height());
    }
    if (minSize_.width() > maxSize_.width() ||
            minSize_.height() > maxSize_.height()) {
        //kWarning(1212) << "ClientGroup's min size is greater than its max size. Setting max to min.";
        maxSize_ = minSize_;
    }

    // Ensure all windows are within these sizes
    const QSize size = clients_[visible_]->clientSize();
    QSize newSize(
        qBound(minSize_.width(), size.width(), maxSize_.width()),
        qBound(minSize_.height(), size.height(), maxSize_.height()));
    if (newSize != size)
        for (ClientList::const_iterator i = clients_.constBegin(); i != clients_.constEnd(); ++i)
            // TODO: Doesn't affect shaded windows?
            // There seems to be a race condition when using plainResize() which causes the window
            // to sometimes be located at new window's location instead of the visible window's location
            // when a window with a large min size is added to a group with a small window size.
            (*i)->setGeometry(QRect(clients_[visible_]->pos(), (*i)->sizeForClientSize(newSize)));
}

}