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

#include "RiaLogging.h"

#include "RicMswCompletions.h"
#include "RicMswExportInfo.h"

#include "RifTextDataTableFormatter.h"

#include "RigWellPath.h"

#include "RimMswCompletionParameters.h"
#include "RimWellPath.h" // TODO: Consider adding wellnameforexport to RicMswExportInfo to avoid these includes
#include "RimWellPathCompletionSettings.h"

#include <cmath>

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
class RicMswTableFormatterTools::WsegvalveData
{
public:
    explicit WsegvalveData( const QString& wellName, const QString& comment, int segmentNumber, double cv, double ac )
        : m_wellName( wellName )
        , m_comment( comment )
        , m_segmentNumber( segmentNumber )
        , m_cv( cv )
        , m_ac( ac )
    {
    }

    QString m_wellName;
    QString m_comment;
    int     m_segmentNumber;
    double  m_cv;
    double  m_ac;
};

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
class RicMswTableFormatterTools::AicdWsegvalveData
{
public:
    explicit AicdWsegvalveData( const QString&                             wellName,
                                const QString&                             comment,
                                int                                        segmentNumber,
                                double                                     flowScalingFactor,
                                bool                                       isOpen,
                                const std::array<double, AICD_NUM_PARAMS>& values )
        : m_wellName( wellName )
        , m_comment( comment )
        , m_segmentNumber( segmentNumber )
        , m_flowScalingFactor( flowScalingFactor )
        , m_isOpen( isOpen )
        , m_values( values )

    {
    }

    QString                             m_wellName;
    QString                             m_comment;
    int                                 m_segmentNumber;
    double                              m_flowScalingFactor;
    bool                                m_isOpen;
    std::array<double, AICD_NUM_PARAMS> m_values;
};

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicMswTableFormatterTools::generateWelsegsTable( RifTextDataTableFormatter& formatter,
                                                      RicMswExportInfo&          exportInfo,
                                                      double                     maxSegmentLength,
                                                      bool                       exportCompletionSegmentsAfterMainBore )
{
    formatter.keyword( "WELSEGS" );

    double startMD  = exportInfo.mainBoreBranch()->startMD();
    double startTVD = exportInfo.mainBoreBranch()->startTVD();

    {
        std::vector<RifTextDataTableColumn> header = {
            RifTextDataTableColumn( "Name" ),
            RifTextDataTableColumn( "Dep 1" ),
            RifTextDataTableColumn( "Tlen 1" ),
            RifTextDataTableColumn( "Vol 1" ),
            RifTextDataTableColumn( "Len&Dep" ),
            RifTextDataTableColumn( "PresDrop" ),
        };
        formatter.header( header );

        formatter.add( exportInfo.mainBoreBranch()->wellPath()->completionSettings()->wellNameForExport() );
        formatter.add( startTVD );
        formatter.add( startMD );
        formatter.addValueOrDefaultMarker( exportInfo.topWellBoreVolume(), RicMswExportInfo::defaultDoubleValue() );
        formatter.add( exportInfo.lengthAndDepthText() );
        formatter.add( QString( "'%1'" ).arg( exportInfo.pressureDropText() ) );

        formatter.rowCompleted();
    }

    {
        std::vector<RifTextDataTableColumn> header = { RifTextDataTableColumn( "First Seg" ),
                                                       RifTextDataTableColumn( "Last Seg" ),
                                                       RifTextDataTableColumn( "Branch Num" ),
                                                       RifTextDataTableColumn( "Outlet Seg" ),
                                                       RifTextDataTableColumn( "Length" ),
                                                       RifTextDataTableColumn( "Depth Change" ),
                                                       RifTextDataTableColumn( "Diam" ),
                                                       RifTextDataTableColumn( "Rough", RifTextDataTableDoubleFormatting( RIF_FLOAT, 7 ) ) };
        formatter.header( header );
    }

    int segmentNumber = 2; // There's an implicit segment number 1.

    RicMswSegment* parentSegment = nullptr;
    writeWelsegsSegmentsRecursively( formatter,
                                     exportInfo,
                                     exportInfo.mainBoreBranch(),
                                     &segmentNumber,
                                     maxSegmentLength,
                                     exportCompletionSegmentsAfterMainBore,
                                     parentSegment );

    formatter.tableCompleted();
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicMswTableFormatterTools::writeWelsegsSegmentsRecursively( RifTextDataTableFormatter&   formatter,
                                                                 RicMswExportInfo&            exportInfo,
                                                                 gsl::not_null<RicMswBranch*> branch,
                                                                 gsl::not_null<int*>          segmentNumber,
                                                                 double                       maxSegmentLength,
                                                                 bool                         exportCompletionSegmentsAfterMainBore,
                                                                 RicMswSegment*               connectedToSegment )
{
    auto outletSegment = connectedToSegment;

    RicMswValve* outletValve = nullptr;

    auto branchSegments = branch->segments();
    auto it             = branchSegments.begin();
    if ( outletValve = dynamic_cast<RicMswTieInICV*>( branch.get() ); outletValve != nullptr )
    {
        writeValveWelsegsSegment( outletSegment, outletValve, formatter, exportInfo, maxSegmentLength, segmentNumber );

        auto valveSegments = outletValve->segments();
        outletSegment      = valveSegments.front();

        *segmentNumber = outletSegment->segmentNumber() + 1;
        ++it; // skip segment below
    }

    formatter.addOptionalComment( QString( "Segments on branch %1" ).arg( branch->label() ) );

    auto branchStartSegmentIterator = it;
    for ( ; it != branchSegments.end(); ++it )
    {
        auto segment = *it;
        segment->setSegmentNumber( *segmentNumber );

        if ( segment->subIndex() != cvf::UNDEFINED_SIZE_T )
        {
            QString comment = segment->label() + QString( ", sub %1" ).arg( segment->subIndex() + 1 );
            formatter.addOptionalComment( comment );
        }

        writeWelsegsSegment( segment, outletSegment, formatter, exportInfo, maxSegmentLength, branch, segmentNumber );
        outletSegment = segment;

        if ( !exportCompletionSegmentsAfterMainBore )
        {
            writeCompletionsForSegment( outletSegment, segment, &outletValve, formatter, exportInfo, maxSegmentLength, segmentNumber );
        }
    }

    if ( exportCompletionSegmentsAfterMainBore )
    {
        it = branchStartSegmentIterator;

        for ( ; it != branchSegments.end(); ++it )
        {
            auto segment = *it;

            writeCompletionsForSegment( outletSegment, segment, &outletValve, formatter, exportInfo, maxSegmentLength, segmentNumber );
        }
    }

    for ( auto childBranch : branch->branches() )
    {
        RicMswSegment* outletSegmentForChildBranch = outletSegment;

        RicMswSegment* tieInSegmentOnParentBranch = branch->findClosestSegmentWithLowerMD( childBranch->startMD() );
        if ( tieInSegmentOnParentBranch ) outletSegmentForChildBranch = tieInSegmentOnParentBranch;

        writeWelsegsSegmentsRecursively( formatter,
                                         exportInfo,
                                         childBranch,
                                         segmentNumber,
                                         maxSegmentLength,
                                         exportCompletionSegmentsAfterMainBore,
                                         outletSegmentForChildBranch );
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicMswTableFormatterTools::writeWelsegsCompletionCommentHeader( RifTextDataTableFormatter&        formatter,
                                                                     RigCompletionData::CompletionType completionType )
{
    QString optionalCommentText;

    switch ( completionType )
    {
        case RigCompletionData::CompletionType::FISHBONES:
            break;
        case RigCompletionData::CompletionType::FRACTURE:
            optionalCommentText = "Fracture Segments";
            break;
        case RigCompletionData::CompletionType::PERFORATION:
            optionalCommentText = "Perforation Segments";
            break;
        case RigCompletionData::CompletionType::FISHBONES_ICD:
            optionalCommentText = "Fishbones Segments - ICD";
            break;
        case RigCompletionData::CompletionType::PERFORATION_ICD:
            optionalCommentText = "Perforation Segments - ICD";
            break;
        case RigCompletionData::CompletionType::PERFORATION_AICD:
            optionalCommentText = "Perforation Segments - AICD";
            break;
        case RigCompletionData::CompletionType::PERFORATION_ICV:
            optionalCommentText = "Perforation Segments - ICV";
            break;
        case RigCompletionData::CompletionType::CT_UNDEFINED:
            optionalCommentText = "Main Stem";
            break;
        default:
            break;
    }

    if ( !optionalCommentText.isEmpty() )
    {
        formatter.addOptionalComment( optionalCommentText );
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicMswTableFormatterTools::generateCompsegTables( RifTextDataTableFormatter& formatter, RicMswExportInfo& exportInfo, bool exportLgrData )
{
    /*
     * TODO: Creating the regular perforation COMPSEGS table should come in here, before the others
     * should take precedence by appearing later in the output. See #3230.
     */

    std::set<size_t> intersectedCells;

    std::set<RigCompletionData::CompletionType> perforationTypes = { RigCompletionData::CompletionType::PERFORATION,
                                                                     RigCompletionData::CompletionType::PERFORATION_ICD,
                                                                     RigCompletionData::CompletionType::PERFORATION_ICV,
                                                                     RigCompletionData::CompletionType::PERFORATION_AICD };

    std::set<RigCompletionData::CompletionType> fishbonesTypes = { RigCompletionData::CompletionType::FISHBONES_ICD,
                                                                   RigCompletionData::CompletionType::FISHBONES };

    std::set<RigCompletionData::CompletionType> fractureTypes = { RigCompletionData::CompletionType::FRACTURE };

    {
        bool headerGenerated = false;
        generateCompsegTable( formatter, exportInfo, exportInfo.mainBoreBranch(), exportLgrData, perforationTypes, &headerGenerated, &intersectedCells );

        generateCompsegTable( formatter, exportInfo, exportInfo.mainBoreBranch(), exportLgrData, fishbonesTypes, &headerGenerated, &intersectedCells );

        generateCompsegTable( formatter, exportInfo, exportInfo.mainBoreBranch(), exportLgrData, fractureTypes, &headerGenerated, &intersectedCells );

        if ( headerGenerated ) formatter.tableCompleted();
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicMswTableFormatterTools::generateCompsegTable( RifTextDataTableFormatter&                         formatter,
                                                      RicMswExportInfo&                                  exportInfo,
                                                      gsl::not_null<const RicMswBranch*>                 branch,
                                                      bool                                               exportSubGridIntersections,
                                                      const std::set<RigCompletionData::CompletionType>& exportCompletionTypes,
                                                      gsl::not_null<bool*>                               headerGenerated,
                                                      gsl::not_null<std::set<size_t>*>                   intersectedCells )
{
    for ( auto segment : branch->segments() )
    {
        auto completion = dynamic_cast<const RicMswCompletion*>( branch.get() );

        for ( auto intersection : segment->intersections() )
        {
            bool isSubGridIntersection = !intersection->gridName().isEmpty();
            if ( isSubGridIntersection != exportSubGridIntersections ) continue;

            double startLength = segment->startMD();
            double endLength   = segment->endMD();

            if ( completion )
            {
                bool isPerforationValve = completion->completionType() == RigCompletionData::CompletionType::PERFORATION_ICD ||
                                          completion->completionType() == RigCompletionData::CompletionType::PERFORATION_AICD ||
                                          completion->completionType() == RigCompletionData::CompletionType::PERFORATION_ICV;

                if ( isPerforationValve )
                {
                    startLength = segment->startMD();
                    endLength   = segment->endMD();
                }
            }

            size_t globalCellIndex = intersection->globalCellIndex();

            // Here we check if the cell is already reported. Make sure we report intersections before other completions
            // on the segment to be able to connect the branch with most flow
            if ( !intersectedCells->count( globalCellIndex ) )
            {
                if ( exportSubGridIntersections )
                {
                    formatter.add( intersection->gridName() );
                }

                cvf::Vec3st ijk = intersection->gridLocalCellIJK();
                formatter.addOneBasedCellIndex( ijk.x() ).addOneBasedCellIndex( ijk.y() ).addOneBasedCellIndex( ijk.z() );

                int branchNumber = -1;
                if ( completion ) branchNumber = completion->branchNumber();
                formatter.add( branchNumber );

                formatter.add( startLength );
                formatter.add( endLength );

                formatter.rowCompleted();
                intersectedCells->insert( globalCellIndex );
            }
        }

        // Report connected completions after the intersection on current segment has been reported
        for ( auto completion : segment->completions() )
        {
            if ( completion->segments().empty() || !exportCompletionTypes.count( completion->completionType() ) ) continue;

            if ( !*headerGenerated )
            {
                generateCompsegHeader( formatter, exportInfo, completion->completionType(), exportSubGridIntersections );
                *headerGenerated = true;
            }

            generateCompsegTable( formatter, exportInfo, completion, exportSubGridIntersections, exportCompletionTypes, headerGenerated, intersectedCells );
        }
    }

    for ( auto childBranch : branch->branches() )
    {
        generateCompsegTable( formatter, exportInfo, childBranch, exportSubGridIntersections, exportCompletionTypes, headerGenerated, intersectedCells );
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicMswTableFormatterTools::generateCompsegHeader( RifTextDataTableFormatter&        formatter,
                                                       RicMswExportInfo&                 exportInfo,
                                                       RigCompletionData::CompletionType completionType,
                                                       bool                              exportSubGridIntersections )
{
    if ( exportSubGridIntersections )
    {
        formatter.keyword( "COMPSEGL" );
    }
    else
    {
        formatter.keyword( "COMPSEGS" );
    }

    if ( completionType == RigCompletionData::CompletionType::FISHBONES_ICD )
    {
        formatter.comment( "Fishbones" );
    }
    else if ( completionType == RigCompletionData::CompletionType::FRACTURE )
    {
        formatter.comment( "Fractures" );
    }

    {
        std::vector<RifTextDataTableColumn> header = { RifTextDataTableColumn( "Name" ) };
        formatter.header( header );
        formatter.add( exportInfo.mainBoreBranch()->wellPath()->completionSettings()->wellNameForExport() );
        formatter.rowCompleted();
    }

    {
        std::vector<RifTextDataTableColumn> allHeaders;
        if ( exportSubGridIntersections )
        {
            allHeaders.push_back( RifTextDataTableColumn( "Grid" ) );
        }

        std::vector<RifTextDataTableColumn> commonHeaders = { RifTextDataTableColumn( "I" ),
                                                              RifTextDataTableColumn( "J" ),
                                                              RifTextDataTableColumn( "K" ),
                                                              RifTextDataTableColumn( "Branch no" ),
                                                              RifTextDataTableColumn( "Start Length" ),
                                                              RifTextDataTableColumn( "End Length" ),
                                                              RifTextDataTableColumn( "Dir Pen" ),
                                                              RifTextDataTableColumn( "End Range" ),
                                                              RifTextDataTableColumn( "Connection Depth" ) };
        allHeaders.insert( allHeaders.end(), commonHeaders.begin(), commonHeaders.end() );
        formatter.header( allHeaders );
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicMswTableFormatterTools::generateWsegvalvTable( RifTextDataTableFormatter& formatter, RicMswExportInfo& exportInfo )
{
    std::map<size_t, std::vector<WsegvalveData>> wsegvalveData;

    generateWsegvalvTableRecursively( exportInfo.mainBoreBranch(),
                                      exportInfo.mainBoreBranch()->wellPath()->completionSettings()->wellNameForExport(),
                                      wsegvalveData );

    if ( !wsegvalveData.empty() )
    {
        writeWsegvalHeader( formatter );

        for ( auto [globalCellIndex, dataForSameGridCell] : wsegvalveData )
        {
            if ( dataForSameGridCell.empty() ) continue;

            double combinedFlowArea = 0.0;
            for ( const auto& cellData : dataForSameGridCell )
            {
                combinedFlowArea += cellData.m_ac;
            }

            auto firstDataObject = dataForSameGridCell.front();

            formatter.add( firstDataObject.m_wellName );
            formatter.add( firstDataObject.m_segmentNumber );
            formatter.add( firstDataObject.m_cv );
            formatter.add( QString( "%1" ).arg( combinedFlowArea, 8, 'g', 4 ) );
            formatter.rowCompleted();
        }

        formatter.tableCompleted();
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicMswTableFormatterTools::generateWsegvalvTableRecursively( gsl::not_null<RicMswBranch*>                  branch,
                                                                  const QString&                                wellNameForExport,
                                                                  std::map<size_t, std::vector<WsegvalveData>>& wsegvalveData )
{
    {
        auto tieInValve = dynamic_cast<RicMswTieInICV*>( branch.get() );
        if ( tieInValve && !tieInValve->segments().empty() )
        {
            auto firstSubSegment = tieInValve->segments().front();
            CAF_ASSERT( tieInValve->completionType() == RigCompletionData::CompletionType::PERFORATION_ICV );

            size_t cellIndex = std::numeric_limits<size_t>::max();
            if ( !firstSubSegment->intersections().empty() )
            {
                cellIndex = firstSubSegment->intersections().front()->globalCellIndex();
            }

            auto flowCoefficient = tieInValve->flowCoefficient();

            wsegvalveData[cellIndex].push_back(
                WsegvalveData( wellNameForExport, tieInValve->label(), firstSubSegment->segmentNumber(), flowCoefficient, tieInValve->area() ) );
        }
    }

    for ( auto segment : branch->segments() )
    {
        for ( auto completion : segment->completions() )
        {
            if ( RigCompletionData::isWsegValveTypes( completion->completionType() ) )
            {
                // Related function RicWellPathExportMswCompletionsImpl::moveIntersectionsToSuperICDsOrAICDs

                auto wsegValve     = static_cast<RicMswWsegValve*>( completion );
                int  segmentNumber = -1;
                for ( auto seg : wsegValve->segments() )
                {
                    if ( seg->segmentNumber() > -1 ) segmentNumber = seg->segmentNumber();
                    if ( seg->intersections().empty() ) continue;

                    size_t cellIndex = seg->intersections().front()->globalCellIndex();

                    QString comment;
                    if ( wsegValve->completionType() == RigCompletionData::CompletionType::PERFORATION_ICD ||
                         wsegValve->completionType() == RigCompletionData::CompletionType::PERFORATION_ICV )
                    {
                        comment = wsegValve->label();
                    }

                    wsegvalveData[cellIndex].push_back(
                        WsegvalveData( wellNameForExport, comment, segmentNumber, wsegValve->flowCoefficient(), wsegValve->area() ) );
                }
            }
        }
    }

    for ( auto childBranch : branch->branches() )
    {
        generateWsegvalvTableRecursively( childBranch, wellNameForExport, wsegvalveData );
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicMswTableFormatterTools::generateWsegAicdTable( RifTextDataTableFormatter& formatter, RicMswExportInfo& exportInfo )
{
    std::map<size_t, std::vector<AicdWsegvalveData>> aicdValveData;

    generateWsegAicdTableRecursively( exportInfo, exportInfo.mainBoreBranch(), aicdValveData );

    if ( !aicdValveData.empty() )
    {
        RifTextDataTableFormatter tighterFormatter( formatter );
        tighterFormatter.setColumnSpacing( 1 );
        tighterFormatter.setTableRowPrependText( "   " );

        {
            // Write out header for AICD table

            std::vector<QString> columnDescriptions = { "Well Name",
                                                        "Segment Number",
                                                        "Segment Number",
                                                        "Strength of AICD",
                                                        "Flow Scaling Factor for AICD",
                                                        "Density of Calibration Fluid",
                                                        "Viscosity of Calibration Fluid",
                                                        "Critical water in liquid fraction for emulsions viscosity model",
                                                        "Emulsion viscosity transition region",
                                                        "Max ratio of emulsion viscosity to continuous phase viscosity",
                                                        "Flow scaling factor method",
                                                        "Maximum flow rate for AICD device",
                                                        "Volume flow rate exponent, x",
                                                        "Viscosity function exponent, y",
                                                        "Device OPEN/SHUT",
                                                        "Exponent of the oil flowing fraction in the density mixture calculation",
                                                        "Exponent of the water flowing fraction in the density mixture calculation",
                                                        "Exponent of the gas flowing fraction in the density mixture calculation",
                                                        "Exponent of the oil flowing fraction in the density viscosity calculation",
                                                        "Exponent of the water flowing fraction in the density viscosity calculation",
                                                        "Exponent of the gas flowing fraction in the density viscosity calculation" };

            tighterFormatter.keyword( "WSEGAICD" );
            tighterFormatter.comment( "Column Overview:" );
            for ( size_t i = 0; i < columnDescriptions.size(); ++i )
            {
                tighterFormatter.comment( QString( "%1: %2" ).arg( i + 1, 2, 10, QChar( '0' ) ).arg( columnDescriptions[i] ) );
            }

            std::vector<RifTextDataTableColumn> header;
            for ( size_t i = 1; i <= 21; ++i )
            {
                QString                cName = QString( "%1" ).arg( i, 2, 10, QChar( '0' ) );
                RifTextDataTableColumn col( cName, RifTextDataTableDoubleFormatting( RifTextDataTableDoubleFormat::RIF_CONSISE ), RIGHT );
                header.push_back( col );
            }
            tighterFormatter.header( header );
        }

        // Export data for each cell with AICD valves

        for ( auto [globalCellIndex, aicdDataForSameCell] : aicdValveData )
        {
            if ( aicdDataForSameCell.empty() ) continue;

            double      accumulatedFlowScalingFactorDivisor = 0.0;
            QStringList comments;

            // See RicMswAICDAccumulator::accumulateValveParameters for similar accumulation for multiple valves in same
            // segment

            for ( const auto& aicdData : aicdDataForSameCell )
            {
                accumulatedFlowScalingFactorDivisor += 1.0 / aicdData.m_flowScalingFactor;
                comments.push_back( aicdData.m_comment );
            }

            for ( auto comment : comments )
            {
                tighterFormatter.comment( comment );
            }

            auto firstDataObject = aicdDataForSameCell.front();

            tighterFormatter.add( firstDataObject.m_wellName ); // #1
            tighterFormatter.add( firstDataObject.m_segmentNumber );
            tighterFormatter.add( firstDataObject.m_segmentNumber );

            std::array<double, AICD_NUM_PARAMS> values = firstDataObject.m_values;
            tighterFormatter.add( values[AICD_STRENGTH] );

            double flowScalingFactor = 1.0 / accumulatedFlowScalingFactorDivisor;
            tighterFormatter.add( flowScalingFactor ); // #5 Eclipse Flow scaling factor used when
                                                       // item #11 is set to '1'

            tighterFormatter.add( values[AICD_DENSITY_CALIB_FLUID] );
            tighterFormatter.add( values[AICD_VISCOSITY_CALIB_FLUID] );
            tighterFormatter.addValueOrDefaultMarker( values[AICD_CRITICAL_WATER_IN_LIQUID_FRAC], RicMswExportInfo::defaultDoubleValue() );
            tighterFormatter.addValueOrDefaultMarker( values[AICD_EMULSION_VISC_TRANS_REGION], RicMswExportInfo::defaultDoubleValue() );
            tighterFormatter.addValueOrDefaultMarker( values[AICD_MAX_RATIO_EMULSION_VISC],
                                                      RicMswExportInfo::defaultDoubleValue() ); // #10

            tighterFormatter.add( 1 ); // #11 : Always use method "b. Scale factor". The value of the
                                       // scale factor is given in item #5

            tighterFormatter.addValueOrDefaultMarker( values[AICD_MAX_FLOW_RATE], RicMswExportInfo::defaultDoubleValue() );
            tighterFormatter.add( values[AICD_VOL_FLOW_EXP] );
            tighterFormatter.add( values[AICD_VISOSITY_FUNC_EXP] );
            tighterFormatter.add( firstDataObject.m_isOpen ? "OPEN" : "SHUT" ); // #15
            tighterFormatter.addValueOrDefaultMarker( values[AICD_EXP_OIL_FRAC_DENSITY], RicMswExportInfo::defaultDoubleValue() );
            tighterFormatter.addValueOrDefaultMarker( values[AICD_EXP_WATER_FRAC_DENSITY], RicMswExportInfo::defaultDoubleValue() );
            tighterFormatter.addValueOrDefaultMarker( values[AICD_EXP_GAS_FRAC_DENSITY], RicMswExportInfo::defaultDoubleValue() );
            tighterFormatter.addValueOrDefaultMarker( values[AICD_EXP_OIL_FRAC_VISCOSITY], RicMswExportInfo::defaultDoubleValue() );
            tighterFormatter.addValueOrDefaultMarker( values[AICD_EXP_WATER_FRAC_VISCOSITY],
                                                      RicMswExportInfo::defaultDoubleValue() ); // #20
            tighterFormatter.addValueOrDefaultMarker( values[AICD_EXP_GAS_FRAC_VISCOSITY], RicMswExportInfo::defaultDoubleValue() );
            tighterFormatter.rowCompleted();
        }

        tighterFormatter.tableCompleted();
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicMswTableFormatterTools::generateWsegAicdTableRecursively( RicMswExportInfo&                                 exportInfo,
                                                                  gsl::not_null<const RicMswBranch*>                branch,
                                                                  std::map<size_t, std::vector<AicdWsegvalveData>>& aicdValveData )
{
    for ( auto segment : branch->segments() )
    {
        for ( auto completion : segment->completions() )
        {
            if ( completion->completionType() == RigCompletionData::CompletionType::PERFORATION_AICD )
            {
                auto aicd = static_cast<const RicMswPerforationAICD*>( completion );
                if ( aicd->isValid() )
                {
                    int segmentNumber = -1;
                    for ( auto seg : aicd->segments() )
                    {
                        if ( seg->segmentNumber() > -1 ) segmentNumber = seg->segmentNumber();
                        if ( seg->intersections().empty() ) continue;

                        size_t cellIndex = seg->intersections().front()->globalCellIndex();

                        auto wellName = exportInfo.mainBoreBranch()->wellPath()->completionSettings()->wellNameForExport();
                        auto comment  = aicd->label();
                        aicdValveData[cellIndex].push_back(
                            AicdWsegvalveData( wellName, comment, segmentNumber, aicd->flowScalingFactor(), aicd->isOpen(), aicd->values() ) );
                    }
                }
                else
                {
                    RiaLogging::error( QString( "Export AICD Valve (%1): Valve is invalid. At least one required "
                                                "template parameter is not set." )
                                           .arg( aicd->label() ) );
                }
            }
        }
    }

    for ( auto childBranch : branch->branches() )
    {
        generateWsegAicdTableRecursively( exportInfo, childBranch, aicdValveData );
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicMswTableFormatterTools::writeWelsegsSegment( RicMswSegment*               segment,
                                                     const RicMswSegment*         previousSegment,
                                                     RifTextDataTableFormatter&   formatter,
                                                     RicMswExportInfo&            exportInfo,
                                                     double                       maxSegmentLength,
                                                     gsl::not_null<RicMswBranch*> branch,
                                                     int*                         segmentNumber )
{
    CVF_ASSERT( segment && segmentNumber );

    double startMD = segment->startMD();
    double endMD   = segment->endMD();

    std::vector<std::pair<double, double>> segments = createSubSegmentMDPairs( startMD, endMD, maxSegmentLength );

    CVF_ASSERT( branch->wellPath() );

    double prevOutMD  = branch->startMD();
    double prevOutTVD = branch->startTVD();
    if ( previousSegment )
    {
        prevOutMD  = previousSegment->outputMD();
        prevOutTVD = previousSegment->outputTVD();
    }

    const auto linerDiameter   = branch->wellPath()->mswCompletionParameters()->linerDiameter( exportInfo.unitSystem() );
    const auto roughnessFactor = branch->wellPath()->mswCompletionParameters()->roughnessFactor( exportInfo.unitSystem() );

    auto outletSegment = previousSegment;
    for ( const auto& [subStartMD, subEndMD] : segments )
    {
        double depth  = 0;
        double length = 0;

        double midPointMD  = 0.5 * ( subStartMD + subEndMD );
        double midPointTVD = tvdFromMeasuredDepth( branch->wellPath(), midPointMD );

        if ( midPointMD < prevOutMD )
        {
            // The first segment of parent branch may sometimes have a MD that is larger than the first segment on the
            // lateral. If this is the case, use the startMD of the branch instead
            prevOutMD  = branch->startMD();
            prevOutTVD = branch->startTVD();
        }

        if ( exportInfo.lengthAndDepthText() == QString( "INC" ) )
        {
            depth  = midPointTVD - prevOutTVD;
            length = midPointMD - prevOutMD;
        }
        else
        {
            depth  = midPointTVD;
            length = midPointMD;
        }
        segment->setOutputMD( midPointMD );
        segment->setOutputTVD( midPointTVD );
        segment->setSegmentNumber( *segmentNumber );

        formatter.add( *segmentNumber ).add( *segmentNumber );
        formatter.add( branch->branchNumber() );
        if ( outletSegment )
            formatter.add( outletSegment->segmentNumber() );
        else
            formatter.add( 1 );
        formatter.add( length );
        formatter.add( depth );
        formatter.add( linerDiameter );
        formatter.add( roughnessFactor );
        formatter.rowCompleted();
        ( *segmentNumber )++;

        outletSegment = segment;
        prevOutMD     = outletSegment->outputMD();
        prevOutTVD    = outletSegment->outputTVD();
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicMswTableFormatterTools::writeValveWelsegsSegment( const RicMswSegment*       outletSegment,
                                                          RicMswValve*               valve,
                                                          RifTextDataTableFormatter& formatter,
                                                          RicMswExportInfo&          exportInfo,
                                                          double                     maxSegmentLength,
                                                          int*                       segmentNumber )
{
    CVF_ASSERT( valve );
    if ( !valve->isValid() ) return;

    CVF_ASSERT( !valve->label().isEmpty() );
    CVF_ASSERT( valve->wellPath() );

    formatter.addOptionalComment( valve->label() );

    auto segments = valve->segments();

    double startMD = 0.0;
    double endMD   = 0.0;

    if ( valve->completionType() == RigCompletionData::CompletionType::PERFORATION_ICD ||
         valve->completionType() == RigCompletionData::CompletionType::PERFORATION_AICD )
    {
        CVF_ASSERT( segments.size() > 1 );

        // The 0.1 valve segment is the first, the perforated segment is the second
        auto subSegment = segments[0];
        subSegment->setSegmentNumber( *segmentNumber );

        double midPointMD = subSegment->outputMD();
        startMD           = midPointMD;
        endMD             = startMD + 0.1;

        double midPointTVD = tvdFromMeasuredDepth( valve->wellPath(), midPointMD );

        subSegment->setOutputMD( midPointMD );
        subSegment->setOutputTVD( midPointTVD );
    }
    else
    {
        auto subSegment = segments.front();
        subSegment->setSegmentNumber( *segmentNumber );

        startMD = subSegment->startMD();
        endMD   = subSegment->endMD();

        double midPointMD  = 0.5 * ( startMD + endMD );
        double midPointTVD = tvdFromMeasuredDepth( valve->wellPath(), midPointMD );

        subSegment->setOutputMD( midPointMD );
        subSegment->setOutputTVD( midPointTVD );
    }

    std::vector<std::pair<double, double>> splitSegments = createSubSegmentMDPairs( startMD, endMD, maxSegmentLength );

    const auto linerDiameter   = valve->wellPath()->mswCompletionParameters()->linerDiameter( exportInfo.unitSystem() );
    const auto roughnessFactor = valve->wellPath()->mswCompletionParameters()->roughnessFactor( exportInfo.unitSystem() );

    for ( const auto& [subStartMD, subEndMD] : splitSegments )
    {
        int subSegmentNumber = ( *segmentNumber )++;

        double subStartTVD = tvdFromMeasuredDepth( valve->wellPath(), subStartMD );
        double subEndTVD   = tvdFromMeasuredDepth( valve->wellPath(), subEndMD );

        double depth  = 0;
        double length = 0;

        if ( exportInfo.lengthAndDepthText() == QString( "INC" ) )
        {
            depth  = subEndTVD - subStartTVD;
            length = subEndMD - subStartMD;
        }
        else
        {
            depth  = subEndTVD;
            length = subEndMD;
        }

        formatter.add( subSegmentNumber );
        formatter.add( subSegmentNumber );
        formatter.add( valve->branchNumber() );
        formatter.add( outletSegment->segmentNumber() );

        formatter.add( length );
        formatter.add( depth );

        formatter.add( linerDiameter );
        formatter.add( roughnessFactor );
        formatter.rowCompleted();
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicMswTableFormatterTools::writeCompletionWelsegsSegments( gsl::not_null<const RicMswSegment*>    outletSegment,
                                                                gsl::not_null<const RicMswCompletion*> completion,
                                                                RifTextDataTableFormatter&             formatter,
                                                                RicMswExportInfo&                      exportInfo,
                                                                double                                 maxSegmentLength,
                                                                int*                                   segmentNumber )
{
    writeWelsegsCompletionCommentHeader( formatter, completion->completionType() );

    if ( completion->completionType() == RigCompletionData::CompletionType::FISHBONES )
    {
        formatter.addOptionalComment( QString( "Sub index %1 - %2" ).arg( outletSegment->subIndex() + 1 ).arg( completion->label() ) );
    }
    else if ( completion->completionType() == RigCompletionData::CompletionType::FRACTURE )
    {
        formatter.addOptionalComment( QString( "%1 connected to segment %2" ).arg( completion->label() ).arg( outletSegment->segmentNumber() ) );
    }

    CVF_ASSERT( completion->wellPath() );

    int outletSegmentNumber = outletSegment->segmentNumber();

    for ( auto segment : completion->segments() )
    {
        double startMD  = segment->startMD();
        double endMD    = segment->endMD();
        double startTVD = segment->startTVD();
        double endTVD   = segment->endTVD();

        std::vector<std::pair<double, double>> splitSegments = createSubSegmentMDPairs( startMD, endMD, maxSegmentLength );

        for ( const auto& [subStartMD, subEndMD] : splitSegments )
        {
            int subSegmentNumber = ( *segmentNumber )++;

            // TODO: Verify this calculation for fractures
            double subStartTVD = tvdFromMeasuredDepth( completion->wellPath(), subStartMD );
            double subEndTVD   = tvdFromMeasuredDepth( completion->wellPath(), subEndMD );

            if ( completion->completionType() == RigCompletionData::CompletionType::FISHBONES )
            {
                // Not possible to do interpolation based on well path geometry here
                // Use linear interpolation based on start/end TVD for segment
                {
                    auto normalizedWeight = ( subStartMD - startMD ) / ( endMD - startMD );
                    subStartTVD           = startTVD * ( 1.0 - normalizedWeight ) + endTVD * normalizedWeight;
                }
                {
                    auto normalizedWeight = ( subEndMD - startMD ) / ( endMD - startMD );

                    subEndTVD = startTVD * ( 1.0 - normalizedWeight ) + endTVD * normalizedWeight;
                }
            }

            double depth  = 0;
            double length = 0;

            if ( exportInfo.lengthAndDepthText() == QString( "INC" ) )
            {
                depth  = subEndTVD - subStartTVD;
                length = subEndMD - subStartMD;
            }
            else
            {
                depth  = subEndTVD;
                length = subEndMD;
            }

            double diameter = segment->equivalentDiameter();
            if ( segment->effectiveDiameter() > 0.0 ) diameter = segment->effectiveDiameter();

            formatter.add( subSegmentNumber );
            formatter.add( subSegmentNumber );
            formatter.add( completion->branchNumber() );
            formatter.add( outletSegmentNumber );
            formatter.add( length );
            formatter.add( depth );
            formatter.add( diameter );
            formatter.add( segment->openHoleRoughnessFactor() );
            formatter.rowCompleted();
            outletSegmentNumber = subSegmentNumber;
        }

        for ( auto completionSegment : completion->segments() )
        {
            auto noConst = const_cast<RicMswSegment*>( completionSegment );
            noConst->setSegmentNumber( outletSegmentNumber );
            for ( auto comp : completionSegment->completions() )
            {
                writeCompletionWelsegsSegments( completionSegment, comp, formatter, exportInfo, maxSegmentLength, segmentNumber );
            }
        }
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicMswTableFormatterTools::writeCompletionsForSegment( gsl::not_null<const RicMswSegment*> outletSegment,
                                                            gsl::not_null<RicMswSegment*>       segment,
                                                            RicMswValve**                       outletValve,
                                                            RifTextDataTableFormatter&          formatter,
                                                            RicMswExportInfo&                   exportInfo,
                                                            double                              maxSegmentLength,
                                                            int*                                segmentNumber )
{
    for ( auto& completion : segment->completions() )
    {
        // For a well with perforation intervals, the WELSEGS segments are reported twice if if we include the
        // RicMswPerforation completions. Investigate when this class is intended to be exported to file
        auto performationMsw = dynamic_cast<RicMswPerforation*>( completion );
        if ( performationMsw ) continue;

        auto segmentValve = dynamic_cast<RicMswValve*>( completion );
        auto fishboneIcd  = dynamic_cast<RicMswFishbonesICD*>( completion );
        if ( !fishboneIcd && segmentValve != nullptr )
        {
            writeValveWelsegsSegment( segment, segmentValve, formatter, exportInfo, maxSegmentLength, segmentNumber );
            *outletValve = segmentValve;
        }
        else
        {
            // If we have a valve, the outlet segment is the valve's segment
            RicMswSegment* outletSegment = *outletValve && ( *outletValve )->segmentCount() > 0 ? ( *outletValve )->segments().front()
                                                                                                : segment.get();
            writeCompletionWelsegsSegments( outletSegment, completion, formatter, exportInfo, maxSegmentLength, segmentNumber );
        }
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<std::pair<double, double>> RicMswTableFormatterTools::createSubSegmentMDPairs( double startMD, double endMD, double maxSegmentLength )
{
    int subSegmentCount = (int)( std::trunc( ( endMD - startMD ) / maxSegmentLength ) + 1 );

    double subSegmentLength = ( endMD - startMD ) / subSegmentCount;

    std::vector<std::pair<double, double>> subSegmentMDPairs;

    double subStartMD = startMD;
    double subEndMD   = startMD + subSegmentLength;
    for ( int i = 0; i < subSegmentCount; ++i )
    {
        subSegmentMDPairs.push_back( std::make_pair( subStartMD, subEndMD ) );
        subStartMD += subSegmentLength;
        subEndMD += std::min( subSegmentLength, endMD );
    }
    return subSegmentMDPairs;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RicMswTableFormatterTools::tvdFromMeasuredDepth( gsl::not_null<const RimWellPath*> wellPath, double measuredDepth )
{
    auto wellPathGeometry = wellPath->wellPathGeometry();
    CVF_ASSERT( wellPathGeometry );

    double tvdValue = -wellPathGeometry->interpolatedPointAlongWellPath( measuredDepth ).z();

    return tvdValue;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicMswTableFormatterTools::writeWsegvalHeader( RifTextDataTableFormatter& formatter )
{
    formatter.keyword( "WSEGVALV" );
    std::vector<RifTextDataTableColumn> header = {
        RifTextDataTableColumn( "Well Name" ),
        RifTextDataTableColumn( "Seg No" ),
        RifTextDataTableColumn( "Cv" ),
        RifTextDataTableColumn( "Ac" ),
    };
    formatter.header( header );
}
