/*
 * Copyright 2024 Bloomberg LP
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef INCLUDED_BUILDBOXCOMMON_TOMLUTILS
#define INCLUDED_BUILDBOXCOMMON_TOMLUTILS

#include <functional>
#include <toml++/toml.h>
#include <variant>

namespace buildboxcommon {

/**
 * Representing the access path when traversing a TOML tree.
 * Elements can be either a `std::string` or `size_t`,
 * representing a table key or an array index.
 */
using AccessPath = std::vector<std::variant<std::string, size_t>>;

template <typename T>
using NodeReader = std::function<T(const toml::node &, AccessPath &)>;

struct TOMLUtils {
    /**
     * Throw an `std::invalid_argument` exception to indicate a type error
     * when reading a TOML node.
     */
    [[noreturn]] void static throwTOMLTypeError(
        const toml::node &node, const toml::node_type expectedType,
        AccessPath &accessPath, const std::string &message = "");

    template <typename T>
    T static readValue(const toml::node &node, AccessPath &path)
    {
        const std::optional<T> value = node.value<T>();
        if (!value.has_value()) {
            throwTOMLTypeError(node, toml::value<T>().type(), path);
        }
        return value.value();
    }

    /**
     * Read an optional from a TOML table.
     *
     * reader: The reader function to read the internal value from TOML node
     * table: The table to read
     * key: The key name in the table
     * path: Current access path
     */
    template <typename T>
    std::optional<T> static readOptionalFromTable(NodeReader<T> reader,
                                                  const toml::table &table,
                                                  const std::string &key,
                                                  AccessPath &path)
    {
        if (!table.contains(key)) {
            return {};
        }
        const auto &node = *table[key].node();
        path.push_back({key});
        const auto result = reader(node, path);
        path.pop_back();
        return {result};
    }

    /**
     * Read a TOML array to produce a `std::vector`.
     *
     * reader: The reader function to read one internal value from TOML node
     * node: The array to read
     * path: Current access path
     */
    template <typename T>
    static std::vector<T> readFromArray(NodeReader<T> reader,
                                        const toml::node &node,
                                        AccessPath &path)
    {
        if (!node.is_array()) {
            throwTOMLTypeError(node, toml::node_type::array, path);
        }
        const toml::array array = *node.as_array();
        std::vector<T> result;
        for (size_t index = 0; index < array.size(); index++) {
            path.push_back({index});
            result.push_back(reader(array[index], path));
            path.pop_back();
        }
        return result;
    }
};
} // namespace buildboxcommon

#endif
