/*****************************************************************************
 * Qwt Examples - Copyright (C) 2002 Uwe Rathmann
 * This file may be used under the terms of the 3-clause BSD License
 *****************************************************************************/

#include "Plot.h"
#include "SignalData.h"

#include <QwtPlotGrid>
#include <QwtPlotLayout>
#include <QwtPlotCanvas>
#include <QwtPlotMarker>
#include <QwtPlotCurve>
#include <QwtScaleDiv>
#include <QwtScaleMap>
#include <QwtPlotDirectPainter>
#include <QwtPainter>

#include <QEvent>

namespace
{
    class Canvas : public QwtPlotCanvas
    {
      public:
        Canvas( QwtPlot* plot = NULL )
            : QwtPlotCanvas( plot )
        {
            /*
                The backing store is important, when working with widget
                overlays ( f.e rubberbands for zooming ).
                Here we don't have them and the internal
                backing store of QWidget is good enough.
             */

            setPaintAttribute( QwtPlotCanvas::BackingStore, false );
            setBorderRadius( 10 );

            if ( QwtPainter::isX11GraphicsSystem() )
            {
#if QT_VERSION < 0x050000
                /*
                    Qt::WA_PaintOutsidePaintEvent works on X11 and has a
                    nice effect on the performance.
                 */

                setAttribute( Qt::WA_PaintOutsidePaintEvent, true );
#endif

                /*
                    Disabling the backing store of Qt improves the performance
                    for the direct painter even more, but the canvas becomes
                    a native window of the window system, receiving paint events
                    for resize and expose operations. Those might be expensive
                    when there are many points and the backing store of
                    the canvas is disabled. So in this application
                    we better don't disable both backing stores.
                 */

                if ( testPaintAttribute( QwtPlotCanvas::BackingStore ) )
                {
                    setAttribute( Qt::WA_PaintOnScreen, true );
                    setAttribute( Qt::WA_NoSystemBackground, true );
                }
            }

            setupPalette();
        }

      private:
        void setupPalette()
        {
            QPalette pal = palette();

            QLinearGradient gradient;
            gradient.setCoordinateMode( QGradient::StretchToDeviceMode );
            gradient.setColorAt( 0.0, QColor( 0, 49, 110 ) );
            gradient.setColorAt( 1.0, QColor( 0, 87, 174 ) );

            pal.setBrush( QPalette::Window, QBrush( gradient ) );

            // QPalette::WindowText is used for the curve color
            pal.setColor( QPalette::WindowText, Qt::green );

            setPalette( pal );
        }
    };

    class CurveData : public QwtSeriesData< QPointF >
    {
      public:
        const SignalData& values() const
        {
            return SignalData::instance();
        }

        SignalData& values()
        {
            return SignalData::instance();
        }

        virtual QPointF sample( size_t index ) const QWT_OVERRIDE
        {
            return SignalData::instance().value( index );
        }

        virtual size_t size() const QWT_OVERRIDE
        {
            return SignalData::instance().size();
        }

        virtual QRectF boundingRect() const QWT_OVERRIDE
        {
            return SignalData::instance().boundingRect();
        }
    };
}

Plot::Plot( QWidget* parent )
    : QwtPlot( parent )
    , m_paintedPoints( 0 )
    , m_interval( 0.0, 10.0 )
    , m_timerId( -1 )
{
    m_directPainter = new QwtPlotDirectPainter();

    setAutoReplot( false );
    setCanvas( new Canvas() );

    plotLayout()->setAlignCanvasToScales( true );

    setAxisTitle( QwtAxis::XBottom, "Time [s]" );
    setAxisScale( QwtAxis::XBottom, m_interval.minValue(), m_interval.maxValue() );
    setAxisScale( QwtAxis::YLeft, -200.0, 200.0 );

    QwtPlotGrid* grid = new QwtPlotGrid();
    grid->setPen( Qt::gray, 0.0, Qt::DotLine );
    grid->enableX( true );
    grid->enableXMin( true );
    grid->enableY( true );
    grid->enableYMin( false );
    grid->attach( this );

    m_origin = new QwtPlotMarker();
    m_origin->setLineStyle( QwtPlotMarker::Cross );
    m_origin->setValue( m_interval.minValue() + m_interval.width() / 2.0, 0.0 );
    m_origin->setLinePen( Qt::gray, 0.0, Qt::DashLine );
    m_origin->attach( this );

    m_curve = new QwtPlotCurve();
    m_curve->setStyle( QwtPlotCurve::Lines );
    m_curve->setPen( canvas()->palette().color( QPalette::WindowText ) );
    m_curve->setRenderHint( QwtPlotItem::RenderAntialiased, true );
    m_curve->setPaintAttribute( QwtPlotCurve::ClipPolygons, false );
    m_curve->setData( new CurveData() );
    m_curve->attach( this );
}

Plot::~Plot()
{
    delete m_directPainter;
}

void Plot::start()
{
    m_elapsedTimer.start();
    m_timerId = startTimer( 10 );
}

void Plot::replot()
{
    CurveData* curveData = static_cast< CurveData* >( m_curve->data() );
    curveData->values().lock();

    QwtPlot::replot();
    m_paintedPoints = curveData->size();

    curveData->values().unlock();
}

void Plot::setIntervalLength( double interval )
{
    if ( interval > 0.0 && interval != m_interval.width() )
    {
        m_interval.setMaxValue( m_interval.minValue() + interval );
        setAxisScale( QwtAxis::XBottom,
            m_interval.minValue(), m_interval.maxValue() );

        replot();
    }
}

void Plot::updateCurve()
{
    CurveData* curveData = static_cast< CurveData* >( m_curve->data() );
    curveData->values().lock();

    const int numPoints = curveData->size();
    if ( numPoints > m_paintedPoints )
    {
        const bool doClip = !canvas()->testAttribute( Qt::WA_PaintOnScreen );
        if ( doClip )
        {
            /*
                Depending on the platform setting a clip might be an important
                performance issue. F.e. for Qt Embedded this reduces the
                part of the backing store that has to be copied out - maybe
                to an unaccelerated frame buffer device.
             */

            const QwtScaleMap xMap = canvasMap( m_curve->xAxis() );
            const QwtScaleMap yMap = canvasMap( m_curve->yAxis() );

            QRectF br = qwtBoundingRect( *curveData,
                m_paintedPoints - 1, numPoints - 1 );

            const QRect clipRect = QwtScaleMap::transform( xMap, yMap, br ).toRect();
            m_directPainter->setClipRegion( clipRect );
        }

        m_directPainter->drawSeries( m_curve,
            m_paintedPoints - 1, numPoints - 1 );
        m_paintedPoints = numPoints;
    }

    curveData->values().unlock();
}

void Plot::incrementInterval()
{
    m_interval = QwtInterval( m_interval.maxValue(),
        m_interval.maxValue() + m_interval.width() );

    CurveData* curveData = static_cast< CurveData* >( m_curve->data() );
    curveData->values().clearStaleValues( m_interval.minValue() );

    // To avoid, that the grid is jumping, we disable
    // the autocalculation of the ticks and shift them
    // manually instead.

    QwtScaleDiv scaleDiv = axisScaleDiv( QwtAxis::XBottom );
    scaleDiv.setInterval( m_interval );

    for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ )
    {
        QList< double > ticks = scaleDiv.ticks( i );
        for ( int j = 0; j < ticks.size(); j++ )
            ticks[j] += m_interval.width();
        scaleDiv.setTicks( i, ticks );
    }
    setAxisScaleDiv( QwtAxis::XBottom, scaleDiv );

    m_origin->setValue( m_interval.minValue() + m_interval.width() / 2.0, 0.0 );

    m_paintedPoints = 0;
    replot();
}

void Plot::timerEvent( QTimerEvent* event )
{
    if ( event->timerId() == m_timerId )
    {
        updateCurve();

        const double elapsed = m_elapsedTimer.elapsed() / 1e3;
        if ( elapsed > m_interval.maxValue() )
            incrementInterval();

        return;
    }

    QwtPlot::timerEvent( event );
}

void Plot::resizeEvent( QResizeEvent* event )
{
    m_directPainter->reset();
    QwtPlot::resizeEvent( event );
}

void Plot::showEvent( QShowEvent* )
{
    replot();
}

bool Plot::eventFilter( QObject* object, QEvent* event )
{
    if ( object == canvas() &&
        event->type() == QEvent::PaletteChange )
    {
        m_curve->setPen( canvas()->palette().color( QPalette::WindowText ) );
    }

    return QwtPlot::eventFilter( object, event );
}

#include "moc_Plot.cpp"
