#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include "cantera/base/AnyMap.h"

using namespace Cantera;

TEST(AnyValue, is_copyable) {
    AnyMap map1, map2;
    map1["key"] = "1";
    map2["key"] = "2";
    AnyValue value1 = map1["key"];
    AnyValue value2 = map2["key"];
    value2 = value1;
    EXPECT_EQ(value1.asString(), "1");
    EXPECT_EQ(value2.asString(), "1");
}

TEST(AnyValue, is_moveable) {
    AnyMap map1, map2;
    map1["key"] = "1";
    map2["key"] = "2";
    AnyValue value1 = map1["key"];
    AnyValue value2 = map2["key"];
    value2 = std::move(value1);
    EXPECT_EQ(value2.asString(), "1");
}

TEST(AnyValue, getMapWhere_initial_list)
{
    AnyMap m = AnyMap::fromYamlString(
        "data: [{a: foo, x: 2}, {a: bar, x: 3}]");

    EXPECT_TRUE(m["data"].hasMapWhere("a", "foo"));
    EXPECT_EQ(m["data"].getMapWhere("a", "bar")["x"].asInt(), 3);

    EXPECT_THROW(m["data"].getMapWhere("a", "baz"), CanteraError);
    auto& newChild = m["data"].getMapWhere("a", "baz", true);
    EXPECT_EQ(newChild.size(), (size_t) 1);
    newChild["x"] = 4;
    EXPECT_EQ(m["data"].getMapWhere("a", "baz")["x"].asInt(), 4);
}

TEST(AnyValue, numeric_yaml_string)
{
    AnyMap m = AnyMap::fromYamlString(
        "data: {a: '12345', b: '123.45', 'c': 12345, 'd': 123.45}");

    EXPECT_THROW(m["data"]["a"].asInt(), CanteraError);
    EXPECT_EQ(m["data"]["a"].asString(), "12345");
    EXPECT_THROW(m["data"]["b"].asDouble(), CanteraError);
    EXPECT_EQ(m["data"]["b"].asString(), "123.45");
    EXPECT_EQ(m["data"]["c"].asInt(), 12345);
    EXPECT_EQ(m["data"]["d"].asDouble(), 123.45);
}

TEST(AnyValue, getMapWhere_initial_map)
{
    AnyMap m = AnyMap::fromYamlString(
        "data: {a: foo, x: 2}");

    EXPECT_TRUE(m["data"].hasMapWhere("a", "foo"));
    EXPECT_EQ(m["data"].getMapWhere("a", "foo")["x"].asInt(), 2);

    EXPECT_THROW(m["data"].getMapWhere("a", "baz"), CanteraError);
    auto& newChild = m["data"].getMapWhere("a", "baz", true);
    EXPECT_EQ(newChild.size(), (size_t) 1);
    newChild["x"] = 4;
    EXPECT_EQ(m["data"].getMapWhere("a", "baz")["x"].asInt(), 4);
    EXPECT_EQ(m["data"].getMapWhere("a", "foo")["x"].asInt(), 2);
}

TEST(AnyValue, convert_vectorAnyMap)
{
    AnyMap m = AnyMap::fromYamlString(
        "data: {a: foo, x: 2}");

    auto& v = m["data"].asVector<AnyMap>();
    EXPECT_EQ(v.size(), (size_t) 1);
    EXPECT_EQ(v[0]["a"].asString(), "foo");
}

TEST(AnyValue, equality) {
    AnyValue three(3);
    EXPECT_EQ(three, 3);
    EXPECT_EQ(3, three);
    EXPECT_EQ(3.0, three);
    EXPECT_NE(three, 4);
    EXPECT_NE(three, "three");

    AnyValue word("word");
    EXPECT_EQ(word, "word");
    EXPECT_EQ("word", word);
}

TEST(AnyMap, paths) {
    AnyMap m;
    m["simple"] = "qux";
    m["compound"]["first"] = "bar";
    m["compound"]["second"] = "baz";
    m["several"]["layers"]["deep"] = "foo";

    EXPECT_TRUE(m.hasKey("simple"));
    EXPECT_TRUE(m.hasKey("compound"));
    EXPECT_TRUE(m["compound"].hasKey("second"));

    EXPECT_EQ(m["compound"]["first"].asString(), "bar");
    EXPECT_EQ(m["compound"]["second"].as<string>(), "baz");
    EXPECT_EQ(m["several"]["layers"]["deep"].asString(), "foo");
    EXPECT_THROW(m.at("missing"), std::exception);
    EXPECT_FALSE(m.hasKey("missing"));
    EXPECT_FALSE(m["compound"].hasKey("missing"));
    EXPECT_THROW(m["missing"].asString(), std::exception);
}

TEST(AnyMap, equality1) {
    AnyMap m1;
    m1["simple"] = "qux";
    m1["compound"]["first"] = "bar";
    m1["compound"]["second"] = 3.14;
    AnyMap m2 = m1;
    EXPECT_EQ(m1, m2);
    m1["compound"]["second"] = 4.0;
    EXPECT_NE(m1, m2);
    m2["compound"]["second"] = 4.0;
    EXPECT_EQ(m1, m2);
    m2["foo"] = 5;
    EXPECT_NE(m1, m2);
}

TEST(AnyMap, equality2) {
    // Build two identical maps
    vector<AnyMap> M(2);
    for (auto& m : M) {
        m["group"]["vector_double"] = vector<double>{1.1, 3.2, 2.4};
        m["group"]["vector_int"] = vector<long int>{3,5,7,9};
        m["group"]["changes"] = "a string";
        m["group"]["changes"] = 9;
        m["group"]["vector_vector_double"] = vector<vector<double>>{
            {1.2, 2.1}, {3.4, 4.3}, {5.6, 6.5}
        };
        m["bool"] = true;
        m["int"] = 33;
        m["vector_any_int"] = vector<long int>{3, 9, -1};
        m["strings"] = vector<string>{"spam", "eggs", "spam"};
    }

    EXPECT_EQ(M[0], M[1]);

    // Hidden keys shouldn't affect equality
    M[0]["__secret__"] = true;
    EXPECT_EQ(M[0], M[1]);
    EXPECT_EQ(M[1], M[0]);

    M[0]["group"]["changes"] = 8;
    EXPECT_NE(M[0], M[1]);

    M[0]["group"]["changes"] = 9.0;
    M[0]["int"].asDouble();
    EXPECT_EQ(M[0], M[1]);

    // These conversions affect the type of the held value, but they should
    // still be considered equal
    M[0]["group"]["vector_int"].asVector<double>();
    EXPECT_EQ(M[0], M[1]);

    M[0]["vector_any_int"].asVector<AnyValue>();
    EXPECT_EQ(M[0], M[1]);

    M[1]["group"]["vector_double"].asVector<AnyValue>();
    EXPECT_EQ(M[0], M[1]);

    M[0]["strings"].asVector<AnyValue>();
    EXPECT_EQ(M[0], M[1]);
    EXPECT_EQ(M[1], M[0]);
}

TEST(AnyMap, map_conversion) {
    AnyMap m;
    m["compound"]["first"] = "bar";
    m["compound"]["second"] = "baz";
    m["empty"] = AnyMap();

    auto x = m["compound"].asMap<string>();
    EXPECT_EQ(x.size(), (size_t) 2);
    EXPECT_EQ(x["first"], "bar");
    EXPECT_EQ(x["second"], "baz");
    string keys = m["compound"].as<AnyMap>().keys_str();
    EXPECT_NE(keys.find("first"), npos);
    EXPECT_NE(keys.find("second"), npos);
    EXPECT_EQ(keys.size(), (size_t) 13);
    EXPECT_EQ(m["empty"].as<AnyMap>().keys_str(), "");

    map<string, double> zz{{"a", 9.1}, {"b", 13.5}};
    m["foo"] = zz;
    EXPECT_TRUE(m["foo"].hasKey("a"));
    EXPECT_DOUBLE_EQ(m["foo"]["b"].asDouble(), 13.5);

    EXPECT_EQ(m["foo"]["a"].asString(), "9.1"); // Implicit conversion
    EXPECT_THROW(m["foo"]["b"].asVector<double>(), CanteraError);

    m["qux"]["c"] = 3;
    m["qux"]["d"] = 3.14;
    auto y = m["qux"].asMap<double>();
    EXPECT_DOUBLE_EQ(y["c"], 3.0);
    EXPECT_DOUBLE_EQ(y["d"], 3.14);
    EXPECT_THROW(m["qux"].asMap<long int>(), CanteraError);
}

TEST(AnyMap, nested)
{
    AnyMap m;
    AnyMap b;
    b["foo"] = "bar";
    b["qux"] = 0.5;
    m["baz"] = b;
    b["late"] = "nope"; // Should have added a copy
    EXPECT_EQ(m["baz"]["foo"].asString(), "bar");
    EXPECT_EQ(m["baz"]["qux"].asDouble(), 0.5);
    EXPECT_FALSE(m["baz"].hasKey("late"));
}

TEST(AnyMap, vector)
{
    AnyMap m;
    vector<double> yy{9.6, 14.4, 28.8};
    m["nested"]["item"] = yy;
    vector<double>& yref = m["nested"]["item"].asVector<double>();
    yref.push_back(-1);
    EXPECT_EQ(yy.size(), (size_t) 3); // Should have added a copy
    // Should have modified the copy in the map
    EXPECT_EQ(m["nested"]["item"].asVector<double>().size(), (size_t) 4);
}

TEST(AnyMap, vector_length)
{
    AnyMap m;
    m["foo"] = vector<double>{2.4, 9.6, 14.4, 28.8};
    // Valid lengths
    m["foo"].asVector<double>(4);
    m["foo"].asVector<double>(2, 5);
    // Invalid lengths
    EXPECT_THROW(m["foo"].asVector<double>(3), CanteraError);
    EXPECT_THROW(m["foo"].asVector<double>(5, 8), CanteraError);
}

TEST(AnyMap, getters_with_defaults)
{
    AnyMap m;
    map<string, double> zz{{"a", 9.0}, {"b", 13.5}};
    m["foo"] = zz;
    m["foo"]["c"] = 4;
    m["bar"] = "baz";
    m["qux"] = false;
    EXPECT_FALSE(m.getBool("qux", true));
    EXPECT_TRUE(m.getBool("missing", true));
    EXPECT_EQ(m.getString("missing", "hi"), "hi");
    EXPECT_EQ(m.getString("bar", "hi"), "baz");
    EXPECT_EQ(m.getInt("missing", 3), 3);
    auto& f = m["foo"].as<AnyMap>();
    EXPECT_EQ(f.getInt("c", 3), 4);
    EXPECT_EQ(f.getDouble("missing", 3), 3);
    EXPECT_EQ(f.getDouble("b", 3), 13.5);
}

TEST(AnyMap, conversion_to_double)
{
    AnyMap m = AnyMap::fromYamlString(
        "{scalar: 8, list: [7, 4, 1], nested: [[3, 4, 5], [12, 22, 91]]}");
    const AnyMap n = m;
    EXPECT_EQ(m["scalar"].asDouble(), 8);
    EXPECT_EQ(m["list"].asVector<double>()[1], 4);
    EXPECT_EQ(m["nested"].asVector<vector<double>>()[1][2], 91);
    EXPECT_EQ(n.at("scalar").asDouble(), 8);
    EXPECT_EQ(n.at("list").asVector<double>()[0], 7);
    EXPECT_EQ(n.at("nested").asVector<vector<double>>()[0][2], 5);
}

TEST(AnyMap, conversion_to_anyvalue)
{
    AnyMap m = AnyMap::fromYamlString(
        "{floats: [7.5, 40, -3.14], strings: [foo, bar]}");
    const AnyMap n = m;
    EXPECT_EQ(m["floats"].asVector<AnyValue>()[0].asDouble(), 7.5);
    EXPECT_EQ(m["strings"].asVector<AnyValue>()[1].asString(), "bar");
    EXPECT_EQ(n.at("floats").asVector<AnyValue>()[2].asDouble(), -3.14);
    EXPECT_EQ(n.at("strings").asVector<AnyValue>()[0].asString(), "foo");
}

TEST(AnyMap, conversion_from_quoted)
{
    AnyMap m = AnyMap::fromYamlString(
        "{top: '99', mixed: [5.1, '122599', '3.140'], uniform: ['99', 100.1']}");
    EXPECT_TRUE(m["top"].is<string>());
    EXPECT_FALSE(m["top"].is<int>());
    EXPECT_THROW(m["mixed"].asVector<double>(), InputFileError);
    EXPECT_DOUBLE_EQ(m["mixed"].asVector<AnyValue>()[0].asDouble(), 5.1);
    EXPECT_THROW(m["uniform"].asVector<double>(), InputFileError);
}

TEST(AnyMap, read_subnormal)
{
    AnyMap m = AnyMap::fromYamlString(
        "scalar: 4.940656458412465e-324\n"
        "vector: [1.999999997714111, 1.482285197259138, 1.151630981915516,\n"
        "2.957408463878464, 2.035192404627, -4.940656458412465e-324]\n"
        "matrix: [[1.999999997714111, -4.940656458412465e-324, 1.151630981915516],\n"
        "[2.957408463878464, 2.035192404627, 1.482285197259138]]");

    EXPECT_DOUBLE_EQ(m["scalar"].asDouble(), 0.0);
    EXPECT_DOUBLE_EQ(m["vector"].asVector<double>()[5], 0.0);
    EXPECT_DOUBLE_EQ(m["matrix"].asVector<vector<double>>()[0][1], 0.0);
}

TEST(AnyMap, iterators)
{
    AnyMap m = AnyMap::fromYamlString(
        "{a: 1, b: two, c: 3.01, d: {foo: 1, bar: 2}}");

    vector<string> keys;
    for (const auto& [key, value] : m) {
        keys.push_back(key);
    }
    EXPECT_EQ(keys.size(), (size_t) 4);
    EXPECT_TRUE(std::find(keys.begin(), keys.end(), "c") != keys.end());
    keys.clear();

    for (const auto& [key, value] : m.at("d")) {
        keys.push_back(key);
    }
    EXPECT_EQ(keys.size(), (size_t) 2);
    EXPECT_TRUE(std::find(keys.begin(), keys.end(), "bar") != keys.end());
}

TEST(AnyMap, null_values)
{
    AnyMap m = AnyMap::fromYamlString(
        "{a: 1, b: ~, c: , d: 5}"
    );
    EXPECT_EQ(m.size(), (size_t) 4);
    EXPECT_TRUE(m.at("c").is<void>());

    try {
        m.at("b").asString();
        FAIL();
    } catch (CanteraError& err) {
        EXPECT_THAT(err.what(),
                    testing::HasSubstr("Key 'b' not found or contains no value"));
    }
}

TEST(AnyMap, sizes) {
    AnyMap m = AnyMap::fromYamlString("{__hidden__: 3}");
    m.exclude("baz");
    m.setFlowStyle();
    EXPECT_TRUE(m.empty());
    EXPECT_EQ(m.size(), 0U);
}

TEST(AnyMap, error_loc) {
    AnyMap m = AnyMap::fromYamlString("{spam: 4, eggs: 12}");
    try {
        m.at("fake");
        FAIL();
    } catch (CanteraError& err) {
        EXPECT_THAT(err.what(), testing::HasSubstr("Error on line 1"));
        // Column marker lines up with opening brace
        EXPECT_THAT(err.what(), testing::HasSubstr("\n          ^"));
    }
}

TEST(AnyMap, loadYaml)
{
    AnyMap m = AnyMap::fromYamlString(
        "name: NO2\n"
        "composition: {N: 1, O: 2}\n"
        "thermo:\n"
        "  model: NASA7\n"
        "  reference-pressure: 1 atm\n"
        "  temperature-ranges: [200, 1000, 6000]\n"
        "  data:\n"
        "  - [3.944031200E+00, -1.585429000E-03, 1.665781200E-05, -2.047542600E-08,\n"
        "     7.835056400E-12, 2.896617900E+03, 6.311991700E+00]\n"
        "  - [4.884754200E+00, 2.172395600E-03, -8.280690600E-07, 1.574751000E-10,\n"
        "     -1.051089500E-14, 2.316498300E+03, -1.174169500E-01]\n"
        "transport: # this is a comment\n"
        "  model: gas\n"
        "  geometry: nonlinear\n"
        "  flag: true\n");

    EXPECT_EQ(m["name"].asString(), "NO2");
    EXPECT_EQ(m["composition"]["N"].asInt(), 1);
    EXPECT_EQ(m["thermo"]["reference-pressure"].asString(), "1 atm");
    EXPECT_EQ(m["thermo"]["temperature-ranges"].asVector<long int>()[0], 200);
    EXPECT_EQ(m["transport"]["geometry"].asString(), "nonlinear");
    EXPECT_TRUE(m["transport"]["flag"].asBool());
    auto coeffs = m["thermo"]["data"].asVector<vector<double>>();
    EXPECT_EQ(coeffs.size(), (size_t) 2);
    EXPECT_EQ(coeffs[0].size(), (size_t) 7);
    EXPECT_DOUBLE_EQ(coeffs[1][2], -8.280690600E-07);
}

TEST(AnyMap, missingKey)
{
    AnyMap root = AnyMap::fromYamlFile("ideal-gas.yaml");
    try {
        root["spam"].getMapWhere("name", "unknown");
    } catch (std::exception& ex) {
        EXPECT_THAT(ex.what(), ::testing::HasSubstr("Key 'spam' not found"));
    }
}

TEST(AnyMap, missingKeyAt)
{
    AnyMap root = AnyMap::fromYamlFile("ideal-gas.yaml");
    try {
        root.at("spam").getMapWhere("name", "unknown");
    } catch (std::exception& ex) {
        EXPECT_THAT(ex.what(), ::testing::HasSubstr("Key 'spam' not found"));
    }
}

TEST(AnyMap, dumpYamlString)
{
    AnyMap original = AnyMap::fromYamlFile("h2o2.yaml");
    string serialized = original.toYamlString();
    AnyMap generated = AnyMap::fromYamlString(serialized);
    for (const auto& [key, value] : original) {
        EXPECT_TRUE(generated.hasKey(key));
    }
    EXPECT_EQ(original["species"].getMapWhere("name", "OH")["thermo"]["data"].asVector<vector<double>>(),
        generated["species"].getMapWhere("name", "OH")["thermo"]["data"].asVector<vector<double>>());
}

TEST(AnyMap, YamlFlowStyle)
{
    AnyMap original;
    original["x"] = 3;
    original["y"] = true;
    original["z"] = AnyMap::fromYamlString("{zero: 1, half: 2}");
    original.setFlowStyle();
    string serialized = original.toYamlString();
    // The serialized version should contain two lines, and end with a newline.
    EXPECT_EQ(std::count(serialized.begin(), serialized.end(), '\n'), 2);
    AnyMap generated = AnyMap::fromYamlString(serialized);
    for (const auto& [key, value] : original) {
        EXPECT_TRUE(generated.hasKey(key));
    }
}

TEST(AnyMap, nestedVectorsToYaml)
{
    vector<string> words{"foo", "bar", "baz", "qux", "foobar"};
    vector<vector<string>> strings;
    vector<vector<bool>> booleans;
    vector<vector<long int>> integers;
    for (size_t i = 0; i < 3; i++) {
        strings.emplace_back();
        booleans.emplace_back();
        integers.emplace_back();
        for (size_t j = 0; j < 4; j++) {
            strings.back().push_back(words[(i + 3 * j) % words.size()]);
            booleans.back().push_back(i == j);
            integers.back().push_back(static_cast<int>(6*i + j));
        }
    }
    AnyMap original;
    original["strings"] = strings;
    original["booleans"] = booleans;
    original["integers"] = integers;
    string serialized = original.toYamlString();
    AnyMap generated = AnyMap::fromYamlString(serialized);

    EXPECT_EQ(generated["strings"].asVector<vector<string>>(), strings);
    EXPECT_EQ(generated["booleans"].asVector<vector<bool>>(), booleans);
    EXPECT_EQ(generated["integers"].asVector<vector<long int>>(), integers);
}

TEST(AnyMap, definedKeyOrdering)
{
    AnyMap m = AnyMap::fromYamlString("{zero: 1, half: 2}");
    m["one"] = 1;
    m["two"] = 2;
    m["three"] = 3;
    m["four"] = 4;
    m["five"] = 5;
    m["six"] = 6;
    m["__type__"] = "Test";

    AnyMap::addOrderingRules("Test", {
        {"head", "three"},
        {"head", "five"},
        {"tail", "six"},
        {"tail", "one"}
    });

    string result = m.toYamlString();
    std::unordered_map<string, size_t> loc;
    for (auto& [key, value] : m) {
        loc[key] = result.find(key);
    }
    EXPECT_LT(loc["three"], loc["one"]);
    EXPECT_LT(loc["three"], loc["half"]);
    EXPECT_LT(loc["four"], loc["one"]);
    EXPECT_LT(loc["one"], loc["half"]);
    EXPECT_LT(loc["zero"], loc["half"]);
    EXPECT_LT(loc["three"], loc["five"]);
    EXPECT_LT(loc["six"], loc["one"]);
}
