//
// Copyright 2018 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
//    names, trademarks, service marks, or product names of the Licensor
//    and its affiliates, except as required to comply with Section 4(c) of
//    the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//

#ifndef PXR_BASE_TRACE_TRACE_H
#define PXR_BASE_TRACE_TRACE_H

/// \file trace/trace.h

#include "pxr/pxr.h"

#include "pxr/base/trace/api.h"
#include "pxr/base/trace/collector.h"

#include "pxr/base/tf/preprocessorUtilsLite.h"

#include <atomic>

#if !defined(TRACE_DISABLE)

/// Records a timestamp when constructed and a timespan event when destructed,
/// using the name of the function or method as the key.
#define TRACE_FUNCTION() \
        _TRACE_FUNCTION_INSTANCE(__LINE__, __ARCH_FUNCTION__, __ARCH_PRETTY_FUNCTION__)

/// Records a timestamp when constructed and a timespan event when destructed,
/// using \a name as the key.
#define TRACE_SCOPE(name) \
        _TRACE_SCOPE_INSTANCE(__LINE__, name)

/// Records a timestamp when constructed and a timespan event when destructed,
/// using the name of the function concatenated with \a name as the key.
#define TRACE_FUNCTION_SCOPE(name) \
        _TRACE_FUNCTION_SCOPE_INSTANCE( \
            __LINE__, __ARCH_FUNCTION__, __ARCH_PRETTY_FUNCTION__, name)

/// Records a timestamp when constructed, using \a name as the key.
#define TRACE_MARKER(name) \
        _TRACE_MARKER_INSTANCE(__LINE__, name)

/// Records a timestamp when constructed, using \a name as the key.
#define TRACE_MARKER_DYNAMIC(name) \
        _TRACE_MARKER_DYNAMIC_INSTANCE(__LINE__, name)

/// Records a counter \a delta using the \a name as the counter key. The delta can
/// be positive or negative. A positive delta will increment the total counter
/// value, whereas a negative delta will decrement it. The recorded value will
/// be stored at the currently traced scope, and will propagate up to the
/// parent scopes.
#define TRACE_COUNTER_DELTA(name, delta) \
        _TRACE_COUNTER_INSTANCE(__LINE__, name, delta, /* isDelta */ true)

/// Records a counter delta using the name as the counter key. Similar to 
/// TRACE_COUNTER_DELTA except that \p name does not need to be a compile time
/// string.
/// \sa TRACE_COUNTER_DELTA
#define TRACE_COUNTER_DELTA_DYNAMIC(name, delta) \
        TraceCollector::GetInstance().RecordCounterDelta(name, delta);

/// Records a counter value using the name as the counter key. The recorded 
/// value will be stored at the currently traced scope, and will propagate up to
/// the parent scopes.
#define TRACE_COUNTER_VALUE(name, value) \
        _TRACE_COUNTER_INSTANCE(__LINE__, name, value, /* isDelta */ false)

/// Records a counter value using the name as the counter key. Similar to 
/// TRACE_COUNTER_VALUE except that \p name does not need to be a compile time
/// string.
/// \sa TRACE_COUNTER_VALUE
#define TRACE_COUNTER_VALUE_DYNAMIC(name, value) \
        TraceCollector::GetInstance().RecordCounterValue(name, value);

/// Records a counter value using the name as the counter key. The value can
/// be positive or negative. A positive value will increment the total counter
/// value, whereas a negative value will decrement it. The recorded value will
/// be stored at the currently traced scope, and will propagate up to the
/// parent scopes.
///
/// This macro provides the same functionality as TRACE_COUNTER_DELTA, but takes
/// a section of code in brackets, which assumes that a value will be
/// assigned to 'value'. The section of code will not be executed, when
/// tracing is turned off, which makes it possible to gather counter values
/// from potentially expensive logic, without incurring an overhead with
/// tracing turned off.
///
/// Usage:
///
/// TRACE_COUNTER_DELTA_CODE("My counter", {
///     value = _ComputeExpensiveCounterValue();
/// })
#define TRACE_COUNTER_DELTA_CODE(name, code) \
        _TRACE_COUNTER_CODE_INSTANCE(__LINE__, name, code, true)

/// Records a begin event when constructed and an end event when destructed,
/// using name of the function or method and the supplied name as the key. 
/// Unlike TRACE_FUNCTION, the name argument will be evaluated each time this 
/// macro is invoked. This allows for a single TRACE_FUNCTION to track time 
/// under different keys, but incurs greater overhead.
#define TRACE_FUNCTION_DYNAMIC(name) \
        _TRACE_FUNCTION_DYNAMIC_INSTANCE(__LINE__, __ARCH_FUNCTION__, __ARCH_PRETTY_FUNCTION__, name)

/// Records a begin event when constructed and an end event when destructed,
/// using \a name as the key. Unlike TRACE_SCOPE, the name argument will
/// be evaluated each time this macro is invoked. This allows for a single 
/// TRACE_SCOPE to track time under different keys, but incurs greater 
/// overhead.
#define TRACE_SCOPE_DYNAMIC(name) \
        _TRACE_SCOPE_DYNAMIC_INSTANCE(__LINE__, name)


/// These pair a uniquely named TraceScopeHolder with a TraceScopeAuto.
/// Together these will register a TraceScope only the first time the
/// code is executed or if the TraceScope expires.  Otherwise, the held
/// TraceScope will be used to record begin and end events.


#define _TRACE_FUNCTION_INSTANCE(instance, name, prettyName) \
constexpr static PXR_NS::TraceStaticKeyData \
    TF_PP_CAT(TraceKeyData_, instance)(name, prettyName); \
PXR_NS::TraceScopeAuto TF_PP_CAT(TraceScopeAuto_, instance)(\
    TF_PP_CAT(TraceKeyData_, instance));

#define _TRACE_SCOPE_INSTANCE(instance, name) \
constexpr static PXR_NS::TraceStaticKeyData \
    TF_PP_CAT(TraceKeyData_, instance)(name); \
PXR_NS::TraceScopeAuto TF_PP_CAT(TraceScopeAuto_, instance)(\
    TF_PP_CAT(TraceKeyData_, instance));

#define _TRACE_FUNCTION_SCOPE_INSTANCE(instance, name, prettyName, scopeName) \
constexpr static PXR_NS::TraceStaticKeyData \
    TF_PP_CAT(TraceKeyData_, instance)(name, prettyName, scopeName); \
PXR_NS::TraceScopeAuto TF_PP_CAT(TraceScopeAuto_, instance)(\
    TF_PP_CAT(TraceKeyData_, instance));

#define _TRACE_MARKER_INSTANCE(instance, name) \
constexpr static PXR_NS::TraceStaticKeyData \
    TF_PP_CAT(TraceKeyData_, instance)(name); \
    TraceCollector::GetInstance().MarkerEventStatic(TF_PP_CAT(TraceKeyData_, instance));

#define _TRACE_COUNTER_INSTANCE(instance, name, value, isDelta) \
constexpr static PXR_NS::TraceStaticKeyData \
    TF_PP_CAT(TraceKeyData_, instance)(name); \
static PXR_NS::TraceCounterHolder \
    TF_PP_CAT(TraceCounterHolder_, instance) \
    (TF_PP_CAT(TraceKeyData_, instance)); \
TF_PP_CAT(TraceCounterHolder_, instance).Record(value, isDelta);

#define _TRACE_COUNTER_CODE_INSTANCE(instance, name, code, isDelta) \
static PXR_NS::TraceCounterHolder \
    TF_PP_CAT(TraceCounterHolder_, instance)(name); \
if (TF_PP_CAT(TraceCounterHolder_, instance).IsEnabled()) { \
    double value = 0.0; \
    code \
    TF_PP_CAT(TraceCounterHolder_, instance).RecordDelta(value, isDelta); \
}

#define _TRACE_FUNCTION_DYNAMIC_INSTANCE(instance, fnName, fnPrettyName, name) \
PXR_NS::TraceAuto TF_PP_CAT(TraceAuto_, instance)(fnName, fnPrettyName, name)

#define _TRACE_SCOPE_DYNAMIC_INSTANCE(instance, str) \
PXR_NS::TraceAuto TF_PP_CAT(TraceAuto_, instance)(str)

#define _TRACE_MARKER_DYNAMIC_INSTANCE(instance, name) \
    TraceCollector::GetInstance().MarkerEvent(name);

#else // TRACE_DISABLE

#define TRACE_FUNCTION()
#define TRACE_FUNCTION_DYNAMIC(name)
#define TRACE_SCOPE(name)
#define TRACE_SCOPE_DYNAMIC(name)
#define TRACE_FUNCTION_SCOPE(name)
#define TRACE_MARKER(name)
#define TRACE_MARKER_DYNAMIC(name)

#endif // TRACE_DISABLE

PXR_NAMESPACE_OPEN_SCOPE

////////////////////////////////////////////////////////////////////////////////
/// \class TraceScopeAuto
///
/// A class which records a timestamp when it is created and a
/// scope event when it is destructed.
///
class TraceScopeAuto {
public:
    /// Constructor for TRACE_FUNCTION macro.
    ///
    explicit TraceScopeAuto(const TraceStaticKeyData& key)
        : _key(nullptr)
        , _start(0) {

        if (ARCH_UNLIKELY(TraceCollector::IsEnabled())) {
            // Init the key if needed.
            _key = &key;
            _start = ArchGetTickTime();
        }
        
    }

    /// Constructor that also records scope arguments.
    ///
    template < typename... Args>
    TraceScopeAuto(const TraceStaticKeyData& key, Args&&... args)
        : TraceScopeAuto(key) {
        if (ARCH_UNLIKELY(_key)) {
            TraceCollector::GetInstance().ScopeArgs(std::forward<Args>(args)...);
        }
    }

    /// Destructor.
    ///
    ~TraceScopeAuto() {
        if (ARCH_UNLIKELY(_key)) {
            TraceCollector::GetInstance().Scope(*_key, _start);
        }
    }
    
private:
    const TraceStaticKeyData* _key;
    TraceEvent::TimeStamp _start;
};

////////////////////////////////////////////////////////////////////////////////
/// \class TraceAuto
///
/// A class which records a begin event when it is constructed, and a matching
/// end event when it is destructed.  It is intended to help ensure begin-end
/// pairing correctness when the begin-end pairing can be expressed by automatic
/// variable lifetime.
/// 
/// The TRACE_FUNCTION() macro may be even more convenient in some
/// circumstances.
///
struct TraceAuto {
    /// Constructor taking function name, pretty function name and a scope name.
    ///
    TraceAuto(const char *funcName, const char *prettyFuncName,
              const std::string &name) 
        : _key(_CreateKeyString(funcName, prettyFuncName, name)) {
        std::atomic_thread_fence(std::memory_order_seq_cst);
        _collector = &TraceCollector::GetInstance();
        _collector->BeginEvent(_key);
        std::atomic_thread_fence(std::memory_order_seq_cst);
    }

    /// Constructor taking a TfToken key.
    ///
    explicit TraceAuto(const TfToken& key)
        : _key(key) {
        std::atomic_thread_fence(std::memory_order_seq_cst);
        _collector = &TraceCollector::GetInstance();
        _collector->BeginEvent(_key);
        std::atomic_thread_fence(std::memory_order_seq_cst);
    }

    /// Constructor taking a string key.
    ///
    explicit TraceAuto(const std::string& key) 
        : TraceAuto(TfToken(key)) {}

    // Non-copyable
    //
    TraceAuto(const TraceAuto &) = delete;
    TraceAuto& operator=(const TraceAuto &) = delete;

    // Non-movable
    //
    TraceAuto(TraceAuto &&) = delete;
    TraceAuto& operator=(TraceAuto &&) = delete;

    /// Destructor.
    ///
    ~TraceAuto() {
        std::atomic_thread_fence(std::memory_order_seq_cst);
        _collector->EndEvent(_key);
        std::atomic_thread_fence(std::memory_order_seq_cst);
    }

private:
    static std::string _CreateKeyString(
        const char *funcName,
        const char *prettyFuncName,
        const std::string &name) {
        std::string key = ArchGetPrettierFunctionName(funcName, prettyFuncName);
        key += " [";
        key += name;
        key += "]";
        return key;
    }

    TraceCollector* _collector;
    TraceDynamicKey _key;
};

////////////////////////////////////////////////////////////////////////////////
/// \class TraceCounterHolder
///
/// Holds on to a counter key, as well as the global
/// collector for fast lookup.
///
class TraceCounterHolder {
public:
    /// Constructor used by TRACE_COUNTER_* macro.
    ///
    explicit TraceCounterHolder(const TraceKey& key) 
        : _key(key) {}

    /// Returns whether the TraceCollector is enabled or not.
    ///
    bool IsEnabled() const {
        return TraceCollector::IsEnabled();
    }

    /// Records a counter delta \p value if the TraceCollector is enabled.
    ///
    void Record(double value, bool delta) {
        if (delta) {
            TraceCollector::GetInstance().RecordCounterDelta(_key, value);
        } else {
            TraceCollector::GetInstance().RecordCounterValue(_key, value);
        }
    }

private:
    TraceKey _key;
};

PXR_NAMESPACE_CLOSE_SCOPE

#endif // PXR_BASE_TRACE_TRACE_H
