/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  Licensed 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
 *
 *         https://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  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 itkSimpleFilterWatcher_h
#define itkSimpleFilterWatcher_h

#include "itkCommand.h"
#include "itkProcessObject.h"
#include "itkTimeProbe.h"
#include <mutex>

namespace itk
{
/** \class SimpleFilterWatcher
 * \brief Simple mechanism for monitoring the pipeline events of a filter
 * and reporting these events to std::cout.
 *
 * SimpleFilterWatcher provides a simple mechanism for monitoring the
 * execution of filter.  SimpleFilterWatcher is a stack-based object
 * which takes a pointer to a ProcessObject at constructor
 * time. SimpleFilterWatcher creates a series of commands that are
 * registered as observers to the specified ProcessObject. The events
 * monitored are:
 *
 *      StartEvent
 *      EndEvent
 *      ProgressEvent
 *      IterationEvent
 *      AbortEvent
 *
 * The callbacks routines registered for these events emit a simple
 * message to std::cout.
 *
 * Example of use:
 *
 * using FilterType = itk::BinaryThresholdImageFilter<ImageType>;
 * auto thresholdFilter = FilterType::New();
 *
 * SimpleFilterWatcher watcher(thresholdFilter, "Threshold");
 *
 * The second argument to the constructor to SimpleFilterWatcher is an
 * optional string that is prepended to the event messages. This
 * allows the user to associate the emitted messages to a particular
 * filter/variable.
 *
 * \todo Allow any stream object to be used for the output (not just std::cout)
 *
 * \ingroup ITKCommon
 *
 * \sphinx
 * \sphinxexample{Core/Common/WatchAFilter,Watch A filter}
 * \endsphinx
 */
class ITKCommon_EXPORT SimpleFilterWatcher
{
public:
  /** Constructor. Takes a ProcessObject to monitor and an optional
   * comment string that is prepended to each event message. */
  SimpleFilterWatcher(itk::ProcessObject * o, const char * comment = "");

  /** Copy constructor */
  SimpleFilterWatcher(const SimpleFilterWatcher &);

  /** Default constructor. Only provided so that you can have
   * std::vectors of SimpleFilterWatchers. */
  SimpleFilterWatcher();

  /** operator=  */
  SimpleFilterWatcher &
  operator=(const SimpleFilterWatcher &);

  /** Destructor. */
  virtual ~SimpleFilterWatcher();

  /** Method to get the name of the class be monitored by this
   *  SimpleFilterWatcher */
  const char *
  GetNameOfClass()
  {
    return (m_Process ? m_Process->GetNameOfClass() : "None");
  }

  /** Methods to control the verbosity of the messages. Quiet
   * reporting limits the information emitted at a ProgressEvent. */
  void
  QuietOn()
  {
    m_Quiet = true;
  }
  void
  QuietOff()
  {
    m_Quiet = false;
  }

  /** Methods to use to test the AbortEvent of the a filter. If
   * TestAbort is on, the filter being watched will be aborted when
   * the progress reaches 30%. */
  void
  TestAbortOn()
  {
    m_TestAbort = true;
  }
  void
  TestAbortOff()
  {
    m_TestAbort = false;
  }

  /** Methods to access member data */
  /** Get a pointer to the process object being watched. */
  ProcessObject *
  GetProcess()
  {
    return m_Process.GetPointer();
  }

  /** Set/Get the steps completed. */
  void
  SetSteps(int val)
  {
    m_Steps = val;
  }
  int
  GetSteps() const
  {
    return m_Steps;
  }

  /** Set/Get the number of iterations completed. */
  void
  SetIterations(int val)
  {
    m_Iterations = val;
  }
  int
  GetIterations() const
  {
    return m_Iterations;
  }

  /** Set/Get the quiet mode boolean. If true, verbose progress is
   * reported. */
  void
  SetQuiet(bool val)
  {
    m_Quiet = val;
  }
  bool
  GetQuiet() const
  {
    return m_Quiet;
  }

  /** Get the comment for the watcher. */
  std::string
  GetComment()
  {
    return m_Comment;
  }

  /** Get a reference to the TimeProbe */
  TimeProbe &
  GetTimeProbe()
  {
    return m_TimeProbe;
  }

protected:
  /** Callback method to show the ProgressEvent */
  virtual void
  ShowProgress()
  {
    if (m_Process)
    {
      const std::lock_guard<std::mutex> lockGuard(m_ProgressOutput);
      ++m_Steps;
      if (!m_Quiet)
      {
        std::cout << " | " << m_Process->GetProgress() << std::flush;
        if ((m_Steps % 10) == 0)
        {
          std::cout << std::endl;
        }
      }
      if (m_TestAbort)
      {
        if (m_Process->GetProgress() > .03)
        {
          m_Process->AbortGenerateDataOn();
        }
      }
    }
  }

  /** Create commands for different event types. */
  void
  CreateCommands();

  /** Remove observers we have on the process object. */
  void
  RemoveObservers();

  /** The common code for copying this class. */
  void
  DeepCopy(const SimpleFilterWatcher & watch);

  /** Callback method to show the AbortEvent */
  virtual void
  ShowAbort()
  {
    std::cout << std::endl << "-------Aborted" << std::endl << std::flush;
  }

  /** Callback method to show the IterationEvent */
  virtual void
  ShowIteration()
  {
    std::cout << " #" << std::flush;
    ++m_Iterations;
  }

  /** Callback method to show the StartEvent */
  virtual void
  StartFilter()
  {
    m_Steps = 0;
    m_Iterations = 0;
    m_TimeProbe.Start();
    std::cout << "-------- Start " << (m_Process.GetPointer() ? m_Process->GetNameOfClass() : "None") << " \""
              << m_Comment << "\" ";
    if (!m_Quiet)
    {
      if (m_Process)
      {
        std::cout << m_Process;
      }
      else
      {
        std::cout << "Null";
      }
    }
    std::cout << (m_Quiet ? "Progress Quiet " : "Progress ") << std::flush;
  }

  /** Callback method to show the EndEvent */
  virtual void
  EndFilter()
  {
    m_TimeProbe.Stop();
    std::cout << std::endl
              << "Filter took " << m_TimeProbe.GetMean() << " seconds." << std::endl
              << "-------- End " << (m_Process.GetPointer() ? m_Process->GetNameOfClass() : "None") << " \""
              << m_Comment << "\" " << std::endl;
    if (!m_Quiet)
    {
      if (m_Process)
      {
        std::cout << m_Process;
      }
      else
      {
        std::cout << "None";
      }
      std::cout << std::flush;
    }
    if (m_Steps < 1)
    {
      itkExceptionMacro("Filter does not have progress.");
    }
  }

private:
  TimeProbe                   m_TimeProbe{};
  int                         m_Steps{ 0 };
  int                         m_Iterations{ 0 };
  bool                        m_Quiet{ false };
  bool                        m_TestAbort{ false };
  std::string                 m_Comment{};
  itk::ProcessObject::Pointer m_Process{};
  std::mutex                  m_ProgressOutput{};

  using CommandType = SimpleMemberCommand<SimpleFilterWatcher>;
  CommandType::Pointer m_StartFilterCommand{};
  CommandType::Pointer m_EndFilterCommand{};
  CommandType::Pointer m_ProgressFilterCommand{};
  CommandType::Pointer m_IterationFilterCommand{};
  CommandType::Pointer m_AbortFilterCommand{};

  unsigned long m_StartTag{ 0 };
  unsigned long m_EndTag{ 0 };
  unsigned long m_ProgressTag{ 0 };
  unsigned long m_IterationTag{ 0 };
  unsigned long m_AbortTag{ 0 };
};
} // end namespace itk

#endif
