/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2024, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

#include "config-precomp.h"  // Precompiled headers

// Fix to SimpleIni bug: not able to build with C++17
#include <functional>
#ifdef _MSC_VER
namespace std
{
template <typename T1, typename T2, typename RET>
using binary_function = std::function<RET(T1, T2)>;
}
#endif

#include <mrpt/config.h>
#if MRPT_HAS_SIMPLEINI_SYSTEM
// Enforce using libuci instead of copyrighted ConvertUTF.h
#define SI_CONVERT_ICU 1
#endif
#include <SimpleIni.h>
#include <mrpt/config/CConfigFile.h>
#include <mrpt/config/config_parser.h>
#include <mrpt/system/os.h>

#include <fstream>
#include <iostream>

using namespace mrpt;
using namespace mrpt::config;
using namespace std;

struct CConfigFile::Impl
{
  std::shared_ptr<CSimpleIniA> ini = std::make_shared<CSimpleIniA>();
};

// copied from mrpt-io to avoid lib dependency:
namespace
{
std::string local_file_get_contents(const std::string& fileName)
{
  // Credits: https://stackoverflow.com/a/2602258/1631514
  // Note: Add "binary" to make sure the "tellg" file size matches the actual
  // number of read bytes afterwards:
  std::ifstream t(fileName, ios::binary);
  if (!t.is_open()) return {};

  t.seekg(0, std::ios::end);
  std::size_t size = t.tellg();
  std::string buffer(size, ' ');
  t.seekg(0);
  t.read(&buffer[0], size);
  return buffer;
}
}  // namespace

/*---------------------------------------------------------------
          Constructor
 ---------------------------------------------------------------*/
CConfigFile::CConfigFile(const std::string& fileName) : m_impl(mrpt::make_impl<CConfigFile::Impl>())
{
  MRPT_START

  m_file = fileName;
  m_modified = false;

  const auto sIn = local_file_get_contents(fileName);
  const auto sOut = mrpt::config::config_parser(sIn);
  m_impl->ini->LoadData(sOut);

  MRPT_END
}

/*---------------------------------------------------------------
          Constructor
 ---------------------------------------------------------------*/
CConfigFile::CConfigFile() : m_impl(mrpt::make_impl<CConfigFile::Impl>())
{
  MRPT_START

  m_file = "";
  m_modified = false;

  MRPT_END
}

/*---------------------------------------------------------------
          setFileName
 ---------------------------------------------------------------*/
void CConfigFile::setFileName(const std::string& fil_path)
{
  MRPT_START

  m_file = fil_path;
  m_modified = false;

  m_impl->ini->LoadFile(fil_path.c_str());
  MRPT_END
}

/*---------------------------------------------------------------
          writeNow
 ---------------------------------------------------------------*/
void CConfigFile::writeNow()
{
  MRPT_START
  if (m_modified && !m_file.empty())
  {
    if (SI_Error err = m_impl->ini->SaveFile(m_file.c_str()); err != SI_OK)
    {
      THROW_EXCEPTION_FMT(
          "SimpleIni error %i for file='%s'", static_cast<int>(err), m_file.c_str());
    }
    m_modified = false;
  }
  MRPT_END
}

void CConfigFile::discardSavingChanges() { m_modified = false; }

CConfigFile::~CConfigFile()
{
  try
  {
    writeNow();
  }
  catch (const std::exception& e)
  {
    std::cerr << "[~CConfigFile] Exception:\n" << mrpt::exception_to_str(e);
  }
}

void CConfigFile::writeString(
    const std::string& section, const std::string& name, const std::string& str)
{
  MRPT_START

  m_modified = true;

  if (0 > m_impl->ini->SetValue(section.c_str(), name.c_str(), str.c_str(), nullptr))
    THROW_EXCEPTION("Error changing value in INI-style file!");

  MRPT_END
}

/*---------------------------------------------------------------
          readString
 ---------------------------------------------------------------*/
std::string CConfigFile::readString(
    const std::string& section,
    const std::string& name,
    const std::string& defaultStr,
    bool failIfNotFound) const
{
  MRPT_START
  const char* defVal = failIfNotFound ? nullptr : defaultStr.c_str();

  const char* aux = m_impl->ini->GetValue(
      section.c_str(), name.c_str(), defVal,
      nullptr);  // The memory is managed by the SimpleIni object

  if (failIfNotFound && !aux)
  {
    string tmpStr(format(
        "Value '%s' not found in section '%s' of file '%s' and "
        "failIfNotFound=true.",
        name.c_str(), section.c_str(), m_file.c_str()));
    THROW_EXCEPTION(tmpStr);
  }

  // Remove possible comments: "//"
  std::string ret = aux;
  size_t pos;
  if ((pos = ret.find("//")) != string::npos && pos > 0 && isspace(ret[pos - 1]))
    ret = ret.substr(0, pos);
  return ret;

  MRPT_END
}

/*---------------------------------------------------------------
           getAllSections
 ---------------------------------------------------------------*/
void CConfigFile::getAllSections(std::vector<std::string>& sections) const
{
  CSimpleIniA::TNamesDepend names;
  m_impl->ini->GetAllSections(names);

  CSimpleIniA::TNamesDepend::iterator n;
  std::vector<std::string>::iterator s;
  sections.resize(names.size());
  for (n = names.begin(), s = sections.begin(); n != names.end(); ++n, ++s) *s = n->pItem;
}

/*---------------------------------------------------------------
            getAllKeys
 ---------------------------------------------------------------*/
void CConfigFile::getAllKeys(const string& section, std::vector<std::string>& keys) const
{
  CSimpleIniA::TNamesDepend names;
  m_impl->ini->GetAllKeys(section.c_str(), names);

  CSimpleIniA::TNamesDepend::iterator n;
  std::vector<std::string>::iterator s;
  keys.resize(names.size());
  for (n = names.begin(), s = keys.begin(); n != names.end(); ++n, ++s) *s = n->pItem;
}

void CConfigFile::clear() { m_impl->ini->Reset(); }
