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

 Copyright (C) 2008 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 "coverswitch.h"
// KConfigSkeleton
#include "coverswitchconfig.h"

#include <kwinconfig.h>
#include <QFont>
#include <QMatrix4x4>
#include <QMouseEvent>
#include <KDE/KLocalizedString>
#include <kapplication.h>
#include <kcolorscheme.h>
#include <kglobal.h>
#include <kstandarddirs.h>
#include <KDE/KIcon>

#include <kwinglutils.h>
#include <kwinglplatform.h>

#include <math.h>

#include <kdebug.h>

namespace KWin
{

KWIN_EFFECT(coverswitch, CoverSwitchEffect)
KWIN_EFFECT_SUPPORTED(coverswitch, CoverSwitchEffect::supported())

CoverSwitchEffect::CoverSwitchEffect()
    : mActivated(0)
    , angle(60.0)
    , animation(false)
    , start(false)
    , stop(false)
    , stopRequested(false)
    , startRequested(false)
    , zPosition(900.0)
    , scaleFactor(0.0)
    , direction(Left)
    , selected_window(0)
    , captionFrame(NULL)
    , primaryTabBox(false)
    , secondaryTabBox(false)
{
    reconfigure(ReconfigureAll);

    // Caption frame
    captionFont.setBold(true);
    captionFont.setPointSize(captionFont.pointSize() * 2);

    if (effects->compositingType() == OpenGL2Compositing) {
        QString shadersDir = "kwin/shaders/1.10/";
#ifdef KWIN_HAVE_OPENGLES
        const qint64 coreVersionNumber = kVersionNumber(3, 0);
#else
        const qint64 coreVersionNumber = kVersionNumber(1, 40);
#endif
        if (GLPlatform::instance()->glslVersion() >= coreVersionNumber)
            shadersDir = "kwin/shaders/1.40/";
        const QString fragmentshader = KGlobal::dirs()->findResource("data", shadersDir + "coverswitch-reflection.glsl");
        m_reflectionShader = ShaderManager::instance()->loadFragmentShader(ShaderManager::GenericShader, fragmentshader);
    } else {
        m_reflectionShader = NULL;
    }
    connect(effects, SIGNAL(windowClosed(KWin::EffectWindow*)), this, SLOT(slotWindowClosed(KWin::EffectWindow*)));
    connect(effects, SIGNAL(tabBoxAdded(int)), this, SLOT(slotTabBoxAdded(int)));
    connect(effects, SIGNAL(tabBoxClosed()), this, SLOT(slotTabBoxClosed()));
    connect(effects, SIGNAL(tabBoxUpdated()), this, SLOT(slotTabBoxUpdated()));
    connect(effects, SIGNAL(tabBoxKeyEvent(QKeyEvent*)), this, SLOT(slotTabBoxKeyEvent(QKeyEvent*)));
}

CoverSwitchEffect::~CoverSwitchEffect()
{
    delete captionFrame;
    delete m_reflectionShader;
}

bool CoverSwitchEffect::supported()
{
    return effects->isOpenGLCompositing();
}

void CoverSwitchEffect::reconfigure(ReconfigureFlags)
{
    CoverSwitchConfig::self()->readConfig();
    animationDuration = animationTime<CoverSwitchConfig>(200);
    animateSwitch     = CoverSwitchConfig::animateSwitch();
    animateStart      = CoverSwitchConfig::animateStart();
    animateStop       = CoverSwitchConfig::animateStop();
    reflection        = CoverSwitchConfig::reflection();
    windowTitle       = CoverSwitchConfig::windowTitle();
    zPosition         = CoverSwitchConfig::zPosition();
    timeLine.setCurveShape(QTimeLine::EaseInOutCurve);
    timeLine.setDuration(animationDuration);

    // Defined outside the ui
    primaryTabBox     = CoverSwitchConfig::tabBox();
    secondaryTabBox   = CoverSwitchConfig::tabBoxAlternative();

    QColor tmp        = CoverSwitchConfig::mirrorFrontColor();
    mirrorColor[0][0] = tmp.redF();
    mirrorColor[0][1] = tmp.greenF();
    mirrorColor[0][2] = tmp.blueF();
    mirrorColor[0][3] = 1.0;
    tmp               = CoverSwitchConfig::mirrorRearColor();
    mirrorColor[1][0] = tmp.redF();
    mirrorColor[1][1] = tmp.greenF();
    mirrorColor[1][2] = tmp.blueF();
    mirrorColor[1][3] = -1.0;

}

void CoverSwitchEffect::prePaintScreen(ScreenPrePaintData& data, int time)
{
    if (mActivated || stop || stopRequested) {
        data.mask |= Effect::PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS;
        if (animation || start || stop) {
            timeLine.setCurrentTime(timeLine.currentTime() + time);
        }
        if (selected_window == NULL)
            abort();
    }
    effects->prePaintScreen(data, time);
}

void CoverSwitchEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data)
{
    effects->paintScreen(mask, region, data);

    if (mActivated || stop || stopRequested) {
        QMatrix4x4 origProjection;
        QMatrix4x4 origModelview;
        ShaderManager *shaderManager = ShaderManager::instance();
        if (effects->numScreens() > 1) {
            // unfortunatelly we have to change the projection matrix in dual screen mode
            QRect fullRect = effects->clientArea(FullArea, activeScreen, effects->currentDesktop());
            float fovy = 60.0f;
            float aspect = 1.0f;
            float zNear = 0.1f;
            float zFar = 100.0f;
            float ymax = zNear * tan(fovy  * M_PI / 360.0f);
            float ymin = -ymax;
            float xmin =  ymin * aspect;
            float xmax = ymax * aspect;
            float xTranslate = 0.0;
            float yTranslate = 0.0;
            float xminFactor = 1.0;
            float xmaxFactor = 1.0;
            float yminFactor = 1.0;
            float ymaxFactor = 1.0;
            if (area.x() == 0 && area.width() != fullRect.width()) {
                // horizontal layout: left screen
                xminFactor = (float)area.width() / (float)fullRect.width();
                xmaxFactor = ((float)fullRect.width() - (float)area.width() * 0.5f) / ((float)fullRect.width() * 0.5f);
                xTranslate = (float)fullRect.width() * 0.5f - (float)area.width() * 0.5f;
            }
            if (area.x() != 0 && area.width() != fullRect.width()) {
                // horizontal layout: right screen
                xminFactor = ((float)fullRect.width() - (float)area.width() * 0.5f) / ((float)fullRect.width() * 0.5f);
                xmaxFactor = (float)area.width() / (float)fullRect.width();
                xTranslate = (float)fullRect.width() * 0.5f - (float)area.width() * 0.5f;
            }
            if (area.y() == 0 && area.height() != fullRect.height()) {
                // vertical layout: top screen
                yminFactor = ((float)fullRect.height() - (float)area.height() * 0.5f) / ((float)fullRect.height() * 0.5f);
                ymaxFactor = (float)area.height() / (float)fullRect.height();
                yTranslate = (float)fullRect.height() * 0.5f - (float)area.height() * 0.5f;
            }
            if (area.y() != 0 && area.height() != fullRect.height()) {
                // vertical layout: bottom screen
                yminFactor = (float)area.height() / (float)fullRect.height();
                ymaxFactor = ((float)fullRect.height() - (float)area.height() * 0.5f) / ((float)fullRect.height() * 0.5f);
                yTranslate = (float)fullRect.height() * 0.5f - (float)area.height() * 0.5f;
            }
            QMatrix4x4 projection;
            projection.frustum(xmin * xminFactor, xmax * xmaxFactor, ymin * yminFactor, ymax * ymaxFactor, zNear, zFar);
            QMatrix4x4 modelview;
            modelview.translate(xTranslate, yTranslate, 0.0);
            if (shaderManager->isShaderBound()) {
                GLShader *shader = shaderManager->pushShader(ShaderManager::GenericShader);
                origProjection = shader->getUniformMatrix4x4("projection");
                origModelview = shader->getUniformMatrix4x4("modelview");
                shader->setUniform("projection", projection);
                shader->setUniform("modelview", origModelview * modelview);
                shaderManager->popShader();
            } else {
#ifdef KWIN_HAVE_OPENGL_1
                glMatrixMode(GL_PROJECTION);
                pushMatrix();
                loadMatrix(projection);
                glMatrixMode(GL_MODELVIEW);
                pushMatrix(modelview);
#endif
            }
        }

        QList< EffectWindow* > tempList = currentWindowList;
        int index = tempList.indexOf(selected_window);
        if (animation || start || stop) {
            if (!start && !stop) {
                if (direction == Right)
                    index++;
                else
                    index--;
                if (index < 0)
                    index = tempList.count() + index;
                if (index >= tempList.count())
                    index = index % tempList.count();
            }
            foreach (Direction direction, scheduled_directions) {
                if (direction == Right)
                    index++;
                else
                    index--;
                if (index < 0)
                    index = tempList.count() + index;
                if (index >= tempList.count())
                    index = index % tempList.count();
            }
        }
        int leftIndex = index - 1;
        if (leftIndex < 0)
            leftIndex = tempList.count() - 1;
        int rightIndex = index + 1;
        if (rightIndex == tempList.count())
            rightIndex = 0;

        EffectWindow* frontWindow = tempList[ index ];
        leftWindows.clear();
        rightWindows.clear();

        bool evenWindows = (tempList.count() % 2 == 0) ? true : false;
        int leftWindowCount = 0;
        if (evenWindows)
            leftWindowCount = tempList.count() / 2 - 1;
        else
            leftWindowCount = (tempList.count() - 1) / 2;
        for (int i = 0; i < leftWindowCount; i++) {
            int tempIndex = (leftIndex - i);
            if (tempIndex < 0)
                tempIndex = tempList.count() + tempIndex;
            leftWindows.prepend(tempList[ tempIndex ]);
        }
        int rightWindowCount = 0;
        if (evenWindows)
            rightWindowCount = tempList.count() / 2;
        else
            rightWindowCount = (tempList.count() - 1) / 2;
        for (int i = 0; i < rightWindowCount; i++) {
            int tempIndex = (rightIndex + i) % tempList.count();
            rightWindows.prepend(tempList[ tempIndex ]);
        }

        if (reflection) {
            // no reflections during start and stop animation
            // except when using a shader
            if ((!start && !stop) || effects->compositingType() == OpenGL2Compositing)
                paintScene(frontWindow, leftWindows, rightWindows, true);
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            // we can use a huge scale factor (needed to calculate the rearground vertices)
            // as we restrict with a PaintClipper painting on the current screen
            float reflectionScaleFactor = 100000 * tan(60.0 * M_PI / 360.0f) / area.width();
            const float width = area.width();
            const float height = area.height();
            float vertices[] = {
                -width * 0.5f, height, 0.0,
                width * 0.5f, height, 0.0,
                width*reflectionScaleFactor, height, -5000,
                -width*reflectionScaleFactor, height, -5000
            };
            // foreground
            if (start) {
                mirrorColor[0][3] = timeLine.currentValue();
            } else if (stop) {
                mirrorColor[0][3] = 1.0 - timeLine.currentValue();
            } else {
                mirrorColor[0][3] = 1.0;
            }

            int y = 0;
            // have to adjust the y values to fit OpenGL
            // in OpenGL y==0 is at bottom, in Qt at top
            if (effects->numScreens() > 1) {
                QRect fullArea = effects->clientArea(FullArea, 0, 1);
                if (fullArea.height() != area.height()) {
                    if (area.y() == 0)
                        y = fullArea.height() - area.height();
                    else
                        y = fullArea.height() - area.y() - area.height();
                }
            }
            // use scissor to restrict painting of the reflection plane to current screen
            glScissor(area.x(), y, area.width(), area.height());
            glEnable(GL_SCISSOR_TEST);

            if (shaderManager->isValid() && m_reflectionShader && m_reflectionShader->isValid()) {
                shaderManager->pushShader(m_reflectionShader);
                QMatrix4x4 windowTransformation;
                windowTransformation.translate(area.x() + area.width() * 0.5f, 0.0, 0.0);
                m_reflectionShader->setUniform("windowTransformation", windowTransformation);
                m_reflectionShader->setUniform("u_frontColor", QVector4D(mirrorColor[0][0], mirrorColor[0][1], mirrorColor[0][2], mirrorColor[0][3]));
                m_reflectionShader->setUniform("u_backColor", QVector4D(mirrorColor[1][0], mirrorColor[1][1], mirrorColor[1][2], mirrorColor[1][3]));
                // TODO: make this one properly
                QVector<float> verts;
                QVector<float> texcoords;
                verts.reserve(18);
                texcoords.reserve(12);
                texcoords << 1.0 << 0.0;
                verts << vertices[6] << vertices[7] << vertices[8];
                texcoords << 1.0 << 0.0;
                verts << vertices[9] << vertices[10] << vertices[11];
                texcoords << 0.0 << 0.0;
                verts << vertices[0] << vertices[1] << vertices[2];
                texcoords << 0.0 << 0.0;
                verts << vertices[0] << vertices[1] << vertices[2];
                texcoords << 0.0 << 0.0;
                verts << vertices[3] << vertices[4] << vertices[5];
                texcoords << 1.0 << 0.0;
                verts << vertices[6] << vertices[7] << vertices[8];
                GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
                vbo->reset();
                vbo->setData(6, 3, verts.data(), texcoords.data());
                vbo->render(GL_TRIANGLES);

                shaderManager->popShader();
            } else {
#ifdef KWIN_HAVE_OPENGL_1
                glPushMatrix();
                QRect fullRect = effects->clientArea(FullArea, activeScreen, effects->currentDesktop());
                if (effects->numScreens() > 1 && area.x() != fullRect.x()) {
                    // have to change the reflection area in horizontal layout and right screen
                    glTranslatef(-area.x(), 0.0, 0.0);
                }
                glTranslatef(area.x() + area.width() * 0.5f, 0.0, 0.0);
                glColor4fv(mirrorColor[0]);
                glBegin(GL_POLYGON);
                glVertex3f(vertices[0], vertices[1], vertices[2]);
                glVertex3f(vertices[3], vertices[4], vertices[5]);
                // rearground
                glColor4fv(mirrorColor[1]);
                glVertex3f(vertices[6], vertices[7], vertices[8]);
                glVertex3f(vertices[9], vertices[10], vertices[11]);
                glEnd();

                glPopMatrix();
#endif
            }
            glDisable(GL_SCISSOR_TEST);
            glDisable(GL_BLEND);
        }
        paintScene(frontWindow, leftWindows, rightWindows);

        if (effects->numScreens() > 1) {
            if (shaderManager->isShaderBound())  {
                GLShader *shader = shaderManager->pushShader(ShaderManager::GenericShader);
                shader->setUniform("projection", origProjection);
                shader->setUniform("modelview", origModelview);
                shaderManager->popShader();
            } else {
#ifdef KWIN_HAVE_OPENGL_1
                popMatrix();
                // revert change of projection matrix
                glMatrixMode(GL_PROJECTION);
                popMatrix();
                glMatrixMode(GL_MODELVIEW);
#endif
            }
        }

        // Render the caption frame
        if (windowTitle) {
            double opacity = 1.0;
            if (start)
                opacity = timeLine.currentValue();
            else if (stop)
                opacity = 1.0 - timeLine.currentValue();
            if (animation)
                captionFrame->setCrossFadeProgress(timeLine.currentValue());
            captionFrame->render(region, opacity);
        }
    }
}

void CoverSwitchEffect::postPaintScreen()
{
    if ((mActivated && (animation || start)) || stop || stopRequested) {
        if (timeLine.currentValue() == 1.0) {
            timeLine.setCurrentTime(0);
            if (stop) {
                stop = false;
                effects->setActiveFullScreenEffect(0);
                foreach (EffectWindow * window, referrencedWindows) {
                    window->unrefWindow();
                }
                referrencedWindows.clear();
                currentWindowList.clear();
                if (startRequested) {
                    startRequested = false;
                    mActivated = true;
                    effects->refTabBox();
                    currentWindowList = effects->currentTabBoxWindowList();
                    if (animateStart) {
                        start = true;
                    }
                }
            } else if (!scheduled_directions.isEmpty()) {
                direction = scheduled_directions.dequeue();
                if (start) {
                    animation = true;
                    start = false;
                }
            } else {
                animation = false;
                start = false;
                if (stopRequested) {
                    stopRequested = false;
                    stop = true;
                }
            }
        }
        effects->addRepaintFull();
    }
    effects->postPaintScreen();
}

void CoverSwitchEffect::paintScene(EffectWindow* frontWindow, const EffectWindowList& leftWindows,
                                   const EffectWindowList& rightWindows, bool reflectedWindows)
{
    // LAYOUT
    // one window in the front. Other windows left and right rotated
    // for odd number of windows: left: (n-1)/2; front: 1; right: (n-1)/2
    // for even number of windows: left: n/2; front: 1; right: n/2 -1
    //
    // ANIMATION
    // forward (alt+tab)
    // all left windows are moved to next position
    // top most left window is rotated and moved to front window position
    // front window is rotated and moved to next right window position
    // right windows are moved to next position
    // last right window becomes totally transparent in half the time
    // appears transparent on left side and becomes totally opaque again
    // backward (alt+shift+tab) same as forward but opposite direction
    int width = area.width();
    int leftWindowCount = leftWindows.count();
    int rightWindowCount = rightWindows.count();


    // Problem during animation: a window which is painted after another window
    // appears in front of the other
    // so during animation the painting order has to be rearreanged
    // paint sequence no animation: left, right, front
    // paint sequence forward animation: right, front, left

    if (!animation) {
        paintWindows(leftWindows, true, reflectedWindows);
        paintWindows(rightWindows, false, reflectedWindows);
        paintFrontWindow(frontWindow, width, leftWindowCount, rightWindowCount, reflectedWindows);
    } else {
        if (direction == Right) {
            if (timeLine.currentValue() < 0.5) {
                // paint in normal way
                paintWindows(leftWindows, true, reflectedWindows);
                paintWindows(rightWindows, false, reflectedWindows);
                paintFrontWindow(frontWindow, width, leftWindowCount, rightWindowCount, reflectedWindows);
            } else {
                paintWindows(rightWindows, false, reflectedWindows);
                paintFrontWindow(frontWindow, width, leftWindowCount, rightWindowCount, reflectedWindows);
                paintWindows(leftWindows, true, reflectedWindows, rightWindows.at(0));
            }
        } else {
            paintWindows(leftWindows, true, reflectedWindows);
            if (timeLine.currentValue() < 0.5) {
                paintWindows(rightWindows, false, reflectedWindows);
                paintFrontWindow(frontWindow, width, leftWindowCount, rightWindowCount, reflectedWindows);
            } else {
                EffectWindow* leftWindow;
                if (leftWindowCount > 0) {
                    leftWindow = leftWindows.at(0);
                    paintFrontWindow(frontWindow, width, leftWindowCount, rightWindowCount, reflectedWindows);
                } else
                    leftWindow = frontWindow;
                paintWindows(rightWindows, false, reflectedWindows, leftWindow);
            }
        }
    }
}

void CoverSwitchEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data)
{
    if (mActivated || stop || stopRequested) {
        if (!(mask & PAINT_WINDOW_TRANSFORMED) && !w->isDesktop()) {
            if ((start || stop) && w->isDock()) {
                data.setOpacity(1.0 - timeLine.currentValue());
                if (stop)
                    data.setOpacity(timeLine.currentValue());
            } else
                return;
        }
    }
    if ((start || stop) && (!w->isOnCurrentDesktop() || w->isMinimized())) {
        if (stop)  // Fade out windows not on the current desktop
            data.setOpacity((1.0 - timeLine.currentValue()));
        else // Fade in Windows from other desktops when animation is started
            data.setOpacity(timeLine.currentValue());
    }
    effects->paintWindow(w, mask, region, data);
}

void CoverSwitchEffect::slotTabBoxAdded(int mode)
{
    if (effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this)
        return;
    if (!mActivated) {
        // only for windows mode
        if (((mode == TabBoxWindowsMode && primaryTabBox) ||
                (mode == TabBoxWindowsAlternativeMode && secondaryTabBox) ||
                (mode == TabBoxCurrentAppWindowsMode && primaryTabBox) ||
                (mode == TabBoxCurrentAppWindowsAlternativeMode && secondaryTabBox))
                && effects->currentTabBoxWindowList().count() > 0) {
            effects->startMouseInterception(this, Qt::ArrowCursor);
            activeScreen = effects->activeScreen();
            if (!stop && !stopRequested) {
                effects->refTabBox();
                effects->setActiveFullScreenEffect(this);
                scheduled_directions.clear();
                selected_window = effects->currentTabBoxWindow();
                currentWindowList = effects->currentTabBoxWindowList();
                direction = Left;
                mActivated = true;
                if (animateStart) {
                    start = true;
                }

                // Calculation of correct area
                area = effects->clientArea(FullScreenArea, activeScreen, effects->currentDesktop());
                scaleFactor = (zPosition + 1100) * 2.0 * tan(60.0 * M_PI / 360.0f) / displayWidth();
                if (displayWidth() - area.width() != 0) {
                    // one of the screens is smaller than the other (horizontal)
                    if (area.width() < displayWidth() - area.width())
                        scaleFactor *= (float)area.width() / (float)(displayWidth() - area.width());
                    else if (area.width() != displayWidth() - area.width()) {
                        // vertical layout with different width
                        // but we don't want to catch screens with same width and different height
                        if (displayHeight() != area.height())
                            scaleFactor *= (float)area.width() / (float)(displayWidth());
                    }
                }

                // Setup caption frame geometry
                if (windowTitle) {
                    QRect frameRect = QRect(area.width() * 0.25f + area.x(),
                                            area.height() * 0.9f + area.y(),
                                            area.width() * 0.5f,
                                            QFontMetrics(captionFont).height());
                    if (!captionFrame) {
                        captionFrame = effects->effectFrame(EffectFrameStyled);
                        captionFrame->setFont(captionFont);
                        captionFrame->enableCrossFade(true);
                    }
                    captionFrame->setGeometry(frameRect);
                    captionFrame->setIconSize(QSize(frameRect.height(), frameRect.height()));
                    // And initial contents
                    updateCaption();
                }

                effects->addRepaintFull();
            } else {
                startRequested = true;
            }
        }
    }
}

void CoverSwitchEffect::slotTabBoxClosed()
{
    if (mActivated) {
        if (animateStop) {
            if (!animation && !start) {
                stop = true;
            } else if (start && scheduled_directions.isEmpty()) {
                start = false;
                stop = true;
                timeLine.setCurrentTime(timeLine.duration() - timeLine.currentValue());
            } else {
                stopRequested = true;
            }
        } else
            effects->setActiveFullScreenEffect(0);
        mActivated = false;
        effects->unrefTabBox();
        effects->stopMouseInterception(this);
        effects->addRepaintFull();
    }
}

void CoverSwitchEffect::slotTabBoxUpdated()
{
    if (mActivated) {
        if (animateSwitch && currentWindowList.count() > 1) {
            // determine the switch direction
            if (selected_window != effects->currentTabBoxWindow()) {
                if (selected_window != NULL) {
                    int old_index = currentWindowList.indexOf(selected_window);
                    int new_index = effects->currentTabBoxWindowList().indexOf(effects->currentTabBoxWindow());
                    Direction new_direction;
                    int distance = new_index - old_index;
                    if (distance > 0)
                        new_direction = Left;
                    if (distance < 0)
                        new_direction = Right;
                    if (effects->currentTabBoxWindowList().count() == 2) {
                        new_direction = Left;
                        distance = 1;
                    }
                    if (distance != 0) {
                        distance = abs(distance);
                        int tempDistance = effects->currentTabBoxWindowList().count() - distance;
                        if (tempDistance < abs(distance)) {
                            distance = tempDistance;
                            if (new_direction == Left)
                                new_direction = Right;
                            else
                                new_direction = Left;
                        }
                        if (!animation && !start) {
                            animation = true;
                            direction = new_direction;
                            distance--;
                        }
                        for (int i = 0; i < distance; i++) {
                            if (!scheduled_directions.isEmpty() && scheduled_directions.last() != new_direction)
                                scheduled_directions.pop_back();
                            else
                                scheduled_directions.enqueue(new_direction);
                            if (scheduled_directions.count() == effects->currentTabBoxWindowList().count())
                                scheduled_directions.clear();
                        }
                    }
                }
                selected_window = effects->currentTabBoxWindow();
                currentWindowList = effects->currentTabBoxWindowList();
                updateCaption();
            }
        }
        effects->addRepaintFull();
    }
}

void CoverSwitchEffect::paintWindowCover(EffectWindow* w, bool reflectedWindow, WindowPaintData& data)
{
    QRect windowRect = w->geometry();
    data.setYTranslation(area.height() - windowRect.y() - windowRect.height());
    data.setZTranslation(-zPosition);
    if (start) {
        if (w->isMinimized()) {
            data.multiplyOpacity(timeLine.currentValue());
        } else {
            const QVector3D translation = data.translation() * timeLine.currentValue();
            data.setXTranslation(translation.x());
            data.setYTranslation(translation.y());
            data.setZTranslation(translation.z());
            if (effects->numScreens() > 1) {
                QRect clientRect = effects->clientArea(FullScreenArea, w->screen(), effects->currentDesktop());
                QRect fullRect = effects->clientArea(FullArea, activeScreen, effects->currentDesktop());
                if (w->screen() == activeScreen) {
                    if (clientRect.width() != fullRect.width() && clientRect.x() != fullRect.x()) {
                        data.translate(- clientRect.x() * (1.0f - timeLine.currentValue()));
                    }
                    if (clientRect.height() != fullRect.height() && clientRect.y() != fullRect.y()) {
                        data.translate(0.0, - clientRect.y() * (1.0f - timeLine.currentValue()));
                    }
                } else {
                    if (clientRect.width() != fullRect.width() && clientRect.x() < area.x()) {
                        data.translate(- clientRect.width() * (1.0f - timeLine.currentValue()));
                    }
                    if (clientRect.height() != fullRect.height() && clientRect.y() < area.y()) {
                        data.translate(0.0, - clientRect.height() * (1.0f - timeLine.currentValue()));
                    }
                }
            }
            data.setRotationAngle(data.rotationAngle() * timeLine.currentValue());
        }
    }
    if (stop) {
        if (w->isMinimized() && w != effects->activeWindow()) {
            data.multiplyOpacity((1.0 - timeLine.currentValue()));
        } else {
            const QVector3D translation = data.translation() * (1.0 - timeLine.currentValue());
            data.setXTranslation(translation.x());
            data.setYTranslation(translation.y());
            data.setZTranslation(translation.z());
            if (effects->numScreens() > 1) {
                QRect clientRect = effects->clientArea(FullScreenArea, w->screen(), effects->currentDesktop());
                QRect rect = effects->clientArea(FullScreenArea, activeScreen, effects->currentDesktop());
                QRect fullRect = effects->clientArea(FullArea, activeScreen, effects->currentDesktop());
                if (w->screen() == activeScreen) {
                    if (clientRect.width() != fullRect.width() && clientRect.x() != fullRect.x()) {
                        data.translate(- clientRect.x() * timeLine.currentValue());
                    }
                    if (clientRect.height() != fullRect.height() && clientRect.y() != fullRect.y()) {
                        data.translate(0.0, - clientRect.y() * timeLine.currentValue());
                    }
                } else {
                    if (clientRect.width() != fullRect.width() && clientRect.x() < rect.x()) {
                        data.translate(- clientRect.width() * timeLine.currentValue());
                    }
                    if (clientRect.height() != fullRect.height() && clientRect.y() < area.y()) {
                        data.translate(0.0, - clientRect.height() * timeLine.currentValue());
                    }
                }
            }
            data.setRotationAngle(data.rotationAngle() * (1.0 - timeLine.currentValue()));
        }
    }

    if (reflectedWindow) {
        if (effects->compositingType() == OpenGL2Compositing) {
            GLShader *shader = ShaderManager::instance()->pushShader(ShaderManager::GenericShader);
            QMatrix4x4 origMatrix = shader->getUniformMatrix4x4("screenTransformation");
            QMatrix4x4 reflectionMatrix;
            reflectionMatrix.scale(1.0, -1.0, 1.0);
            shader->setUniform("screenTransformation", origMatrix * reflectionMatrix);
            data.setYTranslation(- area.height() - windowRect.y() - windowRect.height());
            if (start) {
                data.multiplyOpacity(timeLine.currentValue());
            } else if (stop) {
                data.multiplyOpacity(1.0 - timeLine.currentValue());
            }
            effects->drawWindow(w,
                                 PAINT_WINDOW_TRANSFORMED,
                                 infiniteRegion(), data);
            shader->setUniform("screenTransformation", origMatrix);
            ShaderManager::instance()->popShader();
        } else {
#ifdef KWIN_HAVE_OPENGL_1
            glPushMatrix();
            glScalef(1.0, -1.0, 1.0);
            data.setYTranslation(- area.height() - windowRect.y() - windowRect.height());
            effects->paintWindow(w,
                                 PAINT_WINDOW_TRANSFORMED,
                                 infiniteRegion(), data);
            glPopMatrix();
#endif
        }
    } else {
        effects->paintWindow(w,
                             PAINT_WINDOW_TRANSFORMED,
                             infiniteRegion(), data);
    }
}

void CoverSwitchEffect::paintFrontWindow(EffectWindow* frontWindow, int width, int leftWindows, int rightWindows, bool reflectedWindow)
{
    if (frontWindow == NULL)
        return;
    bool specialHandlingForward = false;
    WindowPaintData data(frontWindow);
    data.setXTranslation(area.width() * 0.5 - frontWindow->geometry().x() - frontWindow->geometry().width() * 0.5);
    if (leftWindows == 0) {
        leftWindows = 1;
        if (!start && !stop)
            specialHandlingForward = true;
    }
    if (rightWindows == 0) {
        rightWindows = 1;
    }
    if (animation) {
      float distance = 0.0;
      if (direction == Right) {
            // move to right
            distance = -frontWindow->geometry().width() * 0.5f + area.width() * 0.5f +
                       (((float)displayWidth() * 0.5 * scaleFactor) - (float)area.width() * 0.5f) / rightWindows;
            data.translate(distance * timeLine.currentValue());
            data.setRotationAxis(Qt::YAxis);
            data.setRotationAngle(-angle * timeLine.currentValue());
            data.setRotationOrigin(QVector3D(frontWindow->geometry().width(), 0.0, 0.0));
        } else {
            // move to left
            distance = frontWindow->geometry().width() * 0.5f - area.width() * 0.5f +
                       ((float)width * 0.5f - ((float)displayWidth() * 0.5 * scaleFactor)) / leftWindows;
            float factor = 1.0;
            if (specialHandlingForward)
                factor = 2.0;
            data.translate(distance * timeLine.currentValue() * factor);
            data.setRotationAxis(Qt::YAxis);
            data.setRotationAngle(angle * timeLine.currentValue());
        }
    }
    if (specialHandlingForward) {
        data.multiplyOpacity((1.0 - timeLine.currentValue() * 2.0));
        paintWindowCover(frontWindow, reflectedWindow, data);
    } else
        paintWindowCover(frontWindow, reflectedWindow, data);
}

void CoverSwitchEffect::paintWindows(const EffectWindowList& windows, bool left, bool reflectedWindows, EffectWindow* additionalWindow)
{
    int width = area.width();
    int windowCount = windows.count();
    EffectWindow* window;

    int rotateFactor = 1;
    if (!left) {
        rotateFactor = -1;
    }

    float xTranslate = -((float)(width) * 0.5f - ((float)displayWidth() * 0.5 * scaleFactor));
    if (!left)
        xTranslate = ((float)displayWidth() * 0.5 * scaleFactor) - (float)width * 0.5f;
    // handling for additional window from other side
    // has to appear on this side after half of the time
    if (animation && timeLine.currentValue() >= 0.5 && additionalWindow != NULL) {
        WindowPaintData data(additionalWindow);
        data.setRotationAxis(Qt::YAxis);
        data.setRotationAngle(angle * rotateFactor);
        if (left) {
            data.translate(-xTranslate - additionalWindow->geometry().x());
        }
        else {
            data.translate(xTranslate + area.width() -
                           additionalWindow->geometry().x() - additionalWindow->geometry().width());
            data.setRotationOrigin(QVector3D(additionalWindow->geometry().width(), 0.0, 0.0));
        }
        data.multiplyOpacity((timeLine.currentValue() - 0.5) * 2.0);
        paintWindowCover(additionalWindow, reflectedWindows, data);
    }
    // normal behaviour
    for (int i = 0; i < windows.count(); i++) {
        window = windows.at(i);
        if (window == NULL || window->isDeleted()) {
            continue;
        }
        WindowPaintData data(window);
        data.setRotationAxis(Qt::YAxis);
        data.setRotationAngle(angle);
        if (left)
            data.translate(-xTranslate + xTranslate * i / windowCount - window->geometry().x());
        else
            data.translate(xTranslate + width - xTranslate * i / windowCount - window->geometry().x() - window->geometry().width());
        if (animation) {
            if (direction == Right) {
                if ((i == windowCount - 1) && left) {
                    // right most window on left side -> move to front
                    // have to move one window distance plus half the difference between the window and the desktop size
                    data.translate((xTranslate / windowCount + (width - window->geometry().width()) * 0.5f) * timeLine.currentValue());
                    data.setRotationAngle(angle - angle * timeLine.currentValue());
                }
                // right most window does not have to be moved
                else if (!left && (i == 0));     // do nothing
                else {
                    // all other windows - move to next position
                    data.translate(xTranslate / windowCount * timeLine.currentValue());
                }
            } else {
                if ((i == windowCount - 1) && !left) {
                    // left most window on right side -> move to front
                    data.translate(- (xTranslate / windowCount + (width - window->geometry().width()) * 0.5f) * timeLine.currentValue());
                    data.setRotationAngle(angle - angle * timeLine.currentValue());
                }
                // left most window does not have to be moved
                else if (i == 0 && left); // do nothing
                else {
                    // all other windows - move to next position
                    data.translate(- xTranslate / windowCount * timeLine.currentValue());
                }
            }
        }
        if (!left)
            data.setRotationOrigin(QVector3D(window->geometry().width(), 0.0, 0.0));
        data.setRotationAngle(data.rotationAngle() * rotateFactor);
        // make window most to edge transparent if animation
        if (animation && i == 0 && ((direction == Left && left) || (direction == Right && !left))) {
            // only for the first half of the animation
            if (timeLine.currentValue() < 0.5) {
                data.multiplyOpacity((1.0 - timeLine.currentValue() * 2.0));
                paintWindowCover(window, reflectedWindows, data);
            }
        } else {
            paintWindowCover(window, reflectedWindows, data);
        }
    }
}

void CoverSwitchEffect::windowInputMouseEvent(QEvent* e)
{
    if (e->type() != QEvent::MouseButtonPress)
        return;
    // we don't want click events during animations
    if (animation)
        return;
    QMouseEvent* event = static_cast< QMouseEvent* >(e);

    switch (event->button()) {
    case Qt::XButton1: // wheel up
        selectPreviousWindow();
        break;
    case Qt::XButton2: // wheel down
        selectNextWindow();
        break;
    case Qt::LeftButton:
    case Qt::RightButton:
    case Qt::MidButton:
    default:
        QPoint pos = event->pos();

        // determine if a window has been clicked
        // not interested in events above a fullscreen window (ignoring panel size)
        if (pos.y() < (area.height()*scaleFactor - area.height()) * 0.5f *(1.0f / scaleFactor))
            return;

        // if there is no selected window (that is no window at all) we cannot click it
        if (!selected_window)
            return;

        if (pos.x() < (area.width()*scaleFactor - selected_window->width()) * 0.5f *(1.0f / scaleFactor)) {
            float availableSize = (area.width() * scaleFactor - area.width()) * 0.5f * (1.0f / scaleFactor);
            for (int i = 0; i < leftWindows.count(); i++) {
                int windowPos = availableSize / leftWindows.count() * i;
                if (pos.x() < windowPos)
                    continue;
                if (i + 1 < leftWindows.count()) {
                    if (pos.x() > availableSize / leftWindows.count()*(i + 1))
                        continue;
                }

                effects->setTabBoxWindow(leftWindows[i]);
                return;
            }
        }

        if (pos.x() > area.width() - (area.width()*scaleFactor - selected_window->width()) * 0.5f *(1.0f / scaleFactor)) {
            float availableSize = (area.width() * scaleFactor - area.width()) * 0.5f * (1.0f / scaleFactor);
            for (int i = 0; i < rightWindows.count(); i++) {
                int windowPos = area.width() - availableSize / rightWindows.count() * i;
                if (pos.x() > windowPos)
                    continue;
                if (i + 1 < rightWindows.count()) {
                    if (pos.x() < area.width() - availableSize / rightWindows.count()*(i + 1))
                        continue;
                }

                effects->setTabBoxWindow(rightWindows[i]);
                return;
            }
        }
        break;
    }
}

void CoverSwitchEffect::abort()
{
    // it's possible that abort is called after tabbox has been closed
    // in this case the cleanup is already done (see bug 207554)
    if (mActivated) {
        effects->unrefTabBox();
        effects->stopMouseInterception(this);
    }
    effects->setActiveFullScreenEffect(0);
    mActivated = false;
    stop = false;
    stopRequested = false;
    effects->addRepaintFull();
    captionFrame->free();
}

void CoverSwitchEffect::slotWindowClosed(EffectWindow* c)
{
    if (c == selected_window)
        selected_window = 0;
    // if the list is not empty, the effect is active
    if (!currentWindowList.isEmpty()) {
        c->refWindow();
        referrencedWindows.append(c);
        currentWindowList.removeAll(c);
        leftWindows.removeAll(c);
        rightWindows.removeAll(c);
    }
}

bool CoverSwitchEffect::isActive() const
{
    return mActivated || stop || stopRequested;
}

void CoverSwitchEffect::updateCaption()
{
    if (!selected_window || !windowTitle) {
        return;
    }
    if (selected_window->isDesktop()) {
        captionFrame->setText(i18nc("Special entry in alt+tab list for minimizing all windows",
                     "Show Desktop"));
        static QPixmap pix = KIcon("user-desktop").pixmap(captionFrame->iconSize());
        captionFrame->setIcon(pix);
    } else {
        captionFrame->setText(selected_window->caption());
        captionFrame->setIcon(selected_window->icon());
    }
}

void CoverSwitchEffect::slotTabBoxKeyEvent(QKeyEvent *event)
{
    if (event->type() == QEvent::KeyPress) {
        switch (event->key()) {
        case Qt::Key_Left:
            selectPreviousWindow();
            break;
        case Qt::Key_Right:
            selectNextWindow();
            break;
        default:
            // nothing
            break;
        }
    }
}

void CoverSwitchEffect::selectNextOrPreviousWindow(bool forward)
{
    if (!mActivated || !selected_window) {
        return;
    }
    const int index = effects->currentTabBoxWindowList().indexOf(selected_window);
    int newIndex = index;
    if (forward) {
        ++newIndex;
    } else {
        --newIndex;
    }
    if (newIndex == effects->currentTabBoxWindowList().size()) {
        newIndex = 0;
    } else if (newIndex < 0) {
        newIndex = effects->currentTabBoxWindowList().size() -1;
    }
    if (index == newIndex) {
        return;
    }
    effects->setTabBoxWindow(effects->currentTabBoxWindowList().at(newIndex));
}

} // namespace
