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

Copyright (C) 2009 Lucas Murray <lmurray@undefinedfire.com>
Copyright (C) 2012 Martin Gräßlin <mgraesslin@kde.org>

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 "virtualdesktops.h"
#include "input.h"
// KDE
#include <KConfigGroup>
#include <KGlobalAccel>
#include <KLocalizedString>
#include <NETWM>
// Qt
#include <QAction>

namespace KWin {

extern int screen_number;

VirtualDesktopGrid::VirtualDesktopGrid()
    : m_size(1, 2) // Default to tow rows
    , m_grid(new uint[2])
{
    // Initializing grid array
    m_grid[0] = 0;
    m_grid[1] = 0;
}

VirtualDesktopGrid::~VirtualDesktopGrid()
{
    delete[] m_grid;
}

void VirtualDesktopGrid::update(const QSize &size, Qt::Orientation orientation)
{
    // Set private variables
    delete[] m_grid;
    m_size = size;
    const uint width = size.width();
    const uint height = size.height();
    const uint length = width * height;
    const uint desktopCount = VirtualDesktopManager::self()->count();
    m_grid = new uint[length];

    // Populate grid
    uint desktop = 1;
    if (orientation == Qt::Horizontal) {
        for (uint y = 0; y < height; ++y) {
            for (uint x = 0; x < width; ++x) {
                m_grid[y * width + x] = (desktop <= desktopCount ? desktop++ : 0);
            }
        }
    } else {
        for (uint x = 0; x < width; ++x) {
            for (uint y = 0; y < height; ++y) {
                m_grid[y * width + x] = (desktop <= desktopCount ? desktop++ : 0);
            }
        }
    }
}

QPoint VirtualDesktopGrid::gridCoords(uint id) const
{
    for (int y = 0; y < m_size.height(); ++y) {
        for (int x = 0; x < m_size.width(); ++x) {
            if (m_grid[y * m_size.width() + x] == id) {
                return QPoint(x, y);
            }
        }
    }
    return QPoint(-1, -1);
}

KWIN_SINGLETON_FACTORY_VARIABLE(VirtualDesktopManager, s_manager)

VirtualDesktopManager::VirtualDesktopManager(QObject *parent)
    : QObject(parent)
    , m_current(0)
    , m_count(0)
    , m_navigationWrapsAround(false)
    , m_rootInfo(NULL)
{
}

VirtualDesktopManager::~VirtualDesktopManager()
{
    s_manager = NULL;
}

QString VirtualDesktopManager::name(uint desktop) const
{
    if (!m_rootInfo) {
        return defaultName(desktop);
    }
    return QString::fromUtf8(m_rootInfo->desktopName(desktop));
}

uint VirtualDesktopManager::above(uint id, bool wrap) const
{
    if (id == 0) {
        id = current();
    }
    QPoint coords = m_grid.gridCoords(id);
    Q_ASSERT(coords.x() >= 0);
    while (true) {
        coords.ry()--;
        if (coords.y() < 0) {
            if (wrap) {
                coords.setY(m_grid.height() - 1);
            } else {
                return id; // Already at the top-most desktop
            }
        }
        const uint desktop = m_grid.at(coords);
        if (desktop > 0) {
            return desktop;
        }
    }
}

uint VirtualDesktopManager::toRight(uint id, bool wrap) const
{
    if (id == 0) {
        id = current();
    }
    QPoint coords = m_grid.gridCoords(id);
    Q_ASSERT(coords.x() >= 0);
    while (true) {
        coords.rx()++;
        if (coords.x() >= m_grid.width()) {
            if (wrap) {
                coords.setX(0);
            } else {
                return id; // Already at the right-most desktop
            }
        }
        const uint desktop = m_grid.at(coords);
        if (desktop > 0) {
            return desktop;
        }
    }
}

uint VirtualDesktopManager::below(uint id, bool wrap) const
{
    if (id == 0) {
        id = current();
    }
    QPoint coords = m_grid.gridCoords(id);
    Q_ASSERT(coords.x() >= 0);
    while (true) {
        coords.ry()++;
        if (coords.y() >= m_grid.height()) {
            if (wrap) {
                coords.setY(0);
            } else {
                // Already at the bottom-most desktop
                return id;
            }
        }
        const uint desktop = m_grid.at(coords);
        if (desktop > 0) {
            return desktop;
        }
    }
}

uint VirtualDesktopManager::toLeft(uint id, bool wrap) const
{
    if (id == 0) {
        id = current();
    }
    QPoint coords = m_grid.gridCoords(id);
    Q_ASSERT(coords.x() >= 0);
    while (true) {
        coords.rx()--;
        if (coords.x() < 0) {
            if (wrap) {
                coords.setX(m_grid.width() - 1);
            } else {
                return id; // Already at the left-most desktop
            }
        }
        const uint desktop = m_grid.at(coords);
        if (desktop > 0) {
            return desktop;
        }
    }
}

uint VirtualDesktopManager::next(uint id, bool wrap) const
{
    if (id == 0) {
        id = current();
    }
    const uint desktop = id + 1;
    if (desktop > count()) {
        if (wrap) {
            return 1;
        } else {
            // are at the last desktop, without wrap return current
            return id;
        }
    }
    return desktop;
}

uint VirtualDesktopManager::previous(uint id, bool wrap) const
{
    if (id == 0) {
        id = current();
    }
    const uint desktop = id - 1;
    if (desktop == 0) {
        if (wrap) {
            return count();
        } else {
            // are at the first desktop, without wrap return current
            return id;
        }
    }
    return desktop;
}

bool VirtualDesktopManager::setCurrent(uint newDesktop)
{
    if (newDesktop < 1 || newDesktop > count() || newDesktop == m_current) {
        return false;
    }
    const uint oldDesktop = m_current;
    // change the desktop
    m_current = newDesktop;
    emit currentChanged(oldDesktop, newDesktop);
    return true;
}

void VirtualDesktopManager::setCount(uint count)
{
    count = qBound<uint>(1, count, VirtualDesktopManager::maximum());
    if (count == m_count) {
        // nothing to change
        return;
    }
    const uint oldCount = m_count;
    m_count = count;

    if (oldCount > m_count) {
        handleDesktopsRemoved(oldCount);
    }
    updateRootInfo();

    save();
    emit countChanged(oldCount, m_count);
}

void VirtualDesktopManager::handleDesktopsRemoved(uint previousCount)
{
    if (current() > count()) {
        setCurrent(count());
    }
    emit desktopsRemoved(previousCount);
}

void VirtualDesktopManager::updateRootInfo()
{
    if (!m_rootInfo) {
        // Make sure the layout is still valid
        updateLayout();
        return;
    }
    const int n = count();
    m_rootInfo->setNumberOfDesktops(n);
    NETPoint *viewports = new NETPoint[n];
    m_rootInfo->setDesktopViewport(n, *viewports);
    delete[] viewports;
    // Make sure the layout is still valid
    updateLayout();
}

void VirtualDesktopManager::updateLayout()
{
    int width = 0;
    int height = 0;
    Qt::Orientation orientation = Qt::Horizontal;
    if (m_rootInfo) {
        // TODO: Is there a sane way to avoid overriding the existing grid?
        width = m_rootInfo->desktopLayoutColumnsRows().width();
        height = m_rootInfo->desktopLayoutColumnsRows().height();
        orientation = m_rootInfo->desktopLayoutOrientation() == NET::OrientationHorizontal ? Qt::Horizontal : Qt::Vertical;
    }
    if (width == 0 && height == 0) {
        // Not given, set default layout
        height = count() == 1u ? 1 : 2;
    }
    setNETDesktopLayout(orientation,
        width, height, 0 //rootInfo->desktopLayoutCorner() // Not really worth implementing right now.
    );
}

static bool s_loadingDesktopSettings = false;

void VirtualDesktopManager::load()
{
    s_loadingDesktopSettings = true;
    if (!m_config) {
        return;
    }
    QString groupname;
    if (screen_number == 0) {
        groupname = QStringLiteral("Desktops");
    } else {
        groupname.sprintf("Desktops-screen-%d", screen_number);
    }
    KConfigGroup group(m_config, groupname);
    const int n = group.readEntry("Number", 1);
    setCount(n);
    if (m_rootInfo) {
        for (int i = 1; i <= n; i++) {
            QString s = group.readEntry(QStringLiteral("Name_%1").arg(i), i18n("Desktop %1", i));
            m_rootInfo->setDesktopName(i, s.toUtf8().data());
            // TODO: update desktop focus chain, why?
//         m_desktopFocusChain.value()[i-1] = i;
        }

        int rows = group.readEntry<int>("Rows", 2);
        rows = qBound(1, rows, n);
        // avoid weird cases like having 3 rows for 4 desktops, where the last row is unused
        int columns = n / rows;
        if (n % rows > 0) {
            columns++;
        }
        m_rootInfo->setDesktopLayout(NET::OrientationHorizontal, columns, rows, NET::DesktopLayoutCornerTopLeft);
        m_rootInfo->activate();
    }
    s_loadingDesktopSettings = false;
}

void VirtualDesktopManager::save()
{
    if (s_loadingDesktopSettings) {
        return;
    }
    if (!m_config) {
        return;
    }
    QString groupname;
    if (screen_number == 0) {
        groupname = QStringLiteral("Desktops");
    } else {
        groupname.sprintf("Desktops-screen-%d", screen_number);
    }
    KConfigGroup group(m_config, groupname);

    group.writeEntry("Number", count());
    for (uint i = 1; i <= count(); ++i) {
        QString s = name(i);
        const QString defaultvalue = defaultName(i);
        if (s.isEmpty()) {
            s = defaultvalue;
            if (m_rootInfo) {
                m_rootInfo->setDesktopName(i, s.toUtf8().data());
            }
        }

        if (s != defaultvalue) {
            group.writeEntry(QStringLiteral("Name_%1").arg(i), s);
        } else {
            QString currentvalue = group.readEntry(QStringLiteral("Name_%1").arg(i), QString());
            if (currentvalue != defaultvalue) {
                group.deleteEntry(QStringLiteral("Name_%1").arg(i));
            }
        }
    }

    // Save to disk
    group.sync();
}

QString VirtualDesktopManager::defaultName(int desktop) const
{
    return i18n("Desktop %1", desktop);
}

void VirtualDesktopManager::setNETDesktopLayout(Qt::Orientation orientation, uint width, uint height, int startingCorner)
{
    Q_UNUSED(startingCorner);   // Not really worth implementing right now.

    // Calculate valid grid size
    Q_ASSERT(width > 0 || height > 0);
    if ((width <= 0) && (height > 0)) {
        width = (m_count + height - 1) / height;
    } else if ((height <= 0) && (width > 0)) {
        height = (m_count + width - 1) / width;
    }
    while (width * height < m_count) {
        if (orientation == Qt::Horizontal) {
            ++width;
        } else {
            ++height;
        }
    }

    m_grid.update(QSize(width, height), orientation);
    // TODO: why is there no call to m_rootInfo->setDesktopLayout?
    emit layoutChanged(width, height);
}

void VirtualDesktopManager::initShortcuts()
{
    initSwitchToShortcuts();

    addAction(QStringLiteral("Switch to Next Desktop"), i18n("Switch to Next Desktop"), &VirtualDesktopManager::slotNext);
    addAction(QStringLiteral("Switch to Previous Desktop"), i18n("Switch to Previous Desktop"), &VirtualDesktopManager::slotPrevious);
    addAction(QStringLiteral("Switch One Desktop to the Right"), i18n("Switch One Desktop to the Right"), &VirtualDesktopManager::slotRight);
    addAction(QStringLiteral("Switch One Desktop to the Left"), i18n("Switch One Desktop to the Left"), &VirtualDesktopManager::slotLeft);
    addAction(QStringLiteral("Switch One Desktop Up"), i18n("Switch One Desktop Up"), &VirtualDesktopManager::slotUp);
    addAction(QStringLiteral("Switch One Desktop Down"), i18n("Switch One Desktop Down"), &VirtualDesktopManager::slotDown);

    // axis events
    input()->registerAxisShortcut(Qt::ControlModifier | Qt::AltModifier, PointerAxisDown,
                                  findChild<QAction*>(QStringLiteral("Switch to Next Desktop")));
    input()->registerAxisShortcut(Qt::ControlModifier | Qt::AltModifier, PointerAxisUp,
                                  findChild<QAction*>(QStringLiteral("Switch to Previous Desktop")));
}

void VirtualDesktopManager::initSwitchToShortcuts()
{
    const QString toDesktop = QStringLiteral("Switch to Desktop %1");
    const KLocalizedString toDesktopLabel = ki18n("Switch to Desktop %1");
    addAction(toDesktop, toDesktopLabel, 1, QKeySequence(Qt::CTRL + Qt::Key_F1), &VirtualDesktopManager::slotSwitchTo);
    addAction(toDesktop, toDesktopLabel, 2, QKeySequence(Qt::CTRL + Qt::Key_F2), &VirtualDesktopManager::slotSwitchTo);
    addAction(toDesktop, toDesktopLabel, 3, QKeySequence(Qt::CTRL + Qt::Key_F3), &VirtualDesktopManager::slotSwitchTo);
    addAction(toDesktop, toDesktopLabel, 4, QKeySequence(Qt::CTRL + Qt::Key_F4), &VirtualDesktopManager::slotSwitchTo);

    for (uint i = 5; i <= maximum(); ++i) {
        addAction(toDesktop, toDesktopLabel, i, QKeySequence(), &VirtualDesktopManager::slotSwitchTo);
    }
}

void VirtualDesktopManager::addAction(const QString &name, const KLocalizedString &label, uint value, const QKeySequence &key, void (VirtualDesktopManager::*slot)())
{
    QAction *a = new QAction(this);
    a->setObjectName(name.arg(value));
    a->setText(label.subs(value).toString());
    a->setData(value);
    KGlobalAccel::setGlobalShortcut(a, key);
    input()->registerShortcut(key, a, this, slot);
}

void VirtualDesktopManager::addAction(const QString &name, const QString &label, void (VirtualDesktopManager::*slot)())
{
    QAction *a = new QAction(this);
    a->setObjectName(name);
    a->setText(label);
    KGlobalAccel::setGlobalShortcut(a, QKeySequence());
    input()->registerShortcut(QKeySequence(), a, this, slot);
}

void VirtualDesktopManager::slotSwitchTo()
{
    QAction *act = qobject_cast<QAction*>(sender());
    if (!act) {
        return;
    }
    bool ok = false;
    const uint i = act->data().toUInt(&ok);
    if (!ok) {
        return;
    }
    setCurrent(i);
}

void VirtualDesktopManager::setNavigationWrappingAround(bool enabled)
{
    if (enabled == m_navigationWrapsAround) {
        return;
    }
    m_navigationWrapsAround = enabled;
    emit navigationWrappingAroundChanged();
}

void VirtualDesktopManager::slotDown()
{
    moveTo<DesktopBelow>(isNavigationWrappingAround());
}

void VirtualDesktopManager::slotLeft()
{
    moveTo<DesktopLeft>(isNavigationWrappingAround());
}

void VirtualDesktopManager::slotPrevious()
{
    moveTo<DesktopPrevious>(isNavigationWrappingAround());
}

void VirtualDesktopManager::slotNext()
{
    moveTo<DesktopNext>(isNavigationWrappingAround());
}

void VirtualDesktopManager::slotRight()
{
    moveTo<DesktopRight>(isNavigationWrappingAround());
}

void VirtualDesktopManager::slotUp()
{
    moveTo<DesktopAbove>(isNavigationWrappingAround());
}

} // KWin