// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#ifdef ENABLE_LOGS_PREVIEW
#  include "opentelemetry/sdk/logs/logger.h"
#  include "opentelemetry/sdk/logs/log_record.h"
#  include "opentelemetry/sdk_config.h"
#  include "opentelemetry/trace/provider.h"

OPENTELEMETRY_BEGIN_NAMESPACE
namespace sdk
{
namespace logs
{
namespace trace_api = opentelemetry::trace;
namespace nostd     = opentelemetry::nostd;
namespace common    = opentelemetry::common;

Logger::Logger(
    nostd::string_view name,
    std::shared_ptr<LoggerContext> context,
    std::unique_ptr<instrumentationscope::InstrumentationScope> instrumentation_scope) noexcept
    : logger_name_(std::string(name)),
      instrumentation_scope_(std::move(instrumentation_scope)),
      context_(context)
{}

const nostd::string_view Logger::GetName() noexcept
{
  return logger_name_;
}

/**
 * Create and populate recordable with the log event's fields passed in.
 * The timestamp, severity, traceid, spanid, and traceflags, are injected
 * if the user does not specify them.
 */
void Logger::Log(opentelemetry::logs::Severity severity,
                 nostd::string_view body,
                 const common::KeyValueIterable &attributes,
                 trace_api::TraceId trace_id,
                 trace_api::SpanId span_id,
                 trace_api::TraceFlags trace_flags,
                 common::SystemTimestamp timestamp) noexcept
{
  // If this logger does not have a processor, no need to create a log record
  if (!context_)
  {
    return;
  }
  auto &processor = context_->GetProcessor();

  // TODO: Sampler (should include check for minSeverity)

  auto recordable = processor.MakeRecordable();
  if (recordable == nullptr)
  {
    OTEL_INTERNAL_LOG_ERROR("[LOGGER] Recordable creation failed");
    return;
  }

  // Populate recordable fields
  recordable->SetTimestamp(timestamp);
  recordable->SetSeverity(severity);
  recordable->SetBody(body);
  recordable->SetInstrumentationScope(GetInstrumentationScope());

  recordable->SetResource(context_->GetResource());

  attributes.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept {
    recordable->SetAttribute(key, value);
    return true;
  });

  // Inject trace_id/span_id/trace_flags if none is set by user
  auto provider     = trace_api::Provider::GetTracerProvider();
  auto tracer       = provider->GetTracer(logger_name_);
  auto span_context = tracer->GetCurrentSpan()->GetContext();

  // Leave these fields in the recordable empty if neither the passed in values
  // nor the context values are valid (e.g. the application is not using traces)

  // TraceId
  if (trace_id.IsValid())
  {
    recordable->SetTraceId(trace_id);
  }
  else if (span_context.trace_id().IsValid())
  {
    recordable->SetTraceId(span_context.trace_id());
  }

  // SpanId
  if (span_id.IsValid())
  {
    recordable->SetSpanId(span_id);
  }
  else if (span_context.span_id().IsValid())
  {
    recordable->SetSpanId(span_context.span_id());
  }

  // TraceFlags
  if (trace_flags.IsSampled())
  {
    recordable->SetTraceFlags(trace_flags);
  }
  else if (span_context.trace_flags().IsSampled())
  {
    recordable->SetTraceFlags(span_context.trace_flags());
  }

  // Send the log record to the processor
  processor.OnReceive(std::move(recordable));
}

const opentelemetry::sdk::instrumentationscope::InstrumentationScope &
Logger::GetInstrumentationScope() const noexcept
{
  return *instrumentation_scope_;
}

}  // namespace logs
}  // namespace sdk
OPENTELEMETRY_END_NAMESPACE
#endif
