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

#ifndef UDQINPUT_HPP_
#define UDQINPUT_HPP_

#include <opm/input/eclipse/Schedule/UDQ/UDQAssign.hpp>
#include <opm/input/eclipse/Schedule/UDQ/UDQDefine.hpp>
#include <opm/input/eclipse/Schedule/UDQ/UDQEnums.hpp>
#include <opm/input/eclipse/Schedule/UDQ/UDQFunctionTable.hpp>
#include <opm/input/eclipse/Schedule/UDQ/UDQInput.hpp>
#include <opm/input/eclipse/Schedule/UDQ/UDQParams.hpp>
#include <opm/input/eclipse/Schedule/UDQ/UDT.hpp>

#include <opm/input/eclipse/EclipseState/Util/OrderedMap.hpp>
#include <opm/input/eclipse/EclipseState/Util/IOrderSet.hpp>

#include <cstddef>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>

namespace Opm {

    class DeckRecord;
    class KeywordLocation;
    class RegionSetMatcher;
    class Schedule;
    class SegmentMatcher;
    class SummaryState;
    class UDQState;
    class WellMatcher;

} // namespace Opm

namespace Opm { namespace RestartIO {
    struct RstState;
}} // namespace Opm::RestartIO

namespace Opm {

    class UDQConfig
    {
    public:
        using RegionSetMatcherFactory = std::function<std::unique_ptr<RegionSetMatcher>()>;
        using SegmentMatcherFactory = std::function<std::unique_ptr<SegmentMatcher>()>;

        UDQConfig() = default;
        explicit UDQConfig(const UDQParams& params);
        UDQConfig(const UDQParams& params, const RestartIO::RstState& rst_state);

        static UDQConfig serializationTestObject();

        const std::string& unit(const std::string& key) const;
        bool has_unit(const std::string& keyword) const;
        bool has_keyword(const std::string& keyword) const;

        void add_record(SegmentMatcherFactory  create_segment_matcher,
                        const DeckRecord&      record,
                        const KeywordLocation& location,
                        std::size_t            report_step);

        void add_unit(const std::string& keyword,
                      const std::string& unit);

        void add_update(const std::string&              keyword,
                        std::size_t                     report_step,
                        const KeywordLocation&          location,
                        const std::vector<std::string>& data);

        void add_assign(const std::string&              quantity,
                        SegmentMatcherFactory           create_segment_matcher,
                        const std::vector<std::string>& selector,
                        double                          value,
                        std::size_t                     report_step);

        void add_assign(const std::string&                     quantity,
                        const std::unordered_set<std::string>& selector,
                        double                                 value,
                        std::size_t                            report_step);

        void add_define(const std::string&              quantity,
                        const KeywordLocation&          location,
                        const std::vector<std::string>& expression,
                        std::size_t                     report_step);

        void add_table(const std::string& name, UDT udt);

        bool clear_pending_assignments();

        void eval_assign(std::size_t           report_step,
                         const Schedule&       sched,
                         const WellMatcher&    wm,
                         SegmentMatcherFactory create_segment_matcher,
                         SummaryState&         st,
                         UDQState&             udq_state) const;

        void eval(std::size_t             report_step,
                  const Schedule&         sched,
                  const WellMatcher&      wm,
                  SegmentMatcherFactory   create_segment_matcher,
                  RegionSetMatcherFactory create_region_matcher,
                  SummaryState&           st,
                  UDQState&               udq_state) const;

        const UDQDefine& define(const std::string& key) const;
        const UDQAssign& assign(const std::string& key) const;

        std::vector<UDQDefine> definitions() const;
        std::vector<UDQDefine> definitions(UDQVarType var_type) const;
        std::vector<UDQInput> input() const;

        // The size() method will return the number of active DEFINE and ASSIGN
        // statements; this will correspond to the length of the vector returned
        // from input().
        std::size_t size() const;

        UDQInput operator[](const std::string& keyword) const;
        UDQInput operator[](std::size_t insert_index) const;

        std::vector<UDQAssign> assignments() const;
        std::vector<UDQAssign> assignments(UDQVarType var_type) const;
        const UDQParams& params() const;
        const UDQFunctionTable& function_table() const;
        const std::unordered_map<std::string, UDT>& tables() const;

        bool operator==(const UDQConfig& config) const;
        void required_summary(std::unordered_set<std::string>& summary_keys) const;

        template<class Serializer>
        void serializeOp(Serializer& serializer)
        {
            serializer(udq_params);
            serializer(m_definitions);
            serializer(m_assignments);
            serializer(m_tables);
            serializer(units);
            serializer(input_index);
            serializer(type_count);
            serializer(pending_assignments_);

            // The UDQFunction table is constant up to udq_params, so we can
            // just construct a new instance here.
            if (!serializer.isSerializing()) {
                udqft = UDQFunctionTable(udq_params);
            }
        }

    private:
        UDQParams udq_params;
        UDQFunctionTable udqft;

        // The choices of data structures are strongly motivated by the
        // constraints imposed by the ECLIPSE formatted restart files; for
        // writing restart files it is essential to keep meticulous control
        // over the ordering of the keywords.  In this class the ordering is
        // mainly maintained by the input_index map which keeps track of the
        // insert order of each keyword, and whether the keyword is
        // currently DEFINE'ed or ASSIGN'ed.
        std::unordered_map<std::string, UDQDefine> m_definitions;
        std::unordered_map<std::string, UDQAssign> m_assignments;
        std::unordered_map<std::string, UDT> m_tables;
        std::unordered_map<std::string, std::string> units;

        IOrderSet<std::string> define_order;
        OrderedMap<UDQIndex> input_index;
        std::map<UDQVarType, std::size_t> type_count;

        mutable std::vector<std::string> pending_assignments_{};

        void add_node(const std::string& quantity, UDQAction action);
        UDQAction action_type(const std::string& udq_key) const;

        void eval_assign(std::size_t     report_step,
                         const Schedule& sched,
                         UDQContext&     context) const;

        void eval_define(std::size_t report_step,
                         UDQState&   udq_state,
                         UDQContext& context) const;

        void add_named_assign(const std::string&              quantity,
                              const std::vector<std::string>& selector,
                              double                          value,
                              std::size_t                     report_step);

        void add_enumerated_assign(const std::string&              quantity,
                                   SegmentMatcherFactory           create_segment_matcher,
                                   const std::vector<std::string>& selector,
                                   double                          value,
                                   std::size_t                     report_step);
    };

} // namespace Opm

#endif // UDQINPUT_HPP_
