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

#include "PlotMatrix.h"

#include <QwtPlot>
#include <QwtPlotCanvas>
#include <QwtScaleWidget>
#include <QwtScaleDraw>

#include <QLayout>

static void enablePlotAxis( QwtPlot* plot, int axis, bool on )
{
    // when false we still enable the axis to have an effect
    // of the minimal extent active. Instead we hide all visible
    // parts and margins/spacings.

    plot->setAxisVisible( axis, true );

    QwtScaleDraw* sd = plot->axisScaleDraw( axis );
    sd->enableComponent( QwtScaleDraw::Backbone, on );
    sd->enableComponent( QwtScaleDraw::Ticks, on );
    sd->enableComponent( QwtScaleDraw::Labels, on );

    QwtScaleWidget* sw = plot->axisWidget( axis );
    sw->setMargin( on ? 4 : 0 );
    sw->setSpacing( on ? 20 : 0 );
}

namespace
{
    class Plot : public QwtPlot
    {
      public:
        Plot( QWidget* parent = NULL )
            : QwtPlot( parent )
        {
            QwtPlotCanvas* canvas = new QwtPlotCanvas();
            canvas->setLineWidth( 1 );
            canvas->setFrameStyle( QFrame::Box | QFrame::Plain );

            setCanvas( canvas );
        }

        virtual QSize sizeHint() const QWT_OVERRIDE
        {
            return minimumSizeHint();
        }
    };
}

class PlotMatrix::PrivateData
{
  public:
    PrivateData():
        inScaleSync( false )
    {
        isAxisEnabled[QwtAxis::XBottom] = true;
        isAxisEnabled[QwtAxis::XTop] = false;
        isAxisEnabled[QwtAxis::YLeft] = true;
        isAxisEnabled[QwtAxis::YRight] = false;
    }

    bool isAxisEnabled[QwtAxis::AxisPositions];
    QVector< QwtPlot* > plotWidgets;
    mutable bool inScaleSync;
};

PlotMatrix::PlotMatrix( int numRows, int numColumns, QWidget* parent )
    : QFrame( parent )
{
    m_data = new PrivateData();
    m_data->plotWidgets.resize( numRows * numColumns );

    QGridLayout* layout = new QGridLayout( this );
    layout->setSpacing( 5 );

    for ( int row = 0; row < numRows; row++ )
    {
        for ( int col = 0; col < numColumns; col++ )
        {
            QwtPlot* plot = new Plot( this );

            layout->addWidget( plot, row, col );

            for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ )
            {
                connect( plot->axisWidget( axisPos ),
                    SIGNAL(scaleDivChanged()), SLOT(scaleDivChanged()),
                    Qt::QueuedConnection );
            }
            m_data->plotWidgets[row * numColumns + col] = plot;
        }
    }

    updateLayout();
}

PlotMatrix::~PlotMatrix()
{
    delete m_data;
}

int PlotMatrix::numRows() const
{
    const QGridLayout* l = qobject_cast< const QGridLayout* >( layout() );
    if ( l )
        return l->rowCount();

    return 0;
}

int PlotMatrix::numColumns() const
{
    const QGridLayout* l = qobject_cast< const QGridLayout* >( layout() );
    if ( l )
        return l->columnCount();
    return 0;
}

QwtPlot* PlotMatrix::plotAt( int row, int column )
{
    const int index = row * numColumns() + column;
    if ( index < m_data->plotWidgets.size() )
        return m_data->plotWidgets[index];

    return NULL;
}

const QwtPlot* PlotMatrix::plotAt( int row, int column ) const
{
    const int index = row * numColumns() + column;
    if ( index < m_data->plotWidgets.size() )
        return m_data->plotWidgets[index];

    return NULL;
}

void PlotMatrix::setAxisVisible( int axis, bool tf )
{
    if ( QwtAxis::isValid( axis ) )
    {
        if ( tf != m_data->isAxisEnabled[axis] )
        {
            m_data->isAxisEnabled[axis] = tf;
            updateLayout();
        }
    }
}

bool PlotMatrix::isAxisVisible( int axis ) const
{
    if ( QwtAxis::isValid( axis ) )
        return m_data->isAxisEnabled[axis];

    return false;
}

void PlotMatrix::setAxisScale( int axis, int rowOrColumn,
    double min, double max, double step )
{
    int row = 0;
    int col = 0;

    if ( QwtAxis::isXAxis( axis ) )
        col = rowOrColumn;
    else
        row = rowOrColumn;

    QwtPlot* plt = plotAt( row, col );
    if ( plt )
    {
        plt->setAxisScale( axis, min, max, step );
        plt->updateAxes();
    }
}

void PlotMatrix::scaleDivChanged()
{
    if ( m_data->inScaleSync )
        return;

    m_data->inScaleSync = true;

    QwtPlot* plt = NULL;
    int axisId = -1;
    int rowOrColumn = -1;

    // find the changed axis
    for ( int row = 0; row < numRows(); row++ )
    {
        for ( int col = 0; col < numColumns(); col++ )
        {
            QwtPlot* p = plotAt( row, col );
            if ( p )
            {
                for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ )
                {
                    if ( p->axisWidget( axisPos ) == sender() )
                    {
                        plt = p;
                        axisId = axisPos;
                        rowOrColumn = QwtAxis::isXAxis( axisId ) ? col : row;
                    }
                }
            }
        }
    }

    if ( plt )
    {
        const QwtScaleDiv scaleDiv = plt->axisScaleDiv( axisId );

        // synchronize the axes
        if ( QwtAxis::isXAxis( axisId ) )
        {
            for ( int row = 0; row < numRows(); row++ )
            {
                QwtPlot* p = plotAt( row, rowOrColumn );
                if ( p != plt )
                {
                    p->setAxisScaleDiv( axisId, scaleDiv );
                }
            }
        }
        else
        {
            for ( int col = 0; col < numColumns(); col++ )
            {
                QwtPlot* p = plotAt( rowOrColumn, col );
                if ( p != plt )
                {
                    p->setAxisScaleDiv( axisId, scaleDiv );
                }
            }
        }

        updateLayout();
    }

    m_data->inScaleSync = false;
}

void PlotMatrix::updateLayout()
{
    using namespace QwtAxis;

    for ( int row = 0; row < numRows(); row++ )
    {
        for ( int col = 0; col < numColumns(); col++ )
        {
            QwtPlot* p = plotAt( row, col );
            if ( p )
            {
                bool showAxis[AxisPositions];

                showAxis[XBottom] = isAxisVisible( XBottom ) && row == numRows() - 1;
                showAxis[XTop] = isAxisVisible( XTop ) && row == 0;
                showAxis[YLeft] = isAxisVisible( YLeft ) && col == 0;
                showAxis[YRight] = isAxisVisible( YRight ) && col == numColumns() - 1;

                for ( int axis = 0; axis < AxisPositions; axis++ )
                {
                    enablePlotAxis( p, axis, showAxis[axis] );
                }
            }
        }
    }

    for ( int row = 0; row < numRows(); row++ )
    {
        alignAxes( row, XTop );
        alignAxes( row, XBottom );

        alignScaleBorder( row, YLeft );
        alignScaleBorder( row, YRight );
    }

    for ( int col = 0; col < numColumns(); col++ )
    {
        alignAxes( col, YLeft );
        alignAxes( col, YRight );

        alignScaleBorder( col, XBottom );
        alignScaleBorder( col, XTop );
    }

    for ( int row = 0; row < numRows(); row++ )
    {
        for ( int col = 0; col < numColumns(); col++ )
        {
            QwtPlot* p = plotAt( row, col );
            if ( p )
                p->replot();
        }
    }
}

void PlotMatrix::alignAxes( int rowOrColumn, int axis )
{
    if ( QwtAxis::isYAxis( axis ) )
    {
        double maxExtent = 0;

        for ( int row = 0; row < numRows(); row++ )
        {
            QwtPlot* p = plotAt( row, rowOrColumn );
            if ( p )
            {
                QwtScaleWidget* scaleWidget = p->axisWidget( axis );

                QwtScaleDraw* sd = scaleWidget->scaleDraw();
                sd->setMinimumExtent( 0.0 );

                const double extent = sd->extent( scaleWidget->font() );
                if ( extent > maxExtent )
                    maxExtent = extent;
            }
        }

        for ( int row = 0; row < numRows(); row++ )
        {
            QwtPlot* p = plotAt( row, rowOrColumn );
            if ( p )
            {
                QwtScaleWidget* scaleWidget = p->axisWidget( axis );
                scaleWidget->scaleDraw()->setMinimumExtent( maxExtent );
            }
        }
    }
    else
    {
        double maxExtent = 0;

        for ( int col = 0; col < numColumns(); col++ )
        {
            QwtPlot* p = plotAt( rowOrColumn, col );
            if ( p )
            {
                QwtScaleWidget* scaleWidget = p->axisWidget( axis );

                QwtScaleDraw* sd = scaleWidget->scaleDraw();
                sd->setMinimumExtent( 0.0 );

                const double extent = sd->extent( scaleWidget->font() );
                if ( extent > maxExtent )
                    maxExtent = extent;
            }
        }

        for ( int col = 0; col < numColumns(); col++ )
        {
            QwtPlot* p = plotAt( rowOrColumn, col );
            if ( p )
            {
                QwtScaleWidget* scaleWidget = p->axisWidget( axis );
                scaleWidget->scaleDraw()->setMinimumExtent( maxExtent );
            }
        }
    }
}

void PlotMatrix::alignScaleBorder( int rowOrColumn, int axis )
{
    int startDist = 0;
    int endDist = 0;

    if ( axis == QwtAxis::YLeft )
    {
        QwtPlot* plot = plotAt( rowOrColumn, 0 );
        if ( plot )
            plot->axisWidget( axis )->getBorderDistHint( startDist, endDist );

        for ( int col = 1; col < numColumns(); col++ )
        {
            plot = plotAt( rowOrColumn, col );
            if ( plot )
                plot->axisWidget( axis )->setMinBorderDist( startDist, endDist );
        }
    }
    else if ( axis == QwtAxis::YRight )
    {
        QwtPlot* plot = plotAt( rowOrColumn, numColumns() - 1 );
        if ( plot )
            plot->axisWidget( axis )->getBorderDistHint( startDist, endDist );

        for ( int col = 0; col < numColumns() - 1; col++ )
        {
            plot = plotAt( rowOrColumn, col );
            if ( plot )
                plot->axisWidget( axis )->setMinBorderDist( startDist, endDist );
        }
    }

    if ( axis == QwtAxis::XTop )
    {
        QwtPlot* plot = plotAt( rowOrColumn, 0 );
        if ( plot )
            plot->axisWidget( axis )->getBorderDistHint( startDist, endDist );

        for ( int row = 1; row < numRows(); row++ )
        {
            plot = plotAt( row, rowOrColumn );
            if ( plot )
                plot->axisWidget( axis )->setMinBorderDist( startDist, endDist );
        }
    }
    else if ( axis == QwtAxis::XBottom )
    {
        QwtPlot* plot = plotAt( numRows() - 1, rowOrColumn );
        if ( plot )
            plot->axisWidget( axis )->getBorderDistHint( startDist, endDist );

        for ( int row = 0; row < numRows() - 1; row++ )
        {
            plot = plotAt( row, rowOrColumn );
            if ( plot )
                plot->axisWidget( axis )->setMinBorderDist( startDist, endDist );
        }
    }
}

#include "moc_PlotMatrix.cpp"
