//
//
// Copyright 2015 gRPC authors.
//
// 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.
//
//

#include <grpc/support/port_platform.h>

#include "src/core/lib/debug/trace.h"

#include <string>
#include <type_traits>
#include <utility>

#include "absl/strings/match.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"

#include <grpc/grpc.h>
#include <grpc/support/log.h>

#include "src/core/lib/config/config_vars.h"

int grpc_tracer_set_enabled(const char* name, int enabled);

namespace grpc_core {

TraceFlag* TraceFlagList::root_tracer_ = nullptr;

bool TraceFlagList::Set(absl::string_view name, bool enabled) {
  TraceFlag* t;
  if (name == "all") {
    for (t = root_tracer_; t; t = t->next_tracer_) {
      t->set_enabled(enabled);
    }
  } else if (name == "list_tracers") {
    LogAllTracers();
  } else if (name == "refcount") {
    for (t = root_tracer_; t; t = t->next_tracer_) {
      if (absl::StrContains(t->name_, "refcount")) {
        t->set_enabled(enabled);
      }
    }
  } else {
    bool found = false;
    for (t = root_tracer_; t; t = t->next_tracer_) {
      if (name == t->name_) {
        t->set_enabled(enabled);
        found = true;
      }
    }
    // check for unknowns, but ignore "", to allow to GRPC_TRACE=
    if (!found && !name.empty()) {
      gpr_log(GPR_ERROR, "Unknown trace var: '%s'", std::string(name).c_str());
      return false;  // early return
    }
  }
  return true;
}

void TraceFlagList::Add(TraceFlag* flag) {
  flag->next_tracer_ = root_tracer_;
  root_tracer_ = flag;
}

void TraceFlagList::LogAllTracers() {
  gpr_log(GPR_DEBUG, "available tracers:");
  for (TraceFlag* t = root_tracer_; t != nullptr; t = t->next_tracer_) {
    gpr_log(GPR_DEBUG, "\t%s", t->name_);
  }
}

void TraceFlagList::SaveTo(std::map<std::string, bool>& values) {
  for (TraceFlag* t = root_tracer_; t != nullptr; t = t->next_tracer_) {
    values[t->name_] = t->enabled();
  }
}

// Flags register themselves on the list during construction
TraceFlag::TraceFlag(bool default_enabled, const char* name) : name_(name) {
  static_assert(std::is_trivially_destructible<TraceFlag>::value,
                "TraceFlag needs to be trivially destructible.");
  set_enabled(default_enabled);
  TraceFlagList::Add(this);
}

SavedTraceFlags::SavedTraceFlags() { TraceFlagList::SaveTo(values_); }

void SavedTraceFlags::Restore() {
  for (const auto& flag : values_) {
    TraceFlagList::Set(flag.first, flag.second);
  }
}

namespace {
void ParseTracers(absl::string_view tracers) {
  for (auto s : absl::StrSplit(tracers, ',', absl::SkipWhitespace())) {
    if (s[0] == '-') {
      TraceFlagList::Set(s.substr(1), false);
    } else {
      TraceFlagList::Set(s, true);
    }
  }
}
}  // namespace

}  // namespace grpc_core

void grpc_tracer_init() {
  grpc_core::ParseTracers(grpc_core::ConfigVars::Get().Trace());
}

int grpc_tracer_set_enabled(const char* name, int enabled) {
  return grpc_core::TraceFlagList::Set(name, enabled != 0);
}
