/////////////////////////////////////////////////////////////////////////////////
//
//  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 "RimMultipleValveLocations.h"

#include "RigWellPath.h"

#include "RiaEclipseUnitTools.h"

#include "RimFishbones.h"
#include "RimPerforationInterval.h"
#include "RimWellPath.h"
#include "RimWellPathValve.h"

#include "cafPdmUiDoubleValueEditor.h"
#include "cafPdmUiListEditor.h"

#include <cmath>

CAF_PDM_SOURCE_INIT( RimMultipleValveLocations, "RimMultipleValveLocations" );

namespace caf
{
template <>
void AppEnum<RimMultipleValveLocations::LocationType>::setUp()
{
    addItem( RimMultipleValveLocations::VALVE_COUNT, "VALVE_COUNT", "Start/End/Number" );
    addItem( RimMultipleValveLocations::VALVE_SPACING, "VALVE_SPACING", "Start/End/Spacing" );
    addItem( RimMultipleValveLocations::VALVE_CUSTOM, "VALVE_CUSTOM", "User Specification" );
    setDefault( RimMultipleValveLocations::VALVE_COUNT );
}
} // namespace caf

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimMultipleValveLocations::RimMultipleValveLocations()
{
    CAF_PDM_InitObject( "RimMultipleValveLocations", ":/FishBoneGroup16x16.png" );

    CAF_PDM_InitField( &m_locationType, "LocationMode", caf::AppEnum<LocationType>( VALVE_COUNT ), "Location Defined By" );
    CAF_PDM_InitField( &m_rangeStart, "RangeStart", 100.0, "Start MD [m]" );
    m_rangeStart.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleValueEditor::uiEditorTypeName() );

    CAF_PDM_InitField( &m_rangeEnd, "RangeEnd", 250.0, "End MD [m]" );
    m_rangeEnd.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleValueEditor::uiEditorTypeName() );

    CAF_PDM_InitFieldNoDefault( &m_rangeValveSpacing, "ValveSpacing", "Spacing [m]" );
    m_rangeValveSpacing.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleValueEditor::uiEditorTypeName() );

    CAF_PDM_InitField( &m_rangeValveCount, "RangeValveCount", 13, "Number of Valves" );

    CAF_PDM_InitFieldNoDefault( &m_locationOfValves, "LocationOfValves", "Measured Depths [m]" );
    m_locationOfValves.uiCapability()->setUiEditorTypeName( caf::PdmUiListEditor::uiEditorTypeName() );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimMultipleValveLocations::perforationIntervalUpdated()
{
    double existingRangeStart = m_rangeStart();
    double existingRangeEnd   = m_rangeEnd();
    m_rangeStart              = std::clamp( m_rangeStart(), perforationStartMD(), perforationEndMD() );
    m_rangeEnd                = std::clamp( m_rangeEnd(), perforationStartMD(), perforationEndMD() );
    if ( existingRangeStart != m_rangeStart() || existingRangeEnd != m_rangeEnd() )
    {
        computeRangesAndLocations();
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RimMultipleValveLocations::measuredDepth( size_t valveIndex ) const
{
    CVF_ASSERT( valveIndex < m_locationOfValves().size() );

    return m_locationOfValves()[valveIndex];
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RimMultipleValveLocations::rangeStart() const
{
    return m_rangeStart;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RimMultipleValveLocations::rangeEnd() const
{
    return m_rangeEnd;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const std::vector<double>& RimMultipleValveLocations::valveLocations() const
{
    return m_locationOfValves();
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimMultipleValveLocations::setLocationType( LocationType locationType )
{
    m_locationType = locationType;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimMultipleValveLocations::computeRangesAndLocations()
{
    if ( m_locationType == VALVE_COUNT )
    {
        int divisor = 1;
        if ( m_rangeValveCount > 2 ) divisor = m_rangeValveCount - 1;

        m_rangeValveSpacing = std::abs( m_rangeStart - m_rangeEnd ) / divisor;
        if ( m_rangeValveSpacing < minimumSpacingMeters() )
        {
            m_rangeValveSpacing = minimumSpacingMeters();
            m_rangeValveCount   = rangeCountFromSpacing();
        }
    }
    else if ( m_locationType == VALVE_SPACING )
    {
        m_rangeValveCount = rangeCountFromSpacing();
    }

    if ( m_locationType == VALVE_COUNT || m_locationType == VALVE_SPACING )
    {
        std::vector<double> validMeasuredDepths;
        {
            auto wellPath = firstAncestorOrThisOfTypeAsserted<RimWellPath>();

            double firstWellPathMD = wellPath->startMD();
            double lastWellPathMD  = wellPath->endMD();

            double overlapStart = std::max( firstWellPathMD, m_rangeStart() );
            double overlapEnd   = std::min( lastWellPathMD, m_rangeEnd() );
            double overlap      = std::max( 0.0, overlapEnd - overlapStart );

            if ( overlap )
            {
                for ( auto md : locationsFromStartSpacingAndCount( overlapStart, m_rangeValveSpacing, m_rangeValveCount ) )
                {
                    validMeasuredDepths.push_back( md );
                }
            }
        }

        m_locationOfValves = validMeasuredDepths;
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimMultipleValveLocations::initFields( LocationType               locationType,
                                            double                     rangeStart,
                                            double                     rangeEnd,
                                            double                     valveSpacing,
                                            int                        valveCount,
                                            const std::vector<double>& locationOfValves )
{
    if ( locationType != VALVE_UNDEFINED )
    {
        m_locationType = locationType;
    }
    if ( rangeStart != std::numeric_limits<double>::infinity() )
    {
        m_rangeStart = rangeStart;
    }
    if ( rangeEnd != std::numeric_limits<double>::infinity() )
    {
        m_rangeEnd = rangeEnd;
    }
    if ( valveSpacing != std::numeric_limits<double>::infinity() )
    {
        m_rangeValveSpacing = valveSpacing;
    }
    if ( valveCount != -1 )
    {
        m_rangeValveCount = valveCount;
    }
    if ( !locationOfValves.empty() )
    {
        m_locationOfValves = locationOfValves;
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimMultipleValveLocations::applyOffset( double offset )
{
    m_rangeStart = m_rangeStart + offset;
    m_rangeEnd   = m_rangeEnd + offset;

    computeRangesAndLocations();
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimMultipleValveLocations::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
{
    {
        auto wellPath = firstAncestorOrThisOfType<RimWellPath>();
        if ( wellPath )
        {
            if ( wellPath->unitSystem() == RiaDefines::EclipseUnitSystem::UNITS_METRIC )
            {
                m_locationOfValves.uiCapability()->setUiName( "Measured Depths [m]" );
                m_rangeStart.uiCapability()->setUiName( "Start MD [m]" );
                m_rangeEnd.uiCapability()->setUiName( "End MD [m]" );
                m_rangeValveSpacing.uiCapability()->setUiName( "Spacing [m]" );
            }
            else if ( wellPath->unitSystem() == RiaDefines::EclipseUnitSystem::UNITS_FIELD )
            {
                m_locationOfValves.uiCapability()->setUiName( "Measured Depths [ft]" );
                m_rangeStart.uiCapability()->setUiName( "Start MD [ft]" );
                m_rangeEnd.uiCapability()->setUiName( "End MD [ft]" );
                m_rangeValveSpacing.uiCapability()->setUiName( "Spacing [ft]" );
            }
        }
    }

    {
        uiOrdering.add( &m_locationType );
        if ( m_locationType() != VALVE_CUSTOM )
        {
            uiOrdering.add( &m_rangeStart );
            uiOrdering.add( &m_rangeEnd );

            if ( m_locationType() == VALVE_COUNT )
            {
                uiOrdering.add( &m_rangeValveCount );
                uiOrdering.add( &m_rangeValveSpacing );
            }
            else if ( m_locationType() == VALVE_SPACING )
            {
                uiOrdering.add( &m_rangeValveSpacing );
                uiOrdering.add( &m_rangeValveCount );
            }
        }

        uiOrdering.add( &m_locationOfValves );
    }

    if ( m_locationType() == VALVE_CUSTOM )
    {
        m_locationOfValves.uiCapability()->setUiReadOnly( false );

        m_rangeValveSpacing.uiCapability()->setUiReadOnly( true );
        m_rangeValveCount.uiCapability()->setUiReadOnly( true );
        m_rangeStart.uiCapability()->setUiReadOnly( true );
        m_rangeEnd.uiCapability()->setUiReadOnly( true );
    }
    else
    {
        m_locationOfValves.uiCapability()->setUiReadOnly( true );

        m_rangeValveSpacing.uiCapability()->setUiReadOnly( false );
        m_rangeValveCount.uiCapability()->setUiReadOnly( false );
        m_rangeStart.uiCapability()->setUiReadOnly( false );
        m_rangeEnd.uiCapability()->setUiReadOnly( false );

        if ( m_locationType() == VALVE_COUNT )
        {
            m_rangeValveSpacing.uiCapability()->setUiReadOnly( true );
            m_rangeValveCount.uiCapability()->setUiReadOnly( false );
        }
        else
        {
            m_rangeValveSpacing.uiCapability()->setUiReadOnly( false );
            m_rangeValveCount.uiCapability()->setUiReadOnly( true );
        }
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimMultipleValveLocations::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue )
{
    bool recomputeLocations = false;

    if ( changedField == &m_locationType )
    {
        if ( m_locationType == VALVE_COUNT || m_locationType == VALVE_SPACING )
        {
            recomputeLocations = true;
        }
    }

    if ( changedField == &m_rangeStart || changedField == &m_rangeEnd || changedField == &m_rangeValveCount ||
         changedField == &m_rangeValveSpacing )
    {
        recomputeLocations = true;
        m_rangeStart       = std::clamp( m_rangeStart(), perforationStartMD(), perforationEndMD() );
        m_rangeEnd         = std::clamp( m_rangeEnd(), perforationStartMD(), perforationEndMD() );
    }

    if ( changedField == &m_rangeValveSpacing )
    {
        double minimumDistanceMeter = minimumSpacingMeters();

        auto wellPath = firstAncestorOrThisOfTypeAsserted<RimWellPath>();
        if ( wellPath->unitSystem() == RiaDefines::EclipseUnitSystem::UNITS_FIELD )
        {
            double minimumDistanceFeet = RiaEclipseUnitTools::meterToFeet( minimumDistanceMeter );
            m_rangeValveSpacing =
                std::clamp( m_rangeValveSpacing(), minimumDistanceFeet, std::max( m_rangeValveSpacing(), minimumDistanceFeet ) );
        }
        else
        {
            m_rangeValveSpacing =
                std::clamp( m_rangeValveSpacing(), minimumDistanceMeter, std::max( m_rangeValveSpacing(), minimumDistanceMeter ) );
        }
    }

    if ( recomputeLocations )
    {
        computeRangesAndLocations();
    }

    auto            parentCompletion = firstAncestorOrThisOfType<RimWellPathComponentInterface>();
    caf::PdmObject* pdmParent        = dynamic_cast<caf::PdmObject*>( parentCompletion );

    if ( recomputeLocations || changedField == &m_locationOfValves )
    {
        if ( parentCompletion )
        {
            RimFishbones*     fishbones = dynamic_cast<RimFishbones*>( parentCompletion );
            RimWellPathValve* valve     = dynamic_cast<RimWellPathValve*>( parentCompletion );
            if ( fishbones )
            {
                fishbones->recomputeLateralLocations();
            }
            else if ( valve )
            {
                valve->multipleValveGeometryUpdated();
            }
        }
    }

    if ( pdmParent )
    {
        pdmParent->updateConnectedEditors();
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RimMultipleValveLocations::rangeCountFromSpacing() const
{
    int rangeCount = ( std::fabs( m_rangeStart - m_rangeEnd ) / m_rangeValveSpacing ) + 1;

    if ( rangeCount < 1 )
    {
        rangeCount = 1;
    }
    return rangeCount;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RimMultipleValveLocations::minimumSpacingMeters() const
{
    // Minimum distance between fishbones is 13.0m
    // Use 10.0m to allow for some flexibility
    return 10.0;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RimMultipleValveLocations::perforationStartMD() const
{
    const RimPerforationInterval* perfInterval = firstAncestorOrThisOfType<RimPerforationInterval>();
    if ( perfInterval )
    {
        return perfInterval->startMD();
    }
    return 0.0;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RimMultipleValveLocations::perforationEndMD() const
{
    const RimPerforationInterval* perfInterval = firstAncestorOrThisOfType<RimPerforationInterval>();
    if ( perfInterval )
    {
        return perfInterval->endMD();
    }

    auto wellPath = firstAncestorOrThisOfTypeAsserted<RimWellPath>();

    return wellPath->endMD();
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<double> RimMultipleValveLocations::locationsFromStartSpacingAndCount( double start, double spacing, size_t count )
{
    std::vector<double> measuredDepths;

    for ( size_t i = 0; i < count; i++ )
    {
        measuredDepths.push_back( start + spacing * i );
    }

    return measuredDepths;
}
