/* +------------------------------------------------------------------------+
   |                     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 <mrpt/core/exceptions.h>

/** \example containers_yaml_example/test.cpp */

//! [example-yaml]
#include <mrpt/containers/yaml.h>
#include <mrpt/core/demangle.h>  // demangle() utility

#include <iostream>

void YamlTest_1()
{
  std::cout << "==== YamlTest_1 ====\n";

  // Load from file:
  // const auto p = mrpt::containers::yaml::FromFile("xxx.yaml");
  // or load from text block:
  // const auto p = mrpt::containers::yaml::FromText(txt);

  // or build a document programatically:
  mrpt::containers::yaml p;
  p["K"] = 2.0;
  p["N"] = 10;
  p["name"] = "Foo";
  p["enabled"] = true;
  p["books"] = mrpt::containers::yaml::Sequence();
  p["books"].push_back("The Hobbit");
  p["books"].push_back(10.0);

  // You can use {}-initializers as well:
  p["movies"] = mrpt::containers::yaml::Sequence({
      mrpt::containers::yaml::Map({{"title", "xxx"}, {"year", 2001}}
      ),
      mrpt::containers::yaml::Map({{"title", "yyy"}, {"year", 1986}}
      )
  });

  std::cout << "K=" << p["K"] << " N=" << p["N"] << "\n";
  std::cout << "name=" << p["name"] << "\n";
  std::cout << "Movie year=" << p["movies"](1)["year"] << "\n";

  // Get a value, or default if not found.
  // YAMLCPP equivalent: p["bar"].as<std::string>("none")
  std::cout << "bar=" << p.getOrDefault<std::string>("bar", "none") << "\n";

  // Iterate a dictionary:
  for (const auto& kv : p.asMap())
  {
    const std::string key = kv.first.as<std::string>();
    const auto& valueNode = kv.second;
    std::cout << "`" << key << "`: " << mrpt::demangle(valueNode.typeName()) << "\n";
  }

  // Iterate a dictionary bis:
  mrpt::containers::yaml p2;
  p2["a"] = 1.0;
  p2["b"] = 10;
  p2["c"] = -1;

  for (const auto& kv : p2.asMap())
  {
    // This will raise an exception if stored type cannot be converted to
    // double:
    std::cout << "key: `" << kv.first << "` val: " << kv.second.as<double>() << "\n";
  }

  // Iterate sequence:
  for (const auto& item : p["books"].asSequence())
  {
    std::cout << "sequence item type: " << mrpt::demangle(item.typeName()) << "\n";
    // Access value: kv.second.as<std::string>(), etc.
  }

  // Print:
  std::cout << "\n\nPrint as YAML:\n";
  p.printAsYAML();
}

void YamlTest_2()
{
  std::cout << "\n\n==== YamlTest_2 ====\n";

  // You can use {} to initialize mappings (dictionaries):
  using mrpt::containers::CommentPosition;
  using mrpt::containers::vcp;
  using mrpt::containers::vkcp;

  mrpt::containers::yaml p;

  // Insert a key in a map with a "comment" block.
  p << vkcp("L", 5.5, "Arm length [meters]") << vkcp("D", 1.0, "Distance [meters]")
    << vkcp("Y", -5, "Comment for Y");

  ASSERT_(p.isMap());
  ASSERT_(p.has("L"));
  ASSERT_(p["L"].isScalar());
  ASSERT_(p.keyHasComment("D"));

  // Add comment associated to value (not key):
  p["X"] = vcp(1.0, "Default value");

  std::cout << "D key comment: " << p.keyComment("D") << "\n";
  std::cout << "X value comment: " << p["X"].comment() << "\n";

  // Print:
  std::cout << "\n\nPrint as YAML:\n";
  p.printAsYAML(std::cout);
}

const auto sData = std::string(R"xxx(
myMap:
  K: 10.0
  P: -5.0
  Q: ~
  nestedMap:
    a: 1  # comment for a
    b: 2
    c: 3
)xxx");

void YamlTest_3()
{
  std::cout << "\n\n==== YamlTest_3 ====\n";

  // Parse a YAML or JSON text:
  mrpt::containers::yaml p = mrpt::containers::yaml::FromText(sData);

  // Get comments:
  std::cout << "Comment: '" << p["myMap"]["nestedMap"]["a"].comment() << "'\n";

  // Manipulate comments:
  p["myMap"]["nestedMap"]["b"].comment("This is a comment for b");

  // Add values and comments at once:
  p["myMap"]["foo"] = mrpt::containers::vcp(1.0, "Another constant");

  std::cout << "\n\nPrint as YAML:\n";
  p.printAsYAML(std::cout);
}
//! [example-yaml]

int main()
{
  try
  {
    YamlTest_1();
    YamlTest_2();
    YamlTest_3();
    return 0;
  }
  catch (const std::exception& e)
  {
    std::cerr << "MRPT exception caught: " << mrpt::exception_to_str(e) << std::endl;
    return -1;
  }
}
