/*
  Copyright 2020 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/ScheduleTypes.hpp>

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

#include <stdexcept>
#include <string>

#include <fmt/format.h>

namespace {
namespace ecl {

static constexpr int producer       = 1;
static constexpr int oil_injector   = 2;
static constexpr int water_injector = 3;
static constexpr int gas_injector   = 4;


static constexpr int oil_phase      = 1;
static constexpr int water_phase    = 2;
static constexpr int gas_phase      = 3;
static constexpr int liquid_phase   = 4;

Opm::Phase from_ecl_phase(const int ecl_phase)
{
    switch (ecl_phase) {
    case ::ecl::oil_phase:
        return Opm::Phase::OIL;

    case ::ecl::water_phase:
        return Opm::Phase::WATER;

    case ::ecl::gas_phase:
        return Opm::Phase::GAS;

    case ecl::liquid_phase:
        throw std::logic_error("Sorry wells with preferred phase:LIQUID is not supported");

    default:
        throw std::invalid_argument("Invalid integer well phase");
    }
}

Opm::Phase from_ecl_phase(const int ecl_wtype, const int ecl_phase)
{
    if (Opm::WellType::producer(ecl_wtype)) {
        return from_ecl_phase(ecl_phase);
    }

    switch (ecl_wtype) {
    case ecl::oil_injector:
        return Opm::Phase::OIL;

    case ecl::water_injector:
        return Opm::Phase::WATER;

    case ecl::gas_injector:
        return Opm::Phase::GAS;
    }

    throw std::invalid_argument {
        fmt::format("Invalid integer well type ID {}", ecl_wtype)
    };
}

Opm::Phase from_injector_type(const Opm::InjectorType injector_type)
{
    switch (injector_type) {
    case Opm::InjectorType::WATER:
        return Opm::Phase::WATER;

    case Opm::InjectorType::GAS:
        return Opm::Phase::GAS;

    case Opm::InjectorType::OIL:
        return Opm::Phase::OIL;

    default:
        throw std::logic_error("Unhandled injector type");
    }
}

} // namespace ecl
} // Anonymous namespace

namespace Opm {

bool WellType::producer(int ecl_wtype)
{
    return ecl_wtype == ecl::producer;
}

bool WellType::oil_injector(int ecl_wtype)
{
    return ecl_wtype == ecl::oil_injector;
}

bool WellType::water_injector(int ecl_wtype)
{
    return ecl_wtype == ecl::water_injector;
}

bool WellType::gas_injector(int ecl_wtype)
{
    return ecl_wtype == ecl::gas_injector;
}

Phase WellType::injection_phase() const
{
    if (this->m_producer) {
        throw std::logic_error("Asked for injection phase in a producer");
    }

    return this->m_injection_phase;
}

WellType::WellType(const int ecl_wtype, const int ecl_phase)
    : m_producer        { WellType::producer(ecl_wtype) }
    , m_injection_phase { ecl::from_ecl_phase(ecl_wtype, ecl_phase) }
    , m_welspecs_phase  { ecl::from_ecl_phase(ecl_phase) }
{}

WellType::WellType(const bool producer, const Phase phase)
    : m_producer        { producer }
    , m_injection_phase { phase }
    , m_welspecs_phase  { phase }
{}

WellType::WellType(const Phase phase)
    : WellType { true, phase }
{}

WellType WellType::serializationTestObject()
{
    WellType result;
    result.m_producer = true;
    result.m_injection_phase = Phase::OIL;
    result.m_welspecs_phase = Phase::WATER;

    return result;
}

bool WellType::update(const bool producer_arg)
{
    if (this->m_producer != producer_arg) {
        this->m_producer = producer_arg;
        return true;
    }

    return false;
}

bool WellType::update(const InjectorType injector_type)
{
    bool ret_value = false;
    if (this->m_producer) {
        this->m_producer = false;
        ret_value = true;
    }

    if (const auto inj_phase = ecl::from_injector_type(injector_type);
        this->m_injection_phase != inj_phase)
    {
        this->m_injection_phase = inj_phase;
        ret_value = true;
    }

    return ret_value;
}

bool WellType::producer() const
{
    return this->m_producer;
}

bool WellType::injector() const
{
    return !this->m_producer;
}

int WellType::ecl_wtype() const
{
    if (this->m_producer) {
        return ecl::producer;
    }

    switch (this->m_injection_phase) {
    case Phase::OIL:
        return ecl::oil_injector;

    case Phase::WATER:
        return ecl::water_injector;

    case Phase::GAS:
        return ecl::gas_injector;

    default:
        throw std::logic_error("Internal error - should not be here");
    }
}

// The enum Runspec::Phase is maybe not very well suited; it has lots of
// 'extra' phases like ENERGY and BRINE, and at the same time it is missing
// the phase LIQUID which should map to ecl value 4.

int WellType::ecl_phase() const
{
    switch (this->m_welspecs_phase) {
    case Phase::OIL:
        return ecl::oil_phase;

    case Phase::WATER:
        return ecl::water_phase;

    case Phase::GAS:
        return ecl::gas_phase;

    default:
        throw std::logic_error("Member has invalid phase");
    }
}

Phase WellType::preferred_phase() const
{
    return this->injector() ? this->m_injection_phase : this->m_welspecs_phase;
}

bool WellType::operator==(const WellType& other) const
{
    return (this->m_welspecs_phase == other.m_welspecs_phase)
        && (this->m_injection_phase == other.m_injection_phase)
        && (this->m_producer == other.m_producer)
        ;
}

InjectorType WellType::injector_type() const
{
    if (this->producer()) {
        throw std::invalid_argument("Asked for injector type for a well which is a producer");
    }

    switch (this->m_injection_phase) {
    case Phase::OIL:
        return InjectorType::OIL;

    case Phase::WATER:
        return InjectorType::WATER;

    case Phase::GAS:
        return InjectorType::GAS;

    default:
        throw std::logic_error("Member has invalid phase");
    }
}

std::string InjectorType2String(const InjectorType enumValue)
{
    switch (enumValue) {
    case InjectorType::OIL:
        return "OIL";

    case InjectorType::GAS:
        return "GAS";

    case InjectorType::WATER:
        return "WATER";

    case InjectorType::MULTI:
        return "MULTI";

    default:
        throw std::invalid_argument("unhandled enum value");
    }
}

InjectorType InjectorTypeFromString(const std::string& stringValue)
{
    if (stringValue == "OIL") {
        return InjectorType::OIL;
    }
    else if (stringValue == "WATER") {
        return InjectorType::WATER;
    }
    else if (stringValue == "WAT") {
        return InjectorType::WATER;
    }
    else if (stringValue == "GAS") {
        return InjectorType::GAS;
    }
    else if (stringValue == "MULTI") {
        return InjectorType::MULTI;
    }
    else {
        throw std::invalid_argument("Unknown enum state string: " + stringValue );
    }
}

} // namespace Opm
