/////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2020 Equinor ASA
//
//  ResInsight 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 3 of the License, or
//  (at your option) any later version.
//
//  ResInsight 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 at <http://www.gnu.org/licenses/gpl.html>
//  for more details.
//
/////////////////////////////////////////////////////////////////////////////////

#include "RimParameterResultCrossPlot.h"

#include "RiaColorTables.h"
#include "RiaPreferences.h"
#include "RiaQDateTimeTools.h"
#include "RiaTextStringTools.h"

#include "RifSummaryReaderInterface.h"

#include "RigEnsembleParameter.h"

#include "RimDerivedSummaryCase.h"
#include "RimEnsembleCurveSet.h"
#include "RimMultiPlot.h"
#include "RimPlotAxisProperties.h"
#include "RimPlotAxisPropertiesInterface.h"
#include "RimPlotDataFilterCollection.h"
#include "RimProject.h"
#include "RimSummaryAddress.h"
#include "RimSummaryCase.h"
#include "RimSummaryCaseCollection.h"
#include "RimSummaryPlotAxisFormatter.h"

#include "RiuPlotCurve.h"
#include "RiuPlotMainWindowTools.h"
#include "RiuQwtPlotCurve.h"
#include "RiuSummaryQwtPlot.h"
#include "RiuSummaryVectorSelectionDialog.h"

#include "cafPdmUiComboBoxEditor.h"
#include "cafPdmUiLineEditor.h"
#include "cafPdmUiPushButtonEditor.h"

#include "qwt_legend.h"
#include "qwt_plot.h"
#include "qwt_plot_curve.h"
#include "qwt_scale_engine.h"

#include <QStringList>

#include <limits>
#include <map>
#include <set>

CAF_PDM_SOURCE_INIT( RimParameterResultCrossPlot, "ParameterResultCrossPlot" );

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimParameterResultCrossPlot::RimParameterResultCrossPlot()
    : RimAbstractCorrelationPlot()
{
    CAF_PDM_InitObject( "ParameterResultCross Plot", ":/CorrelationCrossPlot16x16.png" );

    CAF_PDM_InitField( &m_ensembleParameter, "EnsembleParameter", QString( "" ), "Ensemble Parameter" );
    m_ensembleParameter.uiCapability()->setUiEditorTypeName( caf::PdmUiComboBoxEditor::uiEditorTypeName() );

    m_selectMultipleVectors = true;

    m_legendFontSize = caf::FontTools::RelativeSize::XSmall;

    m_xRange = std::make_pair( std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity() );
    m_yRange = std::make_pair( std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity() );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimParameterResultCrossPlot::~RimParameterResultCrossPlot()
{
    if ( isMdiWindow() ) removeMdiWindowFromMdiArea();
    cleanupBeforeClose();
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimParameterResultCrossPlot::setEnsembleParameter( const QString& ensembleParameter )
{
    m_ensembleParameter = ensembleParameter;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimParameterResultCrossPlot::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue )
{
    RimAbstractCorrelationPlot::fieldChangedByUi( changedField, oldValue, newValue );
    if ( changedField == &m_ensembleParameter )
    {
        loadDataAndUpdate();
        updateConnectedEditors();
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimParameterResultCrossPlot::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
{
    appendDataSourceFields( uiConfigName, uiOrdering );

    caf::PdmUiGroup* crossPlotGroup = uiOrdering.addNewGroup( "Cross Plot Parameters" );
    crossPlotGroup->add( &m_ensembleParameter );

    caf::PdmUiGroup* plotGroup = uiOrdering.addNewGroup( "Plot Settings" );
    plotGroup->add( &m_showPlotTitle );
    plotGroup->add( &m_useAutoPlotTitle );
    plotGroup->add( &m_description );
    RimPlot::defineUiOrdering( uiConfigName, *plotGroup );
    plotGroup->add( &m_titleFontSize );
    plotGroup->add( &m_legendFontSize );
    plotGroup->add( &m_axisTitleFontSize );
    plotGroup->add( &m_axisValueFontSize );

    m_description.uiCapability()->setUiReadOnly( m_useAutoPlotTitle() );
    uiOrdering.skipRemainingFields( true );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QList<caf::PdmOptionItemInfo> RimParameterResultCrossPlot::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions )
{
    QList<caf::PdmOptionItemInfo> options = RimAbstractCorrelationPlot::calculateValueOptions( fieldNeedingOptions );
    if ( fieldNeedingOptions == &m_ensembleParameter )
    {
        for ( const auto& param : ensembleParameters() )
        {
            options.push_back( caf::PdmOptionItemInfo( param.uiName(), param.name ) );
        }
    }
    return options;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimParameterResultCrossPlot::onLoadDataAndUpdate()
{
    updateMdiWindowVisibility();

    m_selectedVarsUiField = selectedVectorNamesText();

    if ( m_plotWidget && m_analyserOfSelectedCurveDefs )
    {
        createPoints();
        if ( m_showPlotLegends && !isSubPlot<RimMultiPlot>() )
        {
            QwtLegend* legend = new QwtLegend( m_plotWidget );
            m_plotWidget->qwtPlot()->insertLegend( legend, QwtPlot::RightLegend );
            m_plotWidget->setLegendFontSize( legendFontSize() );
            m_plotWidget->updateLegend();
        }

        updateAxes();
        updatePlotTitle();
        m_plotWidget->scheduleReplot();
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimParameterResultCrossPlot::updateAxes()
{
    if ( !m_plotWidget ) return;

    m_plotWidget->setAxisTitleText( RiuPlotAxis::defaultLeft(), completeAddressText() );
    m_plotWidget->setAxisTitleEnabled( RiuPlotAxis::defaultLeft(), true );
    m_plotWidget->setAxisFontsAndAlignment( RiuPlotAxis::defaultLeft(), axisTitleFontSize(), axisValueFontSize(), false, Qt::AlignCenter );

    double yRangeWidth = m_yRange.second - m_yRange.first;
    m_plotWidget->setAxisRange( RiuPlotAxis::defaultLeft(), m_yRange.first - yRangeWidth * 0.1, m_yRange.second + yRangeWidth * 0.1 );

    m_plotWidget->setAxisTitleText( RiuPlotAxis::defaultBottom(), m_ensembleParameter );
    m_plotWidget->setAxisTitleEnabled( RiuPlotAxis::defaultBottom(), true );
    m_plotWidget->setAxisFontsAndAlignment( RiuPlotAxis::defaultBottom(), axisTitleFontSize(), axisValueFontSize(), false, Qt::AlignCenter );

    double xRangeWidth = m_xRange.second - m_xRange.first;
    m_plotWidget->setAxisRange( RiuPlotAxis::defaultBottom(), m_xRange.first - xRangeWidth * 0.1, m_xRange.second + xRangeWidth * 0.1 );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QStringList caseNamesOfValidEnsembleCases( const RimSummaryCaseCollection* ensemble )
{
    QStringList caseNames;
    for ( auto summaryCase : ensemble->allSummaryCases() )
    {
        RifSummaryReaderInterface* reader = summaryCase->summaryReader();

        if ( !reader ) continue;
        if ( !summaryCase->caseRealizationParameters() ) continue;

        caseNames.push_back( summaryCase->displayCaseName() );
    }
    return caseNames;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimParameterResultCrossPlot::createPoints()
{
    detachAllCurves();
    m_valuesForTextReport.clear();

    time_t selectedTimestep = m_timeStep().toSecsSinceEpoch();

    caf::ColorTable colorTable = RiaColorTables::categoryPaletteColors();

    if ( ensembles().empty() ) return;
    if ( addresses().empty() ) return;

    bool showEnsembleName = ensembles().size() > 1u;
    bool showAddressName  = addresses().size() > 1u;

    m_xRange = std::make_pair( std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity() );
    m_yRange = std::make_pair( std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity() );

    int ensembleIdx = 0;
    for ( auto ensemble : ensembles() )
    {
        int addressIdx = 0;
        for ( auto address : addresses() )
        {
            RigEnsembleParameter parameter = ensembleParameter( m_ensembleParameter );
            if ( !( parameter.isNumeric() && parameter.isValid() ) ) return;

            QStringList caseNames      = caseNamesOfValidEnsembleCases( ensemble );
            QString     commonCaseRoot = RiaTextStringTools::commonRoot( caseNames );

            std::set<RimSummaryCase*> activeCases = filterEnsembleCases( ensemble );

            for ( size_t caseIdx = 0u; caseIdx < ensemble->allSummaryCases().size(); ++caseIdx )
            {
                auto summaryCase = ensemble->allSummaryCases()[caseIdx];
                if ( activeCases.count( summaryCase ) == 0 ) continue;

                RifSummaryReaderInterface* reader = summaryCase->summaryReader();
                if ( !reader ) continue;

                if ( !summaryCase->caseRealizationParameters() ) continue;

                double closestValue    = std::numeric_limits<double>::infinity();
                time_t closestTimeStep = 0;
                auto [isOk, values]    = reader->values( address );
                if ( isOk )
                {
                    const std::vector<time_t>& timeSteps = reader->timeSteps( address );
                    for ( size_t i = 0; i < timeSteps.size(); ++i )
                    {
                        if ( timeDiff( timeSteps[i], selectedTimestep ) < timeDiff( selectedTimestep, closestTimeStep ) )
                        {
                            closestValue    = values[i];
                            closestTimeStep = timeSteps[i];
                        }
                    }
                }
                if ( closestValue != std::numeric_limits<double>::infinity() )
                {
                    std::vector<double> caseValuesAtTimestep;
                    std::vector<double> parameterValues;

                    caseValuesAtTimestep.push_back( closestValue );
                    double paramValue = parameter.values[caseIdx].toDouble();
                    parameterValues.push_back( paramValue );

                    m_valuesForTextReport.push_back( { paramValue, closestValue } );

                    m_xRange.first  = std::min( m_xRange.first, paramValue );
                    m_xRange.second = std::max( m_xRange.second, paramValue );

                    m_yRange.first  = std::min( m_yRange.first, closestValue );
                    m_yRange.second = std::max( m_yRange.second, closestValue );

                    RiuQwtPlotCurve* plotCurve = new RiuQwtPlotCurve;
                    plotCurve->setSamplesValues( parameterValues, caseValuesAtTimestep );
                    plotCurve->setStyle( QwtPlotCurve::NoCurve );
                    RiuQwtSymbol* symbol = new RiuQwtSymbol( RiuPlotCurveSymbol::cycledSymbolStyle( ensembleIdx, addressIdx ), "" );
                    symbol->setSize( legendFontSize(), legendFontSize() );
                    symbol->setColor( colorTable.cycledQColor( caseIdx ) );
                    plotCurve->setSymbol( symbol );
                    QStringList curveName;
                    if ( showEnsembleName ) curveName += ensemble->name();
                    curveName += summaryCase->displayCaseName().replace( commonCaseRoot, "" );
                    if ( showAddressName ) curveName += QString::fromStdString( address.uiText() );

                    plotCurve->setTitle( curveName.join( " - " ) );

                    plotCurve->attach( m_plotWidget->qwtPlot() );
                }
            }
            addressIdx++;
        }
        ensembleIdx++;
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimParameterResultCrossPlot::asciiDataForPlotExport() const
{
    QString asciiData;

    asciiData += "Parameter\tResult\n";
    for ( const auto& valuePair : m_valuesForTextReport )
    {
        asciiData += QString( "%1\t%2\n" ).arg( valuePair.first ).arg( valuePair.second );
    }

    return asciiData;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimParameterResultCrossPlot::updatePlotTitle()
{
    if ( m_useAutoPlotTitle && !ensembles().empty() )
    {
        auto ensemble = *ensembles().begin();

        QString vectorName = completeAddressText();

        m_description =
            QString( "Cross Plot %1, %2 x %3 at %4" ).arg( ensemble->name() ).arg( m_ensembleParameter ).arg( vectorName ).arg( timeStepString() );
    }
    m_plotWidget->setPlotTitle( m_description );
    m_plotWidget->setPlotTitleEnabled( m_showPlotTitle && !isSubPlot() );
    m_plotWidget->setPlotTitleFontSize( titleFontSize() );
}
