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

 Copyright (C) 2008 Martin Gräßlin <ubuntu@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 "flipswitch.h"

#include <kwinconfig.h>
#include <QFont>
#include <kapplication.h>
#include <kcolorscheme.h>
#include <kconfiggroup.h>

#include <kwinglutils.h>

#ifdef KWIN_HAVE_OPENGL_COMPOSITING
#include <GL/gl.h>
#endif

namespace KWin
{

KWIN_EFFECT( flipswitch, FlipSwitchEffect )
KWIN_EFFECT_SUPPORTED( flipswitch, FlipSwitchEffect::supported() )

FlipSwitchEffect::FlipSwitchEffect()
    : mActivated( 0 )
    , animateFlip( false )
    , forward( true )
    , start( false )
    , stop( false )
    , addFullRepaint( false )
    , rearrangeWindows( 0 )
    , stopRequested( false )
    , startRequested( false )
    , twinview( false )
    {
    reconfigure( ReconfigureAll );
    }

FlipSwitchEffect::~FlipSwitchEffect()
    {
    }

bool FlipSwitchEffect::supported()
    {
    return effects->compositingType() == OpenGLCompositing;
    }

void FlipSwitchEffect::reconfigure( ReconfigureFlags )
    {
    KConfigGroup conf = effects->effectConfig( "FlipSwitch" );
    mFlipDuration = animationTime( conf, "FlipDuration", 200 );
    mAnimation    = conf.readEntry( "AnimateFlip", true );
    timeLine.setCurveShape( TimeLine::EaseInOutCurve );
    timeLine.setDuration( mFlipDuration );
    }

void FlipSwitchEffect::prePaintScreen( ScreenPrePaintData& data, int time )
    {
    if( mActivated || stopRequested || stop )
        {
         data.mask |= Effect::PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS;
        if( mAnimation && ( start || stop || animateFlip ) )
            timeLine.addTime( (double)time );
        }
    effects->prePaintScreen(data, time);
    }

void FlipSwitchEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data )
    {
    effects->paintScreen( mask, region, data );
    if( mActivated || stopRequested || stop )
        {
#ifdef KWIN_HAVE_OPENGL_COMPOSITING
        glMatrixMode( GL_PROJECTION );
        glPushMatrix();
        glPushAttrib( GL_CURRENT_BIT | GL_ENABLE_BIT );
        glEnable( GL_BLEND );
        glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
        glLoadIdentity();
        int viewport[4];
        glGetIntegerv( GL_VIEWPORT, viewport );
        int yPos = area.y();
        QRect fullArea = effects->clientArea( FullArea, effects->activeScreen(), effects->currentDesktop());
        if( twinview )
            {
            if( effects->clientArea( FullScreenArea, effects->activeScreen(), effects->currentDesktop()).x() == 0 
                && effects->clientArea( FullScreenArea, effects->activeScreen()==0?1:0, effects->currentDesktop()).x() == 0 )
                {
                // top <-> bottom
                // have to correct the yPos for top bottom constellation
                yPos = area.height()-area.y();
                if( ( area.height() * 2 != fullArea.height() ) ||
                    ( effects->clientArea( FullScreenArea, effects->activeScreen(), effects->currentDesktop()).width() !=  
                    effects->clientArea( FullScreenArea, effects->activeScreen()==0?1:0, effects->currentDesktop()).width() ) )
                    {
                    // different resolutions
                    if( area.y() > 0 )
                        yPos = 0;
                    else
                        yPos = fullArea.height() - area.height();
                    }
                }
            else
                {
                // left <-> right
                if( ( area.width() * 2 != fullArea.width() ) ||
                    ( effects->clientArea( FullScreenArea, effects->activeScreen(), effects->currentDesktop()).height() !=  
                    effects->clientArea( FullScreenArea, effects->activeScreen()==0?1:0, effects->currentDesktop()).height() ) )
                    {
                    // different resolutions
                    yPos = area.y() + fullArea.height() - area.height();
                    }
                }
            }
        float left, right, top, bottom;
        left = -area.width() * 0.5f;
        right = area.width() * 0.5f;
        top = area.height()*0.5f;
        bottom = -area.height()*0.5f;
        if( twinview && ( start || stop ) )
            {
            // special handling for start and stop animation in twin view setup
            glViewport( fullArea.x(), fullArea.y(), fullArea.width(), fullArea.height() );
            left = -(area.x() + area.width() * 0.5f);
            right = fullArea.width() + left;
            bottom = -(area.y() + area.height() * 0.5f);
            top = fullArea.height() + bottom;
            }
        else
            {
            glViewport( area.x(), yPos, area.width(), area.height() );
            }
        glFrustum( left, right, top, bottom, 10, 50 );

        glMatrixMode( GL_MODELVIEW );
        glLoadIdentity();
        float xOffset = area.width()*0.33f;
        float zOffset = 10.0;

        // bring the selected window to the back of the list
        QList< EffectWindow* > tempList = effects->currentTabBoxWindowList();
        int index = tempList.indexOf( effects->currentTabBoxWindow() );
        QList< EffectWindow* > windowList;
        for( int i=index; i<tempList.count(); i++)
            {
            windowList.push_front( tempList[i] );
            }
        for( int i=0; i<index; i++)
            {
            windowList.push_front( tempList[i] );
            }
        // do we have to rearrange the windows as an animation has to follow?
        if( rearrangeWindows != 0 )
            {
            if( rearrangeWindows < 0 )
                {
                for( int i=0; i>rearrangeWindows; i-- )
                    {
                    EffectWindow* w = windowList.front();
                    windowList.pop_front();
                    windowList.append( w );
                    }
                }
            else
                {
                for( int i=0; i<rearrangeWindows; i++ )
                    {
                    EffectWindow* w = windowList.back();
                    windowList.pop_back();
                    windowList.prepend( w );
                    }
                }
            }

        // do we have a start or stop animation
        if( ( start || stop ) && mAnimation )
            {
            for( int i=0; i<windowList.count(); i++)
                {
                glPushMatrix();
                EffectWindow *w = windowList[ i ];        
                // Position of the window in OpenGL
                float x = w->x()+left;
                float y = bottom-area.height()+w->y()+w->height();
                float z = -10.0;
                if( w->isMinimized() )
                    {
                    // use icon instead of window
                    x = w->iconGeometry().x()+left;
                    y = bottom-area.height()+w->iconGeometry().y()+w->height();
                    }
                // Position of the window in the stack
                float stackX = -area.width()*0.25f-(xOffset*windowList.count())+xOffset*(i+1);
                float stackY = -area.height()*0.5f;
                float stackZ = (-1*zOffset*windowList.count()) -12.5+zOffset*(i+1);

                float animateXOffset;
                float animateYOffset;
                float animateZOffset;
                float rotation;
                // if start move to stack, if stop move from stack
                if( start )
                    {
                    animateXOffset = x+timeLine.value()*(stackX-x);
                    animateYOffset = y+timeLine.value()*(stackY-y);
                    animateZOffset = z+timeLine.value()*(stackZ-z);
                    rotation = timeLine.value()*0.25;
                    }
                else // = if( stop )
                    {
                    animateXOffset = stackX+timeLine.value()*(x-stackX);
                    animateYOffset = stackY+timeLine.value()*(y-stackY);
                    animateZOffset = stackZ+timeLine.value()*(z-stackZ);
                    rotation = 0.25-timeLine.value()*0.25;
                    }

                // go to current position and rotate by the time based factor
                glTranslatef(animateXOffset, animateYOffset, animateZOffset );
                glRotatef(rotation, 0.0, 1.0, 0.0);

                // top most window has to be painted not drawn.
                if( i<windowList.count()-1 )
                    {
                    paintWindowFlip( windowList[i] );
                    }
                else paintWindowFlip( windowList[i], false);
                glPopMatrix();
                }
            // time elapsed - so no more animation
            if( timeLine.value() == 1.0 )
                {
                timeLine.setProgress( 0.0 );
                if( start )
                    {
                    start = false;
                    // more animations have to follow?
                    if( rearrangeWindows != 0 )
                        {
                        animateFlip = true;
                        if( rearrangeWindows < 0 )
                            {
                            forward = true;
                            rearrangeWindows++;
                            }
                        else
                            {
                            forward = false;
                            rearrangeWindows--;
                            }
                        }
                    else if( stopRequested )
                        {
                        // no more animations but effect has to stop
                        stop = true;
                        stopRequested = false;
                        }
                    }
                else if( stop )
                    {
                    stop = false;
                    if( startRequested )
                        {
                        // tabbox already referenced again - so restart
                        start = true;
                        startRequested = false;
                        mActivated = true;
                        }
                    else
                        {
                        mActivated = false;
                        // we need one more FullRepaint
                        addFullRepaint = true;
                        }
                    }
                }
            }
        // normal behaviour - no start or stop animation
        else
            {
            double localProgress = timeLine.value();
            glPushMatrix();
            glTranslatef(-area.width()*0.25f-(xOffset*windowList.count()),
                -area.height()*0.5f,
                (-1*zOffset*windowList.count()) -12.5);
            if( animateFlip && windowList.count() > 1 )
                {
                if( timeLine.value() < 1.0 )
                    {
                    float animateXOffset = timeLine.value()*xOffset;
                    float animateZOffset = timeLine.value()*zOffset;
                    if( forward )
                        {
                        if( animateXOffset > xOffset ) animateXOffset = xOffset;
                        if( animateZOffset > zOffset ) animateZOffset = zOffset;
                        glTranslatef(animateXOffset, 0.0, animateZOffset);
                        EffectWindow* w = windowList.front();
                        windowList.pop_front();
                        windowList.append( w );
                        }
                    else
                        {
                        animateXOffset = xOffset - animateXOffset;
                        animateZOffset = zOffset - animateZOffset;
                        if( animateXOffset < 0.0 ) animateXOffset = 0.0;
                        if( animateZOffset < 0.0 ) animateZOffset = 0.0;
                        glTranslatef(animateXOffset, 0.0, animateZOffset);
                        }
                    }
                else
                    {
                    timeLine.setProgress( 0.0 );
                    if( rearrangeWindows != 0 )
                        {
                        animateFlip = true;
                        if( rearrangeWindows < 0 )
                            {
                            forward = true;
                            rearrangeWindows++;
                            localProgress = 0.0;
                            }
                        else
                            {
                            forward = false;
                            rearrangeWindows--;
                            localProgress = 1.0;
                            }
                        }
                    else
                        {
                        animateFlip = false;
                        if( stopRequested )
                            {
                            stop = true;
                            stopRequested = false;
                            }
                        }
                    }
                }
            for( int i=0; i<windowList.count(); i++)
                {
                glPushMatrix();
                glTranslatef(xOffset*(i+1), 0.0, zOffset*(i+1));
                glRotatef(0.25, 0.0, 1.0, 0.0);
                // top most window has to be painted not drawn.
                if( i<windowList.count()-1 )
                    {
                    paintWindowFlip( windowList[i] );
                    }
                else
                    {
                    // last window - change opacity if animated
                    float opacity = 0.8;
                    if( animateFlip && windowList.count() > 1 )
                        {
                        if( forward ) opacity = opacity - localProgress*opacity;
                        else opacity = localProgress*opacity;
                        }
                    paintWindowFlip( windowList[i], false, opacity);
                    }
                glPopMatrix();
                }
            glPopMatrix();
            }
        glPopAttrib();
        glMatrixMode( GL_PROJECTION );
        glPopMatrix();
        glMatrixMode( GL_MODELVIEW );
        glViewport( viewport[0], viewport[1], viewport[2], viewport[3] );

        // caption of selected window
        QColor color_frame;
        QColor color_text;
        color_frame = KColorScheme( QPalette::Active, KColorScheme::Window ).background().color();
        color_frame.setAlphaF( 0.9 );
        color_text = KColorScheme( QPalette::Active, KColorScheme::Window ).foreground().color();
        if( start )
            {
            color_frame.setAlphaF( 0.9 * timeLine.value() );
            color_text.setAlphaF( timeLine.value() );
            }
        else if( stop )
            {
            color_frame.setAlphaF( 0.9 - 0.9 * timeLine.value() );
            color_text.setAlphaF( 1.0 - timeLine.value() );
            }
        else if( addFullRepaint )
            {
            // special case: timeLine was reset, but actual frame will be painted
            color_frame.setAlphaF( 0.0 );
            color_text.setAlphaF( 0.0 );
            }
        QFont text_font;
        text_font.setBold( true );
        text_font.setPointSize( 20 );
        glPushAttrib( GL_CURRENT_BIT );
        glColor4f( color_frame.redF(), color_frame.greenF(), color_frame.blueF(), color_frame.alphaF());
        QRect frameRect = QRect( area.width()*0.25f + area.x(),
            area.height()*0.9f + area.y(),
            area.width()*0.5f,
            QFontMetrics( text_font ).height() * 1.2 );
        renderRoundBoxWithEdge( frameRect );
        effects->paintText( effects->currentTabBoxWindow()->caption(),
            frameRect.center(),
            frameRect.width() - 100,
            color_text,
            text_font );
        glPopAttrib();
        // icon of selected window
        QPixmap iconPixmap = effects->currentTabBoxWindow()->icon();
        if( start || stop || addFullRepaint )
            {
            int alpha = 255.0f * timeLine.value();
            if( stop )
                {
                alpha = 255.0f - alpha;
                }
            else if( addFullRepaint )
                {
                alpha = 0.0f;
                }
            QPixmap transparency = iconPixmap.copy( iconPixmap.rect() );
            transparency.fill( QColor( 255, 255, 255, alpha ) );
            iconPixmap.setAlphaChannel( transparency.alphaChannel()  );
            }
        GLTexture* icon = new GLTexture( iconPixmap );
        icon->bind();
        glPushAttrib( GL_CURRENT_BIT | GL_ENABLE_BIT );
        icon->bind();
        glEnable( GL_BLEND );
        glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
        // icon takes 80 % of the height of the frame. So each 10 % space left on the top and bottom
        QRect iconRect = QRect( frameRect.x() + frameRect.height()*0.1f,
            frameRect.y() + frameRect.height()*0.1f,
            frameRect.height()*0.8f,
            frameRect.height()*0.8f );
        icon->render( region, iconRect);
        icon->unbind();
        delete icon;
        glDisable( GL_BLEND );
        glPopAttrib();
#endif
        }
    }

void FlipSwitchEffect::postPaintScreen()
    {
    if( (mActivated && ( animateFlip || start )) || stopRequested || stop )
        {
        effects->addRepaintFull();
        }
    if( addFullRepaint )
        {
        addFullRepaint = false;
        effects->setActiveFullScreenEffect( 0 );
        effects->addRepaintFull();
        }
    effects->postPaintScreen();
    }
  
void FlipSwitchEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data )
    {
    if( mActivated || stopRequested || stop )
        {
        if( !( mask & PAINT_WINDOW_TRANSFORMED ) && ( !w->isDesktop() ) )
            {
            if( ( start || stop ) && w->isDock() )
                {
                data.opacity = 1.0 - timeLine.value();
                if( stop )
                    data.opacity = timeLine.value();
                }
            else
                return;
            }
        }
    effects->paintWindow( w, mask, region, data );
    }
  
void FlipSwitchEffect::tabBoxAdded( int mode )
    {
    if( effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this )
        return;
    if( !mActivated )
        {
        // only for windows mode
        if( mode == TabBoxWindowsMode && effects->currentTabBoxWindowList().count() > 0 )
            {
            input = effects->createFullScreenInputWindow( this, Qt::BlankCursor );
            effects->refTabBox();
            effects->setActiveFullScreenEffect( this );
            selectedWindow = effects->currentTabBoxWindowList().indexOf(effects->currentTabBoxWindow());
            if( !stop && !stopRequested )
                {
                mActivated = true;
                start = true;
                effects->addRepaintFull();
                }
            else
                {
                // last tabbox effect still running - schedule start effect
                startRequested = true;
                }
                
            // Calculation of correct area
            area = effects->clientArea( FullScreenArea, effects->activeScreen(), effects->currentDesktop());
            QRect fullArea = effects->clientArea( FullArea, effects->activeScreen(), effects->currentDesktop());
            // twinview?
            if( area.height() != fullArea.height() || area.width() != fullArea.width() )
                twinview = true;
            else
                twinview = false;
            }
        }
    }
void FlipSwitchEffect::tabBoxClosed()
    {
    if( mActivated )
        {
        // if animation than deactivate after animation
        mActivated = false;
        effects->unrefTabBox();
        if( input )
            effects->destroyInputWindow( input );
        if( mAnimation )
            {
            if( start && rearrangeWindows == 0 )
                {
                stop = true;
                start = false;
                timeLine.setProgress( 1.0 - timeLine.value() );
                }
            else if( start || animateFlip )
                stopRequested = true;
            else
                {
                stop = true;
                effects->addRepaintFull();
                }
            }
        else
            {
            effects->setActiveFullScreenEffect( 0 );
            }
        }
    }

void FlipSwitchEffect::tabBoxUpdated()
    {
    if( mActivated )
        {
        if( mAnimation && effects->currentTabBoxWindowList().count() > 1 )
            {
            // determine the switch direction
            int index = effects->currentTabBoxWindowList().indexOf(effects->currentTabBoxWindow());
            bool direction = false;
            int windowCount = effects->currentTabBoxWindowList().count();
            if( index > selectedWindow )
                {
                if( index == windowCount-1 && selectedWindow == 0 ) forward = false;
                else direction = true;
                }
            else if( index == 0 && ( selectedWindow == windowCount-1 ) )
                {
                direction = true;
                }
            else if( index == selectedWindow ) return; // nothing changed
            else
                {
                direction = false;
                }
            selectedWindow = index;
            if( !animateFlip && !start )
                {
                forward = direction;
                animateFlip = true;
                }
            else
                {
                if( direction ) rearrangeWindows--;
                else rearrangeWindows++;
                if( rearrangeWindows >= windowCount ) rearrangeWindows = rearrangeWindows % windowCount;
                else if( (-1*rearrangeWindows) >= windowCount ) rearrangeWindows = -1*((-1*rearrangeWindows) % windowCount);
                }
            }
        effects->addRepaintFull();
        }
    }

void FlipSwitchEffect::paintWindowFlip( EffectWindow* w, bool draw, float opacity )
    {
    WindowPaintData data( w );

    int x = 0;
    int y = area.height() - w->geometry().height();
    QRect thumbnail;
    setPositionTransformations( data,                                             
        thumbnail, w,
        QRect( x, y, w->geometry().width(), w->geometry().height() ),
        Qt::KeepAspectRatio );

    data.opacity *= opacity;
    thumbnail = infiniteRegion();
    // if paintWindow() is used the window behind the initial selected window is not painted on the stack,
    // but painted when it is selected
    // if drawWindow() is used the front window does not glide through the monitor during animation
    // so use drawWindow() for all windows but the selected and paintWindow() for the selected window
    if( draw )
        effects->drawWindow( w,
            PAINT_WINDOW_TRANSFORMED,
            thumbnail, data );
    else 
        effects->paintWindow( w,
            PAINT_WINDOW_TRANSFORMED,
            thumbnail, data );
    }

} // namespace