/********************************************************************
Copyright (C) 2009, 2010 Martin Gräßlin <kde@martin-graesslin.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 "aurorae.h"
#include "auroraescene.h"
#include "auroraetheme.h"

#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsSceneMouseEvent>

#include <KConfig>
#include <KConfigGroup>
#include <Plasma/FrameSvg>

namespace Aurorae
{

AuroraeFactory::AuroraeFactory()
        : QObject()
        , KDecorationFactoryUnstable()
        , m_theme(new AuroraeTheme(this))
{
    init();
}

void AuroraeFactory::init()
{
    KConfig conf("auroraerc");
    KConfigGroup group(&conf, "Engine");

    const QString themeName = group.readEntry("ThemeName", "example-deco");
    KConfig config("aurorae/themes/" + themeName + '/' + themeName + "rc", KConfig::FullConfig, "data");
    KConfigGroup themeGroup(&conf, themeName);
    m_theme->loadTheme(themeName, config);
    m_theme->setBorderSize((KDecorationDefines::BorderSize)themeGroup.readEntry<int>("BorderSize", KDecorationDefines::BorderNormal));
    m_theme->setButtonSize((KDecorationDefines::BorderSize)themeGroup.readEntry<int>("ButtonSize", KDecorationDefines::BorderNormal));
    m_theme->setShowTooltips(options()->showTooltips());
    m_theme->setTabDragMimeType(clientGroupItemDragMimeType());
}

AuroraeFactory::~AuroraeFactory()
{
    s_instance = NULL;
}

AuroraeFactory *AuroraeFactory::instance()
{
    if (!s_instance) {
        s_instance = new AuroraeFactory;
    }

    return s_instance;
}

bool AuroraeFactory::reset(unsigned long changed)
{
    init();
    resetDecorations(changed);
    return false; // need hard reset
}

bool AuroraeFactory::supports(Ability ability) const
{
    switch (ability) {
    case AbilityAnnounceButtons:
    case AbilityUsesAlphaChannel:
    case AbilityButtonMenu:
    case AbilityButtonSpacer:
    case AbilityExtendIntoClientArea:
        return true;
    case AbilityButtonMinimize:
        return m_theme->hasButton(MinimizeButton);
    case AbilityButtonMaximize:
        return m_theme->hasButton(MaximizeButton) || m_theme->hasButton(RestoreButton);
    case AbilityButtonClose:
        return m_theme->hasButton(CloseButton);
    case AbilityButtonAboveOthers:
        return m_theme->hasButton(KeepAboveButton);
    case AbilityButtonBelowOthers:
        return m_theme->hasButton(KeepBelowButton);
    case AbilityButtonShade:
        return m_theme->hasButton(ShadeButton);
    case AbilityButtonOnAllDesktops:
        return m_theme->hasButton(AllDesktopsButton);
    case AbilityButtonHelp:
        return m_theme->hasButton(HelpButton);
    case AbilityProvidesShadow:
        return true; // TODO: correct value from theme
    case AbilityClientGrouping:
        return true;
    default:
        return false;
    }
}

KDecoration *AuroraeFactory::createDecoration(KDecorationBridge *bridge)
{
    AuroraeClient *client = new AuroraeClient(bridge, this);
    return client;
}

QList< KDecorationDefines::BorderSize > AuroraeFactory::borderSizes() const
{
    return QList< BorderSize >() << BorderTiny << BorderNormal <<
        BorderLarge << BorderVeryLarge <<  BorderHuge <<
        BorderVeryHuge << BorderOversized;
}


AuroraeFactory *AuroraeFactory::s_instance = NULL;

/*******************************************************
* Client
*******************************************************/
AuroraeClient::AuroraeClient(KDecorationBridge *bridge, KDecorationFactory *factory)
    : KDecorationUnstable(bridge, factory)
    , m_clickInProgress(false)
{
    m_scene = new AuroraeScene(AuroraeFactory::instance()->theme(),
                               options()->customButtonPositions() ? options()->titleButtonsLeft() : AuroraeFactory::instance()->theme()->defaultButtonsLeft(),
                               options()->customButtonPositions() ? options()->titleButtonsRight() : AuroraeFactory::instance()->theme()->defaultButtonsRight(),
                               providesContextHelp(), NULL);
    connect(m_scene, SIGNAL(closeWindow()), SLOT(closeWindow()));
    connect(m_scene, SIGNAL(maximize(Qt::MouseButtons)), SLOT(maximize(Qt::MouseButtons)));
    connect(m_scene, SIGNAL(showContextHelp()), SLOT(showContextHelp()));
    connect(m_scene, SIGNAL(minimizeWindow()), SLOT(minimize()));
    connect(m_scene, SIGNAL(menuClicked()), SLOT(menuClicked()));
    connect(m_scene, SIGNAL(menuDblClicked()), SLOT(closeWindow()));
    connect(m_scene, SIGNAL(toggleOnAllDesktops()), SLOT(toggleOnAllDesktops()));
    connect(m_scene, SIGNAL(toggleShade()), SLOT(toggleShade()));
    connect(m_scene, SIGNAL(toggleKeepAbove()), SLOT(toggleKeepAbove()));
    connect(m_scene, SIGNAL(toggleKeepBelow()), SLOT(toggleKeepBelow()));
    connect(m_scene, SIGNAL(titlePressed(Qt::MouseButton,Qt::MouseButtons)),
            SLOT(titlePressed(Qt::MouseButton,Qt::MouseButtons)));
    connect(m_scene, SIGNAL(titleReleased(Qt::MouseButton,Qt::MouseButtons)),
            SLOT(titleReleased(Qt::MouseButton,Qt::MouseButtons)));
    connect(m_scene, SIGNAL(titleDoubleClicked()), SLOT(titlebarDblClickOperation()));
    connect(m_scene, SIGNAL(titleMouseMoved(Qt::MouseButton,Qt::MouseButtons)), 
            SLOT(titleMouseMoved(Qt::MouseButton,Qt::MouseButtons)));
    connect(m_scene, SIGNAL(wheelEvent(int)), SLOT(titlebarMouseWheelOperation(int)));
    connect(m_scene, SIGNAL(tabMouseButtonPress(QGraphicsSceneMouseEvent*,int)),
            SLOT(tabMouseButtonPress(QGraphicsSceneMouseEvent*,int)));
    connect(m_scene, SIGNAL(tabMouseButtonRelease(QGraphicsSceneMouseEvent*,int)),
            SLOT(tabMouseButtonRelease(QGraphicsSceneMouseEvent*,int)));
    connect(m_scene, SIGNAL(tabRemoved(int)), SLOT(tabRemoved(int)));
    connect(m_scene, SIGNAL(tabMoved(int,int)), SLOT(tabMoved(int,int)));
    connect(m_scene, SIGNAL(tabMovedToGroup(long int,int)), SLOT(tabMovedToGroup(long int,int)));
    connect(this, SIGNAL(keepAboveChanged(bool)), SLOT(keepAboveChanged(bool)));
    connect(this, SIGNAL(keepBelowChanged(bool)), SLOT(keepBelowChanged(bool)));
}

AuroraeClient::~AuroraeClient()
{
    m_view->setParent(NULL);
    m_view->deleteLater();
    m_scene->deleteLater();
}

void AuroraeClient::init()
{
    // HACK: we need to add the GraphicsView as a child widget to a normal widget
    // the GraphicsView eats the mouse release event and by that kwin core starts to move
    // the decoration each time the decoration is clicked
    // therefore we use two widgets and inject an own mouse release event to the parent widget
    // when the graphics view eats a mouse event
    createMainWidget();
    widget()->setAttribute(Qt::WA_TranslucentBackground);
    widget()->setAttribute(Qt::WA_NoSystemBackground);
    widget()->installEventFilter(this);
    m_view = new QGraphicsView(m_scene, widget());
    m_view->setAttribute(Qt::WA_TranslucentBackground);
    m_view->setFrameShape(QFrame::NoFrame);
    QPalette pal = m_view->palette();
    pal.setColor(m_view->backgroundRole(), Qt::transparent);
    m_view->setPalette(pal);
    QPalette pal2 = widget()->palette();
    pal2.setColor(widget()->backgroundRole(), Qt::transparent);
    widget()->setPalette(pal2);
    m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    // scene initialisation
    m_scene->setActive(isActive(), false);
    m_scene->setIcon(icon());
    m_scene->setAllDesktops(isOnAllDesktops());
    m_scene->setMaximizeMode(options()->moveResizeMaximizedWindows() ? MaximizeRestore : maximizeMode());
    m_scene->setShade(isShade());
    m_scene->setKeepAbove(keepAbove());
    m_scene->setKeepBelow(keepBelow());
    AuroraeFactory::instance()->theme()->setCompositingActive(compositingActive());
}

void AuroraeClient::activeChange()
{
    if (m_scene->isActive() != isActive()) {
        m_scene->setActive(isActive());
    }
}

void AuroraeClient::captionChange()
{
    checkTabs(true);
}

void AuroraeClient::iconChange()
{
    m_scene->setIcon(icon());
}

void AuroraeClient::desktopChange()
{
    m_scene->setAllDesktops(isOnAllDesktops());
}

void AuroraeClient::maximizeChange()
{
    if (!options()->moveResizeMaximizedWindows()) {
        m_scene->setMaximizeMode(maximizeMode());
    }
}

void AuroraeClient::resize(const QSize &s)
{
    m_scene->setSceneRect(QRectF(QPoint(0, 0), s));
    m_scene->updateLayout();
    m_view->resize(s);
    widget()->resize(s);
    updateWindowShape();
}

void AuroraeClient::shadeChange()
{
    m_scene->setShade(isShade());
}

void AuroraeClient::borders(int &left, int &right, int &top, int &bottom) const
{
    const bool maximized = maximizeMode() == MaximizeFull && !options()->moveResizeMaximizedWindows();
    AuroraeFactory::instance()->theme()->borders(left, top, right, bottom, maximized);
}

void AuroraeClient::padding(int &left, int &right, int &top, int &bottom) const
{
    AuroraeFactory::instance()->theme()->padding(left, top, right, bottom);
}

QSize AuroraeClient::minimumSize() const
{
    return widget()->minimumSize();
}

KDecorationDefines::Position AuroraeClient::mousePosition(const QPoint &point) const
{
    // based on the code from deKorator
    int pos = PositionCenter;
    if (isShade()) {
        return Position(pos);
    }

    int borderLeft, borderTop, borderRight, borderBottom;
    borders(borderLeft, borderRight, borderTop, borderBottom);
    int paddingLeft, paddingTop, paddingRight, paddingBottom;
    padding(paddingLeft, paddingRight, paddingTop, paddingBottom);
    const bool maximized = maximizeMode() == MaximizeFull && !options()->moveResizeMaximizedWindows();
    int titleEdgeLeft, titleEdgeRight, titleEdgeTop, titleEdgeBottom;
    AuroraeFactory::instance()->theme()->titleEdges(titleEdgeLeft, titleEdgeTop, titleEdgeRight, titleEdgeBottom, maximized);
    switch (AuroraeFactory::instance()->theme()->decorationPosition()) {
    case DecorationTop:
        borderTop = titleEdgeTop;
        break;
    case DecorationLeft:
        borderLeft = titleEdgeLeft;
        break;
    case DecorationRight:
        borderRight = titleEdgeRight;
        break;
    case DecorationBottom:
        borderBottom = titleEdgeBottom;
        break;
    default:
        break; // nothing
    }
    if (point.x() >= (m_view->width() -  borderRight - paddingRight)) {
        pos |= PositionRight;
    } else if (point.x() <= borderLeft + paddingLeft) {
        pos |= PositionLeft;
    }

    if (point.y() >= m_view->height() - borderBottom - paddingBottom) {
        pos |= PositionBottom;
    } else if (point.y() <= borderTop + paddingTop ) {
        pos |= PositionTop;
    }

    return Position(pos);
}

void AuroraeClient::reset(long unsigned int changed)
{
    if (changed & SettingCompositing) {
        updateWindowShape();
        AuroraeFactory::instance()->theme()->setCompositingActive(compositingActive());
    }
    if (changed & SettingButtons) {
        m_scene->setButtons(options()->customButtonPositions() ? options()->titleButtonsLeft() : AuroraeFactory::instance()->theme()->defaultButtonsLeft(),
                            options()->customButtonPositions() ? options()->titleButtonsRight() : AuroraeFactory::instance()->theme()->defaultButtonsRight());
    }
    KDecoration::reset(changed);
}

void AuroraeClient::menuClicked()
{
    showWindowMenu(QCursor::pos());
}

void AuroraeClient::toggleShade()
{
    setShade(!isShade());
}

void AuroraeClient::keepAboveChanged(bool above)
{
    if (above && m_scene->isKeepBelow()) {
        m_scene->setKeepBelow(false);
    }
    m_scene->setKeepAbove(above);
}

void AuroraeClient::keepBelowChanged(bool below)
{
    if (below && m_scene->isKeepAbove()) {
        m_scene->setKeepAbove(false);
    }
    m_scene->setKeepBelow(below);
}

void AuroraeClient::toggleKeepAbove()
{
    setKeepAbove(!keepAbove());
}

void AuroraeClient::toggleKeepBelow()
{
    setKeepBelow(!keepBelow());
}

void AuroraeClient::updateWindowShape()
{
    bool maximized = maximizeMode()==KDecorationDefines::MaximizeFull && !options()->moveResizeMaximizedWindows();
    int w=widget()->width();
    int h=widget()->height();

    if (maximized || compositingActive()) {
        QRegion mask(0,0,w,h);
        setMask(mask);
        return;
    }

    int pl, pt, pr, pb;
    padding(pl, pr, pt, pb);
    Plasma::FrameSvg *deco = AuroraeFactory::instance()->theme()->decoration();
    if (!deco->hasElementPrefix("decoration-opaque")) {
        // opaque element is missing: set generic mask
        w = w - pl - pr;
        h = h - pt - pb;
        QRegion mask(pl, pt, w, h);
        setMask(mask);
        return;
    }
    deco->setElementPrefix("decoration-opaque");
    deco->resizeFrame(QSize(w-pl-pr, h-pt-pb));
    QRegion mask = deco->mask().translated(pl, pt);
    setMask(mask);
}

void AuroraeClient::titlePressed(Qt::MouseButton button, Qt::MouseButtons buttons)
{
    QMouseEvent *event = new QMouseEvent(QEvent::MouseButtonPress, widget()->mapFromGlobal(QCursor::pos()),
                                         QCursor::pos(), button, buttons, Qt::NoModifier);
    processMousePressEvent(event);
    delete event;
    event = 0;
}

void AuroraeClient::titleReleased(Qt::MouseButton button, Qt::MouseButtons buttons)
{
    QMouseEvent *event = new QMouseEvent(QEvent::MouseButtonRelease, widget()->mapFromGlobal(QCursor::pos()),
                                         QCursor::pos(), button, buttons, Qt::NoModifier);
    QApplication::sendEvent(widget(), event);
    delete event;
    event = 0;
}

void AuroraeClient::titleMouseMoved(Qt::MouseButton button, Qt::MouseButtons buttons)
{
    QMouseEvent *event = new QMouseEvent(QEvent::MouseMove, widget()->mapFromGlobal(QCursor::pos()),
                                         QCursor::pos(), button, buttons, Qt::NoModifier);
    QApplication::sendEvent(widget(), event);
    delete event;
    event = 0;
}

void AuroraeClient::checkTabs(bool force)
{
    if (m_scene->tabCount() == 1 && clientGroupItems().count() == 1 && !force) {
        return;
    }
    while (m_scene->tabCount() < clientGroupItems().count()) {
        m_scene->addTab(QString());
    }
    while (m_scene->tabCount() > clientGroupItems().count()) {
        m_scene->removeLastTab();
    }
    QStringList captions;
    foreach (const ClientGroupItem &item, clientGroupItems()) {
        captions << item.title();
    }
    m_scene->setCaptions(captions);
    m_scene->setFocusedTab(visibleClientGroupItem());
}

bool AuroraeClient::eventFilter(QObject *o, QEvent *e)
{
    if (o != widget()) {
        return false;
    }
    if (e->type() == QEvent::Paint) {
        checkTabs();
    }
    return false;
}

void AuroraeClient::tabMouseButtonPress(QGraphicsSceneMouseEvent *e, int index)
{
    if (buttonToWindowOperation(e->buttons()) == OperationsOp) {
        displayClientMenu(index, e->screenPos());
        return;
    } else if (buttonToWindowOperation(e->buttons()) == ClientGroupDragOp) {
        m_scene->setUniqueTabDragId(index, itemId(index));
    }
    titlePressed(e->button(), e->buttons());
    m_clickInProgress = true;
}

void AuroraeClient::tabMouseButtonRelease(QGraphicsSceneMouseEvent *e, int index)
{
    if (m_clickInProgress) {
        setVisibleClientGroupItem(index);
    }
    titleReleased(e->button(), e->buttons());
    m_clickInProgress = false;
}

void AuroraeClient::tabRemoved(int index)
{
    removeFromClientGroup(index);
}

void AuroraeClient::tabMoved(int index, int before)
{
    moveItemInClientGroup(index, before);
}

void AuroraeClient::tabMovedToGroup(long int uid, int before)
{
    moveItemToClientGroup(uid, before);
}

} // namespace Aurorae

extern "C"
{
    KDE_EXPORT KDecorationFactory *create_factory() {
        return Aurorae::AuroraeFactory::instance();
    }
}


#include "aurorae.moc"
