text: #pragma once
  
  #include <oslo/lib/configbase.hpp>
  #include <oslo/lib/utility.hpp>
  
  #include <boost/filesystem/path.hpp>
  #include <boost/lexical_cast.hpp>
  #include <boost/optional.hpp>
  
  #include <cpptoml.h>
  
  namespace boost
  {
  namespace asio
  {
  	namespace ip
  	{
  		class address_v6;
  	}
  }
  }
  
  namespace oslo
  {
  class error;
  
  /** Manages a table in a toml configuration table hierarchy */
  class tomlconfig : public oslo::configbase
  {
  public:
  	tomlconfig ();
  	tomlconfig (std::shared_ptr<cpptoml::table> const & tree_a, std::shared_ptr<oslo::error> const & error_a = nullptr);
  	void doc (std::string const & key, std::string const & doc);
  	oslo::error & read (boost::filesystem::path const & path_a);
  	oslo::error & read (std::istream & stream_overrides, boost::filesystem::path const & path_a);
  	oslo::error & read (std::istream & stream_a);
  	oslo::error & read (std::istream & stream_first_a, std::istream & stream_second_a);
  	void write (boost::filesystem::path const & path_a);
  	void write (std::ostream & stream_a) const;
  	void open_or_create (std::fstream & stream_a, std::string const & path_a);
  	std::shared_ptr<cpptoml::table> get_tree ();
  	bool empty () const;
  	boost::optional<tomlconfig> get_optional_child (std::string const & key_a);
  	tomlconfig get_required_child (std::string const & key_a);
  	tomlconfig & put_child (std::string const & key_a, oslo::tomlconfig & conf_a);
  	tomlconfig & replace_child (std::string const & key_a, oslo::tomlconfig & conf_a);
  	bool has_key (std::string const & key_a);
  	tomlconfig & erase (std::string const & key_a);
  	std::shared_ptr<cpptoml::array> create_array (std::string const & key, boost::optional<const char *> documentation_a);
  	void erase_default_values (tomlconfig & defaults_a);
  	std::string to_string ();
  	std::string to_string_commented_entries ();
  
  	/** Set value for the given key. Any existing value will be overwritten. */
  	template <typename T>
  	tomlconfig & put (std::string const & key, T const & value, boost::optional<const char *> documentation_a = boost::none)
  	{
  		tree->insert (key, value);
  		if (documentation_a)
  		{
  			doc (key, *documentation_a);
  		}
  		return *this;
  	}
  
  	/**
  	 * Push array element
  	 * @param key Array element key. Qualified (dotted) keys are not supported for arrays so this must be called on the correct tomlconfig node.
  	 */
  	template <typename T>
  	tomlconfig & push (std::string const & key, T const & value)
  	{
  		if (!has_key (key))
  		{
  			auto arr = cpptoml::make_array ();
  			tree->insert (key, arr);
  		}
  		auto arr = tree->get_qualified (key)->as_array ();
  		arr->push_back (value);
  		return *this;
  	}
  
  	/**
  	 * Iterate array entries.
  	 * @param key Array element key. Qualified (dotted) keys are not supported for arrays so this must be called on the correct tomlconfig node.
  	 */
  	template <typename T>
  	tomlconfig & array_entries_required (std::string const & key, std::function<void(T)> callback)
  	{
  		if (tree->contains_qualified (key))
  		{
  			auto items = tree->get_qualified_array_of<T> (key);
  			for (auto & item : *items)
  			{
  				callback (item);
  			}
  		}
  		else
  		{
  			conditionally_set_error<T> (oslo::error_config::missing_value, false, key);
  		}
  		return *this;
  	}
  
  	/** Get optional, using \p default_value if \p key is missing. */
  	template <typename T>
  	tomlconfig & get_optional (std::string const & key, T & target, T default_value)
  	{
  		get_config (true, key, target, default_value);
  		return *this;
  	}
  
  	/**
  	 * Get optional value, using the current value of \p target as the default if \p key is missing.
  	 * @return May return oslo::error_config::invalid_value
  	 */
  	template <typename T>
  	tomlconfig & get_optional (std::string const & key, T & target)
  	{
  		get_config (true, key, target, target);
  		return *this;
  	}
  
  	/** Return a boost::optional<T> for the given key */
  	template <typename T>
  	boost::optional<T> get_optional (std::string const & key)
  	{
  		boost::optional<T> res;
  		if (has_key (key))
  		{
  			T target{};
  			get_config (true, key, target, target);
  			res = target;
  		}
  		return res;
  	}
  
  	/** Get value, using the current value of \p target as the default if \p key is missing. */
  	template <typename T>
  	tomlconfig & get (std::string const & key, T & target)
  	{
  		get_config (true, key, target, target);
  		return *this;
  	}
  
  	/**
  	 * Get value of optional key. Use default value of data type if missing.
  	 */
  	template <typename T>
  	T get (std::string const & key)
  	{
  		T target{};
  		get_config (true, key, target, target);
  		return target;
  	}
  
  	/**
  	 * Get required value.
  	 * @note May set oslo::error_config::missing_value if \p key is missing, oslo::error_config::invalid_value if value is invalid.
  	 */
  	template <typename T>
  	tomlconfig & get_required (std::string const & key, T & target)
  	{
  		get_config (false, key, target);
  		return *this;
  	}
  
  	template <typename T>
  	tomlconfig & get_required (std::string const & key, T & target, T const & default_value)
  	{
  		get_config (false, key, target, default_value);
  		return *this;
  	}
  
  protected:
  	template <typename T, typename = std::enable_if_t<oslo::is_lexical_castable<T>::value>>
  	tomlconfig & get_config (bool optional, std::string const & key, T & target, T default_value = T ())
  	{
  		try
  		{
  			if (tree->contains_qualified (key))
  			{
  				auto val (tree->get_qualified_as<std::string> (key));
  				if (!boost::conversion::try_lexical_convert<T> (*val, target))
  				{
  					conditionally_set_error<T> (oslo::error_config::invalid_value, optional, key);
  				}
  			}
  			else if (!optional)
  			{
  				conditionally_set_error<T> (oslo::error_config::missing_value, optional, key);
  			}
  			else
  			{
  				target = default_value;
  			}
  		}
  		catch (std::runtime_error & ex)
  		{
  			conditionally_set_error<T> (ex, optional, key);
  		}
  
  		return *this;
  	}
  
  	tomlconfig & get_config (bool optional, std::string const & key, uint8_t & target, uint8_t default_value = uint8_t ());
  	tomlconfig & get_config (bool optional, std::string const & key, bool & target, bool default_value = false);
  	tomlconfig & get_config (bool optional, std::string key, boost::asio::ip::address_v6 & target, boost::asio::ip::address_v6 const & default_value);
  
  private:
  	/** The config node being managed */
  	std::shared_ptr<cpptoml::table> tree;
  
  	/** Compare two stringified configs, remove keys where values are equal */
  	void erase_defaults (std::shared_ptr<cpptoml::table> base, std::shared_ptr<cpptoml::table> other, std::shared_ptr<cpptoml::table> update_target);
  };
  }
  