#pragma once
#include "a83/string-util.hpp"
#include "a83/error.hpp"
#include "a83/log.hpp"
#include "common/archive.hpp"
#include <fstream>
#include <typeinfo>
#include <map>
//! класс для чтения INI файлов
class INI {
protected:
  //! <section_name <param_name, str_value>>
  std::map<a83::Str, std::map<a83::Str, a83::Str>> tree = {};
public:
  //! load from file
  void load(a83::Str fname) {
    using namespace a83;
    close();
    convSep(fname);
    a83::log("load INI: \"" + fname + "\"");
    std::ifstream in(fname);
    IFERROR(in.bad() || !in, "INI.load (file): stream error");
    load(in);
  }
  //! loading rom archive
  void load(CN(Archive) arch, a83::Str fname) {
    using namespace a83;
    close();
    convSep(fname);
    a83::log("load INI (from mem): \"" + fname + "\"");
    auto mem = arch.get_file(fname);
    IFERROR(mem.empty(), "INI.load: mem.empty");
    std::string data(rcast(char*, mem.data), mem.size);
    std::istringstream mb(data);
    load(mb);
  }
  void close() { tree.clear(); }
  //! проверка есть ли такая секция
  bool checkSection(a83::Str section) const {
    section = a83::tolower(section);
    for (cnauto section_item: tree)
      if (section_item.first == section)
        return true;
    return false;
  }
  //! maker of get func
  template <typename T> T get_func(a83::Str section,
  a83::Str name, CN(T) default_value) const {
    section = a83::tolower(section);
    name = a83::tolower(name);
    for (cnauto section_item: tree)
      if (section_item.first == section)
        for (cnauto param_item: section_item.second)
          if (param_item.first == name) {
            if (param_item.second.empty())
              return default_value;
            T result = {};
            std::istringstream(param_item.second) >> result;
            return result;
          }
    return default_value;
  }
  a83::Str getStr(a83::Str section, a83::Str name,
  CN(a83::Str) default_value) const {
    section = a83::tolower(section);
    name = a83::tolower(name);
    for (cnauto section_item: tree)
      if (section_item.first == section)
        for (cnauto param_item: section_item.second)
          if (param_item.first == name)
            return param_item.second;
    return default_value;
  }
  #define getInt get_func<int>
  #define getIBool get_func<bool>
  #define getFloat get_func<float>
  #define getDouble get_func<double>
  INI() = default;
  INI(CN(a83::Str) fname) { load(fname); }
  INI(CN(Archive) arch, CN(a83::Str) fname) { load(arch, fname); }
  ~INI() { close(); }
private:
  nocopy(INI);
  template <class T> void load(T& in) {
    using namespace a83;
  // парсинг ini файла:
    Str str; // стока файла
    str.reserve(256);
    Str section_name;
    section_name.reserve(64);
    // текущая записываемая секция
    std::map<Str, Str>* section_p = nullptr;
    while (std::getline(in, str)) {
      remove_all(str, '\r');
      section_name = tolower(getWord(str, '[', ']'));
      if ( !section_name.empty()) {
        section_p = &(tree[section_name] = {});
        continue;
      }
      // строки начинающиеся с коментариев игнорятся
      if (str[0] == '#')
        continue;
      auto param_val_pair = getParamValue(str, '=', ' ');
      param_val_pair.first = tolower(param_val_pair.first);
      if ( !param_val_pair.first.empty()
      and !param_val_pair.second.empty())
        (*section_p)[param_val_pair.first] = param_val_pair.second;
    }
  } // load from istream
}; // INI