/*
  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 <functional>
#include <algorithm>
#include <unordered_map>

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

#include <opm/parser/eclipse/Parser/ParserKeywords/A.hpp>
#include <opm/parser/eclipse/Parser/ParserKeywords/B.hpp>
#include <opm/parser/eclipse/Parser/ParserKeywords/C.hpp>
#include <opm/parser/eclipse/Parser/ParserKeywords/E.hpp>
#include <opm/parser/eclipse/Parser/ParserKeywords/M.hpp>
#include <opm/parser/eclipse/Parser/ParserKeywords/O.hpp>
#include <opm/parser/eclipse/Parser/ParserKeywords/P.hpp>

#include <opm/parser/eclipse/Units/UnitSystem.hpp>
#include <opm/parser/eclipse/Deck/Deck.hpp>
#include <opm/parser/eclipse/EclipseState/Tables/TableManager.hpp>
#include <opm/parser/eclipse/EclipseState/Tables/RtempvdTable.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/EclipseGrid.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/Box.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/FieldPropsManager.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/SatfuncPropertyInitializers.hpp>
#include <opm/parser/eclipse/EclipseState/Runspec.hpp>
#include <opm/common/utility/Serializer.hpp>

#include "FieldProps.hpp"
#include "Operate.hpp"


namespace Opm {

namespace Fieldprops
{

namespace keywords {

static const std::set<std::string> oper_keywords = {"ADD", "EQUALS", "MAXVALUE", "MINVALUE", "MULTIPLY"};
static const std::set<std::string> region_oper_keywords = {"MULTIREG", "ADDREG", "EQUALREG", "OPERATER"};
static const std::set<std::string> box_keywords = {"BOX", "ENDBOX"};


std::string get_keyword_from_alias(const std::string& name) {
    if (ALIAS::aliased_keywords.count(name))
        return ALIAS::aliased_keywords.at(name);
    return name;
}


template <>
keyword_info<double> global_kw_info(const std::string& name,
                                    bool allow_unsupported) {
    if (GRID::double_keywords.count(name))
        return GRID::double_keywords.at(name);

    if (EDIT::double_keywords.count(name))
        return EDIT::double_keywords.at(name);

    if (PROPS::double_keywords.count(name))
        return PROPS::double_keywords.at(name);

    if (PROPS::satfunc.count(name))
        return keyword_info<double>{};

    if (SOLUTION::double_keywords.count(name))
        return SOLUTION::double_keywords.at(name);

    if (SCHEDULE::double_keywords.count(name))
        return SCHEDULE::double_keywords.at(name);

    if (allow_unsupported)
        return keyword_info<double>{};

    throw std::out_of_range("INFO: No such keyword: " + name);
}


template <>
keyword_info<int> global_kw_info(const std::string& name, bool) {
    if (GRID::int_keywords.count(name))
        return GRID::int_keywords.at(name);

    if (EDIT::int_keywords.count(name))
        return EDIT::int_keywords.at(name);

    if (PROPS::int_keywords.count(name))
        return PROPS::int_keywords.at(name);

    if (REGIONS::int_keywords.count(name))
        return REGIONS::int_keywords.at(name);

    if (SCHEDULE::int_keywords.count(name))
        return SCHEDULE::int_keywords.at(name);

    throw std::out_of_range("No such keyword: " + name);
}

} // end namespace keywords

} // end namespace Fieldprops


namespace {
/*
 * The EQUALREG, MULTREG, COPYREG, ... keywords are used to manipulate
 * vectors based on region values; for instance the statement
 *
 *   EQUALREG
 *      PORO  0.25  3    /   -- Region array not specified
 *      PERMX 100   3  F /
 *   /
 *
 * will set the PORO field to 0.25 for all cells in region 3 and the PERMX
 * value to 100 mD for the same cells. The fourth optional argument to the
 * EQUALREG keyword is used to indicate which REGION array should be used
 * for the selection.
 *
 * If the REGION array is not indicated (as in the PORO case) above, the
 * default region to use in the xxxREG keywords depends on the GRIDOPTS
 * keyword:
 *
 *   1. If GRIDOPTS is present, and the NRMULT item is greater than zero,
 *      the xxxREG keywords will default to use the MULTNUM region.
 *
 *   2. If the GRIDOPTS keyword is not present - or the NRMULT item equals
 *      zero, the xxxREG keywords will default to use the FLUXNUM keyword.
 *
 * This quite weird behaviour comes from reading the GRIDOPTS and MULTNUM
 * documentation, and practical experience with ECLIPSE
 * simulations. Ufortunately the documentation of the xxxREG keywords does
 * not confirm this.
 */
std::string default_region_keyword(const Deck& deck) {
    if (deck.hasKeyword("GRIDOPTS")) {
        const auto& gridOpts = deck.getKeyword("GRIDOPTS");
        const auto& record = gridOpts.getRecord(0);
        const auto& nrmult_item = record.getItem("NRMULT");

        if (nrmult_item.get<int>(0) > 0)
            return "MULTNUM"; // GRIDOPTS and positive NRMULT
    }
    return "FLUXNUM";
}


template <typename T>
void verify_deck_data(const DeckKeyword& keyword, const std::vector<T>& deck_data, const Box& box) {
    if (box.size() != deck_data.size()) {
        const auto& location = keyword.location();
        std::string msg = "Fundamental error with keyword: " + keyword.name() +
            " at: " + location.filename + ", line: " + std::to_string(location.lineno) +
            " got " + std::to_string(deck_data.size()) + " elements - expected : " + std::to_string(box.size());
        throw std::invalid_argument(msg);
    }
}


template <typename T>
void assign_deck(const Fieldprops::keywords::keyword_info<T>& kw_info, const DeckKeyword& keyword, Fieldprops::FieldData<T>& field_data, const std::vector<T>& deck_data, const std::vector<value::status>& deck_status, const Box& box) {
    verify_deck_data(keyword, deck_data, box);
    for (const auto& cell_index : box.index_list()) {
        auto active_index = cell_index.active_index;
        auto data_index = cell_index.data_index;

        if (value::has_value(deck_status[data_index])) {
            if (deck_status[data_index] == value::status::deck_value || field_data.value_status[active_index] == value::status::uninitialized) {
                field_data.data[active_index] = deck_data[data_index];
                field_data.value_status[active_index] = deck_status[data_index];
            }
        }
    }

    if (kw_info.global) {
        auto& global_data = field_data.global_data.value();
        auto& global_status = field_data.global_value_status.value();
        const auto& index_list = box.global_index_list();

        for (const auto& cell : index_list) {
            if (deck_status[cell.data_index] == value::status::deck_value || global_status[cell.global_index] == value::status::uninitialized) {
                global_data[cell.global_index] = deck_data[cell.data_index];
                global_status[cell.global_index] = deck_status[cell.data_index];
            }
        }
    }
}


template <typename T>
void multiply_deck(const Fieldprops::keywords::keyword_info<T>& kw_info, const DeckKeyword& keyword, Fieldprops::FieldData<T>& field_data, const std::vector<T>& deck_data, const std::vector<value::status>& deck_status, const Box& box) {
    verify_deck_data(keyword, deck_data, box);
    for (const auto& cell_index : box.index_list()) {
        auto active_index = cell_index.active_index;
        auto data_index = cell_index.data_index;

        if (value::has_value(deck_status[data_index]) && value::has_value(field_data.value_status[active_index])) {
            field_data.data[active_index] *= deck_data[data_index];
            field_data.value_status[active_index] = deck_status[data_index];
        }
    }

    if (kw_info.global) {
        auto& global_data = field_data.global_data.value();
        auto& global_status = field_data.global_value_status.value();
        const auto& index_list = box.global_index_list();

        for (const auto& cell : index_list) {
            if (deck_status[cell.data_index] == value::status::deck_value || global_status[cell.global_index] == value::status::uninitialized) {
                global_data[cell.global_index] *= deck_data[cell.data_index];
                global_status[cell.global_index] = deck_status[cell.data_index];
            }
        }
    }
}


template <typename T>
void assign_scalar(std::vector<T>& data, std::vector<value::status>& value_status, T value, const std::vector<Box::cell_index>& index_list) {
    for (const auto& cell_index : index_list) {
        data[cell_index.active_index] = value;
        value_status[cell_index.active_index] = value::status::deck_value;
    }
}

template <typename T>
void multiply_scalar(std::vector<T>& data, std::vector<value::status>& value_status, T value, const std::vector<Box::cell_index>& index_list) {
    for (const auto& cell_index : index_list) {
        if (value::has_value(value_status[cell_index.active_index]))
            data[cell_index.active_index] *= value;
    }
}

template <typename T>
void add_scalar(std::vector<T>& data, std::vector<value::status>& value_status, T value, const std::vector<Box::cell_index>& index_list) {
    for (const auto& cell_index : index_list) {
        if (value::has_value(value_status[cell_index.active_index]))
            data[cell_index.active_index] += value;
    }
}

template <typename T>
void min_value(std::vector<T>& data, std::vector<value::status>& value_status, T min_value, const std::vector<Box::cell_index>& index_list) {
    for (const auto& cell_index : index_list) {
        if (value::has_value(value_status[cell_index.active_index])) {
            T value = data[cell_index.active_index];
            data[cell_index.active_index] = std::max(value, min_value);
        }
    }
}

template <typename T>
void max_value(std::vector<T>& data, std::vector<value::status>& value_status, T max_value, const std::vector<Box::cell_index>& index_list) {
    for (const auto& cell_index : index_list) {
        if (value::has_value(value_status[cell_index.active_index])) {
            T value = data[cell_index.active_index];
            data[cell_index.active_index] = std::min(value, max_value);
        }
    }
}

std::string make_region_name(const std::string& deck_value) {
    if (deck_value == "O")
        return "OPERNUM";

    if (deck_value == "F")
        return "FLUXNUM";

    if (deck_value == "M")
        return "MULTNUM";

    throw std::invalid_argument("The input string: " + deck_value + " was invalid. Expected: O/F/M");
}

Fieldprops::ScalarOperation fromString(const std::string& keyword) {
    if (keyword == ParserKeywords::ADD::keywordName || keyword == ParserKeywords::ADDREG::keywordName)
        return Fieldprops::ScalarOperation::ADD;

    if (keyword == ParserKeywords::EQUALS::keywordName || keyword == ParserKeywords::EQUALREG::keywordName)
        return Fieldprops::ScalarOperation::EQUAL;

    if (keyword == ParserKeywords::MULTIPLY::keywordName || keyword == ParserKeywords::MULTIREG::keywordName)
        return Fieldprops::ScalarOperation::MUL;

    if (keyword == ParserKeywords::MINVALUE::keywordName)
        return Fieldprops::ScalarOperation::MIN;

    if (keyword == ParserKeywords::MAXVALUE::keywordName)
        return Fieldprops::ScalarOperation::MAX;

    throw std::invalid_argument("Keyword operation not recognized");
}


void handle_box_keyword(const DeckKeyword& deckKeyword,  Box& box) {
    if (deckKeyword.name() == ParserKeywords::BOX::keywordName) {
        const auto& record = deckKeyword.getRecord(0);
        box.update(record);
    } else
        box.reset();
}


std::vector<double> extract_cell_volume(const EclipseGrid& grid) {
    return grid.activeVolume();
}

std::vector<double> extract_cell_depth(const EclipseGrid& grid) {
    std::vector<double> cell_depth(grid.getNumActive());
    for (std::size_t active_index = 0; active_index < grid.getNumActive(); active_index++)
        cell_depth[active_index] = grid.getCellDepth( grid.getGlobalIndex(active_index));
    return cell_depth;
}

}


FieldProps::FieldProps(const Deck& deck, const Phases& phases, const EclipseGrid& grid, const TableManager& tables_arg) :
    active_size(grid.getNumActive()),
    global_size(grid.getCartesianSize()),
    unit_system(deck.getActiveUnitSystem()),
    nx(grid.getNX()),
    ny(grid.getNY()),
    nz(grid.getNZ()),
    m_phases(phases),
    m_satfuncctrl(deck),
    m_actnum(grid.getACTNUM()),
    cell_volume(extract_cell_volume(grid)),
    cell_depth(extract_cell_depth(grid)),
    m_default_region(default_region_keyword(deck)),
    grid_ptr(&grid),
    tables(tables_arg)
{
    this->tran.emplace( "TRANX", Fieldprops::TranCalculator("TRANX") );
    this->tran.emplace( "TRANY", Fieldprops::TranCalculator("TRANY") );
    this->tran.emplace( "TRANZ", Fieldprops::TranCalculator("TRANZ") );

    if (deck.hasKeyword<ParserKeywords::MULTREGP>()) {
        const DeckKeyword& multregpKeyword = deck.getKeyword("MULTREGP");
        for (const auto& record : multregpKeyword) {
            int region_value = record.getItem("REGION").get<int>(0);
            if (region_value <= 0)
                continue;

            std::string region_name = make_region_name( record.getItem("REGION_TYPE").get<std::string>(0) );
            double multiplier = record.getItem("MULTIPLIER").get<double>(0);
            auto iter = std::find_if(this->multregp.begin(), this->multregp.end(), [region_value](const MultregpRecord& mregp) { return mregp.region_value == region_value; });
            /*
              There is some weirdness if the same region value is entered in several records,
              then only the last applies.
            */
            if (iter != this->multregp.end()) {
                iter->region_name = region_name;
                iter->multiplier = multiplier;
            } else
                this->multregp.emplace_back( region_value, multiplier, region_name );
        }
    }


    if (DeckSection::hasGRID(deck))
        this->scanGRIDSection(GRIDSection(deck));

    if (DeckSection::hasEDIT(deck))
        this->scanEDITSection(EDITSection(deck));

    if (DeckSection::hasREGIONS(deck))
        this->scanREGIONSSection(REGIONSSection(deck));

    if (DeckSection::hasPROPS(deck))
        this->scanPROPSSection(PROPSSection(deck));

    if (DeckSection::hasSOLUTION(deck))
        this->scanSOLUTIONSection(SOLUTIONSection(deck));
}



void FieldProps::reset_actnum(const std::vector<int>& new_actnum) {
    if (this->global_size != new_actnum.size())
        throw std::logic_error("reset_actnum() must be called with the same number of global cells");

    if (new_actnum == this->m_actnum)
        return;

    std::vector<bool> active_map(this->active_size, true);
    std::size_t active_index = 0;
    std::size_t new_active_size = 0;
    for (std::size_t g = 0; g < this->m_actnum.size(); g++) {
        if (this->m_actnum[g] != 0) {
            if (new_actnum[g] == 0)
                active_map[active_index] = false;
            else
                new_active_size += 1;

            active_index += 1;
        } else {
            if (new_actnum[g] != 0)
                throw std::logic_error("It is not possible to activate cells");
        }
    }

    for (auto& data : this->double_data)
        data.second.compress(active_map);

    for (auto& data : this->int_data)
        data.second.compress(active_map);

    Fieldprops::compress(this->cell_volume, active_map);
    Fieldprops::compress(this->cell_depth, active_map);

    this->m_actnum = std::move(new_actnum);
    this->active_size = new_active_size;
}


void FieldProps::distribute_toplayer(Fieldprops::FieldData<double>& field_data, const std::vector<double>& deck_data, const Box& box) {
    const std::size_t layer_size = this->nx * this->ny;
    Fieldprops::FieldData<double> toplayer(field_data.kw_info, layer_size, 0);
    for (const auto& cell_index : box.index_list()) {
        if (cell_index.global_index < layer_size) {
            toplayer.data[cell_index.global_index] = deck_data[cell_index.data_index];
            toplayer.value_status[cell_index.global_index] = value::status::deck_value;
        }
    }

    std::size_t active_index = 0;
    for (std::size_t k = 0; k < this->nz; k++) {
        for (std::size_t j = 0; j < this->ny; j++) {
            for (std::size_t i = 0; i < this->nx; i++) {
                std::size_t g = i + j*this->nx + k*this->nx*this->ny;
                if (this->m_actnum[g]) {
                    if (field_data.value_status[active_index] == value::status::uninitialized) {
                        std::size_t layer_index = i + j*this->nx;
                        if (toplayer.value_status[layer_index] == value::status::deck_value) {
                            field_data.data[active_index] = toplayer.data[layer_index];
                            field_data.value_status[active_index] = value::status::valid_default;
                        }
                    }
                    active_index += 1;
                }
            }
        }
    }
}


template <>
bool FieldProps::supported<double>(const std::string& keyword) {
    if (Fieldprops::keywords::GRID::double_keywords.count(keyword) != 0)
        return true;

    if (Fieldprops::keywords::EDIT::double_keywords.count(keyword) != 0)
        return true;

    if (Fieldprops::keywords::PROPS::double_keywords.count(keyword) != 0)
        return true;

    if (Fieldprops::keywords::PROPS::satfunc.count(keyword) != 0)
        return true;

    if (Fieldprops::keywords::SOLUTION::double_keywords.count(keyword) != 0)
        return true;

    return false;
}

template <>
bool FieldProps::supported<int>(const std::string& keyword) {
    if (Fieldprops::keywords::REGIONS::int_keywords.count(keyword) != 0)
        return true;

    if (Fieldprops::keywords::GRID::int_keywords.count(keyword) != 0)
        return true;

    if (Fieldprops::keywords::SCHEDULE::int_keywords.count(keyword) != 0)
        return true;

    return Fieldprops::keywords::isFipxxx(keyword);
}


template <>
Fieldprops::FieldData<double>& FieldProps::init_get(const std::string& keyword_name, const Fieldprops::keywords::keyword_info<double>& kw_info) {
    const std::string& keyword = Fieldprops::keywords::get_keyword_from_alias(keyword_name);

    auto iter = this->double_data.find(keyword);
    if (iter != this->double_data.end())
        return iter->second;

    this->double_data[keyword] = Fieldprops::FieldData<double>(kw_info, this->active_size, kw_info.global ? this->global_size : 0);

    if (keyword == ParserKeywords::PORV::keywordName)
        this->init_porv(this->double_data[keyword]);

    if (keyword == ParserKeywords::TEMPI::keywordName)
        this->init_tempi(this->double_data[keyword]);

    if (Fieldprops::keywords::PROPS::satfunc.count(keyword) == 1)
        this->init_satfunc(keyword, this->double_data[keyword]);

    return this->double_data[keyword];
}

template <>
Fieldprops::FieldData<double>& FieldProps::init_get(const std::string& keyword,
                                        bool allow_unsupported) {
    Fieldprops::keywords::keyword_info<double> kw_info = Fieldprops::keywords::global_kw_info<double>(keyword, allow_unsupported);
    return this->init_get(keyword, kw_info);
}


template <>
Fieldprops::FieldData<int>& FieldProps::init_get(const std::string& keyword, const Fieldprops::keywords::keyword_info<int>& kw_info) {
    auto iter = this->int_data.find(keyword);
    if (iter != this->int_data.end())
        return iter->second;

    this->int_data[keyword] = Fieldprops::FieldData<int>(kw_info, this->active_size, kw_info.global ? this->global_size : 0);
    return this->int_data[keyword];
}

template <>
Fieldprops::FieldData<int>& FieldProps::init_get(const std::string& keyword, bool) {
    if (Fieldprops::keywords::isFipxxx(keyword)) {
        auto kw_info = Fieldprops::keywords::keyword_info<int>{};
        kw_info.init(1);
        return this->init_get(keyword, kw_info);
    } else {
        const Fieldprops::keywords::keyword_info<int>& kw_info = Fieldprops::keywords::global_kw_info<int>(keyword);
        return this->init_get(keyword, kw_info);
    }
}


std::vector<Box::cell_index> FieldProps::region_index( const std::string& region_name, int region_value ) {
    const auto& region = this->init_get<int>(region_name);
    if (!region.valid())
        throw std::invalid_argument("Trying to work with invalid region: " + region_name);

    std::vector<Box::cell_index> index_list;
    std::size_t active_index = 0;
    const auto& region_data = region.data;
    for (std::size_t g = 0; g < this->m_actnum.size(); g++) {
        if (this->m_actnum[g] != 0) {
            if (region_data[active_index] == region_value)
                index_list.emplace_back( g, active_index, g );
            active_index += 1;
        }
    }
    return index_list;
}



std::string FieldProps::region_name(const DeckItem& region_item) {
    return region_item.defaultApplied(0) ? this->m_default_region : make_region_name(region_item.get<std::string>(0));
}

template <>
bool FieldProps::has<double>(const std::string& keyword_name) const {
    const std::string& keyword = Fieldprops::keywords::get_keyword_from_alias(keyword_name);
    return (this->double_data.count(keyword) != 0);
}

template <>
bool FieldProps::has<int>(const std::string& keyword) const {
    return (this->int_data.count(keyword) != 0);
}


/*
  The ACTNUM and PORV keywords are special cased with quite extensive
  postprocessing, and should be access through the special ::porv() and
  ::actnum() methods instead of the general ::get<T>( ) method. These two
  keywords are also hidden from the keys<T>() vectors.
*/

template <>
std::vector<std::string> FieldProps::keys<double>() const {
    std::vector<std::string> klist;
    for (const auto& data_pair : this->double_data) {
        if (data_pair.second.valid() && data_pair.first != "PORV")
            klist.push_back(data_pair.first);
    }
    return klist;
}

template <>
std::vector<std::string> FieldProps::keys<int>() const {
    std::vector<std::string> klist;
    for (const auto& data_pair : this->int_data) {
        if (data_pair.second.valid() && data_pair.first != "ACTNUM")
            klist.push_back(data_pair.first);
    }
    return klist;
}


template <>
void FieldProps::erase<int>(const std::string& keyword) {
    this->int_data.erase(keyword);
}

template <>
void FieldProps::erase<double>(const std::string& keyword) {
    this->double_data.erase(keyword);
}

template <>
std::vector<int> FieldProps::extract<int>(const std::string& keyword) {
    auto field_iter = this->int_data.find(keyword);
    auto field = std::move(field_iter->second);
    std::vector<int> data = std::move( field.data );
    this->int_data.erase( field_iter );
    return data;
}

template <>
std::vector<double> FieldProps::extract<double>(const std::string& keyword) {
    auto field_iter = this->double_data.find(keyword);
    auto field = std::move(field_iter->second);
    std::vector<double> data = std::move( field.data );
    this->double_data.erase( field_iter );
    return data;
}





double FieldProps::getSIValue(const std::string& keyword, double raw_value) const {
    if (this->tran.count(keyword))
        return this->unit_system.to_si(UnitSystem::measure::transmissibility, raw_value);
    else {
        const auto& kw_info = Fieldprops::keywords::global_kw_info<double>(keyword);
        if (kw_info.unit) {
            const auto& dim = this->unit_system.parse( *kw_info.unit );
            return dim.convertRawToSi(raw_value);
        }
        return raw_value;
    }
}



double FieldProps::getSIValue(ScalarOperation op, const std::string& keyword, double raw_value) const {
    if (op == ScalarOperation::MUL)
        return raw_value;

    return this->getSIValue(keyword, raw_value);
}


void FieldProps::handle_int_keyword(const Fieldprops::keywords::keyword_info<int>& kw_info, const DeckKeyword& keyword, const Box& box) {
    auto& field_data = this->init_get<int>(keyword.name());
    const auto& deck_data = keyword.getIntData();
    const auto& deck_status = keyword.getValueStatus();
    assign_deck(kw_info, keyword, field_data, deck_data, deck_status, box);
}


void FieldProps::handle_double_keyword(Section section, const Fieldprops::keywords::keyword_info<double>& kw_info, const DeckKeyword& keyword, const std::string& keyword_name, const Box& box) {
    auto& field_data = this->init_get<double>(keyword_name, kw_info);
    const auto& deck_data = keyword.getSIDoubleData();
    const auto& deck_status = keyword.getValueStatus();

    if (section == Section::EDIT && kw_info.multiplier)
        multiply_deck(kw_info, keyword, field_data, deck_data, deck_status, box);
    else
        assign_deck(kw_info, keyword, field_data, deck_data, deck_status, box);


    if (section == Section::GRID) {
        if (field_data.valid())
            return;

        if (kw_info.top)
            this->distribute_toplayer(field_data, deck_data, box);
    }
}

void FieldProps::handle_double_keyword(Section section, const Fieldprops::keywords::keyword_info<double>& kw_info, const DeckKeyword& keyword, const Box& box) {
    this->handle_double_keyword(section, kw_info, keyword, keyword.name(), box );
}



template <typename T>
void FieldProps::apply(Fieldprops::ScalarOperation op, std::vector<T>& data, std::vector<value::status>& value_status, T scalar_value, const std::vector<Box::cell_index>& index_list) {
    if (op == Fieldprops::ScalarOperation::EQUAL)
        assign_scalar(data, value_status, scalar_value, index_list);

    else if (op == Fieldprops::ScalarOperation::MUL)
        multiply_scalar(data, value_status, scalar_value, index_list);

    else if (op == Fieldprops::ScalarOperation::ADD)
        add_scalar(data, value_status, scalar_value, index_list);

    else if (op == Fieldprops::ScalarOperation::MIN)
        min_value(data, value_status, scalar_value, index_list);

    else if (op == Fieldprops::ScalarOperation::MAX)
        max_value(data, value_status, scalar_value, index_list);
}

double FieldProps::get_alpha(const std::string& func_name, const std::string& target_array, double raw_alpha) {
    if ( !(func_name == "ADDX" || func_name == "MAXLIM" || func_name == "MINLIM") )
        return raw_alpha;

    return this->getSIValue(target_array, raw_alpha);
}

double FieldProps::get_beta(const std::string& func_name, const std::string& target_array, double raw_beta) {
    if ( func_name != "MULTA")
        return raw_beta;

    return this->getSIValue(target_array, raw_beta);
}

template <typename T>
void FieldProps::operate(const DeckRecord& record, Fieldprops::FieldData<T>& target_data, const Fieldprops::FieldData<T>& src_data, const std::vector<Box::cell_index>& index_list) {
    const std::string& func_name = record.getItem("OPERATION").get< std::string >(0);
    const std::string& target_array = record.getItem("TARGET_ARRAY").get<std::string>(0);
    const double alpha           = this->get_alpha(func_name, target_array, record.getItem("PARAM1").get< double >(0));
    const double beta            = this->get_beta( func_name, target_array, record.getItem("PARAM2").get< double >(0));
    Operate::function func       = Operate::get( func_name, alpha, beta );
    bool check_target            = (func_name == "MULTIPLY" || func_name == "POLY");

    if (target_data.global_data)
        throw std::logic_error("The OPERATE and OPERATER keywords are not supported for keywords with global storage");

    if (this->tran.find(target_array) != this->tran.end())
        throw std::logic_error("The OPERATE keyword can not be used for manipulations of TRANX, TRANY or TRANZ");

    for (const auto& cell_index : index_list) {
        if (value::has_value(src_data.value_status[cell_index.active_index])) {
            if ((check_target == false) || (value::has_value(target_data.value_status[cell_index.active_index]))) {
                target_data.data[cell_index.active_index]         = func(target_data.data[cell_index.active_index], src_data.data[cell_index.active_index]);
                target_data.value_status[cell_index.active_index] = src_data.value_status[cell_index.active_index];
            } else
                throw std::invalid_argument("Tried to use unset property value in OPERATE/OPERATER keyword");
        } else
            throw std::invalid_argument("Tried to use unset property value in OPERATE/OPERATER keyword");
    }
}

void FieldProps::handle_region_operation(const DeckKeyword& keyword) {
    for (const auto& record : keyword) {
        const std::string& target_kw = Fieldprops::keywords::get_keyword_from_alias(record.getItem(0).get<std::string>(0));
        int region_value = record.getItem("REGION_NUMBER").get<int>(0);

        if (this->tran.find(target_kw) != this->tran.end())
            throw std::logic_error("The region operations can not be used for manipulations of TRANX, TRANY or TRANZ");

        if (FieldProps::supported<double>(target_kw)) {
            if (keyword.name() == ParserKeywords::OPERATER::keywordName) {
                // For the OPERATER keyword we fetch the region name from the deck record
                // with no extra hoops.
                std::string region_name = record.getItem("REGION_NAME").get<std::string>(0);
                const auto& index_list = this->region_index(region_name, region_value);
                const std::string& src_kw = record.getItem("ARRAY_PARAMETER").get<std::string>(0);
                const auto& src_data = this->init_get<double>(src_kw);
                auto& field_data = this->init_get<double>(target_kw);
                FieldProps::operate(record, field_data, src_data, index_list);
            } else {
                auto operation = fromString(keyword.name());
                const double scalar_value = this->getSIValue(operation, target_kw, record.getItem(1).get<double>(0));
                std::string region_name = this->region_name( record.getItem("REGION_NAME") );
                const auto& index_list = this->region_index( region_name, region_value);
                auto& field_data = this->init_get<double>(target_kw);
                /*
                  To support region operations on keywords with global storage we
                  would need to also have global storage for the xxxNUM region
                  keywords involved. To avoid a situation where a significant
                  fraction of the keywords have global storage the implementation
                  has stopped here - there are no principle problems with extending
                  the implementation to also support region operations on fields
                  with global storage.
                */
                if (field_data.global_data)
                    throw std::logic_error("Region operations on 3D fields with global storage is not implemented");

                FieldProps::apply(fromString(keyword.name()), field_data.data, field_data.value_status, scalar_value, index_list);
            }

            continue;
        }

        if (FieldProps::supported<int>(target_kw)) {
            continue;
        }
    }
}


void FieldProps::handle_OPERATE(const DeckKeyword& keyword, Box box) {
    for (const auto& record : keyword) {
        const std::string& target_kw = Fieldprops::keywords::get_keyword_from_alias(record.getItem(0).get<std::string>(0));
        box.update(record);

        auto& field_data = this->init_get<double>(target_kw);
        const std::string& src_kw = record.getItem("ARRAY").get<std::string>(0);
        const auto& src_data = this->init_get<double>(src_kw);
        FieldProps::operate(record, field_data, src_data, box.index_list());
    }
}


void FieldProps::handle_operation(const DeckKeyword& keyword, Box box) {
    std::unordered_map<std::string, std::string> tran_fields;
    for (const auto& record : keyword) {
        const std::string& target_kw = Fieldprops::keywords::get_keyword_from_alias(record.getItem(0).get<std::string>(0));
        box.update(record);

        if (FieldProps::supported<double>(target_kw) || this->tran.count(target_kw) > 0) {
            std::string unique_name = target_kw;
            auto operation = fromString(keyword.name());
            const double scalar_value = this->getSIValue(operation, target_kw, record.getItem(1).get<double>(0));
            Fieldprops::keywords::keyword_info<double> kw_info;

            auto tran_iter = this->tran.find(target_kw);
            // Check if the target keyword is one of the TRANX, TRANY or TRANZ keywords.
            if (tran_iter != this->tran.end()) {
                auto tran_field_iter = tran_fields.find(target_kw);
                /*
                  The transmissibility calculations are applied to one "work" 3D
                  field per direction and per keyword. Here we check if we have
                  encountered this TRAN direction previously for this keyword,
                  if not we generate a new 3D field and register a new tran
                  calculator operation.
                 */
                if (tran_field_iter == tran_fields.end()) {
                    unique_name = tran_iter->second.next_name();
                    tran_fields.emplace(target_kw, unique_name);
                    tran_iter->second.add_action(operation, unique_name);
                    kw_info = tran_iter->second.make_kw_info(operation);
                } else
                    unique_name = tran_field_iter->second;

            } else
                kw_info = Fieldprops::keywords::global_kw_info<double>(target_kw);

            auto& field_data = this->init_get<double>(unique_name, kw_info);

            FieldProps::apply(operation, field_data.data, field_data.value_status, scalar_value, box.index_list());
            if (field_data.global_data)
                FieldProps::apply(operation, *field_data.global_data, *field_data.global_value_status, scalar_value, box.global_index_list());

            continue;
        }


        if (FieldProps::supported<int>(target_kw)) {
            int scalar_value = static_cast<int>(record.getItem(1).get<double>(0));
            auto& field_data = this->init_get<int>(target_kw);
            FieldProps::apply(fromString(keyword.name()), field_data.data, field_data.value_status, scalar_value, box.index_list());
            continue;
        }

        throw OpmInputError("Operation keyword " + keyword.name() + " does not support the keyword " + target_kw, keyword.location());
    }
}


void FieldProps::handle_COPY(const DeckKeyword& keyword, Box box, bool region) {
    for (const auto& record : keyword) {
        const std::string& src_kw = Fieldprops::keywords::get_keyword_from_alias(record.getItem(0).get<std::string>(0));
        const std::string& target_kw = Fieldprops::keywords::get_keyword_from_alias(record.getItem(1).get<std::string>(0));
        std::vector<Box::cell_index> index_list;

        if (region) {
            int region_value = record.getItem(2).get<int>(0);
            const auto& region_item = record.getItem(3);
            const auto& region_name = this->region_name( region_item );
            index_list = this->region_index(region_name, region_value);
        } else {
            box.update(record);
            index_list = box.index_list();
        }


        if (FieldProps::supported<double>(src_kw)) {
            const auto& src_data = this->try_get<double>(src_kw);
            src_data.verify_status();

            auto& target_data = this->init_get<double>(target_kw);
            target_data.copy(src_data.field_data(), index_list);
            continue;
        }

        if (FieldProps::supported<int>(src_kw)) {
            const auto& src_data = this->try_get<int>(src_kw);
            src_data.verify_status();

            auto& target_data = this->init_get<int>(target_kw);
            target_data.copy(src_data.field_data(), index_list);
            continue;
        }
    }
}

void FieldProps::handle_keyword(const DeckKeyword& keyword, Box& box) {
    const std::string& name = keyword.name();

    if (Fieldprops::keywords::oper_keywords.count(name) == 1)
        this->handle_operation(keyword, box);

    else if (name == ParserKeywords::OPERATE::keywordName)
        this->handle_OPERATE(keyword, box);

    else if (Fieldprops::keywords::region_oper_keywords.count(name) == 1)
        this->handle_region_operation(keyword);

    else if (Fieldprops::keywords::box_keywords.count(name) == 1)
        handle_box_keyword(keyword, box);

    else if (name == ParserKeywords::COPY::keywordName)
        handle_COPY(keyword, box, false);

    else if (name == ParserKeywords::COPYREG::keywordName)
        handle_COPY(keyword, box, true);
}

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


void FieldProps::init_tempi(Fieldprops::FieldData<double>& tempi) {
    if (this->tables.hasTables("RTEMPVD")) {
        const auto& eqlnum = this->get<int>("EQLNUM");
        const auto& rtempvd = this->tables.getRtempvdTables();
        std::vector< double > tempi_values( this->active_size, 0 );

        for (size_t active_index = 0; active_index < this->active_size; active_index++) {
            const auto& table = rtempvd.getTable<RtempvdTable>(eqlnum[active_index] - 1);
            double depth = this->cell_depth[active_index];
            tempi_values[active_index] = table.evaluate("Temperature", depth);
        }

        tempi.default_update(tempi_values);
    } else
        tempi.default_assign(this->tables.rtemp());
}

void FieldProps::init_porv(Fieldprops::FieldData<double>& porv) {
    auto& porv_data = porv.data;
    auto& porv_status = porv.value_status;

    const auto& poro = this->init_get<double>("PORO");
    const auto& poro_status = poro.value_status;
    const auto& poro_data = poro.data;

    for (std::size_t active_index = 0; active_index < this->active_size; active_index++) {
        if (value::has_value(poro_status[active_index])) {
            porv_data[active_index] = this->cell_volume[active_index] * poro_data[active_index];
            porv_status[active_index] = value::status::valid_default;
        }
    }

    if (this->has<double>("NTG")) {
        const auto& ntg = this->get<double>("NTG");
        for (std::size_t active_index = 0; active_index < this->active_size; active_index++)
            porv_data[active_index] *= ntg[active_index];
    }

    if (this->has<double>("MULTPV")) {
        const auto& multpv = this->get<double>("MULTPV");
        std::transform(porv_data.begin(), porv_data.end(), multpv.begin(), porv_data.begin(), std::multiplies<double>());
    }

    for (const auto& mregp: this->multregp) {
        const auto& index_list = this->region_index(mregp.region_name, mregp.region_value);
        for (const auto& cell_index : index_list)
            porv_data[cell_index.active_index] *= mregp.multiplier;
    }
}


/*
  This function generates a new ACTNUM vector.The ACTNUM vector which is
  returned is joined result of three different data sources:

     1. The ACTNUM of if the grid which is part of this FieldProps structure.

     2. If there have been ACTNUM operations in the DECK of the type:

        EQUALS
            ACTNUM 0 1 10 1 10 1 3 /
        /

     3. Cells with PORV == 0 will get ACTNUM = 0.

  Observe that due to steps 2 and 3 the ACTNUM vector returned from this
  function will in general differ from the internal ACTNUM used in the
  FieldProps instance.
*/
std::vector<int> FieldProps::actnum() {
    auto actnum = this->m_actnum;
    const auto& deck_actnum = this->init_get<int>("ACTNUM");

    std::vector<int> global_map(this->active_size);
    {
        std::size_t active_index = 0;
        for (std::size_t g = 0; g < this->global_size; g++) {
            if (this->m_actnum[g]) {
                global_map[active_index] = g;
                active_index++;
            }
        }
    }


    const auto& porv = this->init_get<double>("PORV");
    const auto& porv_data = porv.data;
    for (std::size_t active_index = 0; active_index < this->active_size; active_index++) {
        auto global_index = global_map[active_index];
        actnum[global_index] = deck_actnum.data[active_index];
        if (porv_data[active_index] == 0)
            actnum[global_index] = 0;
    }
    return actnum;
}


void FieldProps::scanGRIDSection(const GRIDSection& grid_section) {
    Box box(*this->grid_ptr);

    for (const auto& keyword : grid_section) {
        const std::string& name = keyword.name();

        if (Fieldprops::keywords::GRID::double_keywords.count(name) == 1) {
            this->handle_double_keyword(Section::GRID, Fieldprops::keywords::GRID::double_keywords.at(name), keyword, box);
            continue;
        }

        if (Fieldprops::keywords::GRID::int_keywords.count(name) == 1) {
            this->handle_int_keyword(Fieldprops::keywords::GRID::int_keywords.at(name), keyword, box);
            continue;
        }

        this->handle_keyword(keyword, box);
    }
}

void FieldProps::scanEDITSection(const EDITSection& edit_section) {
    Box box(*this->grid_ptr);
    for (const auto& keyword : edit_section) {
        const std::string& name = keyword.name();

        auto tran_iter = this->tran.find(name);
        if (tran_iter!= this->tran.end()) {
            auto& tran_calc = tran_iter->second;
            auto unique_name = tran_calc.next_name();
            this->handle_double_keyword(Section::EDIT, {}, keyword, unique_name, box);
            tran_calc.add_action( Fieldprops::ScalarOperation::EQUAL, unique_name );
            continue;
        }

        if (Fieldprops::keywords::EDIT::double_keywords.count(name) == 1) {
            this->handle_double_keyword(Section::EDIT, Fieldprops::keywords::EDIT::double_keywords.at(name), keyword, box);
            continue;
        }

        if (Fieldprops::keywords::EDIT::int_keywords.count(name) == 1) {
            this->handle_int_keyword(Fieldprops::keywords::GRID::int_keywords.at(name), keyword, box);
            continue;
        }

        this->handle_keyword(keyword, box);
    }
}


void FieldProps::init_satfunc(const std::string& keyword, Fieldprops::FieldData<double>& satfunc) {
    if (this->m_rtep == nullptr)
        this->m_rtep = satfunc::getRawTableEndpoints(this->tables, this->m_phases,
                                                     this->m_satfuncctrl.minimumRelpermMobilityThreshold());

    const auto& endnum = this->get<int>("ENDNUM");
    const auto& satreg = (keyword[0] == 'I')
        ? this->get<int>("IMBNUM")
        : this->get<int>("SATNUM");

    satfunc.default_update(satfunc::init(keyword, this->tables, this->m_phases, *this->m_rtep, this->cell_depth, satreg, endnum));
}


void FieldProps::scanPROPSSection(const PROPSSection& props_section) {
    Box box(*this->grid_ptr);

    for (const auto& keyword : props_section) {
        const std::string& name = keyword.name();
        if (Fieldprops::keywords::PROPS::satfunc.count(name) == 1) {
            Fieldprops::keywords::keyword_info<double> sat_info{};
            this->handle_double_keyword(Section::PROPS, sat_info, keyword, box);
            continue;
        }

        if (Fieldprops::keywords::PROPS::double_keywords.count(name) == 1) {
            this->handle_double_keyword(Section::PROPS, Fieldprops::keywords::PROPS::double_keywords.at(name), keyword, box);
            continue;
        }

        if (Fieldprops::keywords::PROPS::int_keywords.count(name) == 1) {
            this->handle_int_keyword(Fieldprops::keywords::PROPS::int_keywords.at(name), keyword, box);
            continue;
        }

        this->handle_keyword(keyword, box);
    }
}


void FieldProps::scanREGIONSSection(const REGIONSSection& regions_section) {
    Box box(*this->grid_ptr);

    for (const auto& keyword : regions_section) {
        const std::string& name = keyword.name();
        if (Fieldprops::keywords::REGIONS::int_keywords.count(name)) {
            this->handle_int_keyword(Fieldprops::keywords::REGIONS::int_keywords.at(name), keyword, box);
            continue;
        }

        if (Fieldprops::keywords::isFipxxx(name)) {
            auto kw_info = Fieldprops::keywords::keyword_info<int>{};
            kw_info.init(1);
            this->handle_int_keyword(kw_info, keyword, box);
            continue;
        }

        this->handle_keyword(keyword, box);
    }
}


void FieldProps::scanSOLUTIONSection(const SOLUTIONSection& solution_section) {
    Box box(*this->grid_ptr);
    for (const auto& keyword : solution_section) {
        const std::string& name = keyword.name();
        if (Fieldprops::keywords::SOLUTION::double_keywords.count(name) == 1) {
            this->handle_double_keyword(Section::SOLUTION, Fieldprops::keywords::SOLUTION::double_keywords.at(name), keyword, box);
            continue;
        }

        this->handle_keyword(keyword, box);
    }
}

void FieldProps::scanSCHEDULESection(const SCHEDULESection& schedule_section) {
    Box box(*this->grid_ptr);
    for (const auto& keyword : schedule_section) {
        const std::string& name = keyword.name();
        if (Fieldprops::keywords::SCHEDULE::double_keywords.count(name) == 1) {
            this->handle_double_keyword(Section::SCHEDULE, Fieldprops::keywords::SCHEDULE::double_keywords.at(name), keyword, box);
            continue;
        }

        if (Fieldprops::keywords::SCHEDULE::int_keywords.count(name) == 1) {
            this->handle_int_keyword(Fieldprops::keywords::SCHEDULE::int_keywords.at(name), keyword, box);
            continue;
        }

        this->handle_keyword(keyword, box);
    }
}

const std::string& FieldProps::default_region() const {
    return this->m_default_region;
}

void FieldProps::apply_tran(const std::string& keyword, std::vector<double>& data) {
    Opm::apply_tran(this->tran, this->double_data, this->active_size, keyword, data);
}


std::vector<char> FieldProps::serialize_tran() const {
    Serializer ser;
    ser.put(this->tran.size());
    for (const auto& tran_pair : this->tran) {
        const auto& calc = tran_pair.second;
        ser.put(calc.name());
        ser.put(calc.size());
        for (const auto& action : calc) {
            ser.put(static_cast<int>(action.op));
            ser.put(action.field);
        }
    }
    return std::move(ser.buffer);
}

void FieldProps::deserialize_tran(const std::vector<char>& buffer) {
    Opm::deserialize_tran(this->tran, buffer);
}

bool FieldProps::tran_active(const std::string& keyword) const {
    auto calculator = this->tran.find(keyword);
    return calculator != this->tran.end() && calculator->second.size() > 0;
}



template std::vector<bool> FieldProps::defaulted<int>(const std::string& keyword);
template std::vector<bool> FieldProps::defaulted<double>(const std::string& keyword);
}
