/////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2018-     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 "RicCreateMultipleFracturesUi.h"

#include "RifTextDataTableFormatter.h"

#include "RigEclipseCaseData.h"
#include "RigMainGrid.h"
#include "RigWellLogExtractor.h"
#include "RigWellPath.h"
#include "RigWellPathIntersectionTools.h"

#include "RimEclipseCase.h"
#include "RimFractureTemplate.h"
#include "RimTools.h"
#include "RimWellPath.h"

#include "cafCmdFeatureMenuBuilder.h"
#include "cafPdmUiPropertyViewDialog.h"
#include "cafPdmUiTableViewEditor.h"
#include "cafPdmUiTextEditor.h"
#include "cafSelectionManagerTools.h"

#include "cvfBoundingBox.h"

#include <algorithm>

CAF_PDM_SOURCE_INIT( RiuCreateMultipleFractionsUi, "RiuCreateMultipleFractionsUi" );

//--------------------------------------------------------------------------------------------------
/// Internal definitions
//--------------------------------------------------------------------------------------------------
#define DOUBLE_INF std::numeric_limits<double>::infinity()

// startMd > endMd (going upwards along well path)
class MultipleFracturesOption
{
public:
    MultipleFracturesOption()
        : startMd( 0 )
        , endMd( 0 )
        , startK( 0 )
        , endK( 0 )
        , uiOption( nullptr )
    {
    }
    double                                  startMd;
    double                                  endMd;
    size_t                                  startK;
    size_t                                  endK;
    RicCreateMultipleFracturesOptionItemUi* uiOption;
};

std::vector<MultipleFracturesOption>    fractureOptions( const RigEclipseCaseData*                                   caseData,
                                                         const RimWellPath*                                          wellPath,
                                                         const std::vector<RicCreateMultipleFracturesOptionItemUi*>& allUiOptions );
RicCreateMultipleFracturesOptionItemUi* firstUiOptionContainingK( size_t k,
                                                                  const std::vector<RicCreateMultipleFracturesOptionItemUi*>& allUiOptions );

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const QString RiuCreateMultipleFractionsUi::ADD_FRACTURES_BUTTON_TEXT     = "Add Fractures";
const QString RiuCreateMultipleFractionsUi::REPLACE_FRACTURES_BUTTON_TEXT = "Replace Fractures";

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiuCreateMultipleFractionsUi::RiuCreateMultipleFractionsUi()
{
    CAF_PDM_InitFieldNoDefault( &m_sourceCase, "SourceCase", "Case" );

    CAF_PDM_InitField( &m_minDistanceFromWellTd, "MinDistanceFromWellTd", 10.0, "Min Distance From Well TD" );
    CAF_PDM_InitField( &m_maxFracturesPerWell, "MaxFracturesPerWell", 10, "Max Fractures Per Well" );

    CAF_PDM_InitFieldNoDefault( &m_options, "Options", "Options" );
    m_options.uiCapability()->setUiEditorTypeName( caf::PdmUiTableViewEditor::uiEditorTypeName() );
    m_options.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::TOP );
    m_options.uiCapability()->setCustomContextMenuEnabled( true );

    CAF_PDM_InitFieldNoDefault( &m_fractureCreationSummary, "FractureCreationSummary", "Generated Fractures" );
    m_fractureCreationSummary.registerGetMethod( this, &RiuCreateMultipleFractionsUi::summaryText );
    m_fractureCreationSummary.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::TOP );
    m_fractureCreationSummary.uiCapability()->setUiEditorTypeName( caf::PdmUiTextEditor::uiEditorTypeName() );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuCreateMultipleFractionsUi::setParentDialog( QPointer<caf::PdmUiPropertyViewDialog> dialog )
{
    m_dialog = dialog;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuCreateMultipleFractionsUi::setValues( RimEclipseCase* eclipseCase, double minimumDistanceFromWellToe, int maxFracturesPerWell )
{
    m_sourceCase            = eclipseCase;
    m_minDistanceFromWellTd = minimumDistanceFromWellToe;
    m_maxFracturesPerWell   = maxFracturesPerWell;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuCreateMultipleFractionsUi::resetValues()
{
    m_sourceCase = nullptr;
    m_options.deleteChildren();
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RicCreateMultipleFracturesOptionItemUi*> RiuCreateMultipleFractionsUi::options() const
{
    return m_options.children();
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuCreateMultipleFractionsUi::insertOptionItem( RicCreateMultipleFracturesOptionItemUi* insertAfterThisObject,
                                                     RicCreateMultipleFracturesOptionItemUi* objectToInsert )
{
    size_t index = m_options.indexOf( insertAfterThisObject );
    if ( index < m_options.size() - 1 )
    {
        m_options.insert( index + 1, objectToInsert );
    }
    else
    {
        m_options.push_back( objectToInsert );
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuCreateMultipleFractionsUi::deleteOptionItem( RicCreateMultipleFracturesOptionItemUi* optionsItem )
{
    m_options.removeChild( optionsItem );
    delete optionsItem;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuCreateMultipleFractionsUi::clearOptions()
{
    m_options.deleteChildren();
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuCreateMultipleFractionsUi::addWellPath( RimWellPath* wellPath )
{
    m_wellPaths.push_back( wellPath );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuCreateMultipleFractionsUi::clearWellPaths()
{
    m_wellPaths.clear();
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QList<caf::PdmOptionItemInfo> RiuCreateMultipleFractionsUi::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions )
{
    QList<caf::PdmOptionItemInfo> options;

    if ( fieldNeedingOptions == &m_sourceCase )
    {
        RimTools::caseOptionItems( &options );
    }

    return options;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuCreateMultipleFractionsUi::defineCustomContextMenu( const caf::PdmFieldHandle* fieldNeedingMenu, QMenu* menu, QWidget* fieldEditorWidget )
{
    caf::CmdFeatureMenuBuilder menuBuilder;

    menuBuilder << "RicNewOptionItemFeature";
    menuBuilder << "RicDeleteOptionItemFeature";

    menuBuilder.appendToMenu( menu );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RiuCreateMultipleFractionsUi::summaryText() const
{
    std::vector<LocationForNewFracture> locations = locationsForNewFractures();

    std::set<RimWellPath*>         wellPathSet;
    std::set<RimFractureTemplate*> fracTemplateSet;

    for ( auto location : locations )
    {
        wellPathSet.insert( location.wellPath );
        fracTemplateSet.insert( location.fractureTemplate );
    }

    QString tableText;

    {
        QTextStream               stream( &tableText );
        RifTextDataTableFormatter formatter( stream );
        formatter.setTableRowLineAppendText( "" );
        formatter.setTableRowPrependText( "   " );

        std::vector<RifTextDataTableColumn> header;
        header.push_back( RifTextDataTableColumn( "Selected Wells" ) );

        for ( auto fracTemplate : fracTemplateSet )
        {
            header.push_back(
                RifTextDataTableColumn( fracTemplate->name(), RifTextDataTableDoubleFormatting(), RifTextDataTableAlignment::RIGHT ) );
        }

        formatter.header( header );

        for ( auto wellPath : wellPathSet )
        {
            formatter.add( wellPath->name() );

            for ( auto fractureTemplate : fracTemplateSet )
            {
                size_t fractureTemplateCount = 0;
                for ( auto fracLocation : locations )
                {
                    if ( fractureTemplate == fracLocation.fractureTemplate && wellPath == fracLocation.wellPath )
                    {
                        fractureTemplateCount++;
                    }
                }

                formatter.add( fractureTemplateCount );
            }

            formatter.rowCompleted();
        }

        formatter.tableCompleted();
    }

    return tableText;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuCreateMultipleFractionsUi::defineEditorAttribute( const caf::PdmFieldHandle* field,
                                                          QString                    uiConfigName,
                                                          caf::PdmUiEditorAttribute* attribute )
{
    if ( field == &m_fractureCreationSummary )
    {
        auto attr = dynamic_cast<caf::PdmUiTextEditorAttribute*>( attribute );
        if ( attr )
        {
            QFont font( "Courier", 8 );

            attr->font     = font;
            attr->wrapMode = caf::PdmUiTextEditorAttribute::NoWrap;
        }
    }
    else if ( field == &m_options )
    {
        auto attr = dynamic_cast<caf::PdmUiTableViewEditorAttribute*>( attribute );
        if ( attr )
        {
            attr->minimumHeight = 130;
            attr->columnWidths  = { 90, 90, 400, 70 };
        }
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<LocationForNewFracture> RiuCreateMultipleFractionsUi::locationsForNewFractures() const
{
    std::vector<LocationForNewFracture> items;

    RigMainGrid* mainGrid = nullptr;

    if ( m_sourceCase && m_sourceCase->eclipseCaseData() )
    {
        mainGrid = m_sourceCase->eclipseCaseData()->mainGrid();
    }

    if ( mainGrid )
    {
        for ( auto w : m_wellPaths )
        {
            if ( w->wellPathGeometry() )
            {
                double endMD = w->endMD();

                int    fractureCountForWell = 0;
                auto   options              = fractureOptions( m_sourceCase->eclipseCaseData(), w, this->options() );
                double lastFracMd           = endMD;

                // Iterate options which are sorted from deeper to shallower
                for ( size_t i = 0; i < options.size(); i++ )
                {
                    const auto& option = options[i];
                    double      fracMdCandidate;
                    if ( i == 0 )
                    {
                        fracMdCandidate = endMD - m_minDistanceFromWellTd;
                    }
                    else
                    {
                        double spacing  = std::max( options[i - 1].uiOption->minimumSpacing(), option.uiOption->minimumSpacing() );
                        fracMdCandidate = lastFracMd - spacing;
                    }

                    if ( fracMdCandidate > option.startMd ) fracMdCandidate = option.startMd;

                    while ( fracMdCandidate > option.endMd )
                    {
                        items.push_back( LocationForNewFracture( option.uiOption->fractureTemplate(), w, fracMdCandidate ) );
                        lastFracMd = fracMdCandidate;
                        fracMdCandidate -= option.uiOption->minimumSpacing();
                        fractureCountForWell++;

                        if ( fractureCountForWell >= m_maxFracturesPerWell ) break;
                    }

                    if ( fractureCountForWell >= m_maxFracturesPerWell ) break;
                }
            }
        }
    }

    std::sort( items.begin(), items.end() );
    return items;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuCreateMultipleFractionsUi::updateButtonsEnableState()
{
    if ( m_dialog )
    {
        bool        hasOverlappingK = false;
        const auto& opts            = options();
        for ( size_t i = 0; i < opts.size(); i++ )
        {
            for ( size_t j = i + 1; j < opts.size(); j++ )
            {
                int absMin = std::min( opts[i]->topKLayer(), opts[j]->topKLayer() );
                int absMax = std::max( opts[i]->baseKLayer(), opts[j]->baseKLayer() );
                int leni   = opts[i]->baseKLayer() - opts[i]->topKLayer() + 1;
                int lenj   = opts[j]->baseKLayer() - opts[j]->topKLayer() + 1;
                if ( absMax - absMin + 1 < leni + lenj )
                {
                    hasOverlappingK = true;
                    break;
                }
            }
        }

        for ( auto button : m_dialog->dialogButtonBox()->buttons() )
        {
            if ( button->text() == ADD_FRACTURES_BUTTON_TEXT || button->text() == REPLACE_FRACTURES_BUTTON_TEXT )
            {
                button->setEnabled( !hasOverlappingK );
            }
        }
    }
}

//--------------------------------------------------------------------------------------------------
/// Internal definitions
//--------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<MultipleFracturesOption> fractureOptions( const RigEclipseCaseData*                                   caseData,
                                                      const RimWellPath*                                          wellPath,
                                                      const std::vector<RicCreateMultipleFracturesOptionItemUi*>& allUiOptions )
{
    std::vector<MultipleFracturesOption> options;

    if ( !caseData->mainGrid() ) return options;

    auto wellPathGeometry = wellPath->wellPathGeometry();
    CAF_ASSERT( wellPathGeometry );

    std::vector<WellPathCellIntersectionInfo> wellPathInfos =
        RigWellPathIntersectionTools::findCellIntersectionInfosAlongPath( caseData,
                                                                          wellPath->name(),
                                                                          wellPathGeometry->wellPathPoints(),
                                                                          wellPathGeometry->measuredDepths() );
    std::reverse( wellPathInfos.begin(), wellPathInfos.end() );

    bool doCreateNewOption = true;
    for ( const auto& wellPathInfo : wellPathInfos )
    {
        size_t i, j, currK;

        if ( !caseData->mainGrid()->ijkFromCellIndex( wellPathInfo.globCellIndex, &i, &j, &currK ) ) continue;

        RicCreateMultipleFracturesOptionItemUi* uiOption = firstUiOptionContainingK( currK, allUiOptions );

        if ( !uiOption )
        {
            doCreateNewOption = true;
            continue;
        }
        else if ( options.empty() || options.back().uiOption != uiOption )
        {
            doCreateNewOption = true;
        }

        if ( doCreateNewOption )
        {
            // New option
            MultipleFracturesOption option;

            option.startMd = wellPathInfo.endMD; // Deeper MD
            option.endMd   = wellPathInfo.startMD; // Upper MD

            option.startK = currK;
            option.endK   = currK;

            option.uiOption = uiOption;
            options.push_back( option );

            doCreateNewOption = false;
        }
        else
        {
            // Update existing option
            MultipleFracturesOption& option = options.back();

            option.endMd  = wellPathInfo.startMD; // Upper MD
            option.startK = std::max( option.startK, currK );
            option.endK   = std::min( option.endK, currK );
        }
    }

    return options;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RicCreateMultipleFracturesOptionItemUi* firstUiOptionContainingK( size_t                                                      k,
                                                                  const std::vector<RicCreateMultipleFracturesOptionItemUi*>& allUiOptions )
{
    for ( auto uiOption : allUiOptions )
    {
        int oneBasedK = static_cast<int>( k ) + 1;
        if ( uiOption->isKLayerContained( oneBasedK ) && uiOption->fractureTemplate() )
        {
            return uiOption;
        }
    }
    return nullptr;
}
