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

#include "Rim3dView.h"
#include "RimEclipseCase.h"
#include "RimEclipseView.h"
#include "RimRegularLegendConfig.h"
#include "RimSimWellInView.h"
#include "RimSimWellInViewCollection.h"
#include "RimStreamlineInViewCollection.h"

#include "RigActiveCellInfo.h"
#include "RigCaseCellResultsData.h"
#include "RigCell.h"
#include "RigEclipseCaseData.h"
#include "RigMainGrid.h"
#include "RigSimulationWellCenterLineCalculator.h"
#include "RigTracer.h"
#include "RigTracerPoint.h"
#include "RigWellResultPoint.h"

#include "RiuViewer.h"

#include "cafDisplayCoordTransform.h"
#include "cafEffectGenerator.h"

#include "cvfDrawableGeo.h"
#include "cvfModelBasicList.h"
#include "cvfPart.h"
#include "cvfPrimitiveSetIndexedUInt.h"
#include "cvfPrimitiveSetIndexedUIntScoped.h"
#include "cvfShaderProgram.h"

#include "cafPdmPointer.h"

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RivStreamlinesPartMgr::RivStreamlinesPartMgr( RimEclipseView* reservoirView )
{
    m_rimReservoirView = reservoirView;
    m_count            = 0;
    m_currentT         = 0;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RivStreamlinesPartMgr::~RivStreamlinesPartMgr()
{
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivStreamlinesPartMgr::appendDynamicGeometryPartsToModel( cvf::ModelBasicList* model, size_t timeStepIndex )
{
    m_streamlines.clear();

    RimStreamlineInViewCollection* streamlineCollection = m_rimReservoirView->streamlineCollection();

    if ( !streamlineCollection->isActive() ) return;

    CVF_ASSERT( model );
    if ( m_rimReservoirView.isNull() ) return;

    RimEclipseCase* eclipseCase = m_rimReservoirView->eclipseCase();
    if ( !eclipseCase ) return;

    RigEclipseCaseData* eclipseCaseData = eclipseCase->eclipseCaseData();
    if ( !eclipseCaseData ) return;

    cvf::ref<caf::DisplayCoordTransform> displayCordXf = m_rimReservoirView->displayCoordTransform();

    for ( const RigTracer& tracer : streamlineCollection->tracers() )
    {
        Streamline streamline;
        for ( size_t i = 0; i < tracer.tracerPoints().size() - 1; i++ )
        {
            streamline.appendTracerPoint( tracer.tracerPoints()[i].position() );
            streamline.appendAbsVelocity( tracer.tracerPoints()[i].absValue() );
            streamline.appendPhase( tracer.tracerPoints()[i].phaseType() );
        }
        m_streamlines.push_back( streamline );
    }
    for ( Streamline& streamline : m_streamlines )
    {
        if ( streamlineCollection->visualizationMode() == RimStreamlineInViewCollection::VisualizationMode::ANIMATION ||
             streamlineCollection->visualizationMode() == RimStreamlineInViewCollection::VisualizationMode::MANUAL )
        {
            if ( streamline.countTracerPoints() > 1 )
            {
                model->addPart( createPart( *streamlineCollection, streamline ).p() );
            }
        }
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivStreamlinesPartMgr::updateAnimation()
{
    RimStreamlineInViewCollection* streamlineCollection = m_rimReservoirView->streamlineCollection();
    if ( streamlineCollection && ( streamlineCollection->visualizationMode() == RimStreamlineInViewCollection::VisualizationMode::ANIMATION ||
                                   streamlineCollection->visualizationMode() == RimStreamlineInViewCollection::VisualizationMode::MANUAL ) )
    {
        for ( Streamline& streamline : m_streamlines )
        {
            if ( streamline.getPart().notNull() && dynamic_cast<cvf::DrawableGeo*>( streamline.getPart()->drawable() ) )
            {
                auto primitiveSet = dynamic_cast<cvf::PrimitiveSetIndexedUIntScoped*>(
                    static_cast<cvf::DrawableGeo*>( streamline.getPart()->drawable() )->primitiveSet( 0 ) );

                if ( primitiveSet )
                {
                    size_t startIndex = 0;
                    size_t endIndex   = primitiveSet->indices()->size();

                    if ( streamlineCollection->visualizationMode() == RimStreamlineInViewCollection::VisualizationMode::ANIMATION )
                    {
                        streamline.incrementAnimationIndex( streamlineCollection->animationSpeed() );

                        startIndex = streamline.getAnimationIndex();

                        // Make sure we have an even animation index, as this index is used to define start of a line
                        // segment
                        startIndex /= 2;
                        startIndex *= 2;

                        endIndex = std::min( endIndex, startIndex + streamlineCollection->tracerLength() );
                    }
                    else
                    {
                        endIndex = std::min( endIndex, streamlineCollection->animationIndex() * 2 );
                    }

                    primitiveSet->setScope( startIndex, endIndex - startIndex );
                }
            }
        }
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::ref<cvf::Part> RivStreamlinesPartMgr::createPart( const RimStreamlineInViewCollection& streamlineCollection, Streamline& streamline )
{
    cvf::ref<caf::DisplayCoordTransform> displayCordXf = m_rimReservoirView->displayCoordTransform();

    std::vector<uint> indices;
    // Each point is used twice except the first and the last one
    indices.reserve( streamline.countTracerPoints() * 2 - 2 );

    std::vector<cvf::Vec3f> vertices;
    vertices.reserve( streamline.countTracerPoints() * 2 - 2 );

    uint count = 0;
    for ( size_t i = 0; i < streamline.countTracerPoints(); i++ )
    {
        vertices.push_back( cvf::Vec3f( displayCordXf->transformToDisplayCoord( streamline.getTracerPoint( i ) ) ) );
        indices.push_back( count++ );
        if ( i > 0 && i < streamline.countTracerPoints() - 1 )
        {
            vertices.push_back( cvf::Vec3f( displayCordXf->transformToDisplayCoord( streamline.getTracerPoint( i ) ) ) );
            indices.push_back( count++ );
        }
    }

    cvf::ref<cvf::PrimitiveSetIndexedUIntScoped> indexedUIntLine  = new cvf::PrimitiveSetIndexedUIntScoped( cvf::PrimitiveType::PT_LINES );
    cvf::ref<cvf::UIntArray>                     indexedArrayLine = new cvf::UIntArray( indices );

    cvf::ref<cvf::DrawableGeo> drawable = new cvf::DrawableGeo();

    indexedUIntLine->setIndices( indexedArrayLine.p(), 0, 0 );
    drawable->addPrimitiveSet( indexedUIntLine.p() );

    cvf::ref<cvf::Vec3fArray> vertexArray = new cvf::Vec3fArray( vertices );
    drawable->setVertexArray( vertexArray.p() );

    cvf::ref<cvf::Vec2fArray> lineTexCoords = const_cast<cvf::Vec2fArray*>( drawable->textureCoordArray() );

    if ( lineTexCoords.isNull() )
    {
        lineTexCoords = new cvf::Vec2fArray;
    }

    cvf::ref<cvf::Effect> effect;

    const cvf::ScalarMapper* activeScalarMapper = streamlineCollection.legendConfig()->scalarMapper();
    createResultColorTextureCoords( lineTexCoords.p(), streamline, activeScalarMapper );

    caf::ScalarMapperMeshEffectGenerator meshEffGen( activeScalarMapper );
    effect = meshEffGen.generateCachedEffect();

    drawable->setTextureCoordArray( lineTexCoords.p() );

    cvf::ref<cvf::Part> part = new cvf::Part;
    part->setName( "RivStreamlinesPartMgr" );

    part->setDrawable( drawable.p() );
    part->setEffect( effect.p() );
    part->updateBoundingBox();
    streamline.setPart( part );

    return part;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivStreamlinesPartMgr::createResultColorTextureCoords( cvf::Vec2fArray*         textureCoords,
                                                            const Streamline&        streamline,
                                                            const cvf::ScalarMapper* mapper )
{
    CVF_ASSERT( textureCoords );
    CVF_ASSERT( mapper );

    RimStreamlineInViewCollection* streamlineCollection = m_rimReservoirView->streamlineCollection();
    CVF_ASSERT( streamlineCollection != nullptr );

    size_t vertexCount = streamline.countTracerPoints() * 2 - 2;
    if ( textureCoords->capacity() != vertexCount ) textureCoords->reserve( vertexCount );

    for ( size_t i = 0; i < streamline.countTracerPoints(); i++ )
    {
        cvf::Vec2f texCoord = mapper->mapToTextureCoord( streamline.getAbsVelocity( i ) );

        if ( streamlineCollection->colorMode() == RimStreamlineInViewCollection::ColorMode::PHASE_COLORS )
        {
            double phaseValue = 0.0;
            auto   phase      = streamline.getPhase( i );
            if ( phase == RiaDefines::PhaseType::GAS_PHASE )
            {
                phaseValue = 1.0;
            }
            else if ( phase == RiaDefines::PhaseType::OIL_PHASE )
            {
                phaseValue = 0.5;
            }
            else if ( phase == RiaDefines::PhaseType::WATER_PHASE )
            {
                phaseValue = 0.0;
            }

            texCoord.x() = phaseValue;
        }

        textureCoords->add( texCoord );
        if ( i > 0 && i < streamline.countTracerPoints() - 1 )
        {
            textureCoords->add( texCoord );
        }
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivStreamlinesPartMgr::setAlpha( cvf::ref<cvf::Part> part, float alpha )
{
    if ( part.notNull() )
    {
        caf::SurfaceEffectGenerator surfaceGen( cvf::Color4f( alpha, alpha, alpha, 1 ), caf::PO_1 );
        surfaceGen.enableLighting( false );
        cvf::ref<cvf::Effect> effect = surfaceGen.generateCachedEffect();
        part->setEffect( effect.p() );
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivStreamlinesPartMgr::Streamline::appendTracerPoint( cvf::Vec3d point )
{
    tracerPoints.push_back( point );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivStreamlinesPartMgr::Streamline::appendAbsVelocity( double velocity )
{
    absVelocities.push_back( velocity );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivStreamlinesPartMgr::Streamline::appendPhase( RiaDefines::PhaseType phase )
{
    dominantPhases.push_back( phase );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivStreamlinesPartMgr::Streamline::clear()
{
    tracerPoints.clear();
    absVelocities.clear();
    dominantPhases.clear();
    delete part.p();
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::ref<cvf::Part> RivStreamlinesPartMgr::Streamline::getPart()
{
    return part;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::Vec3d RivStreamlinesPartMgr::Streamline::getTracerPoint( size_t index ) const
{
    return tracerPoints[index];
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RivStreamlinesPartMgr::Streamline::getAbsVelocity( size_t index ) const
{
    return absVelocities[index];
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiaDefines::PhaseType RivStreamlinesPartMgr::Streamline::getPhase( size_t index ) const
{
    return dominantPhases[index];
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
size_t RivStreamlinesPartMgr::Streamline::countTracerPoints() const
{
    return tracerPoints.size();
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivStreamlinesPartMgr::Streamline::setPart( cvf::ref<cvf::Part> part )
{
    this->part = part;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
size_t RivStreamlinesPartMgr::Streamline::getAnimationIndex() const
{
    return animIndex;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivStreamlinesPartMgr::Streamline::incrementAnimationIndex( size_t increment )
{
    animIndex += increment;

    // Make sure we have an even animation index, as this index is used to define start of a line segment
    animIndex /= 2;
    animIndex *= 2;

    if ( animIndex >= tracerPoints.size() * 2 - 2 )
    {
        animIndex = 0.0;
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivStreamlinesPartMgr::Streamline::setAnimationIndex( size_t index )
{
    animIndex = index;
    if ( animIndex >= tracerPoints.size() * 2 - 2 )
    {
        animIndex = tracerPoints.size() * 2 - 2;
    }
}
