/*
 * Profiler.h
 *
 *  Created on: May 15, 2017
 *      Author: Martin Hierholzer
 */

#ifndef CHIMERATK_PROFILER_H
#define CHIMERATK_PROFILER_H

#include <assert.h>
#include <atomic>
#include <chrono>
#include <list>
#include <mutex>
#include <string>

namespace ChimeraTK {

  class Profiler {
   public:
    class ThreadData {
     public:
      /** Return the name of the thread */
      const std::string& getName() const { return name; }

      /** Return the integrated active time of the thread in microseconds. */
      uint64_t getIntegratedTime() const { return integratedTime; }

      /** Return the integrated active time of the thread in microseconds and
       * atomically reset the counter to 0. */
      uint64_t getAndResetIntegratedTime() {
        uint64_t time = integratedTime;
        integratedTime.fetch_sub(time);
        return time;
      }

     private:
      friend class Profiler;

      /** Copy of Application::threadName(), stored here to make it accessible
       * outside the thread */
      std::string name;

      /** Reference point for the time measurement */
      std::chrono::high_resolution_clock::time_point lastActiated;

      /** Flag whether this thread is currently active */
      bool isActive{false};

      /** Integrated time this thread was active in microseconds */
      std::atomic<uint64_t> integratedTime;
    };

    /** Register a thread in the profiler. This function must be called in each
     * thread before calling startMeasurement() and stopMeasurement() in the same
     * thread. The function must not be called twice in the same thread. The call
     * to this function implicitly triggers starting the time measurement (see
     * startMeasurement()) */
    static void registerThread(const std::string& name) {
      getThreadData().name = name;
      std::lock_guard<std::mutex> lock(threadDataList_mutex);
      threadDataList.emplace_back(&getThreadData());
      startMeasurement();
    }

    /** Obtain a list of ThreadData references for all threads registered with the
     * profiler. */
    static const std::list<ThreadData*>& getDataList() { return threadDataList; }

    /** Start the time measurement for the current thread. Call this immediately
     * after the thread woke up e.g. from blocking read. */
    static void startMeasurement() {
      if(getThreadData().isActive) return;
      getThreadData().isActive = true;
      getThreadData().lastActiated = std::chrono::high_resolution_clock::now();
    }

    /** Stop the time measurement for the current thread. Call this right before
     * putting the thread to sleep e.g. before a blocking read. */
    static void stopMeasurement() {
      if(!getThreadData().isActive) return;
      getThreadData().isActive = false;
      auto duration = std::chrono::high_resolution_clock::now() - getThreadData().lastActiated;
      getThreadData().integratedTime += std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
    }

   private:
    /** Return the ThreadData object associated with the current thread. */
    static ThreadData& getThreadData() {
      thread_local static ThreadData data;
      return data;
    }

    /** List of ThreadData references registered with the profiler.  */
    static std::list<ThreadData*> threadDataList;

    /** Mutex for write access to the threadDataList member. Access to existing
     * list entries through the public member functions of ThreadData is allowed
     * without holding this mutex. */
    static std::mutex threadDataList_mutex;
  };

} // namespace ChimeraTK

#endif /* CHIMERATK_PROFILER_H */