/**
 * 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.
 */

#ifndef APACHE_HTRACE_HTRACE_HPP
#define APACHE_HTRACE_HTRACE_HPP

#include "htrace.h"

#include <string>

/**
 * The public C++ API for the HTrace native client.
 *
 * The C++ API is a wrapper around the C API.  The advantage of this is that we
 * can change the C++ API in this file without breaking binary compatibility.
 *
 * EXCEPTIONS
 * We do not use exceptions in this API.  This code should be usable by
 * libraries and applications that are using the Google C++ coding style.
 * The one case where we do use exceptions is to translate NULL pointer returns
 * on OOM into std::bad_alloc exceptions.  In general, it is extremely unlikely
 * that the size of memory allocations we are doing will produce OOM.  Most
 * C++ programs do not attempt to handle OOM anyway because of the extra code
 * complexity that would be required.  So translating this into an exception is
 * fine.
 *
 * C++11
 * This code should not require C++11.  We might add #ifdefs later to take
 * advantage of certain C++11 or later features if they are available.
 */

namespace htrace {
  class Sampler;
  class Scope;
  class Tracer;

  /**
   * An HTrace Configuration object.
   *
   * Configurations are thread-safe.  They can be used by multiple threads
   * simultaneously.
   */
  class Conf {
  public:
    /**
     * Create a new HTrace Conf.
     *
     * @param values    A configuration string containing a series of
     *                  semicolon-separated key=value entries.
     *                  We do not hold on to a reference to this string.
     * @param defaults  Another semicolon-separated set of key=value entries.
     *                  The defaults to be used when there is no corresponding
     *                  value in 'values.' We do not hold on to a reference to
     *                  this string.
     */
    Conf(const char *values)
      : conf_(htrace_conf_from_str(values))
    {
      if (!conf_) {
        throw std::bad_alloc();
      }
    }

    Conf(const std::string &values)
      : conf_(htrace_conf_from_str(values.c_str()))
    {
      if (!conf_) {
        throw std::bad_alloc();
      }
    }

    ~Conf() {
      htrace_conf_free(conf_);
      conf_ = NULL;
    }

  private:
    friend class Tracer;
    friend class Sampler;
    Conf &operator=(Conf &other); // Can't copy
    Conf(Conf &other);
    struct htrace_conf *conf_;
  };

  /**
   * An HTrace context object.
   *
   * Contexts are thread-safe.  They can be used by multiple threads simultaneoy
   * Most applications will not need more than one HTrace context, which is
   * often global (or at least widely used.)
   */
  class Tracer {
  public:
    /**
     * Create a new Tracer.
     *
     * @param name    The name of the tracer to create.  We do not hold on to a
     *                  reference to this string.
     * @param conf    The configuration to use for the new tracer.  We do not
     *                  hold on to a reference to this configuration.
     */
    Tracer(const std::string &name, const Conf &conf)
      : tracer_(htracer_create(name.c_str(), conf.conf_))
    {
      if (!tracer_) {
        throw std::bad_alloc();
      }
    }

    std::string Name() {
      return std::string(htracer_tname(tracer_));
    }

    /**
     * Free the Tracer.
     *
     * This destructor must not be called until all the other objects which hold
     * a reference (such as samplers and trace scopes) are freed.  It is often
     * not necessary to destroy this object at all unless you are writing a
     * library and want to support unloading your library, or you are writing an
     * application and want to support some kind of graceful shutdown.
     *
     * We could make this friendlier with some kind of reference counting via
     * atomic variables, but only at the cost of reduced performance.
     */
    ~Tracer() {
      htracer_free(tracer_);
      tracer_ = NULL;
    }

  private:
    friend class Sampler;
    friend class Scope;
    Tracer(const Tracer &other); // Can't copy
    const Tracer &operator=(const Tracer &other);
    struct htracer *tracer_;
  };

  /**
   * An HTrace sampler.
   *
   * Samplers determine when new spans are created.
   * See htrace.h for more information.
   *
   * Samplers are thread-safe.  They can be used by multiple threads
   * simultaneously.
   */
  class Sampler {
  public:
    /**
     * Create a new Sampler.
     *
     * @param tracer  The tracer to use.  You must not free this tracer until
     *                  after this sampler is freed.
     * @param conf    The configuration to use for the new sampler.  We do not
     *                  hold on to a reference to this configuration.
     */
    Sampler(Tracer *tracer, const Conf &conf)
        : smp_(htrace_sampler_create(tracer->tracer_, conf.conf_)) {
      if (!smp_) {
        throw std::bad_alloc();
      }
    }

    /**
     * Get a description of this Sampler.
     */
    std::string ToString() {
      return std::string(htrace_sampler_to_str(smp_));
    }

    ~Sampler() {
      htrace_sampler_free(smp_);
      smp_ = NULL;
    }

  private:
    friend class Tracer;
    friend class Scope;
    Sampler(const Sampler &other); // Can't copy
    const Sampler &operator=(const Sampler &other);

    struct htrace_sampler *smp_;
  };

  class Scope {
  public:
    Scope(Tracer &tracer, const char *name)
      : scope_(htrace_start_span(tracer.tracer_, NULL, name)) {
    }

    Scope(Tracer &tracer, const std::string &name)
      : scope_(htrace_start_span(tracer.tracer_, NULL, name.c_str())) {
    }

    Scope(Tracer &tracer, Sampler &smp, const char *name)
      : scope_(htrace_start_span(tracer.tracer_, smp.smp_, name)) {
    }

    Scope(Tracer &tracer, Sampler &smp, const std::string &name)
      : scope_(htrace_start_span(tracer.tracer_, smp.smp_, name.c_str())) {
    }

    ~Scope() {
      htrace_scope_close(scope_);
      scope_ = NULL;
    }

  private:
    friend class Tracer;
    Scope(htrace::Scope &other); // Can't copy
    Scope& operator=(Scope &scope); // Can't assign

    struct htrace_scope *scope_;
  };
}

#endif

// vim: ts=2:sw=2:et
