#ifndef DD_OPENTRACING_SPAN_H
#define DD_OPENTRACING_SPAN_H

#include <msgpack.hpp>

#include "clock.h"
#include "logger.h"
#include "span_context.h"

namespace ot = opentracing;

namespace datadog {
namespace opentracing {

class Tracer;
class SpanBuffer;
typedef std::function<uint64_t()> IdProvider;  // See tracer.h

// `SpanData` contains the data fields associated with a `Span`.  A `Span`
// additionally contains handles to mechanisms it needs in order to implement
// its methods (e.g. the logger, the tracer).  `SpanData` is just the data.
struct SpanData {
  SpanData(std::string type, std::string service, ot::string_view resource, std::string name,
           uint64_t trace_id, uint64_t span_id, uint64_t parent_id, int64_t start,
           int64_t duration, int32_t error);
  SpanData() = default;

  std::string type;
  std::string service;
  std::string resource;
  std::string name;
  uint64_t trace_id = 0;
  uint64_t span_id = 0;
  uint64_t parent_id = 0;
  int64_t start = 0;
  int64_t duration = 0;
  int32_t error = 0;
  std::unordered_map<std::string, std::string> meta;  // Aka, tags.
  std::unordered_map<std::string, double> metrics;

  uint64_t traceId() const;
  uint64_t spanId() const;
  const std::string env() const;

  MSGPACK_DEFINE_MAP(name, service, resource, type, start, duration, meta, metrics, span_id,
                     trace_id, parent_id, error)
};

// A Span, a component of a trace, a single instrumented event.
class Span : public ot::Span {
 public:
  // Creates a new Span.
  Span(std::shared_ptr<const Logger> logger, std::shared_ptr<const Tracer> tracer,
       std::shared_ptr<SpanBuffer> buffer, TimeProvider get_time, uint64_t span_id,
       uint64_t trace_id, uint64_t parent_id, SpanContext context, TimePoint start_time,
       std::string span_service, std::string span_type, std::string span_name,
       std::string resource, std::string operation_name_override, bool legacy_obfuscation = false);

  Span() = delete;
  ~Span() override;

  // Finishes and records the span.
  void FinishWithOptions(const ot::FinishSpanOptions &finish_span_options) noexcept override;

  void SetOperationName(ot::string_view name) noexcept override;

  void SetTag(ot::string_view key, const ot::Value &value) noexcept override;

  void SetBaggageItem(ot::string_view restricted_key, ot::string_view value) noexcept override;

  std::string BaggageItem(ot::string_view restricted_key) const noexcept override;

  void Log(std::initializer_list<std::pair<ot::string_view, ot::Value>> fields) noexcept override;
  virtual void Log(
      ot::SystemTime timestamp,
      std::initializer_list<std::pair<ot::string_view, ot::Value>> fields) noexcept override;

  virtual void Log(
      ot::SystemTime timestamp,
      const std::vector<std::pair<ot::string_view, ot::Value>> &fields) noexcept override;

  const ot::SpanContext &context() const noexcept override;

  const ot::Tracer &tracer() const noexcept override;

  // Datadog-specific member functions

  uint64_t traceId() const;
  uint64_t spanId() const;
  // Sets the SamplingPriority. If priority is null, then unsets SamplingPriority. Returns the
  // value of the SamplingPriority; this may not be the same as the given parameter if this trace
  // has propagated from a remote origin and already has a SamplingPriority.
  OptionalSamplingPriority setSamplingPriority(std::unique_ptr<UserSamplingPriority> priority);
  OptionalSamplingPriority getSamplingPriority() const;

  // Change the service name of this span and its associated trace to the
  // specified `service_name`.
  void setServiceName(ot::string_view service_name);

 private:
  mutable std::mutex mutex_;
  std::atomic<bool> is_finished_{false};

  // Set in constructor initializer:
  std::shared_ptr<const Logger> logger_;
  std::shared_ptr<const Tracer> tracer_;
  std::shared_ptr<SpanBuffer> buffer_;
  TimeProvider get_time_;
  SpanContext context_;
  TimePoint start_time_;
  std::string operation_name_override_;
  bool legacy_obfuscation_ = false;

  // Set in constructor initializer, depends on previous constructor initializer-set members:
  std::unique_ptr<SpanData> span_;
  std::string span_description_;
};

}  // namespace opentracing
}  // namespace datadog

#endif  // DD_OPENTRACING_SPAN_H
