/*
  Copyright 2013 Statoil 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/>.
*/

#define BOOST_TEST_MODULE WellTest
#include <boost/test/unit_test.hpp>

#include <opm/input/eclipse/Parser/ParseContext.hpp>
#include <opm/input/eclipse/Parser/ErrorGuard.hpp>

#include <opm/common/utility/TimeService.hpp>

#include <opm/input/eclipse/EclipseState/Aquifer/NumericalAquifer/NumericalAquifers.hpp>
#include <opm/input/eclipse/EclipseState/EclipseState.hpp>
#include <opm/input/eclipse/EclipseState/Grid/EclipseGrid.hpp>
#include <opm/input/eclipse/EclipseState/Runspec.hpp>

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

#include <opm/input/eclipse/Schedule/Schedule.hpp>
#include <opm/input/eclipse/Schedule/ScheduleTypes.hpp>
#include <opm/input/eclipse/Schedule/SummaryState.hpp>
#include <opm/input/eclipse/Schedule/UDQ/UDQActive.hpp>
#include <opm/input/eclipse/Schedule/Well/Connection.hpp>
#include <opm/input/eclipse/Schedule/Well/Well.hpp>
#include <opm/input/eclipse/Schedule/Well/WellConnections.hpp>

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

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

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

#include <opm/input/eclipse/Parser/ParserKeywords/F.hpp>

#include <cstddef>
#include <memory>
#include <optional>
#include <stdexcept>
#include <utility>

using namespace Opm;

namespace {
    double liquid_PI_unit()
    {
        return UnitSystem::newMETRIC().to_si(UnitSystem::measure::liquid_productivity_index, 1.0);
    }

    double cp_rm3_per_db()
    {
        return UnitSystem::newMETRIC().to_si(UnitSystem::measure::transmissibility, 1.0);
    }
}

BOOST_AUTO_TEST_CASE(WellCOMPDATtestTRACK) {
    const std::string input =
                "START             -- 0 \n"
                "19 JUN 2007 / \n"
                "GRID\n"
                "PORO\n"
                "1000*0.1  /\n"
                "PERMX \n"
                "1000*1 /\n"
                "PERMY \n"
                "1000*0.1 /\n"
                "PERMZ \n"
                "1000*0.01 /\n"
                "SCHEDULE\n"
                "DATES             -- 1\n"
                " 10  OKT 2008 / \n"
                "/\n"
                "WELSPECS\n"
                "    'OP_1'       'OP'   9   9 1*     'OIL' 1*      1*  1*   1*  1*   1*  1*  / \n"
                "/\n"
                "COMPORD\n"
                " OP_1 TRACK / \n"
                "/\n"
                "COMPDAT\n"
                " 'OP_1'  9  9   1   1 'OPEN' 1*   32.948   0.311  3047.839 1*  1*  'X'  22.100 / \n"
                " 'OP_1'  9  9   3   9 'OPEN' 1*   32.948   0.311  3047.839 1*  1*  'X'  22.100 / \n"
                " 'OP_1'  9  9   2   2 'OPEN' 1*   46.825   0.311  4332.346 1*  1*  'X'  22.123 / \n"
                "/\n"
                "DATES             -- 2\n"
                " 20  JAN 2010 / \n"
                "/\n";

    const auto deck = Parser{}.parseString(input);
    EclipseGrid grid(10,10,10);
    const TableManager table ( deck );
    const FieldPropsManager fp( deck, Phases{true, true, true}, grid, table);
    const Runspec runspec (deck);
    const Schedule schedule {
        deck, grid, fp, NumericalAquifers{},
        runspec, std::make_shared<Python>()
    };

    const auto& op_1 = schedule.getWell("OP_1", 2);

    const auto& completions = op_1.getConnections();
    BOOST_CHECK_EQUAL(9U, completions.size());

    //Verify TRACK completion ordering
    for (std::size_t k = 0; k < completions.size(); ++k) {
        BOOST_CHECK_EQUAL(completions.get( k ).getK(), int(k));
    }

    // Output / input ordering
    const auto& output_connections = completions.output(grid);
    std::vector<int> expected = {0,2,3,4,5,6,7,8,1};
    for (std::size_t k = 0; k < completions.size(); ++k) {
        BOOST_CHECK_EQUAL( expected[k], output_connections[k]->getK());
    }
}

BOOST_AUTO_TEST_CASE(WellCOMPDATtestDEPTH) {
    const std::string input  = R"(
START             -- 0
19 JUN 2007 /
GRID
PORO
    1000*0.1 /
PERMX
    1000*1 /
PERMY
    1000*0.1 /
PERMZ
    1000*0.01 /
SCHEDULE
DATES             -- 1
 10  OKT 2008 /
/
WELSPECS
    'OP_1'       'OP'   9   9 1*     'OIL' 1*      1*  1*   1*  1*   1*  1*  /
/
COMPORD
 OP_1 DEPTH /
/
COMPDAT
 'OP_1'  9  9   1   1 'OPEN' 1*   32.948   0.311  3047.839 1*  1*  'X'  22.100 /
 'OP_1'  9  9   3   9 'OPEN' 1*   32.948   0.311  3047.839 1*  1*  'X'  22.100 /
 'OP_1'  9  9   2   2 'OPEN' 1*   46.825   0.311  4332.346 1*  1*  'X'  22.123 /
/
DATES             -- 2
 20  JAN 2010 /
/
)";

    auto deck = Parser{}.parseString(input);
    EclipseGrid grid(10,10,10);
    const TableManager table ( deck );
    const FieldPropsManager fp( deck, Phases{true, true, true}, grid, table);
    const Runspec runspec (deck);
    const Schedule schedule {
        deck, grid, fp, NumericalAquifers{},
        runspec, std::make_shared<Python>()
    };

    const auto& op_1 = schedule.getWell("OP_1", 2);

    const auto& completions = op_1.getConnections();
    BOOST_CHECK_EQUAL(9U, completions.size());

    //Verify TRACK completion ordering
    for (std::size_t k = 0; k < completions.size() - 1; ++k) {
        BOOST_CHECK(completions[k].depth() <= completions[k+1].depth());
    }

    // Output / input ordering
    const auto& output_connections = completions.output(grid);
    std::vector<int> expected = {0,2,3,4,5,6,7,8,1};
    for (std::size_t k = 0; k < completions.size(); ++k) {
        BOOST_CHECK_EQUAL( expected[k], output_connections[k]->getK());
    }
}


BOOST_AUTO_TEST_CASE(WellCOMPDATtestDefaultTRACK) {
    const std::string input =
                "START             -- 0 \n"
                "19 JUN 2007 / \n"
                "GRID\n"
                "PORO\n"
                "1000*0.1  /\n"
                "PERMX \n"
                "1000*1 /\n"
                "PERMY \n"
                "1000*0.1 /\n"
                "PERMZ \n"
                "1000*0.01 /\n"
                "SCHEDULE\n"
                "DATES             -- 1\n"
                " 10  OKT 2008 / \n"
                "/\n"
                "WELSPECS\n"
                "    'OP_1'       'OP'   9   9 1*     'OIL' 1*      1*  1*   1*  1*   1*  1*  / \n"
                "/\n"
                "COMPDAT\n"
                " 'OP_1'  9  9   1   1 'OPEN' 1*   32.948   0.311  3047.839 1*  1*  'X'  22.100 / \n"
                " 'OP_1'  9  9   3   9 'OPEN' 1*   32.948   0.311  3047.839 1*  1*  'X'  22.100 / \n"
                " 'OP_1'  9  9   2   2 'OPEN' 1*   46.825   0.311  4332.346 1*  1*  'X'  22.123 / \n"
                "/\n"
                "DATES             -- 2\n"
                " 20  JAN 2010 / \n"
                "/\n";

    const auto deck = Parser{}.parseString(input);
    EclipseGrid grid(10,10,10);
    const TableManager table ( deck );
    const FieldPropsManager fp( deck, Phases{true, true, true}, grid, table);
    const Runspec runspec (deck);
    const Schedule schedule {
        deck, grid, fp, NumericalAquifers{},
        runspec, std::make_shared<Python>()
    };

    const auto& op_1 = schedule.getWell("OP_1", 2);

    const auto& completions = op_1.getConnections();
    BOOST_CHECK_EQUAL(9U, completions.size());

    //Verify TRACK completion ordering
    for (std::size_t k = 0; k < completions.size(); ++k) {
        BOOST_CHECK_EQUAL(completions.get( k ).getK(), int(k));
    }
}

BOOST_AUTO_TEST_CASE(WellCOMPDATtestINPUT) {
    const std::string input =
                "START             -- 0 \n"
                "19 JUN 2007 / \n"
                "GRID\n"
                "PORO\n"
                "1000*0.1  /\n"
                "PERMX \n"
                "1000*1 /\n"
                "PERMY \n"
                "1000*0.1 /\n"
                "PERMZ \n"
                "1000*0.01 /\n"
                "SCHEDULE\n"
                "DATES             -- 1\n"
                " 10  OKT 2008 / \n"
                "/\n"
                "WELSPECS\n"
                "    'OP_1'       'OP'   9   9 1*     'OIL' 1*      1*  1*   1*  1*   1*  1*  / \n"
                "/\n"
                "COMPORD\n"
                " OP_1 INPUT / \n"
                "/\n"
                "COMPDAT\n"
                " 'OP_1'  9  9   1   1 'OPEN' 1*   32.948   0.311  3047.839 1*  1*  'X'  22.100 / \n"
                " 'OP_1'  9  9   3   9 'OPEN' 1*   32.948   0.311  3047.839 1*  1*  'X'  22.100 / \n"
                " 'OP_1'  9  9   2   2 'OPEN' 1*   46.825   0.311  4332.346 1*  1*  'X'  22.123 / \n"
                "/\n"
                "DATES             -- 2\n"
                " 20  JAN 2010 / \n"
                "/\n";

    const auto deck = Parser{}.parseString(input);
    EclipseGrid grid(10,10,10);
    ErrorGuard errors;
    const TableManager table ( deck );
    const FieldPropsManager fp( deck, Phases{true, true, true}, grid, table);
    const Runspec runspec (deck);
    const Schedule schedule {
        deck, grid, fp, NumericalAquifers{},
        runspec, ParseContext{}, errors,
        std::make_shared<Python>()
    };

    const auto& op_1 = schedule.getWell("OP_1", 2);

    const auto& completions = op_1.getConnections();
    BOOST_CHECK_EQUAL(9U, completions.size());
    BOOST_CHECK_EQUAL(completions.get( 1 ).getK(), 2);
    BOOST_CHECK_EQUAL(completions.get( 2 ).getK(), 3);
    BOOST_CHECK_EQUAL(completions.get( 3 ).getK(), 4);
    BOOST_CHECK_EQUAL(completions.get( 4 ).getK(), 5);
    BOOST_CHECK_EQUAL(completions.get( 5 ).getK(), 6);
    BOOST_CHECK_EQUAL(completions.get( 6 ).getK(), 7);
    BOOST_CHECK_EQUAL(completions.get( 7 ).getK(), 8);
    BOOST_CHECK_EQUAL(completions.get( 8 ).getK(), 1);
}

BOOST_AUTO_TEST_CASE(NewWellZeroCompletions) {
    Opm::Well well("WELL1", "GROUP", 0, 1, 0, 0, 0.0, Opm::WellType(Opm::Phase::OIL), Opm::Well::ProducerCMode::CMODE_UNDEFINED,  Connection::Order::DEPTH, UnitSystem::newMETRIC(), 0, 1.0, false, false, 0, Opm::Well::GasInflowEquation::STD);
    BOOST_CHECK_EQUAL( 0U , well.getConnections( ).size() );
}


BOOST_AUTO_TEST_CASE(isProducerCorrectlySet) {
    // HACK: This test checks correctly setting of isProducer/isInjector. This property depends on which of
    //       WellProductionProperties/WellInjectionProperties is set last, independent of actual values.
    {
        Opm::Well well("WELL1" , "GROUP", 0, 1, 0, 0, 0.0, Opm::WellType(Opm::Phase::OIL), Opm::Well::ProducerCMode::CMODE_UNDEFINED, Connection::Order::DEPTH, UnitSystem::newMETRIC(), 0, 1.0, false, false, 0, Opm::Well::GasInflowEquation::STD);


        /* 1: Well is created as producer */
        BOOST_CHECK_EQUAL( false , well.isInjector());
        BOOST_CHECK_EQUAL( true , well.isProducer());

        /* Set a surface injection rate => Well becomes an Injector */
        auto injectionProps1 = std::make_shared<Opm::Well::WellInjectionProperties>(well.getInjectionProperties());
        injectionProps1->surfaceInjectionRate.update(100);
        well.updateInjection(injectionProps1);
        BOOST_CHECK_EQUAL( true  , well.isInjector());
        BOOST_CHECK_EQUAL( false , well.isProducer());
        BOOST_CHECK_EQUAL( 100 , well.getInjectionProperties().surfaceInjectionRate.get<double>());
    }


    {
        Opm::Well well("WELL1" , "GROUP", 0, 1, 0, 0, 0.0, Opm::WellType(Opm::Phase::OIL), Opm::Well::ProducerCMode::CMODE_UNDEFINED, Connection::Order::DEPTH, UnitSystem::newMETRIC(), 0, 1.0, false, false, 0, Opm::Well::GasInflowEquation::STD);

        /* Set a reservoir injection rate => Well becomes an Injector */
        auto injectionProps2 = std::make_shared<Opm::Well::WellInjectionProperties>(well.getInjectionProperties());
        injectionProps2->reservoirInjectionRate.update(200);
        well.updateInjection(injectionProps2);
        BOOST_CHECK_EQUAL( false , well.isProducer());
        BOOST_CHECK_EQUAL( 200 , well.getInjectionProperties().reservoirInjectionRate.get<double>());
    }

    {
        Opm::Well well("WELL1" , "GROUP", 0, 1, 0, 0, 0.0, Opm::WellType(Opm::Phase::OIL), Opm::Well::ProducerCMode::CMODE_UNDEFINED, Connection::Order::DEPTH, UnitSystem::newMETRIC(), 0, 1.0, false, false, 0, Opm::Well::GasInflowEquation::STD);

        /* Set rates => Well becomes a producer; injection rate should be set to 0. */
        auto injectionProps3 = std::make_shared<Opm::Well::WellInjectionProperties>(well.getInjectionProperties());
        well.updateInjection(injectionProps3);

        auto properties = std::make_shared<Opm::Well::WellProductionProperties>( well.getProductionProperties() );
        properties->OilRate.update(100);
        properties->GasRate.update(200);
        properties->WaterRate.update(300);
        well.updateProduction(properties);

        BOOST_CHECK_EQUAL( false , well.isInjector());
        BOOST_CHECK_EQUAL( true , well.isProducer());
        BOOST_CHECK_THROW( well.getInjectionProperties().surfaceInjectionRate.get<double>(), std::invalid_argument);
        BOOST_CHECK_THROW( well.getInjectionProperties().reservoirInjectionRate.get<double>(), std::invalid_argument);
        BOOST_CHECK_EQUAL( 100 , well.getProductionProperties().OilRate.get<double>());
        BOOST_CHECK_EQUAL( 200 , well.getProductionProperties().GasRate.get<double>());
        BOOST_CHECK_EQUAL( 300 , well.getProductionProperties().WaterRate.get<double>());
    }
}

BOOST_AUTO_TEST_CASE(GroupnameCorretlySet) {
    Opm::Well well("WELL1" , "G1", 0, 1, 0, 0, 0.0, Opm::WellType(Opm::Phase::OIL), Opm::Well::ProducerCMode::CMODE_UNDEFINED, Connection::Order::DEPTH, UnitSystem::newMETRIC(), 0, 1.0, false, false, 0, Opm::Well::GasInflowEquation::STD);

    BOOST_CHECK_EQUAL("G1" , well.groupName());
    well.updateGroup( "GROUP2");
    BOOST_CHECK_EQUAL("GROUP2" , well.groupName());
}


BOOST_AUTO_TEST_CASE(addWELSPECS_setData_dataSet) {
    Opm::Well well("WELL1", "GROUP", 0, 1, 23, 42, 2334.32, Opm::WellType(Opm::Phase::WATER), Opm::Well::ProducerCMode::CMODE_UNDEFINED, Connection::Order::DEPTH, UnitSystem::newMETRIC(), 0, 1.0, false, false, 0, Opm::Well::GasInflowEquation::STD);

    BOOST_CHECK_EQUAL(23, well.getHeadI());
    BOOST_CHECK_EQUAL(42, well.getHeadJ());
    BOOST_CHECK_EQUAL(2334.32, well.getRefDepth());
    BOOST_CHECK_EQUAL(Opm::Phase::WATER, well.getPreferredPhase());
}


BOOST_AUTO_TEST_CASE(XHPLimitDefault) {
    Opm::Well well("WELL1", "GROUP", 0, 1, 23, 42, 2334.32, Opm::WellType(Opm::Phase::WATER), Opm::Well::ProducerCMode::CMODE_UNDEFINED, Connection::Order::DEPTH, UnitSystem::newMETRIC(), 0, 1.0, false, false, 0, Opm::Well::GasInflowEquation::STD);


    auto productionProps = std::make_shared<Opm::Well::WellProductionProperties>(well.getProductionProperties());
    productionProps->BHPTarget.update(100);
    productionProps->addProductionControl(Opm::Well::ProducerCMode::BHP);
    well.updateProduction(productionProps);
    BOOST_CHECK_EQUAL( 100 , well.getProductionProperties().BHPTarget.get<double>());
    BOOST_CHECK_EQUAL( true, well.getProductionProperties().hasProductionControl( Opm::Well::ProducerCMode::BHP ));

    auto injProps = std::make_shared<Opm::Well::WellInjectionProperties>(well.getInjectionProperties());
    injProps->THPTarget.update(200);
    well.updateInjection(injProps);
    BOOST_CHECK_EQUAL( 200 , well.getInjectionProperties().THPTarget.get<double>());
    BOOST_CHECK( !well.getInjectionProperties().hasInjectionControl( Opm::Well::InjectorCMode::THP ));
}



BOOST_AUTO_TEST_CASE(ScheduleTypesInjectorType) {
    Opm::Well well("WELL1", "GROUP", 0, 1, 23, 42, 2334.32, Opm::WellType(Opm::Phase::WATER), Opm::Well::ProducerCMode::CMODE_UNDEFINED, Connection::Order::DEPTH, UnitSystem::newMETRIC(), 0, 1.0, false, false, 0, Opm::Well::GasInflowEquation::STD);


    auto injectionProps = std::make_shared<Opm::Well::WellInjectionProperties>(well.getInjectionProperties());
    injectionProps->injectorType = Opm::InjectorType::WATER;
    well.updateInjection(injectionProps);
    // TODO: Should we test for something other than wate here, as long as
    //       the default value for InjectorType is WellInjector::WATER?
    BOOST_CHECK( Opm::InjectorType::WATER == well.getInjectionProperties().injectorType);

}



/*****************************************************************/


BOOST_AUTO_TEST_CASE(WellHaveProductionControlLimit) {
    Opm::Well well("WELL1", "GROUP", 0, 1, 23, 42, 2334.32, Opm::WellType(Opm::Phase::WATER), Opm::Well::ProducerCMode::CMODE_UNDEFINED, Connection::Order::DEPTH, UnitSystem::newMETRIC(), 0, 1.0, false, false, 0, Opm::Well::GasInflowEquation::STD);


    BOOST_CHECK( !well.getProductionProperties().hasProductionControl( Opm::Well::ProducerCMode::ORAT ));
    BOOST_CHECK( !well.getProductionProperties().hasProductionControl( Opm::Well::ProducerCMode::RESV ));

    auto properties1 = std::make_shared<Opm::Well::WellProductionProperties>(well.getProductionProperties());
    properties1->OilRate.update(100);
    properties1->addProductionControl(Opm::Well::ProducerCMode::ORAT);
    well.updateProduction(properties1);
    BOOST_CHECK(  well.getProductionProperties().hasProductionControl( Opm::Well::ProducerCMode::ORAT ));
    BOOST_CHECK( !well.getProductionProperties().hasProductionControl( Opm::Well::ProducerCMode::RESV ));

    auto properties2 = std::make_shared<Opm::Well::WellProductionProperties>(well.getProductionProperties());
    properties2->ResVRate.update(100);
    properties2->addProductionControl(Opm::Well::ProducerCMode::RESV);
    well.updateProduction(properties2);
    BOOST_CHECK( well.getProductionProperties().hasProductionControl( Opm::Well::ProducerCMode::RESV ));

    auto properties3 = std::make_shared<Opm::Well::WellProductionProperties>(well.getProductionProperties());
    properties3->OilRate.update(100);
    properties3->WaterRate.update(100);
    properties3->GasRate.update(100);
    properties3->LiquidRate.update(100);
    properties3->ResVRate.update(100);
    properties3->BHPTarget.update(100);
    properties3->THPTarget.update(100);
    properties3->addProductionControl(Opm::Well::ProducerCMode::ORAT);
    properties3->addProductionControl(Opm::Well::ProducerCMode::LRAT);
    properties3->addProductionControl(Opm::Well::ProducerCMode::BHP);
    well.updateProduction(properties3);

    BOOST_CHECK( well.getProductionProperties().hasProductionControl( Opm::Well::ProducerCMode::ORAT ));
    BOOST_CHECK( well.getProductionProperties().hasProductionControl( Opm::Well::ProducerCMode::LRAT ));
    BOOST_CHECK( well.getProductionProperties().hasProductionControl( Opm::Well::ProducerCMode::BHP ));

    auto properties4 = std::make_shared<Opm::Well::WellProductionProperties>(well.getProductionProperties());
    properties4->dropProductionControl( Opm::Well::ProducerCMode::LRAT );
    well.updateProduction(properties4);

    BOOST_CHECK( well.getProductionProperties().hasProductionControl( Opm::Well::ProducerCMode::ORAT ));
    BOOST_CHECK(!well.getProductionProperties().hasProductionControl( Opm::Well::ProducerCMode::LRAT ));
    BOOST_CHECK( well.getProductionProperties().hasProductionControl( Opm::Well::ProducerCMode::BHP ));
}


BOOST_AUTO_TEST_CASE(WellHaveInjectionControlLimit) {
    Opm::Well well("WELL1", "GROUP", 0, 1, 23, 42, 2334.32, Opm::WellType(Opm::Phase::WATER), Opm::Well::ProducerCMode::CMODE_UNDEFINED, Connection::Order::DEPTH, UnitSystem::newMETRIC(), 0, 1.0, false, false, 0, Opm::Well::GasInflowEquation::STD);

    BOOST_CHECK( !well.getInjectionProperties().hasInjectionControl( Opm::Well::InjectorCMode::RATE ));
    BOOST_CHECK( !well.getInjectionProperties().hasInjectionControl( Opm::Well::InjectorCMode::RESV ));

    auto injProps1 = std::make_shared<Opm::Well::WellInjectionProperties>(well.getInjectionProperties());
    injProps1->surfaceInjectionRate.update(100);
    injProps1->addInjectionControl(Opm::Well::InjectorCMode::RATE);
    well.updateInjection(injProps1);
    BOOST_CHECK(  well.getInjectionProperties().hasInjectionControl( Opm::Well::InjectorCMode::RATE ));
    BOOST_CHECK( !well.getInjectionProperties().hasInjectionControl( Opm::Well::InjectorCMode::RESV ));

    auto injProps2 = std::make_shared<Opm::Well::WellInjectionProperties>(well.getInjectionProperties());
    injProps2->reservoirInjectionRate.update(100);
    injProps2->addInjectionControl(Opm::Well::InjectorCMode::RESV);
    well.updateInjection(injProps2);
    BOOST_CHECK( well.getInjectionProperties().hasInjectionControl( Opm::Well::InjectorCMode::RESV ));

    auto injProps3 = std::make_shared<Opm::Well::WellInjectionProperties>(well.getInjectionProperties());
    injProps3->BHPTarget.update(100);
    injProps3->addInjectionControl(Opm::Well::InjectorCMode::BHP);
    injProps3->THPTarget.update(100);
    injProps3->addInjectionControl(Opm::Well::InjectorCMode::THP);
    well.updateInjection(injProps3);

    BOOST_CHECK( well.getInjectionProperties().hasInjectionControl( Opm::Well::InjectorCMode::RATE ));
    BOOST_CHECK( well.getInjectionProperties().hasInjectionControl( Opm::Well::InjectorCMode::RESV ));
    BOOST_CHECK( well.getInjectionProperties().hasInjectionControl( Opm::Well::InjectorCMode::THP ));
    BOOST_CHECK( well.getInjectionProperties().hasInjectionControl( Opm::Well::InjectorCMode::BHP ));


    auto injProps4 = std::make_shared<Opm::Well::WellInjectionProperties>(well.getInjectionProperties());
    injProps4->dropInjectionControl( Opm::Well::InjectorCMode::RESV );
    well.updateInjection(injProps4);
    BOOST_CHECK(  well.getInjectionProperties().hasInjectionControl( Opm::Well::InjectorCMode::RATE ));
    BOOST_CHECK( !well.getInjectionProperties().hasInjectionControl( Opm::Well::InjectorCMode::RESV ));
    BOOST_CHECK(  well.getInjectionProperties().hasInjectionControl( Opm::Well::InjectorCMode::THP ));
    BOOST_CHECK(  well.getInjectionProperties().hasInjectionControl( Opm::Well::InjectorCMode::BHP ));
}
/*********************************************************************/

BOOST_AUTO_TEST_CASE(WellGuideRatePhase_GuideRatePhaseSet) {
    Opm::Well well("WELL1" , "GROUP", 0, 1, 0, 0, 0.0, Opm::WellType(Opm::Phase::OIL), Opm::Well::ProducerCMode::CMODE_UNDEFINED, Connection::Order::DEPTH, UnitSystem::newMETRIC(), 0, 1.0, false, false, 0, Opm::Well::GasInflowEquation::STD);

    BOOST_CHECK(Opm::Well::GuideRateTarget::UNDEFINED == well.getGuideRatePhase());

    BOOST_CHECK(well.updateWellGuideRate(true, 100, Opm::Well::GuideRateTarget::RAT, 66.0));
    BOOST_CHECK(Opm::Well::GuideRateTarget::RAT == well.getGuideRatePhase());
    BOOST_CHECK_EQUAL(100, well.getGuideRate());
    BOOST_CHECK_EQUAL(66.0, well.getGuideRateScalingFactor());
}

BOOST_AUTO_TEST_CASE(WellEfficiencyFactorSet) {
    Opm::Well well("WELL1" , "GROUP", 0, 1, 0, 0, 0.0, Opm::WellType(Opm::Phase::OIL), Opm::Well::ProducerCMode::CMODE_UNDEFINED, Connection::Order::DEPTH, UnitSystem::newMETRIC(), 0, 1.0, false, false, 0, Opm::Well::GasInflowEquation::STD);

    BOOST_CHECK_EQUAL(1.0, well.getEfficiencyFactor());
    BOOST_CHECK( well.updateEfficiencyFactor(0.9, true));
    BOOST_CHECK_EQUAL(0.9, well.getEfficiencyFactor());
}


namespace {
    namespace WCONHIST {
        std::string all_specified_CMODE_THP() {
            const std::string input =
                "WCONHIST\n"
                "'P' 'OPEN' 'THP' 1 2 3/\n/\n";

            return input;
        }



        std::string all_specified() {
            const std::string input =
                "WCONHIST\n"
                "'P' 'OPEN' 'ORAT' 1 2 3/\n/\n";

            return input;
        }

        std::string orat_defaulted() {
            const std::string input =
                "WCONHIST\n"
                "'P' 'OPEN' 'WRAT' 1* 2 3/\n/\n";

            return input;
        }

        std::string owrat_defaulted() {
            const std::string input =
                "WCONHIST\n"
                "'P' 'OPEN' 'GRAT' 1* 1* 3/\n/\n";

            return input;
        }

        std::string all_defaulted() {
            const std::string input =
                "WCONHIST\n"
                "'P' 'OPEN' 'LRAT'/\n/\n";

            return input;
        }

        std::string all_defaulted_with_bhp() {
            const std::string input =
                "WCONHIST\n"
                "-- 1    2     3      4-9 10\n"
                "   'P' 'OPEN' 'RESV' 6*  500/\n/\n";

            return input;
        }

        std::string bhp_defaulted() {
            const std::string input =
                "WCONHIST\n"
                "-- 1    2     3    4-9 10\n"
                "  'P' 'OPEN' 'BHP' 6*  500/\n/\n";

            return input;
        }

        std::string all_defaulted_with_bhp_vfp_table() {
            const std::string input =
                "WCONHIST\n"
                "-- 1    2     3    4-6  7  8  9  10\n"
                "  'P' 'OPEN' 'RESV' 3*  3 10. 1* 500/\n/\n";

            return input;
        }


    Opm::Well::WellProductionProperties properties(const std::string& input, std::optional<VFPProdTable::ALQ_TYPE> alq_type = {}) {
            Opm::Parser parser;
            Opm::UnitSystem unit_system(Opm::UnitSystem::UnitType::UNIT_TYPE_METRIC);
            auto deck = parser.parseString(input);
            const auto& record = deck["WCONHIST"].back().getRecord(0);
            auto table_nr = record.getItem("VFP_TABLE").get< int >(0);
            Opm::Well::WellProductionProperties hist(unit_system, "W");
            hist.handleWCONHIST(alq_type, table_nr,
                                Opm::ParserKeywords::FBHPDEF::TARGET_BHP::defaultValue * unit::barsa,
                                unit_system, record);


            return hist;
        }
    } // namespace WCONHIST

    namespace WCONPROD {
        std::string
        all_specified_CMODE_BHP()
        {
            const std::string input =
                "WCONHIST\n"
                "'P' 'OPEN' 'BHP' 1 2 3/\n/\n";

            return input;
        }

        std::string orat_CMODE_other_defaulted()
        {
            const std::string input =
                "WCONPROD\n"
                "'P' 'OPEN' 'ORAT' 1 2 3/\n/\n";

            return input;
        }

        std::string thp_CMODE()
        {
            const std::string input =
                "WCONPROD\n"
                "'P' 'OPEN' 'THP' 1 2 3 3* 10. 8 13./\n/\n";

            return input;
        }

        std::string bhp_CMODE()
        {
            const std::string input =
                "WCONPROD\n"
                "'P' 'OPEN' 'BHP' 1 2 3 2* 20. 10. 8 13./\n/\n";

            return input;
        }

        Opm::UnitSystem unit_system(Opm::UnitSystem::UnitType::UNIT_TYPE_METRIC);
        Opm::Well::WellProductionProperties properties(const std::string& input, std::optional<VFPProdTable::ALQ_TYPE> alq_type = {})
        {
            Opm::Parser parser;
            auto deck = parser.parseString(input);
            const auto& kwd     = deck["WCONPROD"].back();
            const auto&  record = kwd.getRecord(0);
            auto table_nr = record.getItem("VFP_TABLE").get< int >(0);
            Opm::Well::WellProductionProperties pred(unit_system, "W");
            pred.handleWCONPROD(alq_type, table_nr,
                                Opm::ParserKeywords::FBHPDEF::TARGET_BHP::defaultValue * unit::barsa,
                                unit_system, "WELL", record, {});

            return pred;
        }
    }

} // namespace anonymous

BOOST_AUTO_TEST_CASE(WCH_All_Specified_BHP_Defaulted)
{
    Opm::SummaryState st(TimeService::now(), 0.0);
    const Opm::Well::WellProductionProperties p =
        WCONHIST::properties(WCONHIST::all_specified());

    BOOST_CHECK(p.hasProductionControl(Opm::Well::ProducerCMode::ORAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::WRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::GRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::LRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::RESV));

    BOOST_CHECK(p.controlMode == Opm::Well::ProducerCMode::ORAT);

    BOOST_CHECK(p.hasProductionControl(Opm::Well::ProducerCMode::BHP));

    const auto& controls = p.controls(st, 0);
    BOOST_CHECK_EQUAL(controls.bhp_limit, 101325.);
}

BOOST_AUTO_TEST_CASE(WCH_ORAT_Defaulted_BHP_Defaulted)
{
    Opm::SummaryState st(TimeService::now(), 0.0);
    const Opm::Well::WellProductionProperties p =
        WCONHIST::properties(WCONHIST::orat_defaulted());

    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::ORAT));
    BOOST_CHECK(p.hasProductionControl(Opm::Well::ProducerCMode::WRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::GRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::LRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::RESV));
    BOOST_CHECK(p.controlMode == Opm::Well::ProducerCMode::WRAT);

    BOOST_CHECK(p.hasProductionControl(Opm::Well::ProducerCMode::BHP));
    const auto& controls = p.controls(st, 0);
    BOOST_CHECK_EQUAL(controls.bhp_limit, 101325.);
}

BOOST_AUTO_TEST_CASE(WCH_OWRAT_Defaulted_BHP_Defaulted)
{
    Opm::SummaryState st(TimeService::now(), 0.0);
    const Opm::Well::WellProductionProperties p =
        WCONHIST::properties(WCONHIST::owrat_defaulted());

    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::ORAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::WRAT));
    BOOST_CHECK(p.hasProductionControl(Opm::Well::ProducerCMode::GRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::LRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::RESV));
    BOOST_CHECK(p.controlMode == Opm::Well::ProducerCMode::GRAT);

    BOOST_CHECK(p.hasProductionControl(Opm::Well::ProducerCMode::BHP));
    const auto& controls = p.controls(st, 0);
    BOOST_CHECK_EQUAL(controls.bhp_limit, 101325.);
}

BOOST_AUTO_TEST_CASE(WCH_Rates_Defaulted_BHP_Defaulted)
{
    Opm::SummaryState st(TimeService::now(), 0.0);
    const Opm::Well::WellProductionProperties p =
        WCONHIST::properties(WCONHIST::all_defaulted());

    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::ORAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::WRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::GRAT));
    BOOST_CHECK(p.hasProductionControl(Opm::Well::ProducerCMode::LRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::RESV));
    BOOST_CHECK(p.controlMode == Opm::Well::ProducerCMode::LRAT);

    BOOST_CHECK(p.hasProductionControl(Opm::Well::ProducerCMode::BHP));
    const auto& controls = p.controls(st, 0);
    BOOST_CHECK_EQUAL(controls.bhp_limit, 101325.);
}

BOOST_AUTO_TEST_CASE(WCH_Rates_Defaulted_BHP_Specified)
{
    Opm::SummaryState st(TimeService::now(), 0.0);
    const Opm::Well::WellProductionProperties p =
        WCONHIST::properties(WCONHIST::all_defaulted_with_bhp());

    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::ORAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::WRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::GRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::LRAT));
    BOOST_CHECK(p.hasProductionControl(Opm::Well::ProducerCMode::RESV));

    BOOST_CHECK(p.controlMode == Opm::Well::ProducerCMode::RESV);

    BOOST_CHECK_EQUAL(true, p.hasProductionControl(Opm::Well::ProducerCMode::BHP));
    const auto& controls = p.controls(st, 0);
    BOOST_CHECK_EQUAL(controls.bhp_limit, 101325.);
}

BOOST_AUTO_TEST_CASE(WCH_Rates_NON_Defaulted_VFP)
{
    Opm::SummaryState st(TimeService::now(), 0.0);
    const Opm::Well::WellProductionProperties p =
        WCONHIST::properties(WCONHIST::all_defaulted_with_bhp_vfp_table(), VFPProdTable::ALQ_TYPE::ALQ_UNDEF);

    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::ORAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::WRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::GRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::LRAT));
    BOOST_CHECK(p.hasProductionControl(Opm::Well::ProducerCMode::RESV));

    BOOST_CHECK(p.controlMode == Opm::Well::ProducerCMode::RESV);

    BOOST_CHECK_EQUAL(true, p.hasProductionControl(Opm::Well::ProducerCMode::BHP));
    BOOST_CHECK_EQUAL(p.VFPTableNumber, 3);
    BOOST_CHECK_EQUAL(p.ALQValue.get<double>(), 10.);
    const auto& controls = p.controls(st, 0);
    BOOST_CHECK_EQUAL(controls.bhp_limit, 101325.);
}

BOOST_AUTO_TEST_CASE(WCH_BHP_Specified)
{
    Opm::SummaryState st(TimeService::now(), 0.0);
    const Opm::Well::WellProductionProperties p =
        WCONHIST::properties(WCONHIST::bhp_defaulted());

    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::ORAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::WRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::GRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::LRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::RESV));

    BOOST_CHECK(p.controlMode == Opm::Well::ProducerCMode::BHP);

    BOOST_CHECK_EQUAL(true, p.hasProductionControl(Opm::Well::ProducerCMode::BHP));
    const auto& controls = p.controls(st, 0);
    BOOST_CHECK_EQUAL(controls.bhp_limit, 5e7);
}

BOOST_AUTO_TEST_CASE(WCONPROD_ORAT_CMode)
{
    const Opm::Well::WellProductionProperties& p = WCONPROD::properties(WCONPROD::orat_CMODE_other_defaulted());

    BOOST_CHECK( p.hasProductionControl(Opm::Well::ProducerCMode::ORAT));
    BOOST_CHECK( p.hasProductionControl(Opm::Well::ProducerCMode::WRAT));
    BOOST_CHECK( p.hasProductionControl(Opm::Well::ProducerCMode::GRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::LRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::RESV));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::THP));

    BOOST_CHECK(p.controlMode == Opm::Well::ProducerCMode::ORAT);

    BOOST_CHECK_EQUAL(true, p.hasProductionControl(Opm::Well::ProducerCMode::BHP));

    BOOST_CHECK_EQUAL(p.VFPTableNumber, 0);
    BOOST_CHECK_EQUAL(p.ALQValue.get<double>(), 0.);
}

BOOST_AUTO_TEST_CASE(WCONPROD_THP_CMode)
{
    const Opm::Well::WellProductionProperties& p =
        WCONPROD::properties(WCONPROD::thp_CMODE(), VFPProdTable::ALQ_TYPE::ALQ_UNDEF);

    BOOST_CHECK( p.hasProductionControl(Opm::Well::ProducerCMode::ORAT));
    BOOST_CHECK( p.hasProductionControl(Opm::Well::ProducerCMode::WRAT));
    BOOST_CHECK( p.hasProductionControl(Opm::Well::ProducerCMode::GRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::LRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::RESV));
    BOOST_CHECK( p.hasProductionControl(Opm::Well::ProducerCMode::THP));

    BOOST_CHECK(p.controlMode == Opm::Well::ProducerCMode::THP);

    BOOST_CHECK_EQUAL(true, p.hasProductionControl(Opm::Well::ProducerCMode::BHP));

    BOOST_CHECK_EQUAL(p.VFPTableNumber, 8);
    BOOST_CHECK_EQUAL(p.ALQValue.get<double>(), 13.);
    BOOST_CHECK_EQUAL(p.THPTarget.getSI(), 1000000.); // 10 barsa
    BOOST_CHECK_EQUAL(p.BHPTarget.getSI(), 101325.); // 1 atm.
}

BOOST_AUTO_TEST_CASE(WCONPROD_BHP_CMode)
{
    const Opm::Well::WellProductionProperties& p =
        WCONPROD::properties(WCONPROD::bhp_CMODE(), VFPProdTable::ALQ_TYPE::ALQ_UNDEF);

    BOOST_CHECK( p.hasProductionControl(Opm::Well::ProducerCMode::ORAT));
    BOOST_CHECK( p.hasProductionControl(Opm::Well::ProducerCMode::WRAT));
    BOOST_CHECK( p.hasProductionControl(Opm::Well::ProducerCMode::GRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::LRAT));
    BOOST_CHECK( !p.hasProductionControl(Opm::Well::ProducerCMode::RESV));
    BOOST_CHECK( p.hasProductionControl(Opm::Well::ProducerCMode::THP));

    BOOST_CHECK(p.controlMode == Opm::Well::ProducerCMode::BHP);

    BOOST_CHECK_EQUAL(true, p.hasProductionControl(Opm::Well::ProducerCMode::BHP));

    BOOST_CHECK_EQUAL(p.VFPTableNumber, 8);
    BOOST_CHECK_EQUAL(p.ALQValue.get<double>(), 13.);
    BOOST_CHECK_EQUAL(p.THPTarget.get<double>(), 10.); // 10 barsa
    BOOST_CHECK_EQUAL(p.BHPTarget.get<double>(), 20.); // 20 barsa
    BOOST_CHECK_EQUAL(p.THPTarget.getSI(), 1000000.); // 10 barsa
    BOOST_CHECK_EQUAL(p.BHPTarget.getSI(), 2000000.); // 20 barsa
}


BOOST_AUTO_TEST_CASE(BHP_CMODE)
{
    BOOST_CHECK_THROW( WCONHIST::properties(WCONHIST::all_specified_CMODE_THP()) , std::exception);
    BOOST_CHECK_THROW( WCONPROD::properties(WCONPROD::all_specified_CMODE_BHP()) , std::exception);
}




BOOST_AUTO_TEST_CASE(CMODE_DEFAULT) {
    auto unit_system = UnitSystem::newMETRIC();
    const Opm::Well::WellProductionProperties Pproperties(unit_system, "W");
    const Opm::Well::WellInjectionProperties Iproperties(unit_system, "W");

    BOOST_CHECK( Pproperties.controlMode == Opm::Well::ProducerCMode::CMODE_UNDEFINED );
    BOOST_CHECK( Iproperties.controlMode == Opm::Well::InjectorCMode::CMODE_UNDEFINED );
}




BOOST_AUTO_TEST_CASE(WELL_CONTROLS) {
    auto unit_system = UnitSystem::newMETRIC();
    Opm::Well well("WELL", "GROUP", 0, 0, 0, 0, 1000, Opm::WellType(Opm::Phase::OIL), Opm::Well::ProducerCMode::CMODE_UNDEFINED, Opm::Connection::Order::DEPTH, unit_system, 0, 1.0, false, false, 0, Opm::Well::GasInflowEquation::STD);
    Opm::Well::WellProductionProperties prod(unit_system, "OP1");
    Opm::SummaryState st(Opm::TimeService::now(), 0.0);
    well.productionControls(st);

    // Use a scalar FIELD variable - that should work; although it is a bit weird.
    st.update("FUX", 1);
    prod.OilRate = UDAValue("FUX");
    BOOST_CHECK_EQUAL(1, prod.controls(st, 0).oil_rate);


    // Use the wellrate WUX for well OP1; the well is now added with
    // SummaryState::update_well_var() and we should automatically fetch the
    // correct well value.
    prod.OilRate = UDAValue("WUX");
    st.update_well_var("OP1", "WUX", 10);
    BOOST_CHECK_EQUAL(10, prod.controls(st, 0).oil_rate);
}


BOOST_AUTO_TEST_CASE(ExtraAccessors) {
    Opm::Well inj("WELL1" , "GROUP", 0, 1, 0, 0, 0.0,  Opm::WellType(Opm::Phase::OIL), Opm::Well::ProducerCMode::CMODE_UNDEFINED, Connection::Order::DEPTH, UnitSystem::newMETRIC(), 0, 1.0, false, false, 0, Opm::Well::GasInflowEquation::STD);
    Opm::Well prod("WELL1" , "GROUP", 0, 1, 0, 0, 0.0, Opm::WellType(Opm::Phase::OIL), Opm::Well::ProducerCMode::CMODE_UNDEFINED, Connection::Order::DEPTH, UnitSystem::newMETRIC(), 0, 1.0, false, false, 0, Opm::Well::GasInflowEquation::STD);

    auto inj_props= std::make_shared<Opm::Well::WellInjectionProperties>(inj.getInjectionProperties());
    inj_props->VFPTableNumber = 100;
    inj.updateInjection(inj_props);

    auto prod_props = std::make_shared<Opm::Well::WellProductionProperties>( prod.getProductionProperties() );
    prod_props->VFPTableNumber = 200;
    prod.updateProduction(prod_props);

    BOOST_CHECK_THROW(prod.inj_temperature(), std::logic_error);
    BOOST_CHECK_EQUAL(inj.vfp_table_number(), 100);
    BOOST_CHECK_EQUAL(prod.vfp_table_number(), 200);
}

BOOST_AUTO_TEST_CASE(WELOPEN) {
    const std::string input =
                "START             -- 0 \n"
                "19 JUN 2007 / \n"
                "GRID\n"
                "PORO\n"
                "1000*0.1  /\n"
                "PERMX \n"
                "1000*1 /\n"
                "PERMY \n"
                "1000*0.1 /\n"
                "PERMZ \n"
                "1000*0.01 /\n"
                "SCHEDULE\n"
                "DATES             -- 1\n"
                " 10  OKT 2008 / \n"
                "/\n"
                "WELSPECS\n"
                "    'OP_1'       'OP'   9   9 1*     'OIL' 1*      1*  1*   1*  1*   1*  1*  / \n"
                "/\n"
                "COMPDAT\n"
                " 'OP_1'  9  9   1   1 'OPEN' 1*   32.948   0.311  3047.839 1*  1*  'X'  22.100 / \n"
                " 'OP_1'  9  9   3   9 'OPEN' 1*   32.948   0.311  3047.839 1*  1*  'X'  22.100 / \n"
                " 'OP_1'  9  9   2   2 'OPEN' 1*   46.825   0.311  4332.346 1*  1*  'X'  22.123 / \n"
                "/\n"
                "WELOPEN \n"
                " 'OP_1'  'OPEN' /\n"
                "/\n"
                "DATES             -- 2\n"
                " 20  JAN 2010 / \n"
                "/\n"
                "WELOPEN \n"
                " 'OP_1'  'SHUT' 0 0 0 2* /\n"
                "/\n";

    const auto deck = Parser{}.parseString(input);
    EclipseGrid grid(10,10,10);
    const TableManager table ( deck );
    const FieldPropsManager fp(deck, Phases{true, true, true}, grid, table);
    const Runspec runspec (deck);
    const Schedule schedule {
        deck, grid, fp, NumericalAquifers{},
        runspec, std::make_shared<Python>()
    };

    {
        const auto& op_1 = schedule.getWell("OP_1", 1);
        BOOST_CHECK(op_1.getStatus() == Well::Status::OPEN);
    }

    {
        const auto& op_1 = schedule.getWell("OP_1", 2);
        BOOST_CHECK(op_1.getStatus() == Well::Status::SHUT);
    }
}


BOOST_AUTO_TEST_CASE(WellTypeTest) {
    BOOST_CHECK_THROW(Opm::WellType(0, 3), std::invalid_argument);
    BOOST_CHECK_THROW(Opm::WellType(5, 3), std::invalid_argument);
    BOOST_CHECK_THROW(Opm::WellType(3, 0), std::invalid_argument);
    BOOST_CHECK_THROW(Opm::WellType(3, 5), std::invalid_argument);

    Opm::WellType wt1(1,1);
    BOOST_CHECK(wt1.producer());
    BOOST_CHECK(!wt1.injector());
    BOOST_CHECK_EQUAL(wt1.ecl_wtype(), 1);
    BOOST_CHECK_EQUAL(wt1.ecl_phase(), 1);
    BOOST_CHECK(wt1.preferred_phase() == Phase::OIL);
    BOOST_CHECK_THROW(wt1.injector_type(), std::invalid_argument);

    Opm::WellType wt4(4,3);
    BOOST_CHECK(!wt4.producer());
    BOOST_CHECK(wt4.injector());
    BOOST_CHECK_EQUAL(wt4.ecl_wtype(), 4);
    BOOST_CHECK_EQUAL(wt4.ecl_phase(), 3);
    BOOST_CHECK(wt4.preferred_phase() == Phase::GAS);
    BOOST_CHECK(wt4.injector_type() == InjectorType::GAS);

    BOOST_CHECK(wt4.update(true));
    BOOST_CHECK(!wt4.update(true));
    BOOST_CHECK(wt4.producer());
    BOOST_CHECK(!wt4.injector());
    BOOST_CHECK_EQUAL(wt4.ecl_wtype(), 1);
    BOOST_CHECK_EQUAL(wt4.ecl_phase(), 3);
    BOOST_CHECK(wt4.preferred_phase() == Phase::GAS);

    Opm::WellType wtp(false, Phase::WATER);
    BOOST_CHECK(!wtp.producer());
    BOOST_CHECK(wtp.injector());
    BOOST_CHECK_EQUAL(wtp.ecl_wtype(), 3);
    BOOST_CHECK_EQUAL(wtp.ecl_phase(), 2);
    BOOST_CHECK(wtp.preferred_phase() == Phase::WATER);
    BOOST_CHECK(wtp.injector_type() == InjectorType::WATER);

    wtp.update( InjectorType::GAS );
    BOOST_CHECK_EQUAL(wtp.ecl_wtype(), 4);
    BOOST_CHECK_EQUAL(wtp.ecl_phase(), 2);
    BOOST_CHECK(wtp.preferred_phase() == Phase::GAS);
    BOOST_CHECK(wtp.injector_type() == InjectorType::GAS);
}

BOOST_AUTO_TEST_CASE(Injector_Control_Mode) {
    using IMode = ::Opm::Well::InjectorCMode;
    using IType = ::Opm::InjectorType;

    BOOST_CHECK_EQUAL(Well::eclipseControlMode(IMode::GRUP, IType::GAS), -1);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(IMode::GRUP, IType::WATER), -1);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(IMode::GRUP, IType::MULTI), -1);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(IMode::GRUP, IType::OIL), -1);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(IMode::RATE, IType::OIL), 1);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(IMode::RATE, IType::WATER), 2);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(IMode::RATE, IType::GAS), 3);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(IMode::RATE, IType::MULTI), -10);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(IMode::RESV, IType::GAS), 5);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(IMode::RESV, IType::WATER), 5);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(IMode::RESV, IType::MULTI), 5);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(IMode::RESV, IType::OIL), 5);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(IMode::THP, IType::GAS), 6);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(IMode::THP, IType::WATER), 6);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(IMode::THP, IType::MULTI), 6);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(IMode::THP, IType::OIL), 6);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(IMode::BHP, IType::GAS), 7);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(IMode::BHP, IType::WATER), 7);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(IMode::BHP, IType::MULTI), 7);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(IMode::BHP, IType::OIL), 7);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(IMode::CMODE_UNDEFINED, IType::WATER), -10);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(static_cast<IMode>(1729), IType::WATER), -10);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(static_cast<IMode>(1729), IType::WATER), -10); // Unknown combination
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(static_cast<IMode>(1729), IType::WATER), -10); // Unknown combination
}

BOOST_AUTO_TEST_CASE(Producer_Control_Mode) {
    using PMode = ::Opm::Well::ProducerCMode;

    BOOST_CHECK_EQUAL(Well::eclipseControlMode(PMode::GRUP), -1);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(PMode::ORAT), 1);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(PMode::WRAT), 2);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(PMode::GRAT), 3);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(PMode::LRAT), 4);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(PMode::RESV), 5);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(PMode::THP ), 6);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(PMode::BHP ), 7);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(PMode::CRAT), 9);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(PMode::NONE), -10);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(PMode::CMODE_UNDEFINED), -10);
    BOOST_CHECK_EQUAL(Well::eclipseControlMode(static_cast<PMode>(271828)), -10);
}

BOOST_AUTO_TEST_CASE(WPIMULT) {
    std::string input = R"(
START             -- 0
19 JUN 2007 /

REGIONS

PVTNUM
 1000*77 /

GRID
PORO
 1000*0.1 /
PERMX
 1000*1 /
PERMY
 1000*0.1 /
PERMZ
 1000*0.01 /
SCHEDULE

WELSPECS
    'OP_1'       'OP'   9   9 1*     'OIL' 1*      1*  1*   1*  1*   1*  1*  /
    'OP_2'       'OP'   9   9 1*     'OIL' 1*      1*  1*   1*  66 /
/
COMPDAT
 'OP_1'  9  9   1   1 'OPEN' 1*   1.0 0.311  3047.839 1*  1*  'X'  22.100 /
 'OP_1'  9  9   2   2 'OPEN' 1*   2.0 0.311  3047.839 1*  1*  'X'  22.100 /
 'OP_1'  9  9   3   3 'OPEN' 1*   3.0 0.311  4332.346 1*  1*  'X'  22.123 /
/
DATES             -- 1
 20  JAN 2010 /
/

-- Should not hit any connections
WPIMULT
  'OP_1'  2  5  /
/

DATES             -- 2
 20  FEB 2010 /
/

--
WPIMULT
  'OP_1'  2  9  9 1 /
/

DATES             -- 3
 20  MAR 2010 /
/

--
WPIMULT
  'OP_1'  2  9  9 2 /
/

DATES             -- 4
 20  APR 2010 /
/

--
WPIMULT
  'OP_1'  2  9  9  3 /
/

DATES             -- 5
 20  JUN 2010 /
/

--
WPIMULT
  'OP_1'  0.5 /
/

)";

    const auto deck = Parser{}.parseString(input);
    const auto& units = deck.getActiveUnitSystem();
    EclipseGrid grid(10,10,10);
    const TableManager table ( deck );
    const FieldPropsManager fp(deck, Phases{true, true, true}, grid, table);
    const Runspec runspec (deck);
    const Schedule schedule {
        deck, grid, fp, NumericalAquifers{},
        runspec, std::make_shared<Opm::Python>()
    };

    const auto CF0 = units.to_si(Opm::UnitSystem::measure::transmissibility, 1.0);
    {
        const auto& well = schedule.getWell("OP_1", 0);
        const auto& connections = well.getConnections();
        BOOST_CHECK_CLOSE( connections[0].CF(), 1.0 * CF0, 1e-6);
        BOOST_CHECK_CLOSE( connections[1].CF(), 2.0 * CF0, 1e-6);
        BOOST_CHECK_CLOSE( connections[2].CF(), 3.0 * CF0, 1e-6);
        BOOST_CHECK_EQUAL( well.pvt_table_number(), 77);

        const auto& well2 = schedule.getWell("OP_2", 0);
        BOOST_CHECK_EQUAL( well2.pvt_table_number(), 66);
    }
    {
        const auto& well = schedule.getWell("OP_1", 1);
        const auto& connections = well.getConnections();
        BOOST_CHECK_CLOSE( connections[0].CF(), 1.0 * CF0, 1e-6);
        BOOST_CHECK_CLOSE( connections[1].CF(), 2.0 * CF0, 1e-6);
        BOOST_CHECK_CLOSE( connections[2].CF(), 3.0 * CF0, 1e-6);
    }
    {
        const auto& well = schedule.getWell("OP_1", 2);
        const auto& connections = well.getConnections();
        BOOST_CHECK_CLOSE( connections[0].CF(), 2.0 * CF0, 1e-6);
        BOOST_CHECK_CLOSE( connections[1].CF(), 2.0 * CF0, 1e-6);
        BOOST_CHECK_CLOSE( connections[2].CF(), 3.0 * CF0, 1e-6);
    }
    {
        const auto& well = schedule.getWell("OP_1", 3);
        const auto& connections = well.getConnections();
        BOOST_CHECK_CLOSE( connections[0].CF(), 2.0 * CF0, 1e-6);
        BOOST_CHECK_CLOSE( connections[1].CF(), 4.0 * CF0, 1e-6);
        BOOST_CHECK_CLOSE( connections[2].CF(), 3.0 * CF0, 1e-6);
    }
    {
        const auto& well = schedule.getWell("OP_1", 4);
        const auto& connections = well.getConnections();
        BOOST_CHECK_CLOSE( connections[0].CF(), 2.0 * CF0, 1e-6);
        BOOST_CHECK_CLOSE( connections[1].CF(), 4.0 * CF0, 1e-6);
        BOOST_CHECK_CLOSE( connections[2].CF(), 6.0 * CF0, 1e-6);
    }
    {
        const auto& well = schedule.getWell("OP_1", 5);
        const auto& connections = well.getConnections();
        BOOST_CHECK_CLOSE( connections[0].CF(), 1.0 * CF0, 1e-6);
        BOOST_CHECK_CLOSE( connections[1].CF(), 2.0 * CF0, 1e-6);
        BOOST_CHECK_CLOSE( connections[2].CF(), 3.0 * CF0, 1e-6);
    }
}


BOOST_AUTO_TEST_CASE(FIRST_OPEN) {
    std::string input = R"(
START             -- 0
19 JUN 2007 /

REGIONS

PVTNUM
 1000*77 /

GRID
PORO
 1000*0.1 /
PERMX
 1000*1 /
PERMY
 1000*0.1 /
PERMZ
 1000*0.01 /

SCHEDULE

WELSPECS
    'P'       'OP'   9   9 1*     'OIL' 1*      1*  1*   1*  1*   1*  1*  /
    'I'       'OP'   9   9 1*     'OIL' 1*      1*  1*   1*  66 /
/

COMPDAT
 'P'  9  9   2   2 'OPEN' 1*   2.0 0.311  3047.839 1*  1*  'X'  22.100 /
 'I'  9  9   3   3 'OPEN' 1*   3.0 0.311  4332.346 1*  1*  'X'  22.123 /
/

DATES             -- 1
 20  JAN 2010 /
/

DATES             -- 2
 20  FEB 2010 /
/

WCONPROD
 'P' 'OPEN' 'BHP' 1 2 3 2* 20. 2* 13 /
/

WCONINJE
 'I' 'GAS' 'OPEN' 'RATE'  1000 /
/
)";

    const auto deck = Parser{}.parseString(input);
    EclipseGrid grid(10,10,10);
    const TableManager table ( deck );
    const FieldPropsManager fp(deck, Phases{true, true, true}, grid, table);
    const Runspec runspec (deck);
    const Schedule schedule {
        deck, grid, fp, NumericalAquifers{},
        runspec, std::make_shared<Python>()
    };

    {
        const auto& iwell = schedule.getWell("I", 0);
        const auto& pwell = schedule.getWell("P", 0);

        BOOST_CHECK( iwell.getStatus() == Well::Status::SHUT );
        BOOST_CHECK( pwell.getStatus() == Well::Status::SHUT );
        BOOST_CHECK( !iwell.hasProduced() );
        BOOST_CHECK( !pwell.hasProduced() );
    }
    {
        const auto& iwell = schedule.getWell("I", 2);
        const auto& pwell = schedule.getWell("P", 2);

        BOOST_CHECK( iwell.getStatus() == Well::Status::OPEN );
        BOOST_CHECK( pwell.getStatus() == Well::Status::OPEN );
        BOOST_CHECK( !iwell.hasProduced() );
        BOOST_CHECK( pwell.hasProduced() );
    }
}


BOOST_AUTO_TEST_CASE(WellPI) {
    const auto deck = Parser{}.parseString(R"(RUNSPEC
START
7 OCT 2020 /

DIMENS
  10 10 3 /

GRID
DXV
  10*100.0 /
DYV
  10*100.0 /
DZV
  3*10.0 /

DEPTHZ
  121*2000.0 /

PERMX
  300*100.0 /
PERMY
  300*100.0 /
PERMZ
  300*10.0 /
PORO
  300*0.3 /

SCHEDULE
WELSPECS
  'P' 'G' 10 10 2005 'LIQ' /
/
COMPDAT
  'P' 0 0 1 3 OPEN 1 100 /
/

END
)");

    const auto es    = EclipseState{ deck };
    const auto sched = Schedule{ deck, es };

    const auto expectCF = 100.0*cp_rm3_per_db();

    auto wellP = sched.getWell("P", 0);

    for (const auto& conn : wellP.getConnections()) {
        BOOST_CHECK_CLOSE(conn.CF(), expectCF, 1.0e-10);
    }

    // Simulate applying WELPI before WELPI keyword.  No effect.
    {
        std::vector<bool> scalingApplicable;
        wellP.applyWellProdIndexScaling(2.7182818, scalingApplicable);
        for (const auto& conn : wellP.getConnections()) {
            BOOST_CHECK_CLOSE(conn.CF(), expectCF, 1.0e-10);
        }

        for (const bool applicable : scalingApplicable) {
            BOOST_CHECK_MESSAGE(! applicable, "No connection must be eligible for WELPI scaling");
        }
    }

    // Simulate applying WELPI after seeing
    //
    //   WELPI
    //     P 2 /
    //   /
    //
    // (ignoring units of measure)
    BOOST_CHECK_MESSAGE(wellP.updateWellProductivityIndex(),
                        "First call to updateWellProductivityIndex() must be a state change");
    BOOST_CHECK_MESSAGE(!wellP.updateWellProductivityIndex(),
                        "Second call to updateWellProductivityIndex() must NOT be a state change");

    // Want PI=2, but actual/effective PI=1 => scale CF by 2.0/1.0.
    {
        const auto scalingFactor = wellP.convertDeckPI(2.0) /  liquid_PI_unit();
        BOOST_CHECK_CLOSE(scalingFactor, 2.0, 1.0e-10);

        std::vector<bool> scalingApplicable;
        wellP.applyWellProdIndexScaling(scalingFactor, scalingApplicable);
        for (const auto& conn : wellP.getConnections()) {
            BOOST_CHECK_CLOSE(conn.CF(), 2.0*expectCF, 1.0e-10);
        }

        for (const bool applicable : scalingApplicable) {
            BOOST_CHECK_MESSAGE(applicable, "All connections must be eligible for WELPI scaling");
        }
    }

    // Repeated application of WELPI multiplies scaling factors.
    {
        const auto scalingFactor = wellP.convertDeckPI(2.0) /  liquid_PI_unit();
        BOOST_CHECK_CLOSE(scalingFactor, 2.0, 1.0e-10);

        std::vector<bool> scalingApplicable;
        wellP.applyWellProdIndexScaling(scalingFactor, scalingApplicable);
        for (const auto& conn : wellP.getConnections()) {
            BOOST_CHECK_CLOSE(conn.CF(), 4.0*expectCF, 1.0e-10);
        }

        for (const bool applicable : scalingApplicable) {
            BOOST_CHECK_MESSAGE(applicable, "All connections must be eligible for WELPI scaling");
        }
    }

    // New WELPI record does not reset the scaling factors
    wellP.updateWellProductivityIndex();
    for (const auto& conn : wellP.getConnections()) {
        BOOST_CHECK_CLOSE(conn.CF(), 4.0*expectCF, 1.0e-10);
    }

    // Effective PI=desired PI => no scaling change
    {
        const auto scalingFactor = wellP.convertDeckPI(3.0) /  (3.0*liquid_PI_unit());
        BOOST_CHECK_CLOSE(scalingFactor, 1.0, 1.0e-10);

        std::vector<bool> scalingApplicable;
        wellP.applyWellProdIndexScaling(scalingFactor, scalingApplicable);
        for (const auto& conn : wellP.getConnections()) {
            BOOST_CHECK_CLOSE(conn.CF(), 4.0*expectCF, 1.0e-10);
        }

        for (const bool applicable : scalingApplicable) {
            BOOST_CHECK_MESSAGE(applicable, "All connections must be eligible for WELPI scaling");
        }
    }
}

BOOST_AUTO_TEST_CASE(Has_Same_Connections_Pointers) {
    const auto deck = Parser{}.parseString(R"(RUNSPEC
START
7 OCT 2020 /

DIMENS
  10 10 3 /

GRID
DXV
  10*100.0 /
DYV
  10*100.0 /
DZV
  3*10.0 /

DEPTHZ
  121*2000.0 /

PERMX
  300*100.0 /
PERMY
  300*100.0 /
PERMZ
  300*10.0 /
PORO
  300*0.3 /

SCHEDULE
WELSPECS
  'P' 'G' 10 10 2005 'LIQ' /
/
COMPDAT
  'P' 0 0 1 3 OPEN 1 100 /
/

END
)");

    const auto es    = EclipseState{ deck };
    const auto sched = Schedule{ deck, es };

    const auto wellP = sched.getWell("P", 0);
    auto wellQ = wellP;

    BOOST_CHECK_MESSAGE(wellP.hasSameConnectionsPointers(wellQ),
                        "P and Q must have the same internal connections pointers");

    auto connQ = std::make_shared<WellConnections>(wellP.getConnections());
    wellQ.updateConnections(std::move(connQ), true);
    BOOST_CHECK_MESSAGE(! wellP.hasSameConnectionsPointers(wellQ),
                        "P and Q must NOT have the same internal connections pointers "
                        "after forcibly updating the connections structure");

    BOOST_CHECK_MESSAGE(wellP.getConnections() == wellQ.getConnections(),
                        "P and Q must have same WellConnections VALUE");
}



BOOST_AUTO_TEST_CASE(REPERF) {
      const auto deck = Parser{}.parseString(R"(RUNSPEC
START
7 OCT 2020 /

DIMENS
  10 10 4 /

GRID
DXV
  10*100.0 /
DYV
  10*100.0 /
DZV
  4*10.0 /

DEPTHZ
  121*2000.0 /

PERMX
  400*100.0 /
PERMY
  400*100.0 /
PERMZ
  400*10.0 /
PORO
  400*0.3 /

SCHEDULE

WELSPECS
     'W1'   'G' 1  1  1*       'OIL'  2*      'STOP'  4* /
/

COMPDAT
     'W1'   1 1 4 4      'OPEN'  1*     34.720      0.216   3095.832  2*         'Y'     12.828 /
     'W1'   1 1 3 3      'OPEN'  1*     34.720      0.216   3095.832  2*         'Y'     12.828 /
/
-- W0

TSTEP
  1 /

COMPDAT
     'W1'     1    1     2    2      'OPEN'  1*     25.620      0.216   2086.842  2*         'Y'      8.486 /
/

-- W1
TSTEP
  1 /

WELSPECS
  'W1' 'G' 1 1 1995.0 'LIQ' /
/

-- W2

TSTEP
1 /

WELSPECS
  'W1' 'G' 1 1 1* 'LIQ' /
/
-- W3

WPAVEDEP
  'W1'  0 /
/

TSTEP
1  /

COMPDAT
     'W1'     1    1     1    1      'OPEN'  1*     25.620      0.216   2086.842  2*         'Y'      8.486 /
/
-- W4
TSTEP
1 /

WELSPECS
  'W1' 'G' 1 1 -1.0 'LIQ' /
/
-- W5

END
)");

    const auto es    = EclipseState{ deck };
    const auto& grid = es.getInputGrid();
    const auto sched = Schedule{ deck, es };

    const auto& w0 = sched.getWell("W1", 0);
    const auto& w1 = sched.getWell("W1", 1);
    const auto& w2 = sched.getWell("W1", 2);
    const auto& w3 = sched.getWell("W1", 3);
    const auto& w4 = sched.getWell("W1", 4);
    const auto& w5 = sched.getWell("W1", 5);

    BOOST_CHECK_CLOSE(w0.getRefDepth()     , grid.getCellDepth(0, 0, 2), 1.0e-8);
    BOOST_CHECK_CLOSE(w0.getRefDepth()     , w0.getWPaveRefDepth()     , 1.0e-8);
    BOOST_CHECK_CLOSE(w1.getRefDepth()     , w0.getRefDepth()          , 1.0e-8);
    BOOST_CHECK_CLOSE(w2.getRefDepth()     , 1995.0                    , 1.0e-8);
    BOOST_CHECK_CLOSE(w3.getRefDepth()     , 1995.0                    , 1.0e-8);
    BOOST_CHECK_CLOSE(w4.getRefDepth()     , w3.getRefDepth()          , 1.0e-8);
    BOOST_CHECK_CLOSE(w5.getRefDepth()     , grid.getCellDepth(0, 0, 0), 1.0e-8);
    BOOST_CHECK_CLOSE(w5.getWPaveRefDepth(), 0.0                       , 1.0e-8);
}

BOOST_AUTO_TEST_CASE(Missing_RefDepth) {
      const auto deck = Parser{}.parseString(R"(RUNSPEC
START
17 DEC 2021 /

DIMENS
  10 10 4 /
GRID
DXV
  10*100.0 /
DYV
  10*100.0 /
DZV
  4*10.0 /

DEPTHZ
  121*2000.0 /

PERMX
  400*100.0 /
PERMY
  400*100.0 /
PERMZ
  400*10.0 /
PORO
  400*0.3 /

-- Deactivate Cells (1,1,3) And (1,1,4)
ACTNUM
  1 99*1
  1 99*1
  0 99*1
  0 99*1
/

SCHEDULE

WELSPECS
     'W1'   'G' 1  1  1*       'OIL'  2*      'STOP'  4* /
/

COMPDAT
     'W1'   1 1 4 4      'OPEN'  1*     34.720      0.216   3095.832  2*         'Y'     12.828 /
     'W1'   1 1 3 3      'OPEN'  1*     34.720      0.216   3095.832  2*         'Y'     12.828 /
/

TSTEP
  1 /

COMPDAT
     'W1'   1 1 2 2      'OPEN'  1*     25.620      0.216   2086.842  2*         'Y'      8.486 /
/

TSTEP
  1 /

END
)");

    const auto es    = EclipseState{ deck };
    const auto sched = Schedule{ deck, es };

    const auto& w0 = sched[0].wells("W1");
    BOOST_CHECK_MESSAGE(! w0.hasRefDepth(),
                        R"(Well "W1" must NOT have a BHP reference depth at report=1)");
    BOOST_CHECK_THROW(w0.getRefDepth(), std::logic_error);

    const auto& w1 = sched[1].wells("W1");
    BOOST_CHECK_MESSAGE(w1.hasRefDepth(),
                        R"(Well "W1" must have a BHP reference depth at report=2)");
    BOOST_CHECK_CLOSE(w1.getRefDepth(), es.getInputGrid().getCellDepth(0, 0, 1), 1.0e-8);
}

BOOST_AUTO_TEST_CASE(Update_Group_Single_Well)
{
    const auto deck = Parser{}.parseString(R"(RUNSPEC
DIMENS
10 10 3 /
GRID
DXV
10*100.0 /
DYV
10*100.0 /
DZV
3*5.0 /
DEPTHZ
121*2000 /
PERMX
300*100.0 /
COPY
PERMX PERMY /
PERMX PERMZ /
/
MULTIPLY
PERMZ 0.1 /
/
PORO
300*0.3 /
SCHEDULE
WELSPECS
 'P' 'G' 10 10 1* 'OIL' /
 'I' 'G'  1  1 1* 'GAS' /
/
COMPDAT
 'P' 10 10 1 3 'OPEN' /
 'I'  1  1 1 1 'OPEN' /
/
WCONPROD
 'P' 'OPEN' 'LRAT' 1* 1* 1* 1234.567 1* 12.34 /
/
WCONINJE
 'I' 'GAS' 'OPEN' 'RATE' 20.0E3 /
/
TSTEP
30.0 /
WELSPECS
 'P' 'G1' /
/
TSTEP
 30.0 /
END
)");

    const auto es    = EclipseState { deck };
    const auto sched = Schedule { deck, es };

    {
        const auto& wellP = sched.getWell("P", 0);
        BOOST_CHECK_EQUAL(wellP.groupName(), "G");
    }

    {
        const auto& wellP = sched.getWell("P", 1);
        BOOST_CHECK_EQUAL(wellP.groupName(), "G1");
    }
}

BOOST_AUTO_TEST_CASE(Update_Group_Multi_Well)
{
    const auto deck = Parser{}.parseString(R"(RUNSPEC
DIMENS
10 10 3 /
GRID
DXV
10*100.0 /
DYV
10*100.0 /
DZV
3*5.0 /
DEPTHZ
121*2000 /
PERMX
300*100.0 /
COPY
PERMX PERMY /
PERMX PERMZ /
/
MULTIPLY
PERMZ 0.1 /
/
PORO
300*0.3 /
SCHEDULE
WELSPECS
 'P1' 'G'  1 10 1* 'OIL' /
 'P2' 'G' 10  1 1* 'OIL' /
 'P3' 'G' 10 10 1* 'OIL' /
 'I'  'G'  1  1 1* 'GAS' /
/
COMPDAT
 'P1'  1 10 1 3 'OPEN' /
 'P2' 10  1 1 3 'OPEN' /
 'P3' 10 10 1 3 'OPEN' /
 'I'   1  1 1 1 'OPEN' /
/
WCONPROD
 'P*' 'OPEN' 'LRAT' 1* 1* 1* 1234.567 1* 12.34 /
/
WCONINJE
 'I' 'GAS' 'OPEN' 'RATE' 20.0E3 /
/
TSTEP
30.0 /
WELSPECS
 'P*' 'G1' /
/
TSTEP
 30.0 /
END
)");

    const auto es    = EclipseState { deck };
    const auto sched = Schedule { deck, es };

    {
        for (const auto* P : { "P1", "P2", "P3", }) {
            const auto& wellP = sched.getWell(P, 0);
            BOOST_CHECK_MESSAGE(wellP.groupName() == "G",
                                "Well " << P << " must have "
                                "controlling group \"G\" at time zero");
        }
    }

    {
        for (const auto* P : { "P1", "P2", "P3", }) {
            const auto& wellP = sched.getWell(P, 1);
            BOOST_CHECK_MESSAGE(wellP.groupName() == "G1",
                                "Well " << P << " must have "
                                "controlling group \"G1\" at time one");
        }
    }
}

BOOST_AUTO_TEST_CASE(Update_Group_WList)
{
    const auto deck = Parser{}.parseString(R"(RUNSPEC
DIMENS
10 10 3 /
GRID
DXV
10*100.0 /
DYV
10*100.0 /
DZV
3*5.0 /
DEPTHZ
121*2000 /
PERMX
300*100.0 /
COPY
PERMX PERMY /
PERMX PERMZ /
/
MULTIPLY
PERMZ 0.1 /
/
PORO
300*0.3 /
SCHEDULE
WELSPECS
 'P1' 'G'  1 10 1* 'OIL' /
 'P2' 'G' 10  1 1* 'OIL' /
 'P3' 'G' 10 10 1* 'OIL' /
 'I'  'G'  1  1 1* 'GAS' /
/
COMPDAT
 'P1'  1 10 1 3 'OPEN' /
 'P2' 10  1 1 3 'OPEN' /
 'P3' 10 10 1 3 'OPEN' /
 'I'   1  1 1 1 'OPEN' /
/
WLIST
 '*QFS' NEW 'I' 'P3' /
/
WCONPROD
 'P*' 'OPEN' 'LRAT' 1* 1* 1* 1234.567 1* 12.34 /
/
WCONINJE
 'I' 'GAS' 'OPEN' 'RATE' 20.0E3 /
/
TSTEP
30.0 /
WELSPECS
 '*QFS' 'G1' /
/
TSTEP
 30.0 /
END
)");

    const auto es    = EclipseState { deck };
    const auto sched = Schedule { deck, es };

    {
        for (const auto* P : { "P1", "P2", "P3", "I", }) {
            const auto& wellP = sched.getWell(P, 0);
            BOOST_CHECK_MESSAGE(wellP.groupName() == "G",
                                "Well " << P << " must have "
                                "controlling group \"G\" at time zero");
        }
    }

    {
        for (const auto* P : { "P1", "P2", }) {
            const auto& wellP = sched.getWell(P, 1);
            BOOST_CHECK_MESSAGE(wellP.groupName() == "G",
                                "Well " << P << " must have "
                                "controlling group \"G\" at time one");
        }

        for (const auto* P : { "P3", "I", }) {
            const auto& wellP = sched.getWell(P, 1);
            BOOST_CHECK_MESSAGE(wellP.groupName() == "G1",
                                "Well " << P << " must have "
                                "controlling group \"G1\" at time one");
        }
    }
}

BOOST_AUTO_TEST_CASE(FBHPDEF_Basic)
{
    const auto deck = Parser{}.parseString(R"(RUNSPEC
DIMENS
10 10 3 /
GRID
DXV
10*100.0 /
DYV
10*100.0 /
DZV
3*5.0 /
DEPTHZ
121*2000 /
PERMX
300*100.0 /
COPY
PERMX PERMY /
PERMX PERMZ /
/
MULTIPLY
PERMZ 0.1 /
/
PORO
300*0.3 /
SCHEDULE

WELSPECS
 'P' 'G' 10 10 1* 'OIL' /
 'I' 'G'  1  1 1* 'GAS' /
 'I2' 'W'  1  1 1* 'WATER' /
 'I3' 'W'  1  1 1* 'WATER' /
/
COMPDAT
 'P' 10 10 1 3 'OPEN' /
 'I'  1  1 1 1 'OPEN' /
 'I2'  1  1 1 1 'OPEN' /
 'I3'  1  1 1 1 'OPEN' /
/

WCONINJH
  I3 WATER OPEN 116281 1* 0 /
/

FBHPDEF
  5.0 20.0 /

WCONPROD
 'P' 'OPEN' 'LRAT' 1* 1* 1* 1234.567 1* 1* /
/

WCONINJH
  I2 WATER OPEN 116281 1* 0 /
/

FBHPDEF
  2.0 30.0 /

WCONINJE
 'I' 'GAS' 'OPEN' 'RATE' 20.0E3 /
/

TSTEP
30.0 /
WELSPECS
 'P' 'G1' /
/
TSTEP
 30.0 /
END
)");

    const auto es    = EclipseState { deck };
    const auto sched = Schedule { deck, es };

    const auto& wellP = sched.getWell("P", 0);
    BOOST_CHECK_EQUAL(wellP.getProductionProperties().BHPTarget.get<double>(), 5.0);

    const auto& wellI = sched.getWell("I", 0);
    BOOST_CHECK_CLOSE(wellI.getInjectionProperties().BHPTarget.get<double>(), 30.0, 1e-12);

    const auto& wellI2 = sched.getWell("I2", 0);
    BOOST_CHECK_EQUAL(wellI2.getInjectionProperties().bhp_hist_limit, 20.0 * unit::barsa);

    const auto& wellI3 = sched.getWell("I3", 0);
    BOOST_CHECK_CLOSE(wellI3.getInjectionProperties().bhp_hist_limit, 6891.2 * unit::barsa, 1e-12);
}

BOOST_AUTO_TEST_CASE(WCYCLE_Basic)
{
    const auto deck = Parser{}.parseString(R"(RUNSPEC
DIMENS
10 10 3 /
GRID
DXV
10*100.0 /
DYV
10*100.0 /
DZV
3*5.0 /
DEPTHZ
121*2000 /
PERMX
300*100.0 /
COPY
PERMX PERMY /
PERMX PERMZ /
/
MULTIPLY
PERMZ 0.1 /
/
PORO
300*0.3 /
SCHEDULE

WELSPECS
 'P' 'G' 10 10 1* 'OIL' /
 'I' 'G'  1  1 1* 'GAS' /
 'I2' 'W'  1  1 1* 'WATER' /
 'I3' 'W'  1  1 1* 'WATER' /
/
COMPDAT
 'P' 10 10 1 3 'OPEN' /
 'I'  1  1 1 1 'OPEN' /
 'I2'  1  1 1 1 'OPEN' /
 'I3'  1  1 1 1 'OPEN' /
/

WCONINJH
  I3 WATER OPEN 116281 1* 0 /
/

WCONPROD
 'P' 'OPEN' 'LRAT' 1* 1* 1* 1234.567 1* 1* /
/

WCONINJH
  I2 WATER OPEN 116281 1* 0 /
/

WCONINJE
 'I' 'GAS' 'OPEN' 'RATE' 20.0E3 /
/

WCYCLE
-- Name   OnTime OffTime RampUpTime MaxDtOn DtRespectOnOff
  'I1'    5       10        3        1        'NO'  /
  'I2'    7       30        1        10       'YES'  /
/

TSTEP
30.0 /
WELSPECS
 'P' 'G1' /
/
TSTEP
 30.0 /
END
)");

    const auto es    = EclipseState { deck };
    const auto sched = Schedule { deck, es };

    const auto& wcycle1 = sched[0].get<WCYCLE>().get();

    BOOST_CHECK(!wcycle1.empty());
    BOOST_CHECK_EQUAL(std::distance(wcycle1.begin(), wcycle1.end()), 2);

    for (const auto& entry : wcycle1) {
        if (entry.first == "I1") {
            BOOST_CHECK_EQUAL(entry.second.on_time, 5 * unit::day);
            BOOST_CHECK_EQUAL(entry.second.off_time, 10 * unit::day);
            BOOST_CHECK_EQUAL(entry.second.startup_time , 3 * unit::day);
            BOOST_CHECK_EQUAL(entry.second.max_time_step, 1 * unit::day);
            BOOST_CHECK_EQUAL(entry.second.controlled_time_step, false);
        }
        else if (entry.first == "I2") {
            BOOST_CHECK_EQUAL(entry.second.on_time, 7 * unit::day);
            BOOST_CHECK_EQUAL(entry.second.off_time, 30 * unit::day);
            BOOST_CHECK_EQUAL(entry.second.startup_time , 1 * unit::day);
            BOOST_CHECK_EQUAL(entry.second.max_time_step, 10 * unit::day);
            BOOST_CHECK_EQUAL(entry.second.controlled_time_step, true);
        }
        else {
            BOOST_FAIL("Unexpected WCYCLE entry with name " + entry.first);
        }
    }
}
