aboutsummaryrefslogtreecommitdiff
path: root/src/openvic-simulation/history/HistoryMap.hpp
blob: 576f00e7d5dcf85fea71902c5a78e5ea6abbc3f7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#pragma once

#include <map>
#include <memory>

#include "openvic-simulation/dataloader/NodeTools.hpp"
#include "openvic-simulation/types/Date.hpp"

namespace OpenVic {

   struct HistoryEntry {
   private:
      Date PROPERTY(date);

   protected:
      HistoryEntry(Date new_date);
   };

   struct GameManager;

   namespace _HistoryMapHelperFuncs {
      /* Helper functions to avoid cyclic dependency issues */
      Date _get_start_date(GameManager const& game_manager);
      Date _get_end_date(GameManager const& game_manager);
   }

   template<std::derived_from<HistoryEntry> _Entry, typename... Args>
   struct HistoryMap {
      using entry_type = _Entry;

   private:
      std::map<Date, std::unique_ptr<entry_type>> PROPERTY(entries);

      bool _try_load_history_entry(GameManager const& game_manager, Args... args, Date date, ast::NodeCPtr root) {
         entry_type *const entry = _get_or_make_entry(game_manager, date);
         if (entry != nullptr) {
            return _load_history_entry(game_manager, args..., *entry, root);
         } else {
            return false;
         }
      }

   protected:
      HistoryMap() = default;

      virtual std::unique_ptr<entry_type> _make_entry(Date date) const = 0;

      virtual bool _load_history_entry(
         GameManager const& game_manager, Args... args, entry_type& entry, ast::NodeCPtr root
      ) = 0;

      bool _load_history_file(GameManager const& game_manager, Args... args, ast::NodeCPtr root) {
         return _try_load_history_entry(game_manager, args..., _HistoryMapHelperFuncs::_get_start_date(game_manager), root);
      }

      bool _load_history_sub_entry_callback(
         GameManager const& game_manager, Args... args, Date date, ast::NodeCPtr root, std::string_view key,
         ast::NodeCPtr value, NodeTools::key_value_callback_t default_callback = NodeTools::key_value_invalid_callback
      ) {
         /* Date blocks (loaded into the corresponding HistoryEntry) */
         bool is_date = false;
         const Date sub_date { Date::from_string(key, &is_date, true) };
         if (is_date) {
            if (sub_date < date) {
               Logger::error("History entry ", sub_date, " defined before parent entry date ", date);
               return false;
            }
            if (sub_date == date) {
               Logger::warning("History entry ", sub_date, " defined with same date as parent entry");
            }
            if (_try_load_history_entry(game_manager, args..., sub_date, value)) {
               return true;
            } else {
               Logger::error("Failed to load history entry at date ", sub_date);
               return false;
            }
         }

         return default_callback(key, value);
      }

      /* Returns history entry at specific date, if date doesn't have an entry creates one, if that fails returns nullptr. */
      entry_type* _get_or_make_entry(GameManager const& game_manager, Date date) {
         const Date end_date = _HistoryMapHelperFuncs::_get_end_date(game_manager);
         if (date > end_date) {
            Logger::error("History entry ", date, " defined after end date ", end_date);
            return nullptr;
         }
         typename decltype(entries)::iterator it = entries.find(date);
         if (it == entries.end()) {
            const std::pair<typename decltype(entries)::iterator, bool> result = entries.emplace(date, _make_entry(date));
            if (result.second) {
               it = result.first;
            } else {
               Logger::error("Failed to create history entry at date ", date);
               return nullptr;
            }
         }
         return it->second.get();
      }

   public:
      /* Returns history entry at specific date, if date doesn't have an entry returns nullptr. */
      entry_type const* get_entry(Date date) const {
         typename decltype(entries)::const_iterator it = entries.find(date);
         if (it != entries.end()) {
            return it->second.get();
         }
         return nullptr;
      }
      /* Returns history entries up to date as an ordered list of entries. */
      std::vector<entry_type const*> get_entries_up_to(Date end) const {
         std::vector<entry_type const*> ret;
         for (typename decltype(entries)::value_type const& entry : entries) {
            if (entry.first <= end) {
               ret.push_back(entry.second.get());
            }
         }
         std::sort(ret.begin(), ret.end(), [](entry_type const* lhs, entry_type const* rhs) -> bool {
            return lhs->get_date() < rhs->get_date();
         });
         return ret;
      }
   };
}