/** @file

  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you 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.
 */

#include "PrintUtils.h"
#include <swoc/bwf_base.h>

namespace
{
void
YAML_begin_map(YAML::Emitter &doc)
{
  doc << YAML::BeginMap;
}
void
YAML_end_map(YAML::Emitter &doc)
{
  doc << YAML::EndMap;
}
void
YAML_add_key(YAML::Emitter &doc, std::string key)
{
  doc << YAML::Key << key;
}
void
YAML_add_value(YAML::Emitter &doc, std::string value)
{
  doc << YAML::Value << value;
}
void
YAML_add_comment(YAML::Emitter &doc, std::string comment, const char *fmt = "default: {}")
{
  std::string txt;
  doc << YAML::Comment(swoc::bwprint(txt, fmt, comment));
}
void
remove_legacy_config_prefix(swoc::TextView &rec_name)
{
  constexpr std::array<swoc::TextView, 3> Prefixes{"proxy.config.", "local.config.", "proxy.node."};

  for (auto &prefix : Prefixes) {
    if (rec_name.starts_with(prefix)) {
      rec_name.remove_prefix(prefix.size());
      break;
    }
  }
}
} // namespace

RecNameToYaml::RecNameToYaml(RecInfoList records, bool p_include_defaults) : include_defaults(p_include_defaults)
{
  if (records.size() == 0) {
    return;
  }

  RecList recs;

  std::sort(records.begin(), records.end(), [](const auto &lhs, const auto &rhs) {
    // by the name.
    return std::get<0>(lhs) < std::get<0>(rhs);
  });

  // remove duplicated
  records.erase(std::unique(records.begin(), records.end(),
                            [](const auto &lhs, const auto &rhs) { return std::get<0>(lhs) == std::get<0>(rhs); }),
                records.end());

  // work from this list of records.
  for (auto const &e : records) {
    recs.push_back(std::make_pair(e, false));
  }

  build_yaml(std::move(recs));
}

RecNameToYaml::RecordsMatchTracker
RecNameToYaml::find_all_keys_with_prefix(swoc::TextView prefix, RecNameToYaml::RecList &vars)
{
  RecordsMatchTracker ret;
  for (auto &p : vars) {
    if (p.second) {
      continue;
    }

    swoc::TextView name{std::get<0>(p.first)};
    remove_legacy_config_prefix(name);

    // A var name could also start with the same prefix and not being a field
    // like prefix=logfile and field=logfile_perm so we need to make sure we are either the end of the field
    // or the end of the level(with another .)
    if (!name.starts_with(prefix) || name.at(prefix.size()) != '.') {
      continue;
    }
    ret.push_back({p.first, p.second});
  }
  return ret;
}

void
RecNameToYaml::process_var_from_prefix(std::string prefix, RecNameToYaml::RecList &vars, bool &in_a_map)
{
  auto keys = find_all_keys_with_prefix(prefix, vars);

  for (auto &[rec, proccesed] : keys) {
    if (proccesed) {
      continue;
    }

    swoc::TextView name{std::get<0>(rec)};
    remove_legacy_config_prefix(name); // if no prefix to remove, it will ignore it.
    name.remove_prefix(prefix.size() + 1);

    auto const n = name.find(".");

    if (n == std::string_view::npos) {
      if (!in_a_map) {
        YAML_begin_map(doc); // Value map.
        in_a_map = true;
      }

      YAML_add_key(doc, std::string(name));

      auto const &rec_current_value = std::get<1>(rec);
      auto const &rec_default_value = std::get<2>(rec);
      YAML_add_value(doc, rec_current_value);
      if (include_defaults && !rec_default_value.empty()) {
        YAML_add_comment(doc, rec_default_value);
      }
      proccesed = true;
    } else {
      auto key = std::string{name.substr(0, n).data(), name.substr(0, n).size()};
      YAML_add_key(doc, key);

      std::string nprefix;
      nprefix.reserve(prefix.size() + 1 + key.size());
      nprefix.append(prefix);
      nprefix.append(".");
      nprefix.append(key);

      in_a_map = true; // no need for a map down the line if we will just add k,v.
      YAML_begin_map(doc);
      process_var_from_prefix(std::move(nprefix), vars, in_a_map);
      YAML_end_map(doc);
    }
  }
}

void
RecNameToYaml::build_yaml(RecNameToYaml::RecList vars)
{
  YAML_begin_map(doc);
  YAML_add_key(doc, "records");
  YAML_begin_map(doc); // content

  // Just work from every passed records and walk down every field to build up
  // each node from it.
  // It may work just to use a single loop, but this seems easier to follow, it will
  // not process the same records twice because once one is done, the processed mark
  // will be set and then ignored.
  for (auto &[rec, processed] : vars) {
    if (processed) {
      continue;
    }

    swoc::TextView name(std::get<0>(rec));
    remove_legacy_config_prefix(name);
    auto const  n = name.find('.');
    std::string prefix{name.substr(0, n == std::string_view::npos ? name.size() : n)};
    YAML_add_key(doc, prefix);
    if (n == std::string_view::npos) {
      auto const &rec_current_value = std::get<1>(rec);
      auto const &rec_default_value = std::get<2>(rec);
      // Just set this field right here and move on, no need to do it later.
      YAML_add_value(doc, rec_current_value);
      if (include_defaults && !rec_default_value.empty()) {
        YAML_add_comment(doc, rec_default_value);
      }
      processed = true;
      continue;
    }
    bool in_a_map = true;
    YAML_begin_map(doc);
    process_var_from_prefix(std::move(prefix), vars, in_a_map);
    YAML_end_map(doc);
  }

  YAML_end_map(doc); // content
  YAML_end_map(doc);
}
