/*
  Copyright 2019 Equinor ASA.

  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 3 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/>.
*/

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

#include <opm/io/eclipse/rst/well.hpp>
#include <opm/output/eclipse/VectorItems/well.hpp>

#include <opm/input/eclipse/EclipseState/Grid/EclipseGrid.hpp>
#include <opm/input/eclipse/EclipseState/Phase.hpp>
#include <opm/input/eclipse/EclipseState/TracerConfig.hpp>

#include <opm/input/eclipse/Schedule/MSW/WellSegments.hpp>
#include <opm/input/eclipse/Schedule/ScheduleGrid.hpp>
#include <opm/input/eclipse/Schedule/UDQ/UDQActive.hpp>
#include <opm/input/eclipse/Schedule/Well/WDFAC.hpp>
#include <opm/input/eclipse/Schedule/Well/WellBrineProperties.hpp>
#include <opm/input/eclipse/Schedule/Well/WellConnections.hpp>
#include <opm/input/eclipse/Schedule/Well/WellEconProductionLimits.hpp>
#include <opm/input/eclipse/Schedule/Well/WellFoamProperties.hpp>
#include <opm/input/eclipse/Schedule/Well/WellMICPProperties.hpp>
#include <opm/input/eclipse/Schedule/Well/WellPolymerProperties.hpp>
#include <opm/input/eclipse/Schedule/Well/WellTracerProperties.hpp>
#include <opm/input/eclipse/Schedule/Well/WVFPDP.hpp>
#include <opm/input/eclipse/Schedule/Well/WVFPEXP.hpp>

#include <opm/input/eclipse/Units/Units.hpp>

#include <opm/common/utility/OpmInputError.hpp>
#include <opm/common/utility/shmatch.hpp>

#include <opm/input/eclipse/Parser/ParserKeywords/C.hpp>
#include <opm/input/eclipse/Parser/ParserKeywords/S.hpp>
#include <opm/input/eclipse/Parser/ParserKeywords/W.hpp>

#include <opm/input/eclipse/Deck/DeckRecord.hpp>
#include <opm/input/eclipse/Deck/DeckKeyword.hpp>

#include "../MSW/Compsegs.hpp"
#include "../eval_uda.hpp"

#include <cassert>
#include <cmath>
#include <cstddef>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>

#include <fmt/format.h>

namespace {

    bool defaulted(const Opm::DeckRecord& rec, const std::string& s)
    {
        const auto& item = rec.getItem(s);
        return item.defaultApplied(0) || (item.get<int>(0) == 0);
    }

    int limit(const Opm::DeckRecord& rec, const std::string& s, const int shift)
    {
        const auto& item = rec.getItem(s);
        return shift + item.get<int>(0);
    }

    bool match_le(const int              value,
                  const Opm::DeckRecord& rec,
                  const std::string&     s,
                  const int              shift = 0)
    {
        return defaulted(rec, s) || (value <= limit(rec, s, shift));
    }

    bool match_ge(const int              value,
                  const Opm::DeckRecord& rec,
                  const std::string&     s,
                  const int              shift = 0)
    {
        return defaulted(rec, s) || (value >= limit(rec, s, shift));
    }

    bool match_eq(const int              value,
                  const Opm::DeckRecord& rec,
                  const std::string&     s,
                  const int              shift = 0)
    {
        return defaulted(rec, s)|| (limit(rec, s, shift) == value);
    }

Opm::Connection::Order order_from_int(const int int_value)
{
    switch (int_value) {
    case 0: return Opm::Connection::Order::TRACK;
    case 1: return Opm::Connection::Order::DEPTH;
    case 2: return Opm::Connection::Order::INPUT;
    default:
        throw std::invalid_argument {
            fmt::format("Invalid integer value: {} encountered "
                        "when determining connection ordering", int_value)
        };
    }
}

Opm::Well::Status status_from_int(const int int_value)
{
    using Value = Opm::RestartIO::Helpers::VectorItems::IWell::Value::Status;

    switch (int_value) {
    case Value::Shut: return Opm::Well::Status::SHUT;
    case Value::Stop: return Opm::Well::Status::STOP;
    case Value::Open: return Opm::Well::Status::OPEN;
    case Value::Auto: return Opm::Well::Status::AUTO;
    default:
        throw std::logic_error {
            fmt::format("integer value: {} could not be "
                        "converted to a valid well status.", int_value)
        };
    }
}

Opm::Well::ProducerCMode producer_cmode_from_int(const int pmode)
{
    using CModeVal = ::Opm::RestartIO::Helpers::VectorItems::
        IWell::Value::WellCtrlMode;

    switch (pmode) {
    case CModeVal::Group:    return Opm::Well::ProducerCMode::GRUP;
    case CModeVal::OilRate:  return Opm::Well::ProducerCMode::ORAT;
    case CModeVal::WatRate:  return Opm::Well::ProducerCMode::WRAT;
    case CModeVal::GasRate:  return Opm::Well::ProducerCMode::GRAT;
    case CModeVal::LiqRate:  return Opm::Well::ProducerCMode::LRAT;
    case CModeVal::ResVRate: return Opm::Well::ProducerCMode::RESV;
    case CModeVal::THP:      return Opm::Well::ProducerCMode::THP;
    case CModeVal::BHP:      return Opm::Well::ProducerCMode::BHP;
    }

    throw std::invalid_argument {
        fmt::format("Cannot convert integer value {} to producer control mode", pmode)
    };
}

Opm::Well::InjectorCMode injector_cmode_from_int(const int imode)
{
    using CModeVal = ::Opm::RestartIO::Helpers::VectorItems::
        IWell::Value::WellCtrlMode;

    switch (imode) {
    case CModeVal::Group:    return Opm::Well::InjectorCMode::GRUP;
    case CModeVal::OilRate:  [[fallthrough]];
    case CModeVal::WatRate:  [[fallthrough]];
    case CModeVal::GasRate:  [[fallthrough]];
    case CModeVal::LiqRate:  return Opm::Well::InjectorCMode::RATE;
    case CModeVal::ResVRate: return Opm::Well::InjectorCMode::RESV;
    case CModeVal::THP:      return Opm::Well::InjectorCMode::THP;
    case CModeVal::BHP:      return Opm::Well::InjectorCMode::BHP;
    }

    throw std::invalid_argument {
        fmt::format("Cannot convert integer value {} to injector control mode", imode)
    };
}

bool haveEconomicLimits(const Opm::RestartIO::RstWell& rst_well)
{
    namespace Limits = Opm::RestartIO::Helpers::VectorItems::
        IWell::Value::EconLimit;

    auto is_finite = [](const float x)
    {
        const auto infty = 1.0e+20f;

        return std::abs(x) < infty;
    };

    auto is_nonzero = [](const float x)
    {
        return std::abs(x) > 0.0f;
    };

    return (rst_well.econ_workover_procedure   != Limits::WOProcedure::None)
        || (rst_well.econ_workover_procedure_2 != Limits::WOProcedure::None)
        || (rst_well.econ_limit_end_run        == Limits::EndRun::Yes)
        || (rst_well.econ_limit_quantity       != Limits::Rate)
        || is_nonzero(rst_well.econ_limit_min_oil)
        || is_nonzero(rst_well.econ_limit_min_gas)
        || is_nonzero(rst_well.econ_limit_min_liq)
        || is_finite (rst_well.econ_limit_max_wct)
        || is_finite (rst_well.econ_limit_max_gor)
        || is_finite (rst_well.econ_limit_max_wgr)
        || is_finite (rst_well.econ_limit_max_wct_2);
}

std::shared_ptr<Opm::WellEconProductionLimits>
economicLimits(const Opm::RestartIO::RstWell& rst_well)
{
    return haveEconomicLimits(rst_well)
        ? std::make_shared<Opm::WellEconProductionLimits>(rst_well)
        : std::make_shared<Opm::WellEconProductionLimits>();
}

Opm::Well::GuideRateTarget guideRatePhase(const int gr_phase)
{
    namespace WGrupCon = Opm::RestartIO::Helpers::VectorItems::
        IWell::Value::WGrupCon;

    using Target = Opm::Well::GuideRateTarget;

    switch (gr_phase) {
    case WGrupCon::GRPhase::Defaulted:            return Target::UNDEFINED;
    case WGrupCon::GRPhase::Oil:                  return Target::OIL;
    case WGrupCon::GRPhase::Water:                return Target::WAT;
    case WGrupCon::GRPhase::Gas:                  return Target::GAS;
    case WGrupCon::GRPhase::Liquid:               return Target::LIQ;
    case WGrupCon::GRPhase::SurfaceInjectionRate: return Target::RAT;
    case WGrupCon::GRPhase::ReservoirVolumeRate:  return Target::RES;
    }

    throw std::invalid_argument {
        fmt::format("Cannot convert integer value {} "
                    "to guiderate phase target", gr_phase)
    };
}

bool isGroupControllable(const int gr_controllable_flag)
{
    return gr_controllable_flag != Opm::RestartIO::Helpers::VectorItems::
        IWell::Value::WGrupCon::Controllable::No;
}

double guideRateValue(const float gr_value)
{
    return (std::abs(gr_value) < 1.0e+20f)
        ? gr_value
        : Opm::ParserKeywords::WGRUPCON::GUIDE_RATE::defaultValue;
}

Opm::Well::WellGuideRate guideRate(const Opm::RestartIO::RstWell& rst_well)
{
    return {
        isGroupControllable(rst_well.group_controllable_flag),
        guideRateValue(rst_well.grupcon_gr_value),
        guideRatePhase(rst_well.grupcon_gr_phase),
        rst_well.grupcon_gr_scaling
    };
}

std::shared_ptr<Opm::WVFPEXP>
explicitTHPOptions(const Opm::RestartIO::RstWell& rst_well)
{
    auto options = std::make_shared<Opm::WVFPEXP>();
    options->update(rst_well);

    return options;
}

const static bool def_automatic_shutin = true;
constexpr double def_solvent_fraction = 0;

}

namespace Opm {

Well::Well(const RestartIO::RstWell& rst_well,
           const int report_step,
           const int rst_whistctl_cmode,
           const TracerConfig& tracer_config,
           const UnitSystem& unit_system_arg,
           const double udq_undefined_arg) :
    wname(rst_well.name),
    group_name(rst_well.group),
    init_step(report_step),
    headI(rst_well.ij[0]),
    headJ(rst_well.ij[1]),
    ref_depth((std::abs(rst_well.datum_depth) < 1.0e+20) ? std::optional<double>{ rst_well.datum_depth } : std::nullopt),
    drainage_radius(rst_well.drainage_radius),
    allow_cross_flow(rst_well.allow_xflow == 1),
    automatic_shutin(def_automatic_shutin),
    pvt_table(rst_well.pvt_table),
    unit_system(unit_system_arg),
    udq_undefined(udq_undefined_arg),
    wtype(rst_well.wtype),
    guide_rate(guideRate(rst_well)),
    efficiency_factor(rst_well.efficiency_factor),
    solvent_fraction(def_solvent_fraction),
    prediction_mode(rst_well.hist_requested_control == 0),
    econ_limits(economicLimits(rst_well)),
    foam_properties(std::make_shared<WellFoamProperties>()),
    polymer_properties(std::make_shared<WellPolymerProperties>()),
    micp_properties(std::make_shared<WellMICPProperties>()),
    brine_properties(std::make_shared<WellBrineProperties>()),
    tracer_properties(std::make_shared<WellTracerProperties>()),
    connections(std::make_shared<WellConnections>(order_from_int(rst_well.completion_ordering), headI, headJ)),
    production(std::make_shared<WellProductionProperties>(unit_system_arg, wname)),
    injection(std::make_shared<WellInjectionProperties>(unit_system_arg, wname)),
    wvfpdp(std::make_shared<WVFPDP>()),
    wvfpexp(explicitTHPOptions(rst_well)),
    wdfac(std::make_shared<WDFAC>(rst_well)),
    status(status_from_int(rst_well.well_status)),
    well_temperature(Metric::TemperatureOffset + ParserKeywords::STCOND::TEMPERATURE::defaultValue),
    well_inj_mult(std::nullopt)
{
    if (this->wtype.producer()) {
        auto p = std::make_shared<WellProductionProperties>(this->unit_system, wname);

        p->whistctl_cmode = (rst_whistctl_cmode > 0)
            ? producer_cmode_from_int(rst_whistctl_cmode)
            : Well::ProducerCMode::CMODE_UNDEFINED;

        p->BHPTarget.update(rst_well.bhp_target_float);
        p->OilRate.update(rst_well.orat_target);
        p->WaterRate.update(rst_well.wrat_target);
        p->GasRate.update(rst_well.grat_target);
        p->LiquidRate.update(rst_well.lrat_target) ;
        p->ResVRate.update(rst_well.resv_target);
        p->VFPTableNumber = rst_well.vfp_table;
        p->ALQValue.update(rst_well.alq_value); // Uncertain of whether the dimension comes through correct here.
        p->predictionMode = this->prediction_mode;

        if (rst_well.orat_target != 0)
            p->addProductionControl( Well::ProducerCMode::ORAT );

        if (rst_well.wrat_target != 0)
            p->addProductionControl( Well::ProducerCMode::WRAT );

        if (rst_well.grat_target != 0)
            p->addProductionControl( Well::ProducerCMode::GRAT );

        if (rst_well.lrat_target != 0)
            p->addProductionControl( Well::ProducerCMode::LRAT );

        if (rst_well.resv_target != 0)
            p->addProductionControl( Well::ProducerCMode::RESV );

        if (rst_well.thp_target != 0) {
            p->THPTarget.update(rst_well.thp_target);
            p->addProductionControl( Well::ProducerCMode::THP );
        }

        if (! p->predictionMode)
            p->clearControls();

        p->controlMode = producer_cmode_from_int(rst_well.active_control);
        p->addProductionControl(p->controlMode);

        p->addProductionControl(Well::ProducerCMode::BHP);
        if (! p->predictionMode) {
            p->BHPTarget.update(0.0);
            p->setBHPLimit(rst_well.bhp_target_double);
            p->controlMode = producer_cmode_from_int(rst_well.hist_requested_control);
        }
        else if (this->isAvailableForGroupControl())
            p->addProductionControl(Well::ProducerCMode::GRUP);

        this->updateProduction(std::move(p));
    }
    else {
        auto i = std::make_shared<WellInjectionProperties>(this->unit_system, wname);
        i->VFPTableNumber = rst_well.vfp_table;
        i->predictionMode = this->prediction_mode;

        if ((std::abs(rst_well.wrat_target) > 0.0f) ||
            (std::abs(rst_well.grat_target) > 0.0f))
            i->addInjectionControl(Well::InjectorCMode::RATE);

        if (std::abs(rst_well.resv_target) > 0.0f) {
            i->reservoirInjectionRate.update(rst_well.resv_target);
            i->addInjectionControl(Well::InjectorCMode::RESV);
        }

        i->injectorType = rst_well.wtype.injector_type();
        switch (i->injectorType) {
        case InjectorType::WATER:
            i->surfaceInjectionRate.update(rst_well.wrat_target);
            break;
        case InjectorType::GAS:
            i->surfaceInjectionRate.update(rst_well.grat_target);
            break;
        default:
            throw std::invalid_argument("What ...");
        }

        if (rst_well.thp_target != 0.0f) {
            i->THPTarget.update(rst_well.thp_target);
            i->addInjectionControl(Well::InjectorCMode::THP);
        }

        const auto active_control = i->predictionMode
            ? injector_cmode_from_int(rst_well.active_control)
            : injector_cmode_from_int(rst_well.hist_requested_control);

        if (! i->predictionMode) {
            i->clearControls();
            if ((active_control != Well::InjectorCMode::RATE) &&
                (active_control != Well::InjectorCMode::BHP))
            {
                throw std::invalid_argument {
                    fmt::format("Unsupported control mode '{}' for "
                                "history controlled injection well '{}'",
                                WellInjectorCMode2String(active_control), this->name())
                };
            }
        }

        i->controlMode = active_control;
        i->addInjectionControl(active_control);

        i->addInjectionControl(Well::InjectorCMode::BHP);
        i->BHPTarget.update(rst_well.bhp_target_float);
        if (! i->predictionMode) {
            if (i->controlMode == Well::InjectorCMode::BHP)
                i->bhp_hist_limit = rst_well.hist_bhp_target;
            else  {
                // defaults (ie FBHPDEF) are not stored in the restart file.
                // we thus use an empirically obtained default value.
                i->resetDefaultHistoricalBHPLimit();
            }
        }
        else if (this->isAvailableForGroupControl())
            i->addInjectionControl(Well::InjectorCMode::GRUP);

        this->updateInjection(std::move(i));

        if (!rst_well.tracer_concentration_injection.empty()) {
            auto tracer = std::make_shared<WellTracerProperties>(this->getTracerProperties());
            for (std::size_t tracer_index = 0; tracer_index < tracer_config.size(); tracer_index++) {
                const auto& name = tracer_config[tracer_index].name;
                const auto concentration = rst_well.tracer_concentration_injection[tracer_index];
                tracer->setConcentration(name, concentration);
            }
            this->updateTracer(tracer);
        }
    }
}



Well::Well(const std::string& wname_arg,
           const std::string& gname,
           std::size_t init_step_arg,
           std::size_t insert_index_arg,
           int headI_arg,
           int headJ_arg,
           const std::optional<double>& ref_depth_arg,
           const WellType& wtype_arg,
           ProducerCMode whistctl_cmode,
           Connection::Order ordering_arg,
           const UnitSystem& unit_system_arg,
           double udq_undefined_arg,
           double dr,
           bool allow_xflow,
           bool auto_shutin,
           int pvt_table_,
           GasInflowEquation inflow_eq):
    wname(wname_arg),
    group_name(gname),
    init_step(init_step_arg),
    insert_index(insert_index_arg),
    headI(headI_arg),
    headJ(headJ_arg),
    ref_depth(ref_depth_arg),
    drainage_radius(dr),
    allow_cross_flow(allow_xflow),
    automatic_shutin(auto_shutin),
    pvt_table(pvt_table_),
    gas_inflow(inflow_eq),
    unit_system(unit_system_arg),
    udq_undefined(udq_undefined_arg),
    wtype(wtype_arg),
    guide_rate({true, -1, Well::GuideRateTarget::UNDEFINED,ParserKeywords::WGRUPCON::SCALING_FACTOR::defaultValue}),
    efficiency_factor(1.0),
    solvent_fraction(0.0),
    derive_refdepth_from_conns_{ ! ref_depth_arg.has_value() || (*ref_depth_arg < 0.0) },
    econ_limits(std::make_shared<WellEconProductionLimits>()),
    foam_properties(std::make_shared<WellFoamProperties>()),
    polymer_properties(std::make_shared<WellPolymerProperties>()),
    micp_properties(std::make_shared<WellMICPProperties>()),
    brine_properties(std::make_shared<WellBrineProperties>()),
    tracer_properties(std::make_shared<WellTracerProperties>()),
    connections(std::make_shared<WellConnections>(ordering_arg, headI, headJ)),
    production(std::make_shared<WellProductionProperties>(unit_system, wname)),
    injection(std::make_shared<WellInjectionProperties>(unit_system, wname)),
    wvfpdp(std::make_shared<WVFPDP>()),
    wvfpexp(std::make_shared<WVFPEXP>()),
    wdfac(std::make_shared<WDFAC>()),
    status(Status::SHUT),
    well_temperature(Metric::TemperatureOffset + ParserKeywords::STCOND::TEMPERATURE::defaultValue),
    well_inj_mult(std::nullopt)
{
    auto p = std::make_shared<WellProductionProperties>(this->unit_system, this->wname);
    p->whistctl_cmode = whistctl_cmode;
    this->updateProduction(p);
}

Well Well::serializationTestObject()
{
    Well result;
    result.wname = "test1";
    result.group_name = "test2";
    result.init_step = 1;
    result.insert_index = 2;
    result.headI = 3;
    result.headJ = 4;
    result.ref_depth = 5;
    result.unit_system = UnitSystem::serializationTestObject();
    result.udq_undefined = 6.0;
    result.status = Status::AUTO;
    result.drainage_radius = 7.0;
    result.allow_cross_flow = true;
    result.automatic_shutin = false;
    result.pvt_table = 77;
    result.gas_inflow = GasInflowEquation::GPP;
    result.wtype = WellType(Phase::WATER);
    result.guide_rate = WellGuideRate::serializationTestObject();
    result.efficiency_factor = 8.0;
    result.solvent_fraction = 9.0;
    result.prediction_mode = false;
    result.derive_refdepth_from_conns_ = false;
    result.econ_limits = std::make_shared<Opm::WellEconProductionLimits>(Opm::WellEconProductionLimits::serializationTestObject());
    result.foam_properties = std::make_shared<WellFoamProperties>(WellFoamProperties::serializationTestObject());
    result.polymer_properties =  std::make_shared<WellPolymerProperties>(WellPolymerProperties::serializationTestObject());
    result.micp_properties =  std::make_shared<WellMICPProperties>(WellMICPProperties::serializationTestObject());
    result.brine_properties = std::make_shared<WellBrineProperties>(WellBrineProperties::serializationTestObject());
    result.tracer_properties = std::make_shared<WellTracerProperties>(WellTracerProperties::serializationTestObject());
    result.connections = std::make_shared<WellConnections>(WellConnections::serializationTestObject());
    result.production = std::make_shared<Well::WellProductionProperties>(Well::WellProductionProperties::serializationTestObject());
    result.injection = std::make_shared<Well::WellInjectionProperties>(Well::WellInjectionProperties::serializationTestObject());
    result.segments = std::make_shared<WellSegments>(WellSegments::serializationTestObject());
    result.wvfpexp = std::make_shared<WVFPEXP>(WVFPEXP::serializationTestObject());
    result.wdfac = std::make_shared<WDFAC>(WDFAC::serializationTestObject());
    result.m_pavg = PAvg();
    result.well_temperature = 10.0;
    result.well_inj_mult = InjMult::serializationTestObject();
    result.m_filter_concentration = UDAValue::serializationTestObject();

    return result;
}


bool Well::updateWPAVE(const PAvg& pavg) {
    if (this->m_pavg == pavg)
        return false;

    this->m_pavg = pavg;
    return true;
}

bool Well::updateEfficiencyFactor(double efficiency_factor_arg) {
    if (this->efficiency_factor != efficiency_factor_arg) {
        this->efficiency_factor = efficiency_factor_arg;
        return true;
    }

    return false;
}

bool Well::updateWellGuideRate(double guide_rate_arg) {
    if (this->guide_rate.guide_rate != guide_rate_arg) {
        this->guide_rate.guide_rate = guide_rate_arg;
        return true;
    }

    return false;
}


bool Well::updateFoamProperties(std::shared_ptr<WellFoamProperties> foam_properties_arg) {
    if (this->wtype.producer()) {
        throw std::runtime_error("Not allowed to set foam injection properties for well " + name()
                                 + " since it is a production well");
    }
    if (*this->foam_properties != *foam_properties_arg) {
        this->foam_properties = foam_properties_arg;
        return true;
    }

    return false;
}


bool Well::updatePolymerProperties(std::shared_ptr<WellPolymerProperties> polymer_properties_arg) {
    if (this->wtype.producer()) {
        throw std::runtime_error("Not allowed to set polymer injection properties for well " + name() +
                                 " since it is a production well");
    }
    if (*this->polymer_properties != *polymer_properties_arg) {
        this->polymer_properties = polymer_properties_arg;
        return true;
    }

    return false;
}

bool Well::updateMICPProperties(std::shared_ptr<WellMICPProperties> micp_properties_arg) {
    if (this->wtype.producer()) {
        throw std::runtime_error("Not allowed to set micp injection properties for well " + name() +
                                 " since it is a production well");
    }
    if (*this->micp_properties != *micp_properties_arg) {
        this->micp_properties = micp_properties_arg;
        return true;
    }

    return false;
}

bool Well::updateBrineProperties(std::shared_ptr<WellBrineProperties> brine_properties_arg) {
    if (this->wtype.producer()) {
        throw std::runtime_error("Not allowed to set brine injection properties for well " + name() +
                                 " since it is a production well");
    }
    if (*this->brine_properties != *brine_properties_arg) {
        this->brine_properties = brine_properties_arg;
        return true;
    }

    return false;
}


bool Well::updateEconLimits(std::shared_ptr<WellEconProductionLimits> econ_limits_arg) {
    if (*this->econ_limits != *econ_limits_arg) {
        this->econ_limits = econ_limits_arg;
        return true;
    }

    return false;
}

bool Well::updateWVFPDP(std::shared_ptr<WVFPDP> wvfpdp_arg) {
    if (*this->wvfpdp != *wvfpdp_arg) {
        this->wvfpdp = std::move(wvfpdp_arg);
        return true;
    }

    return false;
}

bool Well::updateWVFPEXP(std::shared_ptr<WVFPEXP> wvfpexp_arg) {
    if (*this->wvfpexp != *wvfpexp_arg) {
        this->wvfpexp = std::move(wvfpexp_arg);
        return true;
    }

    return false;
}

bool Well::updateWDFAC(std::shared_ptr<WDFAC> wdfac_arg) {
    if (*this->wdfac != *wdfac_arg) {
        this->wdfac = std::move(wdfac_arg);
        return true;
    }

    return false;
}

void Well::switchToProducer() {
    auto p = std::make_shared<WellInjectionProperties>(this->getInjectionProperties());

    p->BHPTarget.update(0);
    p->dropInjectionControl( Opm::Well::InjectorCMode::BHP );
    this->updateInjection( p );
    this->wtype.update(true);
}


void Well::switchToInjector() {
    auto p = std::make_shared<WellProductionProperties>(getProductionProperties());

    p->setBHPLimit(0);
    p->dropProductionControl( ProducerCMode::BHP );
    this->updateProduction( p );
}

bool Well::updateInjection(std::shared_ptr<WellInjectionProperties> injection_arg) {
    auto update = this->wtype.update(injection_arg->injectorType);
    if (this->wtype.producer()) {
        this->switchToInjector();
        update = true;
    }

    if (*this->injection != *injection_arg) {
        this->injection = injection_arg;
        update = true;
    }

    return update;
}

bool Well::updateWellProductivityIndex() {
    return this->connections->prepareWellPIScaling();
}

bool Well::updateHasProduced() {
    if (this->wtype.producer() && this->getStatus() == Status::OPEN) {
        if (this->has_produced)
            return false;

        this->has_produced = true;
        return true;
    }
    return false;
}

bool Well::updateHasInjected() {
    if (this->wtype.injector() && this->getStatus() == Status::OPEN) {
        if (this->has_injected)
            return false;

        this->has_injected= true;
        return true;
    }
    return false;
}

bool Well::updateProduction(std::shared_ptr<WellProductionProperties> production_arg) {
    if (!this->wtype.producer())
        this->switchToProducer( );

    if (*this->production != *production_arg) {
        this->production = production_arg;
        return true;
    }

    return false;
}

bool Well::updateTracer(std::shared_ptr<WellTracerProperties> tracer_properties_arg) {
    if (*this->tracer_properties != *tracer_properties_arg) {
        this->tracer_properties = tracer_properties_arg;
        return true;
    }

    return false;
}

bool Well::updateWellGuideRate(bool available, double guide_rate_arg, GuideRateTarget guide_phase, double scale_factor) {
    bool update = false;
    if (this->guide_rate.available != available) {
        this->guide_rate.available = available;
        update = true;
    }

    if(this->guide_rate.guide_rate != guide_rate_arg) {
        this->guide_rate.guide_rate = guide_rate_arg;
        update = true;
    }

    if(this->guide_rate.guide_phase != guide_phase) {
        this->guide_rate.guide_phase = guide_phase;
        update = true;
    }

    if(this->guide_rate.scale_factor != scale_factor) {
        this->guide_rate.scale_factor = scale_factor;
        update = true;
    }

    return update;
}

bool Well::updateAvailableForGroupControl(bool available) {
    bool update = false;
    if (this->guide_rate.available != available) {
        this->guide_rate.available = available;
        update = true;
    }

    return update;
}




bool Well::updateGroup(const std::string& group_arg) {
    if (this->group_name != group_arg) {
        this->group_name = group_arg;
        return true;
    }
    return false;
}


bool Well::updateHead(std::optional<int> I, std::optional<int> J)
{
    bool update = false;

    if (I.has_value() && (this->headI != *I)) {
        this->headI = *I;
        update = true;
    }

    if (J.has_value() && (this->headJ != *J)) {
        this->headJ = *J;
        update = true;
    }

    return update;
}



bool Well::updateStatus(Status well_state) {
    this->status = well_state;
    return true;
}





bool Well::updateRefDepth(std::optional<double> ref_depth_arg)
{
    if (this->ref_depth == ref_depth_arg) {
        return false;
    }

    if (ref_depth_arg.has_value()) {
        this->derive_refdepth_from_conns_ = *ref_depth_arg < 0.0;

        this->ref_depth = ref_depth_arg;
        return true;
    }

    // If we get here, then this->ref_depth.has_value() &&
    // !ref_depth_arg.has_value().  Assign the argument's value--i.e., reset
    // this->ref_depth--if we're supposed to calculate the reference depth
    // from the connections.

    if (this->derive_refdepth_from_conns_) {
        this->ref_depth = ref_depth_arg;
        return true;
    }

    return false;
}

bool Well::updateDrainageRadius(std::optional<double> drainage_radius_arg)
{
    if (drainage_radius_arg.has_value() &&
        (this->drainage_radius != *drainage_radius_arg))
    {
        this->drainage_radius = *drainage_radius_arg;
        return true;
    }

    return false;
}


bool Well::updateCrossFlow(bool allow_cross_flow_arg) {
    if (this->allow_cross_flow != allow_cross_flow_arg) {
        this->allow_cross_flow = allow_cross_flow_arg;
        return true;
    }

    return false;
}

bool Well::updateAutoShutin(bool auto_shutin) {
    if (this->automatic_shutin != auto_shutin) {
        this->automatic_shutin = auto_shutin;
        return true;
    }

    return false;
}


bool Well::updateConnections(std::shared_ptr<WellConnections> connections_arg, bool force) {
    connections_arg->order(  );
    if (force || *this->connections != *connections_arg) {
        this->connections = connections_arg;
        return true;
    }
    return false;
}

bool Well::updateConnections(std::shared_ptr<WellConnections> connections_arg, const ScheduleGrid& grid) {
    bool update = this->updateConnections(connections_arg, false);
    if (this->pvt_table == 0 && !this->connections->empty()) {
        const auto& lowest = this->connections->lowest();
        const auto& props = grid.get_cell(lowest.getI(), lowest.getJ(), lowest.getK()).props;
        this->pvt_table = props->pvtnum;
        update = true;
    }
    return update;
}

bool Well::updateSolventFraction(double solvent_fraction_arg) {
    if (this->solvent_fraction != solvent_fraction_arg) {
        this->solvent_fraction = solvent_fraction_arg;
        return true;
    }

    return false;
}


bool Well::handleCOMPSEGS(const DeckKeyword& keyword,
                          const ScheduleGrid& grid,
                          const ParseContext& parseContext,
                          ErrorGuard& errors) {
    if (!this->segments) {
        throw OpmInputError{
                fmt::format("WELSEGS must be specified for well {} "
                            "before COMPSEGS being input.",
                            this->name()),
                keyword.location()
        };
    }
    auto [new_connections, new_segments] = Compsegs::processCOMPSEGS(
        keyword,
        *this->connections,
        *this->segments,
        grid,
        parseContext,
        errors
    );

    this->updateConnections( std::make_shared<WellConnections>(std::move(new_connections)), false );
    this->updateSegments( std::make_shared<WellSegments>( std::move(new_segments)) );
    return true;
}

const std::string& Well::groupName() const {
    return this->group_name;
}


bool Well::isMultiSegment() const {
    if (this->segments)
        return true;
    return false;
}

bool Well::isProducer() const {
    return this->wtype.producer();
}

bool Well::isInjector() const {
    return this->wtype.injector();
}

const WellType& Well::wellType() const {
    return this->wtype;
}

Well::InjectorCMode Well::injection_cmode() const {
    if (this->isInjector())
        return this->injection->controlMode;
    throw std::logic_error(fmt::format("Queried for INJECTION cmode for producer: {}", this->name()));
}

Well::ProducerCMode Well::production_cmode() const {
    if (this->isProducer())
        return this->production->controlMode;
    throw std::logic_error(fmt::format("Queried for PRODUCTION cmode for injector : {}", this->name()));
}

InjectorType Well::injectorType() const {
    if (this->wtype.producer())
        throw std::runtime_error("Can not access injectorType attribute of a producer");

    return this->injection->injectorType;
}



bool Well::isAvailableForGroupControl() const {
    return this->guide_rate.available;
}

double Well::getGuideRate() const {
    return this->guide_rate.guide_rate;
}

Well::GuideRateTarget Well::getGuideRatePhase() const
{
    const auto target = this->getRawGuideRatePhase();

    if (this->isInjector() && (target == GuideRateTarget::RAT)) {
        return this->preferredPhaseAsGuideRatePhase();
    }

    return target;
}

Well::GuideRateTarget Well::getRawGuideRatePhase() const
{
    return this->guide_rate.guide_phase;
}

Well::GuideRateTarget Well::preferredPhaseAsGuideRatePhase() const
{
    switch (this->getPreferredPhase()) {
    case Phase::OIL:   return GuideRateTarget::OIL;
    case Phase::GAS:   return GuideRateTarget::GAS;
    case Phase::WATER: return GuideRateTarget::WAT;

    default:
        throw std::logic_error {
            fmt::format("Unable to convert well preferred "
                        "phase {} to GuideRate target phase",
                        static_cast<int>(this->getPreferredPhase()))
        };
    }
}

double Well::getGuideRateScalingFactor() const {
    return this->guide_rate.scale_factor;
}


double Well::getEfficiencyFactor() const {
    return this->efficiency_factor;
}

double Well::getSolventFraction() const {
    return this->solvent_fraction;
}



std::size_t Well::seqIndex() const {
    return this->insert_index;
}

int Well::getHeadI() const {
    return this->headI;
}

int Well::getHeadJ() const {
    return this->headJ;
}

bool Well::getAutomaticShutIn() const {
    return this->automatic_shutin;
}

bool Well::getAllowCrossFlow() const {
    return this->allow_cross_flow;
}

bool Well::hasRefDepth() const
{
    return this->ref_depth.has_value();
}

double Well::getRefDepth() const {
    if (!this->hasRefDepth())
        throw std::logic_error(fmt::format("Well: {} - tried to access not initialized well reference depth", this->name()));
    return *this->ref_depth;
}

double Well::getWPaveRefDepth() const {
    return this->wpave_ref_depth.value_or( this->getRefDepth() );
}

void Well::updateRefDepth()
{
    if ((! this->ref_depth.has_value() || (*this->ref_depth < 0.0)) &&
        this->derive_refdepth_from_conns_)
    {
        // Reference depth was defaulted and we get the depth of the first
        // connection.

        if (this->connections->empty()) {
            throw std::invalid_argument {
                fmt::format("No reservoir connection defined for well {}. "
                            "Cannot infer reference depth.", this->name())
            };
        }

        this->ref_depth = this->connections->get(0).depth();
    }
}

void Well::updateWPaveRefDepth(double depth) {
    this->wpave_ref_depth = depth;
}

double Well::getDrainageRadius() const {
    return this->drainage_radius;
}

//const std::vector<std::string>& Well::wListNames() const {
//    return this->w_list_names;
//}


const std::string& Well::name() const {
    return this->wname;
}

bool Well::hasSameConnectionsPointers(const Well& other) const
{
    // Note: This is *supposed* to be a pointer comparison.  We need to know
    // if the two connection structures represent the exact same object, not
    // just if they have the same value.
    return this->connections == other.connections;
}

void Well::setInsertIndex(std::size_t index) {
    this->insert_index = index;
}


double Well::convertDeckPI(double deckPI) const {
    using M = UnitSystem::measure;

    // XXX: Should really have LIQUID here too, but the 'Phase' type does
    //      not provide that enumerator.
    switch (this->getPreferredPhase()) {
    case Phase::GAS:
        return this->unit_system.to_si(M::gas_productivity_index, deckPI);

    case Phase::OIL:
    case Phase::WATER:
        return this->unit_system.to_si(M::liquid_productivity_index, deckPI);

    default:
        throw std::invalid_argument {
            "Preferred phase " + std::to_string(static_cast<int>(this->getPreferredPhase())) +
            " is not supported. Must be one of 'OIL', 'GAS', or 'WATER'"
        };
    }
}



void Well::applyWellProdIndexScaling(const double scalingFactor, std::vector<bool>& scalingApplicable) {
    this->connections->applyWellPIScaling(scalingFactor, scalingApplicable);
}

bool Well::hasConnections() const {
    return !this->connections->empty();
}

const WellConnections& Well::getConnections() const {
    return *this->connections;
}

const std::vector<const Connection *> Well::getConnections(int completion) const {
    std::vector<const Connection *> connvector;
    for (const auto& conn : this->getConnections()) {
        if (conn.complnum() == completion)
            connvector.push_back( &conn );
    }
    return connvector;
}

const WellFoamProperties& Well::getFoamProperties() const {
    return *this->foam_properties;
}

const WellPolymerProperties& Well::getPolymerProperties() const {
    return *this->polymer_properties;
}

const WellMICPProperties& Well::getMICPProperties() const {
    return *this->micp_properties;
}

const WellBrineProperties& Well::getBrineProperties() const {
    return *this->brine_properties;
}

const WellTracerProperties& Well::getTracerProperties() const {
    return *this->tracer_properties;
}

const WVFPDP& Well::getWVFPDP() const {
    return *this->wvfpdp;
}

const WVFPEXP& Well::getWVFPEXP() const {
    return *this->wvfpexp;
}

const WDFAC& Well::getWDFAC() const {
    return *this->wdfac;
}

const WellEconProductionLimits& Well::getEconLimits() const {
    return *this->econ_limits;
}

const Well::WellProductionProperties& Well::getProductionProperties() const {
    return *this->production;
}

const WellSegments& Well::getSegments() const {
    if (this->segments)
        return *this->segments;
    else
        throw std::logic_error("Asked for segment information in not MSW well: " + this->name());
}

int Well::maxSegmentID() const
{
    return (this->segments == nullptr)
        ? 0
        : this->segments->maxSegmentID();
}

int Well::maxBranchID() const
{
    return (this->segments == nullptr)
        ? 0
        : this->segments->maxBranchID();
}

const Well::WellInjectionProperties& Well::getInjectionProperties() const {
    return *this->injection;
}


Well::Status Well::getStatus() const {
    return this->status;
}

const PAvg& Well::pavg() const {
    return this->m_pavg;
}


std::map<int, std::vector<Connection>> Well::getCompletions() const {
    std::map<int, std::vector<Connection>> completions;

    for (const auto& conn : *this->connections) {
        auto pair = completions.find( conn.complnum() );
        if (pair == completions.end())
            completions[conn.complnum()] = {};

        pair = completions.find(conn.complnum());
        pair->second.push_back(conn);
    }

    return completions;
}

bool Well::hasCompletion(int completion) const {
    for (const auto& conn : *this->connections) {
        if (conn.complnum() == completion)
            return true;
    }
    return false;
}



Phase Well::getPreferredPhase() const {
    return this->wtype.preferred_phase();
}


int Well::pvt_table_number() const {
    return this->pvt_table;
}

int Well::fip_region_number() const {
    return ParserKeywords::WELSPECS::FIP_REGION::defaultValue;
}

/*
  When all connections of a well are closed with the WELOPEN keywords, the well
  itself should also be SHUT. In the main parsing code this is handled by the
  function checkIfAllConnectionsIsShut() which is called at the end of every
  report step in Schedule::iterateScheduleSection(). This is done in this way
  because there is some twisted logic aggregating connection changes over a
  complete report step.

  However - when the WELOPEN is called runtime (typically as an ACTIONX action)
  the full Schedule::iterateScheduleSection() is not run and the check if all
  connections is closed is not done. Therefor we have a runtime flag here
  which makes sure to close the well in this case.
*/


bool Well::handleWELOPENConnections(const DeckRecord& record, Connection::State state_arg) {

    auto match = [=]( const Connection &c) -> bool {
        if (!match_eq(c.getI(), record, "I" , -1)) return false;
        if (!match_eq(c.getJ(), record, "J" , -1)) return false;
        if (!match_eq(c.getK(), record, "K", -1))  return false;
        if (!match_ge(c.complnum(), record, "C1")) return false;
        if (!match_le(c.complnum(), record, "C2")) return false;

        return true;
    };

    auto new_connections = std::make_shared<WellConnections>(this->connections->ordering(), this->headI, this->headJ);

    for (auto c : *this->connections) {
        if (match(c))
            c.setState( state_arg );

        new_connections->add(c);
    }
    return this->updateConnections(std::move(new_connections), false);
}

bool Well::handleCSKIN(const DeckRecord&      record,
                       const KeywordLocation& location)
{
    using Kw = ParserKeywords::CSKIN;

    auto need_skin_adjustment = [&record](const Connection &c) {
        const auto value_shift = -1;

        return match_eq(c.getI(), record, Kw::I::itemName,       value_shift)
            && match_eq(c.getJ(), record, Kw::J::itemName,       value_shift)
            && match_ge(c.getK(), record, Kw::K_UPPER::itemName, value_shift)
            && match_le(c.getK(), record, Kw::K_LOWER::itemName, value_shift);
    };

    // New connection set which will be updated with new connection level
    // skin factors.
    auto new_connections = std::make_shared<WellConnections>
        (this->connections->ordering(), this->headI, this->headJ);

    const auto skin_factor = record.getItem<Kw::CONNECTION_SKIN_FACTOR>().getSIDouble(0);
    for (const auto& connection : *this->connections) {
        if (! need_skin_adjustment(connection)) {
            // No CSKIN adjustment needed here.  Include connection as-is
            // into new connection set.
            new_connections->add(connection);
            continue;
        }

        // If we get here, we must make the connection's skin factor be
        // 'skin_factor'.
        //
        // First guard against this adjustment making the CTF go negative,
        // typically because the 'skin_factor' value is large and negative
        // itself.
        if (const auto& ctf_props = connection.ctfProperties();
            ctf_props.peaceman_denom + skin_factor - ctf_props.skin_factor < 0.0)
        {
            throw OpmInputError {
                fmt::format("Negative connection transmissibility "
                            "factor generated by skin factor {} "
                            "in connection ({},{},{}) for well {}.",
                            skin_factor,
                            connection.getI() + 1,
                            connection.getJ() + 1,
                            connection.getK() + 1,
                            this->name()),
                location
            };
        }

        auto connection_copy = connection;
        connection_copy.setSkinFactor(skin_factor);

        new_connections->add(connection_copy);
    }

    return this->updateConnections(std::move(new_connections), false);
}

bool Well::handleCOMPLUMP(const DeckRecord& record) {

    auto match = [=]( const Connection &c) -> bool {
        if (!match_eq(c.getI(), record, "I" , -1)) return false;
        if (!match_eq(c.getJ(), record, "J" , -1)) return false;
        if (!match_ge(c.getK(), record, "K1", -1)) return false;
        if (!match_le(c.getK(), record, "K2", -1)) return false;

        return true;
    };

    auto new_connections = std::make_shared<WellConnections>(this->connections->ordering(), this->headI, this->headJ);
    const int complnum = record.getItem("N").get<int>(0);
    if (complnum <= 0)
        throw std::invalid_argument("Completion number must be >= 1. COMPLNUM=" + std::to_string(complnum) + "is invalid");

    for (auto c : *this->connections) {
        if (match(c))
            c.setComplnum( complnum );

        new_connections->add(c);
    }

    return this->updateConnections(std::move(new_connections), false);
}



bool Well::handleWPIMULT(const DeckRecord& record) {

    auto match = [=]( const Connection &c) -> bool {
        if (!match_ge(c.complnum(), record, "FIRST")) return false;
        if (!match_le(c.complnum(), record, "LAST"))  return false;
        if (!match_eq(c.getI()  , record, "I", -1)) return false;
        if (!match_eq(c.getJ()  , record, "J", -1)) return false;
        if (!match_eq(c.getK()  , record, "K", -1)) return false;

        return true;
    };

    auto new_connections = std::make_shared<WellConnections>(this->connections->ordering(), this->headI, this->headJ);
    double wellPi = record.getItem("WELLPI").get< double >(0);

    for (auto c : *this->connections) {
        if (match(c))
            c.scaleWellPi( wellPi );

        new_connections->add(c);
    }

    return this->updateConnections(std::move(new_connections), false);
}

bool Well::handleWINJCLN(const DeckRecord& record, const KeywordLocation& location) {
    auto match = [=] ( const Connection& c) -> bool {
        if (!match_eq(c.getI()  , record, "I", -1)) return false;
        if (!match_eq(c.getJ()  , record, "J", -1)) return false;
        if (!match_eq(c.getK()  , record, "K", -1)) return false;

        return true;
    };

    const double fraction_removal = record.getItem<ParserKeywords::WINJCLN::FRAC_REMOVE>().getSIDouble(0);
    if (fraction_removal < 0. || fraction_removal > 1.) {
        const auto reason = fmt::format(" the item 2 in keyword WINJCLN must be between 0 and 1, while a "
                                                    "value {} is given.", fraction_removal);
        throw OpmInputError(reason, location);
    }
    const double fraction_remain = 1. - fraction_removal;
    auto new_connections = std::make_shared<WellConnections>(this->connections->ordering(), this->headI, this->headJ);
    for (auto c : *(this->connections)) {
        if (match(c)) {
             auto filter_cake = c.getFilterCake();
             filter_cake.applyCleanMultiplier(fraction_remain);
             c.setFilterCake(filter_cake);
        }
        new_connections->add(c);
    }
    return this->updateConnections(std::move(new_connections), false);
}

bool Well::handleWINJDAM(const DeckRecord& record, const KeywordLocation& location) {
    auto match = [=] ( const Connection& c) -> bool {
        if (!match_eq(c.getI()  , record, "I", -1)) return false;
        if (!match_eq(c.getJ()  , record, "J", -1)) return false;
        if (!match_eq(c.getK()  , record, "K", -1)) return false;

        return true;
    };

    const FilterCake filter_cake {record, location};
    auto new_connections = std::make_shared<WellConnections>(this->connections->ordering(),
                                                                          this->headI, this->headJ);
    for (auto c : *(this->connections)) {
        if (match(c)) {
            c.setFilterCake(filter_cake);
        }
        new_connections->add(c);
    }
    return this->updateConnections(std::move(new_connections), false);
}

bool Well::handleWINJMULT(const Opm::DeckRecord& record, const KeywordLocation& location) {
    // for this keyword, the default for I, J, K will be negative
    // it is not totally clear how specifying 0 or a negative values will work
    // current match_eq function only treats 0 and default values for all connections,
    // we might need to revisit this part later when complication regarding this occurs.
    // it is possible that changing (item.get<int>(0) == 0); to (item.get<int>(0) <= 0) is solution to go
    // while it remains to be discussed.
    auto match = [=] ( const Connection& c) -> bool {
        if (!match_eq(c.getI()  , record, "I", -1)) return false;
        if (!match_eq(c.getJ()  , record, "J", -1)) return false;
        if (!match_eq(c.getK()  , record, "K", -1)) return false;

        return true;
    };

    using Kw = ParserKeywords::WINJMULT;
    const InjMultMode mode = InjMult::injMultModeFromString(record.getItem<Kw::MODE>().getTrimmedString(0), location);
    const bool mode_change = (this->inj_mult_mode != mode);
    if (mode_change) {
        this->inj_mult_mode = mode;
    }
    const double fracture_pressure = record.getItem<Kw::FRACTURING_PRESSURE>().getSIDouble(0);
    const double multiple_gradient = record.getItem<Kw::MULTIPLIER_GRADIENT>().getSIDouble(0);
    auto new_connections = std::make_shared<WellConnections>(this->connections->ordering(), this->headI, this->headJ);
    const InjMult inj_mult {fracture_pressure, multiple_gradient};
    bool connections_update = false;
    bool well_inj_update = false;

    if (mode == InjMultMode::WREV) {
        // all the connections will share the same INJMULT setup when under WREV
        // it is stored in the Well object
        this->well_inj_mult = inj_mult;
        well_inj_update = true;
    } else if (mode == InjMultMode::CREV || mode == InjMultMode::CIRR){
        for (auto c : *this->connections) {
            if (match(c)) {
                c.setInjMult(inj_mult);
            }
            new_connections->add(c);
        }
        connections_update = this->updateConnections(std::move(new_connections), false);
    }

    return mode_change || connections_update || well_inj_update;
}


bool Opm::Well::applyGlobalWPIMULT(const double scaling_factor)
{
    auto new_connections = std::make_shared<WellConnections>(this->connections->ordering(), this->headI, this->headJ);
    for (auto c : *this->connections) {
        c.scaleWellPi(scaling_factor);
        new_connections->add(c);
    }

    return this->updateConnections(std::move(new_connections), false);
}


void Well::updateSegments(std::shared_ptr<WellSegments> segments_arg) {
    this->segments = std::move(segments_arg);
    this->updateRefDepth( this->segments->depthTopSegment() );
    this->derive_refdepth_from_conns_ = false;
}


bool Well::handleWELSEGS(const DeckKeyword& keyword) {
    if (this->segments) {
        auto new_segments = std::make_shared<WellSegments>( *this->segments );
        new_segments->loadWELSEGS(keyword);
        this->updateSegments(std::move(new_segments));
    } else
        this->updateSegments( std::make_shared<WellSegments>(keyword) );
    return true;
}

bool Well::updatePVTTable(std::optional<int> pvt_table_)
{
    if (pvt_table_.has_value() && (this->pvt_table != *pvt_table_)) {
        this->pvt_table = *pvt_table_;
        return true;
    }

    return false;
}


bool Well::updateWSEGSICD(const std::vector<std::pair<int, SICD> >& sicd_pairs) {
    auto new_segments = std::make_shared<WellSegments>(*this->segments);
    if (new_segments->updateWSEGSICD(sicd_pairs)) {
        this->segments = new_segments;
        return true;
    } else
        return false;
}


bool Well::updateWSEGAICD(const std::vector<std::pair<int, AutoICD> >& aicd_pairs, const KeywordLocation& location) {
    auto new_segments = std::make_shared<WellSegments>(*this->segments);
    if (new_segments->updateWSEGAICD(aicd_pairs, location)) {
        this->segments = new_segments;
        return true;
    } else
        return false;
}


bool Well::updateWSEGVALV(const std::vector<std::pair<int, Valve> >& valve_pairs) {
    auto new_segments = std::make_shared<WellSegments>(*this->segments);
    if (new_segments->updateWSEGVALV(valve_pairs)) {
        this->segments = new_segments;
        return true;
    } else
        return false;
}


void Well::filterConnections(const ActiveGridCells& grid) {
    this->connections->filter(grid);
}


std::size_t Well::firstTimeStep() const {
    return this->init_step;
}

bool Well::hasBeenDefined(std::size_t timeStep) const {
    if (timeStep < this->init_step)
        return false;
    else
        return true;
}

Well::GasInflowEquation Well::gas_inflow_equation() const {
    return this->gas_inflow;
}

bool Well::predictionMode() const {
    return this->prediction_mode;
}

bool Well::hasProduced( ) const {
    return this->has_produced;
}

bool Well::hasInjected( ) const {
    return this->has_injected;
}


bool Well::updatePrediction(bool prediction_mode_arg) {
    if (this->prediction_mode != prediction_mode_arg) {
        this->prediction_mode = prediction_mode_arg;
        return true;
    }

    return false;
}


double Well::production_rate(const SummaryState& st, Phase prod_phase) const {
    if( !this->isProducer() ) return 0.0;

    const auto controls = this->productionControls(st);

    switch( prod_phase ) {
        case Phase::WATER: return controls.water_rate;
        case Phase::OIL:   return controls.oil_rate;
        case Phase::GAS:   return controls.gas_rate;
        case Phase::SOLVENT:
            throw std::invalid_argument( "Production of 'SOLVENT' requested." );
        case Phase::POLYMER:
            throw std::invalid_argument( "Production of 'POLYMER' requested." );
        case Phase::ENERGY:
            throw std::invalid_argument( "Production of 'ENERGY' requested." );
        case Phase::POLYMW:
            throw std::invalid_argument( "Production of 'POLYMW' requested.");
        case Phase::FOAM:
            throw std::invalid_argument( "Production of 'FOAM' requested.");
        case Phase::BRINE:
            throw std::invalid_argument( "Production of 'BRINE' requested.");
        case Phase::ZFRACTION:
            throw std::invalid_argument( "Production of 'ZFRACTION' requested.");
    }

    throw std::logic_error( "Unreachable state. Invalid Phase value. "
                            "This is likely a programming error." );
}

double Well::injection_rate(const SummaryState& st, Phase phase_arg) const {
    if( !this->isInjector() ) return 0.0;
    const auto controls = this->injectionControls(st);

    const auto type = controls.injector_type;

    if( phase_arg == Phase::WATER && type != InjectorType::WATER ) return 0.0;
    if( phase_arg == Phase::OIL   && type != InjectorType::OIL   ) return 0.0;
    if( phase_arg == Phase::GAS   && type != InjectorType::GAS   ) return 0.0;

    return controls.surface_rate;
}


bool Well::wellNameInWellNamePattern(const std::string& wellName, const std::string& wellNamePattern) {
    bool wellNameInPattern = false;
    if (shmatch( wellNamePattern, wellName)) {
        wellNameInPattern = true;
    }
    return wellNameInPattern;
}


Well::ProductionControls Well::productionControls(const SummaryState& st) const {
    if (this->isProducer()) {
        auto controls = this->production->controls(st, this->udq_undefined);
        controls.prediction_mode = this->predictionMode();
        return controls;
    } else
        throw std::logic_error("Trying to get production data from an injector");
}

Well::InjectionControls Well::injectionControls(const SummaryState& st) const {
    if (!this->isProducer()) {
        auto controls = this->injection->controls(this->unit_system, st, this->udq_undefined);
        controls.prediction_mode = this->predictionMode();
        return controls;
    } else
        throw std::logic_error("Trying to get injection data from a producer");
}

double Well::alq_value(const SummaryState& st) const {
    if (this->wtype.producer()) {
        auto controls = this->production->controls(st, this->udq_undefined);
        return controls.alq_value;
    }


    throw std::runtime_error("Can not ask for ALQ value in an injector");
}

/*
  These accessor functions are at the "wrong" level of abstraction; the same
  properties are part of the InjectionControls and ProductionControls structs.
  They are made available here to avoid passing a SummaryState instance in
  situations where it is not really needed.
*/


int Well::vfp_table_number() const {
    if (this->wtype.producer())
        return this->production->VFPTableNumber;
    else
        return this->injection->VFPTableNumber;
}

double Well::temperature() const {
    if (!this->wtype.producer())
        return this->well_temperature;

    throw std::runtime_error("Can only ask for temperature in an injector");
}
void Well::setWellTemperature(const double temp) {
    this->well_temperature = temp;
}

bool Well::cmp_structure(const Well& other) const {
    if ((this->segments && !other.segments) ||
        (!this->segments && other.segments))
    {
        return false;
    }

    if (this->segments && (this->getSegments() != other.getSegments())) {
        return false;
    }

    return (this->name() == other.name())
        && (this->groupName() == other.groupName())
        && (this->firstTimeStep() == other.firstTimeStep())
        && (this->seqIndex() == other.seqIndex())
        && (this->getHeadI() == other.getHeadI())
        && (this->getHeadJ() == other.getHeadJ())
        && (this->hasRefDepth() == other.hasRefDepth())
        && (!this->hasRefDepth() || (this->getRefDepth() == other.getRefDepth()))
        && (this->getPreferredPhase() == other.getPreferredPhase())
        && (this->unit_system == other.unit_system)
        && (this->udq_undefined == other.udq_undefined)
        && (this->getConnections() == other.getConnections())
        && (this->getDrainageRadius() == other.getDrainageRadius())
        && (this->getAllowCrossFlow() == other.getAllowCrossFlow())
        && (this->getAutomaticShutIn() == other.getAutomaticShutIn())
        && (this->getEfficiencyFactor() == other.getEfficiencyFactor())
        ;
}


bool Well::operator==(const Well& data) const {
    return this->cmp_structure(data)
        && (this->getSolventFraction() == data.getSolventFraction())
        && (this->getEconLimits() == data.getEconLimits())
        && (this->isProducer() == data.isProducer())
        && (this->getFoamProperties() == data.getFoamProperties())
        && (this->getStatus() == data.getStatus())
        && (this->guide_rate == data.guide_rate)
        && (this->solvent_fraction == data.solvent_fraction)
        && (this->hasProduced() == data.hasProduced())
        && (this->hasInjected() == data.hasInjected())
        && (this->predictionMode() == data.predictionMode())
        && (this->derive_refdepth_from_conns_ == data.derive_refdepth_from_conns_)
        && (this->getTracerProperties() == data.getTracerProperties())
        && (this->getWVFPEXP() == data.getWVFPEXP())
        && (this->getWDFAC() == data.getWDFAC())
        && (this->getProductionProperties() == data.getProductionProperties())
        && (this->m_pavg == data.m_pavg)
        && (this->getInjectionProperties() == data.getInjectionProperties())
        && (this->well_temperature == data.well_temperature)
        && (this->inj_mult_mode == data.inj_mult_mode)
        && (this->well_inj_mult == data.well_inj_mult)
        && (this->m_filter_concentration == data.m_filter_concentration)
        ;
}

} // namespace Opm

int Opm::Well::eclipseControlMode(const Well::InjectorCMode imode,
                             const InjectorType        itype)
{
    using IMode = ::Opm::Well::InjectorCMode;
    using Val   = ::Opm::RestartIO::Helpers::VectorItems::IWell::Value::WellCtrlMode;
    using IType = ::Opm::InjectorType;

    switch (imode) {
        case IMode::RATE: {
            switch (itype) {
            case IType::OIL:   return Val::OilRate;
            case IType::WATER: return Val::WatRate;
            case IType::GAS:   return Val::GasRate;
            case IType::MULTI: return Val::WMCtlUnk;
            }}
            break;

        case IMode::RESV: return Val::ResVRate;
        case IMode::THP:  return Val::THP;
        case IMode::BHP:  return Val::BHP;
        case IMode::GRUP: return Val::Group;
        default:
            return Val::WMCtlUnk;
    }
    return Val::WMCtlUnk;
}

int Opm::Well::eclipseControlMode(const Opm::Well::ProducerCMode pmode)
{
    using PMode = ::Opm::Well::ProducerCMode;
    using Val   = ::Opm::RestartIO::Helpers::VectorItems::IWell::Value::WellCtrlMode;

    switch (pmode) {
        case PMode::ORAT: return Val::OilRate;
        case PMode::WRAT: return Val::WatRate;
        case PMode::GRAT: return Val::GasRate;
        case PMode::LRAT: return Val::LiqRate;
        case PMode::RESV: return Val::ResVRate;
        case PMode::THP:  return Val::THP;
        case PMode::BHP:  return Val::BHP;
        case PMode::CRAT: return Val::CombRate;
        case PMode::GRUP: return Val::Group;
        default:
            return Val::WMCtlUnk;
    }
    return Val::WMCtlUnk;
}

/*
  The purpose of this function is to convert OPM well status to an integer value
  suitable for output in the eclipse restart file. In OPM we have different
  variables for the wells status and the active control, when this is written to
  a restart file they are combined to one integer. In OPM a well can have an
  active control while still being shut, when this is converted to an integer
  value suitable for the eclipse formatted restart file the value 0 will be used
  to signal a SHUT well and the active control will be lost.

  In the case of a well which is in state 'STOP' or 'AUTO' an integer
  corresponding to the currently active control is writte to the restart file.
*/

int Opm::Well::eclipseControlMode(const Well&         well,
                                  const SummaryState& st)
{
    if (well.isProducer()) {
        const auto& ctrl = well.productionControls(st);

        return eclipseControlMode(ctrl.cmode);
    }
    else { // Injector
        const auto& ctrl = well.injectionControls(st);

        return eclipseControlMode(ctrl.cmode, well.injectorType());
    }
}

Opm::Well::InjMultMode Opm::Well::getInjMultMode() const {
    return this->inj_mult_mode;
}

const Opm::InjMult& Opm::Well::getWellInjMult() const {
    assert(this->aciveWellInjMult());
    return this->well_inj_mult.value();
}

bool Opm::Well::aciveWellInjMult() const {
   return this->well_inj_mult.has_value();
}


void Opm::Well::setFilterConc(const UDAValue& conc) {
    this->m_filter_concentration = conc;
}

double Opm::Well::evalFilterConc(const SummaryState& summary_sate) const {
    return UDA::eval_well_uda(this->m_filter_concentration, this->name(), summary_sate, 0.);
}
