/*
 * SPDX-FileCopyrightText: Copyright (c) 2020-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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 NVIDIA_GXF_STD_PARAMETER_PARSER_STD_HPP_
#define NVIDIA_GXF_STD_PARAMETER_PARSER_STD_HPP_

#include <array>
#include <sstream>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include "yaml-cpp/yaml.h"

#include "common/assert.hpp"
#include "common/yaml_parser.hpp"
#include "gxf/core/expected.hpp"
#include "gxf/core/gxf.h"
#include "gxf/core/handle.hpp"
#include "gxf/core/parameter_parser.hpp"
#include "gxf/core/parameter_registrar.hpp"

namespace nvidia {
namespace gxf {

// Parses unknown many of known types.
template <typename T>
struct ParameterParser<std::vector<T>> {
  static Expected<std::vector<T>> Parse(gxf_context_t context, gxf_uid_t component_uid,
                                        const char* key, const YAML::Node& node,
                                        const std::string& prefix) {
    if (!node.IsSequence()) {
      const char* component_name = "UNKNOWN";
      GxfParameterGetStr(context, component_uid, kInternalNameParameterKey, &component_name);
      GXF_LOG_ERROR("Parameter '%s' in component '%s' must be a vector", key, component_name);
      return Unexpected{GXF_PARAMETER_PARSER_ERROR};
    }
    std::vector<T> result(node.size());
    for (size_t i = 0; i < node.size(); i++) {
      const auto maybe = ParameterParser<T>::Parse(context, component_uid, key, node[i], prefix);
      if (!maybe) {
        return ForwardError(maybe);
      }
      result[i] = std::move(maybe.value());
    }
    return result;
  }
};

// Parses known many of known types.
template <typename T, std::size_t N>
struct ParameterParser<std::array<T, N>> {
  static Expected<std::array<T, N>> Parse(gxf_context_t context, gxf_uid_t component_uid,
                                                const char* key, const YAML::Node& node,
                                                const std::string& prefix) {
    if (!node.IsSequence()) {
      const char* component_name = "UNKNOWN";
      GxfParameterGetStr(context, component_uid, kInternalNameParameterKey, &component_name);
      GXF_LOG_ERROR("Parameter '%s' in component '%s' must be an array", key, component_name);
      return Unexpected{GXF_PARAMETER_PARSER_ERROR};
    }
    if (node.size() != N) {
      GXF_LOG_ERROR("Length of parameter array (%zu) does not match required length (%zu)",
                    node.size(), N);
      return Unexpected{GXF_PARAMETER_PARSER_ERROR};
    }
    std::array<T, N> result;
    for (size_t i = 0; i < result.size(); i++) {
      const auto maybe = ParameterParser<T>::Parse(context, component_uid, key, node[i], prefix);
      if (!maybe) { return ForwardError(maybe); }
      result[i] = std::move(maybe.value());
    }
    return result;
  }
};

// Parameter support for std::unordered_map<std::string, T>
template <typename T>
struct ParameterParser<std::unordered_map<std::string, T>> {
  static Expected<std::unordered_map<std::string, T>> Parse(gxf_context_t context,
                                                            gxf_uid_t component_uid,
                                                            const char* key, const YAML::Node& node,
                                                            const std::string& prefix) {
    if (!node.IsMap()) {
      const char* component_name = "UNKNOWN";
      GxfParameterGetStr(context, component_uid, kInternalNameParameterKey, &component_name);
      GXF_LOG_ERROR("Parameter '%s' in component '%s' must be a map", key, component_name);
      return Unexpected{GXF_PARAMETER_PARSER_ERROR};
    }
    std::unordered_map<std::string, T> map;
    for (const auto& iter : node) {
      const std::string& key = iter.first.as<std::string>();
      const YAML::Node& value = iter.second;
      auto maybe = ParameterParser<T>::Parse(context, component_uid, key.c_str(), value, prefix);
      if (!maybe) {
        return ForwardError(maybe);
      }
      map[key] = std::move(maybe.value());
    }
    return map;
  }
};


template <typename T>
struct ParameterParser<std::complex<T>> {
  static Expected<std::complex<T>> Parse(gxf_context_t context, gxf_uid_t component_uid,
                                  const char* key, const YAML::Node& node,
                                  const std::string& prefix) {
    try {
      return static_cast<std::complex<T>>(node.as<std::complex<T>>());
    } catch (...) {
      std::stringstream ss;
      ss << node;
      GXF_LOG_ERROR("Could not parse parameter '%s' from '%s'", key, ss.str().c_str());
      return Unexpected{GXF_PARAMETER_PARSER_ERROR};
    }
  }
};


// Note: ParameterInfoOverride specializations for std::vector<T> and std::array<T,N>
// are defined in parameter_registrar.hpp to ensure they are visible at all template
// instantiation points.

}  // namespace gxf
}  // namespace nvidia

#endif  // NVIDIA_GXF_STD_PARAMETER_PARSER_STD_HPP_
