/*
 * (C) Copyright 2013 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation
 * nor does it submit to any jurisdiction.
 */

#pragma once

#include <iosfwd>
#include <string>
#include <vector>

#include "atlas/parallel/omp/omp.h"
#include "atlas/runtime/trace/CallStack.h"
#include "atlas/runtime/trace/CodeLocation.h"
#include "atlas/runtime/trace/Nesting.h"
#include "atlas/runtime/trace/StopWatch.h"
#include "atlas/runtime/trace/Timings.h"

//-----------------------------------------------------------------------------------------------------------

namespace eckit {
class Configuration;
}  // namespace eckit

//-----------------------------------------------------------------------------------------------------------

namespace atlas {
namespace runtime {
namespace trace {

//-----------------------------------------------------------------------------------------------------------

template <typename TraceTraits>
class TraceT {
public:
    using Traits   = TraceTraits;
    using Barriers = typename Traits::Barriers;
    using Tracing  = typename Traits::Tracing;
    using Labels   = std::vector<std::string>;

public:  // static methods
    static std::string report();
    static std::string report( const eckit::Configuration& config );

public:
    TraceT( const CodeLocation& );
    TraceT( const CodeLocation&, const std::string& title );
    TraceT( const CodeLocation&, const std::string& title, const Labels& );

    ~TraceT();

    bool running() const;

    void start();

    void stop();

    void pause();

    void resume();

    double elapsed() const;

private:  // types
    using Identifier = Timings::Identifier;

private:  // member functions
    void barrier() const;

    void updateTimings() const;

    void registerTimer();

    static std::string formatTitle( const std::string& );

private:  // member data
    bool running_{false};
    StopWatch stopwatch_;
    CodeLocation loc_;
    std::string title_;
    Identifier id_;
    CallStack callstack_;
    Labels labels_;
};

//-----------------------------------------------------------------------------------------------------------
// Definitions

template <typename TraceTraits>
inline std::string TraceT<TraceTraits>::formatTitle( const std::string& _title ) {
    std::string title = _title;
    +( Barriers::state() ? " [b]" : "" ) +
        ( atlas_omp_get_num_threads() > 1 ? " @thread[" + std::to_string( atlas_omp_get_thread_num() ) + "]" : "" );
    return title;
}

template <typename TraceTraits>
inline TraceT<TraceTraits>::TraceT( const CodeLocation& loc, const std::string& title ) :
    loc_( loc ), title_( formatTitle( title ) ) {
    start();
}

template <typename TraceTraits>
inline TraceT<TraceTraits>::TraceT( const CodeLocation& loc ) : loc_( loc ), title_( loc_ ? loc_.func() : "" ) {
    start();
}

template <typename TraceTraits>
inline TraceT<TraceTraits>::TraceT( const CodeLocation& loc, const std::string& title, const Labels& labels ) :
    loc_( loc ), title_( title ), labels_( labels ) {
    start();
}

template <typename TraceTraits>
inline TraceT<TraceTraits>::~TraceT() {
    stop();
}

template <typename TraceTraits>
inline void TraceT<TraceTraits>::barrier() const {
    Barriers::execute();
}

template <typename TraceTraits>
inline void TraceT<TraceTraits>::registerTimer() {
    std::string title =
        title_ + ( Barriers::state() ? " [b]" : "" ) +
        ( atlas_omp_get_num_threads() > 1 ? " @thread[" + std::to_string( atlas_omp_get_thread_num() ) + "]" : "" );
    id_ = Timings::add( loc_, callstack_, title, labels_ );
}

template <typename TraceTraits>
inline void TraceT<TraceTraits>::updateTimings() const {
    Timings::update( id_, stopwatch_.elapsed() );
}

template <typename TraceTraits>
inline bool TraceT<TraceTraits>::running() const {
    return running_;
}

template <typename TraceTraits>
inline void TraceT<TraceTraits>::start() {
    if ( Control::enabled() ) {
        running_ = true;
        if ( not callstack_ ) {
            callstack_ = CurrentCallStack::instance().push( loc_, title_ );
        }
        registerTimer();
        Tracing::start( title_ );
        barrier();
        stopwatch_.start();
    }
}

template <typename TraceTraits>
inline void TraceT<TraceTraits>::stop() {
    if ( running_ ) {
        barrier();
        stopwatch_.stop();
        CurrentCallStack::instance().pop();
        updateTimings();
        Tracing::stop( title_, stopwatch_.elapsed() );
        running_ = false;
    }
}

template <typename TraceTraits>
inline void TraceT<TraceTraits>::pause() {
    if ( running_ ) {
        barrier();
        stopwatch_.stop();
        CurrentCallStack::instance().pop();
    }
}

template <typename TraceTraits>
inline void TraceT<TraceTraits>::resume() {
    if ( running_ ) {
        barrier();
        CurrentCallStack::instance().push( loc_, title_ );
        stopwatch_.start();
    }
}

template <typename TraceTraits>
inline double TraceT<TraceTraits>::elapsed() const {
    return stopwatch_.elapsed();
}

template <typename TraceTraits>
inline std::string TraceT<TraceTraits>::report() {
    return Timings::report() + Barriers::report();
}

template <typename TraceTraits>
inline std::string TraceT<TraceTraits>::report( const eckit::Configuration& config ) {
    return Timings::report( config ) + Barriers::report();
}

//-----------------------------------------------------------------------------------------------------------

}  // namespace trace
}  // namespace runtime
}  // namespace atlas
