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

Copyright (C) 2006 Lubos Lunak <l.lunak@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 "showfps.h"

#include <kwinconfig.h>

#include <kconfiggroup.h>
#include <kglobal.h>
#include <ksharedconfig.h>

#ifdef KWIN_HAVE_OPENGL_COMPOSITING
#include <GL/gl.h>
#endif
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
#include <X11/Xlib.h>
#include <X11/extensions/Xrender.h>
#endif

#include <kwinxrenderutils.h>

#include <math.h>
#include <QPainter>

namespace KWin
{

KWIN_EFFECT( showfps, ShowFpsEffect )

const int FPS_WIDTH = 10;
const int MAX_TIME = 100;

ShowFpsEffect::ShowFpsEffect()
    : paints_pos( 0 )
    , frames_pos( 0 )
#ifdef KWIN_HAVE_OPENGL_COMPOSITING
    , fpsText(0)
#endif
    {
    for( int i = 0;
         i < NUM_PAINTS;
         ++i )
        {
        paints[ i ] = 0;
        paint_size[ i ] = 0;
        }
    for( int i = 0;
         i < MAX_FPS;
         ++i )
        frames[ i ] = 0;
    reconfigure( ReconfigureAll );
    }

void ShowFpsEffect::reconfigure( ReconfigureFlags )
    {
    KConfigGroup config( KGlobal::config(), "EffectShowFps" );
    alpha = config.readEntry( "Alpha", 0.5 );
    x = config.readEntry( "X", -10000 );
    y = config.readEntry( "Y", 0 );
    if( x == -10000 ) // there's no -0 :(
        x = displayWidth() - 2*NUM_PAINTS - FPS_WIDTH;
    else if ( x < 0 )
        x = displayWidth() - 2*NUM_PAINTS - FPS_WIDTH - x;
    if( y == -10000 )
        y = displayHeight() - MAX_TIME;
    else if ( y < 0 )
        y = displayHeight() - MAX_TIME - y;
    fps_rect = QRect( x, y, FPS_WIDTH + 2*NUM_PAINTS, MAX_TIME );
    
    config = effects->effectConfig("ShowFps");
    int textPosition = config.readEntry("TextPosition", int(INSIDE_GRAPH));
    textFont = config.readEntry("TextFont", QFont());
    textColor = config.readEntry("TextColor", QColor());
    double textAlpha = config.readEntry("TextAlpha", 1.0);
    
    if(!textColor.isValid())
        textColor = QPalette().color(QPalette::Active, QPalette::WindowText);
    textColor.setAlphaF(textAlpha);
    
    switch(textPosition)
        {
        case TOP_LEFT:
            fpsTextRect = QRect(0, 0, 100, 100);
            textAlign = Qt::AlignTop|Qt::AlignLeft;
            break;
        case TOP_RIGHT:
            fpsTextRect = QRect(displayWidth()-100, 0, 100, 100);
            textAlign = Qt::AlignTop|Qt::AlignRight;
            break;
        case BOTTOM_LEFT:
            fpsTextRect = QRect(0, displayHeight()-100, 100, 100);
            textAlign = Qt::AlignBottom|Qt::AlignLeft;
            break;
        case BOTTOM_RIGHT:
            fpsTextRect = QRect(displayWidth()-100, displayHeight()-100, 100, 100);
            textAlign = Qt::AlignBottom|Qt::AlignRight;
            break;
        case NOWHERE:
            fpsTextRect = QRect();
            break;
        case INSIDE_GRAPH:
        default:
            fpsTextRect = QRect(x, y, FPS_WIDTH + NUM_PAINTS, MAX_TIME);
            textAlign = Qt::AlignTop|Qt::AlignRight;
            break;
        }
    }
    
void ShowFpsEffect::prePaintScreen( ScreenPrePaintData& data, int time )
    {
    if( time == 0 ) {
        // TODO optimized away
    }
    t.start();
    frames[ frames_pos ] = t.minute() * 60000 + t.second() * 1000 + t.msec();
    if( ++frames_pos == MAX_FPS )
        frames_pos = 0;
    effects->prePaintScreen( data, time );
    data.paint += fps_rect;

    paint_size[ paints_pos ] = 0;
    }

void ShowFpsEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data )
    {
    effects->paintWindow( w, mask, region, data );

    // Take intersection of region and actual window's rect, minus the fps area
    //  (since we keep repainting it) and count the pixels.
    QRegion r2 = region & QRect( w->x(), w->y(), w->width(), w->height() );
    r2 -= fps_rect;
    int winsize = 0;
    foreach( const QRect& r, r2.rects())
        winsize += r.width() * r.height();
    paint_size[ paints_pos ] += winsize;
    }

void ShowFpsEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data )
    {
    effects->paintScreen( mask, region, data );
    int fps = 0;
    for( int i = 0;
         i < MAX_FPS;
         ++i )
        if( abs( t.minute() * 60000 + t.second() * 1000 + t.msec() - frames[ i ] ) < 1000 )
            ++fps; // count all frames in the last second
    if( fps > MAX_TIME )
        fps = MAX_TIME; // keep it the same height
#ifdef KWIN_HAVE_OPENGL_COMPOSITING
    if( effects->compositingType() == OpenGLCompositing)
        {
        paintGL( fps );
        glFinish(); // make sure all rendering is done
        }
#endif
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
    if( effects->compositingType() == XRenderCompositing)
        {
        paintXrender( fps );
        XSync( display(), False ); // make sure all rendering is done
        }
#endif
    }

#ifdef KWIN_HAVE_OPENGL_COMPOSITING
void ShowFpsEffect::paintGL( int fps )
    {
    int x = this->x;
    int y = this->y;
    glPushAttrib( GL_CURRENT_BIT | GL_ENABLE_BIT );
    glEnable( GL_BLEND );
    glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
    // TODO painting first the background white and then the contents
    // means that the contents also blend with the background, I guess
    glColor4f( 1, 1, 1, alpha ); // white
    glBegin( GL_QUADS );
    glVertex2i( x, y );
    glVertex2i( x + 2*NUM_PAINTS + FPS_WIDTH, y );
    glVertex2i( x + 2*NUM_PAINTS + FPS_WIDTH, y + MAX_TIME );
    glVertex2i( x, y + MAX_TIME );
    glEnd();
    y += MAX_TIME; // paint up from the bottom
    glBegin( GL_QUADS );
    glColor4f( 0, 0, 1, alpha ); // blue
    glVertex2i( x, y );
    glVertex2i( x + FPS_WIDTH, y );
    glVertex2i( x + FPS_WIDTH, y - fps );
    glVertex2i( x, y - fps );
    glEnd();


    glColor4f( 0, 0, 0, alpha ); // black
    glBegin( GL_LINES );
    for( int i = 10;
         i < MAX_TIME;
         i += 10 )
    {
        glVertex2i( x, y - i );
        glVertex2i( x + FPS_WIDTH, y - i );
    }
    glEnd();
    x += FPS_WIDTH;

    // Paint FPS graph
    paintFPSGraph( x, y );
    x += NUM_PAINTS;

    // Paint amount of rendered pixels graph
    paintDrawSizeGraph( x, y );
    
    // Paint FPS numerical value
    paintFPSText(fps);

    // Paint paint sizes
    glPopAttrib();
    }
#endif

#ifdef KWIN_HAVE_XRENDER_COMPOSITING
/*
 Differences between OpenGL and XRender:
 - differently specified rectangles (X: width/height, O: x2,y2)
 - XRender uses pre-multiplied alpha
*/
void ShowFpsEffect::paintXrender( int fps )
    {
    Pixmap pixmap = XCreatePixmap( display(), rootWindow(), FPS_WIDTH, MAX_TIME, 32 );
    XRenderPicture p( pixmap, 32 );
    XFreePixmap( display(), pixmap );
    XRenderColor col;
    col.alpha = int( alpha * 0xffff );
    col.red = int( alpha * 0xffff ); // white
    col.green = int( alpha * 0xffff );
    col.blue= int( alpha * 0xffff );
    XRenderFillRectangle( display(), PictOpSrc, p, &col, 0, 0, FPS_WIDTH, MAX_TIME );
    col.red = 0; // blue
    col.green = 0;
    col.blue = int( alpha * 0xffff );
    XRenderFillRectangle( display(), PictOpSrc, p, &col, 0, MAX_TIME - fps, FPS_WIDTH, fps );
    col.red = 0; // black
    col.green = 0;
    col.blue = 0;
    for( int i = 10;
         i < MAX_TIME;
         i += 10 )
        {
        XRenderFillRectangle( display(), PictOpSrc, p, &col, 0, MAX_TIME - i, FPS_WIDTH, 1 );
        }
    XRenderComposite( display(), alpha != 1.0 ? PictOpOver : PictOpSrc, p, None,
        effects->xrenderBufferPicture(), 0, 0, 0, 0, x, y, FPS_WIDTH, MAX_TIME );

    // Paint FPS graph
    paintFPSGraph( x + FPS_WIDTH, y );

    // Paint amount of rendered pixels graph
    paintDrawSizeGraph( x + FPS_WIDTH + MAX_TIME, y );
    }
#endif

void ShowFpsEffect::paintFPSGraph(int x, int y)
    {
    // Paint FPS graph
    QList<int> lines;
    lines << 10 << 20 << 50;
    QList<int> values;
    for( int i = 0;
         i < NUM_PAINTS;
         ++i )
        {
        values.append( paints[ ( i + paints_pos ) % NUM_PAINTS ] );
        }
    paintGraph( x, y, values, lines, true );
    }

void ShowFpsEffect::paintDrawSizeGraph(int x, int y)
{
    int max_drawsize = 0;
    for( int i = 0; i < NUM_PAINTS; i++)
        max_drawsize = qMax(max_drawsize, paint_size[ i ] );

    // Log of min/max values shown on graph
    const float max_pixels_log = 7.2f;
    const float min_pixels_log = 2.0f;
    const int minh = 5;  // Minimum height of the bar when  value > 0

    float drawscale = (MAX_TIME - minh) / (max_pixels_log - min_pixels_log);
    QList<int> drawlines;

    for( int logh = (int)min_pixels_log; logh <= max_pixels_log; logh++ )
        drawlines.append( (int)(( logh - min_pixels_log ) * drawscale ) + minh );

    QList<int> drawvalues;
    for( int i = 0;
         i < NUM_PAINTS;
         ++i )
        {
        int value = paint_size[ ( i + paints_pos ) % NUM_PAINTS ];
        int h = 0;
        if( value > 0)
            {
            h = (int)(( log10( (double)value ) - min_pixels_log ) * drawscale );
            h = qMin( qMax( 0, h ) + minh, MAX_TIME );
            }
        drawvalues.append( h );
        }
    paintGraph( x, y, drawvalues, drawlines, false );
    }

void ShowFpsEffect::paintGraph( int x, int y, QList<int> values, QList<int> lines, bool colorize)
    {
#ifdef KWIN_HAVE_OPENGL_COMPOSITING
    if( effects->compositingType() == OpenGLCompositing)
        {
        glColor4f( 0, 0, 0, alpha ); // black
        glBegin( GL_LINES );
        // First draw the lines
        foreach( int h, lines)
            {
            glVertex2i( x, y - h );
            glVertex2i( x + values.count(), y - h );
            }
        // Then the graph values
        glColor4f( 0.5, 0.5, 0.5, alpha );
        for( int i = 0; i < values.count(); i++ )
            {
            int value = values[ i ];
            if( colorize )
                {
                if( value <= 10 )
                    glColor4f( 0, 1, 0, alpha ); // green
                else if( value <= 20 )
                    glColor4f( 1, 1, 0, alpha ); // yellow
                else if( value <= 50 )
                    glColor4f( 1, 0, 0, alpha ); // red
                else
                    glColor4f( 0, 0, 0, alpha ); // black
                }
            glVertex2i( x + values.count() - i, y );
            glVertex2i( x + values.count() - i, y - value );
            }
        glEnd();
        }
#endif
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
    if( effects->compositingType() == XRenderCompositing)
        {
        Pixmap pixmap = XCreatePixmap( display(), rootWindow(), values.count(), MAX_TIME, 32 );
        XRenderPicture p( pixmap, 32 );
        XFreePixmap( display(), pixmap );
        XRenderColor col;
        col.alpha = int( alpha * 0xffff );

        // Draw background
        col.red = col.green = col.blue = int( alpha * 0xffff ); // white
        XRenderFillRectangle( display(), PictOpSrc, p, &col, 0, 0, values.count(), MAX_TIME );

        // Then the values
        col.red = col.green = col.blue = int( alpha * 0x8000 );  // grey
        for( int i = 0; i < values.count(); i++ )
            {
            int value = values[ i ];
            if( colorize )
                {
                if( value <= 10 )
                    { // green
                    col.red = 0;
                    col.green = int( alpha * 0xffff );
                    col.blue = 0;
                    }
                else if( value <= 20 )
                    { // yellow
                    col.red = int( alpha * 0xffff );
                    col.green = int( alpha * 0xffff );
                    col.blue = 0;
                    }
                else if( value <= 50 )
                    { // red
                    col.red = int( alpha * 0xffff );
                    col.green = 0;
                    col.blue = 0;
                    }
                else
                    { // black
                    col.red = 0;
                    col.green = 0;
                    col.blue = 0;
                    }
                }
            XRenderFillRectangle( display(), PictOpSrc, p, &col,
                                  values.count() - i, MAX_TIME - value, 1, value );
            }

        // Then the lines
        col.red = col.green = col.blue = 0;  // black
        foreach( int h, lines)
            XRenderFillRectangle( display(), PictOpSrc, p, &col, 0, MAX_TIME - h, values.count(), 1 );

        // Finally render the pixmap onto screen
        XRenderComposite( display(), alpha != 1.0 ? PictOpOver : PictOpSrc, p, None,
            effects->xrenderBufferPicture(), 0, 0, 0, 0, x, y, values.count(), MAX_TIME );
        }
#endif
    }

void ShowFpsEffect::postPaintScreen()
    {
    effects->postPaintScreen();
    paints[ paints_pos ] = t.elapsed();
    if( ++paints_pos == NUM_PAINTS )
        paints_pos = 0;
    effects->addRepaint( fps_rect );
    }

void ShowFpsEffect::paintFPSText(int fps)
    {
    if( !fpsTextRect.isValid())
        return;
#ifdef KWIN_HAVE_OPENGL_COMPOSITING
    QImage im(100, 100, QImage::Format_ARGB32);
    im.fill(0);
    QPainter painter(&im);
    painter.setFont(textFont);
    painter.setPen(textColor);
    painter.drawText(QRect(0, 0, 100, 100), textAlign, QString::number(fps));
    if(fpsText)
        delete fpsText;
    fpsText = new GLTexture(im);
    fpsText->bind();
    fpsText->render(QRegion(fpsTextRect), fpsTextRect);
    fpsText->unbind();
    effects->addRepaint(fpsTextRect);
#endif
    }

} // namespace