// Copyright 2022 The Abseil 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
//
//      https://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.
//
// -----------------------------------------------------------------------------
// File: log/internal/structured.h
// -----------------------------------------------------------------------------

#ifndef ABSL_LOG_INTERNAL_STRUCTURED_H_
#define ABSL_LOG_INTERNAL_STRUCTURED_H_

#include <ostream>
#include <string>

#include "absl/base/attributes.h"
#include "absl/base/config.h"
#include "absl/functional/any_invocable.h"
#include "absl/log/internal/log_message.h"
#include "absl/log/internal/structured_proto.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"

namespace absl {
ABSL_NAMESPACE_BEGIN
namespace log_internal {

class [[nodiscard]] AsLiteralImpl final {
 public:
  explicit AsLiteralImpl(absl::string_view str ABSL_ATTRIBUTE_LIFETIME_BOUND)
      : str_(str) {}
  AsLiteralImpl(const AsLiteralImpl&) = default;
  AsLiteralImpl& operator=(const AsLiteralImpl&) = default;

 private:
  absl::string_view str_;

  friend std::ostream& operator<<(std::ostream& os,
                                  AsLiteralImpl&& as_literal) {
    return os << as_literal.str_;
  }
  void AddToMessage(log_internal::LogMessage& m) {
    m.CopyToEncodedBuffer<log_internal::LogMessage::StringType::kLiteral>(str_);
  }
  friend log_internal::LogMessage& operator<<(log_internal::LogMessage& m,
                                              AsLiteralImpl as_literal) {
    as_literal.AddToMessage(m);
    return m;
  }
};

enum class StructuredStringType {
  kLiteral,
  kNotLiteral,
};

// Structured log data for a string and associated structured proto field,
// both of which must outlive this object.
template <StructuredStringType str_type>
class [[nodiscard]] AsStructuredStringTypeImpl final {
 public:
  constexpr AsStructuredStringTypeImpl(
      absl::string_view str ABSL_ATTRIBUTE_LIFETIME_BOUND,
      StructuredProtoField field ABSL_ATTRIBUTE_LIFETIME_BOUND)
      : str_(str), field_(field) {}

 private:
  absl::string_view str_;
  StructuredProtoField field_;

  friend std::ostream& operator<<(std::ostream& os,
                                  const AsStructuredStringTypeImpl& impl) {
    return os << impl.str_;
  }
  void AddToMessage(LogMessage& m) const {
    if (str_type == StructuredStringType::kLiteral) {
      return m.CopyToEncodedBufferWithStructuredProtoField<
          log_internal::LogMessage::StringType::kLiteral>(field_, str_);
    } else {
      return m.CopyToEncodedBufferWithStructuredProtoField<
          log_internal::LogMessage::StringType::kNotLiteral>(field_, str_);
    }
  }
  friend LogMessage& operator<<(LogMessage& m,
                                const AsStructuredStringTypeImpl& impl) {
    impl.AddToMessage(m);
    return m;
  }
};

using AsStructuredLiteralImpl =
    AsStructuredStringTypeImpl<StructuredStringType::kLiteral>;
using AsStructuredNotLiteralImpl =
    AsStructuredStringTypeImpl<StructuredStringType::kNotLiteral>;

// Structured log data for a stringifyable type T and associated structured
// proto field, both of which must outlive this object.
template <typename T>
class [[nodiscard]] AsStructuredValueImpl final {
 public:
  using ValueFormatter = absl::AnyInvocable<std::string(T) const>;

  constexpr AsStructuredValueImpl(
      T value ABSL_ATTRIBUTE_LIFETIME_BOUND,
      StructuredProtoField field ABSL_ATTRIBUTE_LIFETIME_BOUND,
      ValueFormatter value_formatter =
          [](T value) { return absl::StrCat(value); })
      : value_(value),
        field_(field),
        value_formatter_(std::move(value_formatter)) {}

 private:
  T value_;
  StructuredProtoField field_;
  ValueFormatter value_formatter_;

  friend std::ostream& operator<<(std::ostream& os,
                                  const AsStructuredValueImpl& impl) {
    return os << impl.value_formatter_(impl.value_);
  }
  void AddToMessage(LogMessage& m) const {
    m.CopyToEncodedBufferWithStructuredProtoField<
        log_internal::LogMessage::StringType::kNotLiteral>(
        field_, value_formatter_(value_));
  }
  friend LogMessage& operator<<(LogMessage& m,
                                const AsStructuredValueImpl& impl) {
    impl.AddToMessage(m);
    return m;
  }
};

// Template deduction guide so `AsStructuredValueImpl(42, data)` works
// without specifying the template type.
template <typename T>
AsStructuredValueImpl(T value, StructuredProtoField field)
    -> AsStructuredValueImpl<T>;

// Template deduction guide so `AsStructuredValueImpl(42, data, formatter)`
// works without specifying the template type.
template <typename T>
AsStructuredValueImpl(
    T value, StructuredProtoField field,
    typename AsStructuredValueImpl<T>::ValueFormatter value_formatter)
    -> AsStructuredValueImpl<T>;

}  // namespace log_internal
ABSL_NAMESPACE_END
}  // namespace absl

#endif  // ABSL_LOG_INTERNAL_STRUCTURED_H_
