/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkParallelTimer.h

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
//
/**
 * @class   vtkParallelTimer
 *
 *
 *  Provides distributed log functionality. When the file is
 *  written each process data is collected by rank 0 who
 *  writes the data to a single file in rank order.
 *
 *  The log works as an event stack. EventStart pushes the
 *  event identifier and its start time onto the stack. EventEnd
 *  pops the most recent event time and identifier computes the
 *  elapsed time and adds an entry to the log recording the
 *  event, it's start and end times, and its elapsed time.
 *  EndEventSynch includes a barrier before the measurement.
 *
 *  The log class implements the singleton pattern so that it
 *  may be shared across class boundaries. If the log instance
 *  doesn't exist then one is created. It will be automatically
 *  destroyed at exit by the signleton destructor. It can be
 *  destroyed explicitly by calling DeleteGlobalInstance.
 */

#ifndef vtkParallelTimer_h
#define vtkParallelTimer_h

#define vtkParallelTimerDEBUG -1

#include "vtkObject.h"
#include "vtkRenderingParallelLICModule.h" // for export

#include <sstream> // for sstream
#include <string>  // for string
#include <vector>  // for vector
#if vtkParallelTimerDEBUG > 0
#include <iostream> // for cerr
#endif

class vtkParallelTimerBuffer;

class VTKRENDERINGPARALLELLIC_EXPORT vtkParallelTimer : public vtkObject
{
public:
  static vtkParallelTimer* New();
  vtkTypeMacro(vtkParallelTimer, vtkObject);
  void PrintSelf(ostream& os, vtkIndent indent) override;

  //@{
  /**
   * Type used to direct an output stream into the log's header. The header
   * is a buffer used only by the root rank.
   */
  class LogHeaderType
  {
  public:
    template <typename T>
    LogHeaderType& operator<<(const T& s);
  };
  //@}

  //@{
  /**
   * Type used to direct an output stream into the log's body. The body is a
   * buffer that all ranks write to.
   */
  class LogBodyType
  {
  public:
    template <typename T>
    LogBodyType& operator<<(const T& s);
  };
  //@}

  //@{
  /**
   * Set the rank who writes.
   */
  vtkSetMacro(WriterRank, int);
  vtkGetMacro(WriterRank, int);
  //@}

  //@{
  /**
   * Set the filename that is used during write when the object
   * is used as a singleton. If nothing is set the default is
   * ROOT_RANKS_PID.log
   */
  vtkSetStringMacro(FileName);
  vtkGetStringMacro(FileName);
  //@}

  void SetFileName(const std::string& fileName) { this->SetFileName(fileName.c_str()); }

  //@{
  /**
   * The log works as an event stack. EventStart pushes the
   * event identifier and its start time onto the stack. EventEnd
   * pops the most recent event time and identifier computes the
   * elapsed time and adds an entry to the log recording the
   * event, it's start and end times, and its elapsed time.
   * EndEventSynch includes a barrier before the measurement.
   */
  void StartEvent(const char* event);
  void StartEvent(int rank, const char* event);
  void EndEvent(const char* event);
  void EndEvent(int rank, const char* event);
  void EndEventSynch(const char* event);
  void EndEventSynch(int rank, const char* event);
  //@}

  /**
   * Insert text into the log header on the writer rank.
   */
  template <typename T>
  vtkParallelTimer& operator<<(const T& s);

  /**
   * stream output to the log's header(root rank only).
   */
  vtkParallelTimer::LogHeaderType GetHeader() { return vtkParallelTimer::LogHeaderType(); }

  /**
   * stream output to log body(all ranks).
   */
  vtkParallelTimer::LogBodyType GetBody() { return vtkParallelTimer::LogBodyType(); }

  /**
   * Clear the log.
   */
  void Clear();

  /**
   * When an object is finished writing data to the log
   * object it must call Update to send the data to the writer
   * rank.
   * This ensures that all data is transferred to the root before
   * MPI_Finalize is called while allowing the write to occur
   * after Mpi_finalize. Note: This is a collective call.
   */
  void Update();

  /**
   * Write the log contents to a file.
   */
  int Write();

  /**
   * The log class implements the singleton pattern so that it
   * may be shared across class boundaries. If the log instance
   * doesn't exist then one is created. It will be automatically
   * destroyed at exit by the signleton destructor. It can be
   * destroyed explicitly by calling DeleteGlobalInstance.
   */
  static vtkParallelTimer* GetGlobalInstance();

  /**
   * Explicitly delete the singleton.
   */
  static void DeleteGlobalInstance();

  //@{
  /**
   * If enabled and used as a singleton the log will write
   * it's contents to disk during program termination.
   */
  vtkSetMacro(WriteOnClose, int);
  vtkGetMacro(WriteOnClose, int);
  //@}

  //@{
  /**
   * Set/Get the global log level. Applications can set this to the
   * desired level so that all pipeline objects will log data.
   */
  vtkSetMacro(GlobalLevel, int);
  vtkGetMacro(GlobalLevel, int);
  //@}

protected:
  vtkParallelTimer();
  virtual ~vtkParallelTimer();

private:
  vtkParallelTimer(const vtkParallelTimer&) = delete;
  void operator=(const vtkParallelTimer&) = delete;

  /**
   * A class responsible for delete'ing the global instance of the log.
   */
  class VTKRENDERINGPARALLELLIC_EXPORT vtkParallelTimerDestructor
  {
  public:
    vtkParallelTimerDestructor()
      : Log(0)
    {
    }
    ~vtkParallelTimerDestructor();

    void SetLog(vtkParallelTimer* log) { this->Log = log; }

  private:
    vtkParallelTimer* Log;
  };

private:
  int GlobalLevel;
  int Initialized;
  int WorldRank;
  int WriterRank;
  char* FileName;
  int WriteOnClose;
  std::vector<double> StartTime;
#if vtkParallelTimerDEBUG < 0
  std::vector<std::string> EventId;
#endif

  vtkParallelTimerBuffer* Log;

  static vtkParallelTimer* GlobalInstance;
  static vtkParallelTimerDestructor GlobalInstanceDestructor;

  std::ostringstream HeaderBuffer;

  friend class LogHeaderType;
  friend class LogBodyType;
};

//-----------------------------------------------------------------------------
template <typename T>
vtkParallelTimer& vtkParallelTimer::operator<<(const T& s)
{
  if (this->WorldRank == this->WriterRank)
  {
    this->HeaderBuffer << s;
#if vtkParallelTimerDEBUG > 0
    std::cerr << s;
#endif
  }
  return *this;
}

//-----------------------------------------------------------------------------
template <typename T>
vtkParallelTimer::LogHeaderType& vtkParallelTimer::LogHeaderType::operator<<(const T& s)
{
  vtkParallelTimer* log = vtkParallelTimer::GetGlobalInstance();

  if (log->WorldRank == log->WriterRank)
  {
    log->HeaderBuffer << s;
#if vtkParallelTimerDEBUG > 0
    std::cerr << s;
#endif
  }

  return *this;
}

//-----------------------------------------------------------------------------
template <typename T>
vtkParallelTimer::LogBodyType& vtkParallelTimer::LogBodyType::operator<<(const T& s)
{
  vtkParallelTimer* log = vtkParallelTimer::GetGlobalInstance();

  *(log->Log) << s;
#if vtkParallelTimerDEBUG > 0
  std::cerr << s;
#endif

  return *this;
}

#endif
