// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
// vi: set et ts=4 sw=4 sts=4:
/*
  This file is part of the Open Porous Media project (OPM).

  OPM 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 2 of the License, or
  (at your option) any later version.

  OPM 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 for more details.

  You should have received a copy of the GNU General Public License
  along with OPM.  If not, see <http://www.gnu.org/licenses/>.

  Consult the COPYING file in the top-level source directory of this
  module for the precise wording of the license and the list of
  copyright holders.
*/
/**
 * \file
 *
 * \copydoc Opm::TemperatureModel
 */
#ifndef OPM_GENERIC_TEMPERATURE_MODEL_IMPL_HPP
#define OPM_GENERIC_TEMPERATURE_MODEL_IMPL_HPP

// Improve IDE experience
#ifndef OPM_GENERIC_TEMPERATURE_MODEL_HPP
#include <config.h>
#include <opm/simulators/flow/GenericTemperatureModel.hpp>
#endif

#include <dune/istl/operators.hh>
#include <dune/istl/solvers.hh>
#include <dune/istl/schwarz.hh>
#include <dune/istl/preconditioners.hh>
#include <dune/istl/schwarz.hh>

#include <opm/common/OpmLog/OpmLog.hpp>

#include <opm/grid/CpGrid.hpp>

#include <opm/input/eclipse/EclipseState/EclipseState.hpp>
#include <opm/input/eclipse/Schedule/Well/Well.hpp>

#include <opm/models/discretization/ecfv/ecfvstencil.hh>

#include <opm/simulators/linalg/ilufirstelement.hh>
#include <opm/simulators/linalg/PropertyTree.hpp>
#include <opm/simulators/linalg/FlexibleSolver.hpp>

#include <memory>
#include <set>
#include <stdexcept>
#include <string>

#include <fmt/format.h>

namespace Opm {

#if HAVE_MPI
template<class M, class V>
struct EnergySolverSelector
{
    using Comm = Dune::OwnerOverlapCopyCommunication<int, int>;
    using EnergyOperator = Dune::OverlappingSchwarzOperator<M, V, V, Comm>;
    using type = Dune::FlexibleSolver<EnergyOperator>;
};

template<class Vector, class Grid, class Matrix>
std::tuple<std::unique_ptr<Dune::OverlappingSchwarzOperator<Matrix,Vector,Vector,
                                                            Dune::OwnerOverlapCopyCommunication<int,int>>>,
           std::unique_ptr<typename EnergySolverSelector<Matrix,Vector>::type>>
createParallelFlexibleSolver(const Grid&, const Matrix&, const PropertyTree&)
{
    OPM_THROW(std::logic_error, "Grid not supported for parallel Temperatures.");
    return {nullptr, nullptr};
}

template<class Vector, class Matrix>
std::tuple<std::unique_ptr<Dune::OverlappingSchwarzOperator<Matrix,Vector,Vector,
                                                            Dune::OwnerOverlapCopyCommunication<int,int>>>,
           std::unique_ptr<typename EnergySolverSelector<Matrix,Vector>::type>>
createParallelFlexibleSolver(const Dune::CpGrid& grid, const Matrix& M, const PropertyTree& prm)
{
        using EnergyOperator = Dune::OverlappingSchwarzOperator<Matrix,Vector,Vector,
                                                                Dune::OwnerOverlapCopyCommunication<int,int>>;
        using EnergySolver = Dune::FlexibleSolver<EnergyOperator>;
        const auto& cellComm = grid.cellCommunication();
        auto op = std::make_unique<EnergyOperator>(M, cellComm);
        auto dummyWeights = [](){ return Vector();};
        return {std::move(op), std::make_unique<EnergySolver>(*op, cellComm, prm, dummyWeights, 0)};
}
#endif

template<class Grid, class GridView, class DofMapper, class Stencil, class FluidSystem, class Scalar>
GenericTemperatureModel<Grid,GridView,DofMapper,Stencil,FluidSystem,Scalar>::
GenericTemperatureModel(const GridView& gridView,
                   const EclipseState& eclState,
                   const CartesianIndexMapper& cartMapper,
                   const DofMapper& dofMapper)
    : gridView_(gridView)
    , eclState_(eclState)
    , cartMapper_(cartMapper)
    , dofMapper_(dofMapper)
{
}

template<class Grid, class GridView, class DofMapper, class Stencil, class FluidSystem, class Scalar>
void GenericTemperatureModel<Grid,GridView,DofMapper,Stencil,FluidSystem,Scalar>::
doInit(std::size_t numGridDof)
{
    doTemp_ = eclState_.getSimulationConfig().isTemp();
    temperature_.resize(numGridDof);

    if (!doTemp_)
        return;

    energyVector_.resize(numGridDof); 
    maxTempChange_ = Parameters::Get<Parameters::MaxTemperatureChange<Scalar>>();
}

template<class Grid, class GridView, class DofMapper, class Stencil, class FluidSystem, class Scalar>
void GenericTemperatureModel<Grid,GridView,DofMapper,Stencil,FluidSystem,Scalar>::
setupLinearSolver(const EnergyMatrix& matrix)
{
    Scalar tolerance = 1e-2;
    int maxIter = 100;
    int verbosity = 0;
    PropertyTree prm;
    prm.put("maxiter", maxIter);
    prm.put("tol", tolerance);
    prm.put("verbosity", verbosity);
    prm.put("solver", std::string("bicgstab"));
    prm.put("preconditioner.type", std::string("ParOverILU0"));

#if HAVE_MPI
    if (gridView_.grid().comm().size() > 1)
    {
        auto [energyOperator, solver] =
            createParallelFlexibleSolver<EnergyVector>(gridView_.grid(), matrix, prm);
        op_ = std::move(energyOperator);
        pre_ = &solver->preconditioner();
        linear_solver_ = std::move(solver);
    }
    else
#endif
    {
        using SeqOperator = Dune::MatrixAdapter<EnergyMatrix, EnergyVector, EnergyVector>;
        using SeqSolver = Dune::FlexibleSolver<SeqOperator>;
        auto seqOp = std::make_unique<SeqOperator>(matrix);
        auto dummyWeights = [](){ return EnergyVector(); };
        auto seqSolver = std::make_unique<SeqSolver>(*seqOp, prm, dummyWeights, 0);
        op_ = std::move(seqOp);
        pre_ = &seqSolver->preconditioner();
        linear_solver_ = std::move(seqSolver);
    }
}

template<class Grid, class GridView, class DofMapper, class Stencil, class FluidSystem, class Scalar>
bool GenericTemperatureModel<Grid,GridView,DofMapper,Stencil,FluidSystem,Scalar>::
linearSolve_(const EnergyMatrix& M, EnergyVector& x, EnergyVector& b)
{
    if (!linear_solver_) {
        setupLinearSolver(M);
    } else {
        pre_->update();
    }

    Dune::InverseOperatorResult result;
    linear_solver_->apply(x, b, result);
    return result.converged;
}

} // namespace Opm

#endif // OPM_GENERIC_TEMPERATURE_MODEL_IMPL_HPP
