//======================================================================================================================
//
//  This file is part of waLBerla. waLBerla is free software: you can
//  redistribute it and/or modify it under the terms of the GNU General Public
//  License as published by the Free Software Foundation, either version 3 of
//  the License, or (at your option) any later version.
//
//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
//  for more details.
//
//  You should have received a copy of the GNU General Public License along
//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
//
//! \file PerformanceLogger.h
//! \ingroup lbm
//! \author Christian Godenschwager <christian.godenschwager@fau.de>
//
//======================================================================================================================

#pragma once

#include "PerformanceEvaluation.h"

#include "core/DataTypes.h"
#include "core/Set.h"
#include "core/uid/SUID.h"
#include "core/timing/Timer.h"

#include "domain_decomposition/BlockDataID.h"
#include "domain_decomposition/StructuredBlockStorage.h"

#include "field/FlagUID.h"

#include "timeloop/ITimeloop.h"

#include <limits>

namespace walberla {
namespace lbm {

//**********************************************************************************************************************
/*!
*   \brief Class for using the PerformanceEvaluation in a timeloop
*
*   Providing a measurement interval, this class will regularly (every <interval> time steps) measure and report the
*   LBM performance. At the end of the simulation logOverallResults() may be called to output minimum, maximum and
*   average performance during the simulation run.   
*/
//**********************************************************************************************************************
template< typename FlagField_T >
class PerformanceLogger
{
public:
   PerformanceLogger( const shared_ptr< StructuredBlockStorage > & blocks,
                      const ConstBlockDataID & flagFieldId, const Set< FlagUID > & fluid,
                      const uint_t interval,
                      const Set<SUID> & requiredSelectors = Set<SUID>::emptySet(),
                      const Set<SUID> & incompatibleSelectors = Set<SUID>::emptySet() );

   PerformanceLogger( const shared_ptr< StructuredBlockStorage > & blocks,
                      const ConstBlockDataID & flagFieldId, const Set< FlagUID > & fluid,
                      const timeloop::ITimeloop * const timeloop,
                      const uint_t interval,
                      const Set<SUID> & requiredSelectors = Set<SUID>::emptySet(),
                      const Set<SUID> & incompatibleSelectors = Set<SUID>::emptySet() );

   void operator()();
   void logOverallResultsOnRoot() const;

   void enableRefreshCellCountOnCall()  { refreshCellCountOnCall_ = true;  }
   void disableRefreshCellCountOnCall() { refreshCellCountOnCall_ = false; }

   void getBestResultsForSQLOnRoot( std::map< std::string, int > &         integerProperties,
                                    std::map< std::string, double > &      realProperties,
                                    std::map< std::string, std::string > & stringProperties );

private:
   enum Mode { MIN, MAX, AVG, LAST };
   real_t getTiming( Mode mode ) const;

   PerformanceEvaluation<FlagField_T> performanceEvaluation_;
   uint_t interval_;
   uint_t timestep_;
   WcTimer timer_;
   bool refreshCellCountOnCall_;
   const timeloop::ITimeloop * timeloop_;

}; // class PerformanceLogger


template< typename FlagField_T >
PerformanceLogger<FlagField_T>::PerformanceLogger( const shared_ptr< StructuredBlockStorage > & blocks,
                   const ConstBlockDataID & flagFieldId, const Set< FlagUID > & fluid,
                   const uint_t interval,
                   const Set<SUID> & requiredSelectors /*= Set<SUID>::emptySet()*/,
                   const Set<SUID> & incompatibleSelectors /*= Set<SUID>::emptySet()*/ )
                   : performanceEvaluation_( blocks, flagFieldId, fluid, requiredSelectors, incompatibleSelectors ),
                     interval_(interval), timestep_(1), refreshCellCountOnCall_(false), timeloop_(NULL)
{
}

template< typename FlagField_T >
PerformanceLogger<FlagField_T>::PerformanceLogger( const shared_ptr< StructuredBlockStorage > & blocks,
                                                   const ConstBlockDataID & flagFieldId, const Set< FlagUID > & fluid,
                                                   const timeloop::ITimeloop * const timeloop,
                                                   const uint_t interval,
                                                   const Set<SUID> & requiredSelectors /*= Set<SUID>::emptySet()*/,
                                                   const Set<SUID> & incompatibleSelectors /*= Set<SUID>::emptySet()*/ )
   : performanceEvaluation_( blocks, flagFieldId, fluid, requiredSelectors, incompatibleSelectors ),
     interval_( interval ), timestep_( 1 ), refreshCellCountOnCall_( false ), timeloop_( timeloop )
{
}


template< typename FlagField_T >
real_t PerformanceLogger<FlagField_T>::getTiming( Mode mode ) const
{
   switch( mode )
   {
   case MIN:
      return timer_.max();
   case MAX:
      return timer_.min();
   case AVG:
      return timer_.average();
   case LAST:
      return timer_.last();
   default:
      WALBERLA_ASSERT( false );
      return std::numeric_limits< real_t >::signaling_NaN();
   }
}

template< typename FlagField_T >
void PerformanceLogger<FlagField_T>::operator()()
{
   if( timeloop_ )
      timestep_ = timeloop_->getCurrentTimeStep() + uint_t(1);

   if( timestep_ % interval_ == 0 )
   {
      WALBERLA_MPI_BARRIER();
      timer_.end();
      
      if( refreshCellCountOnCall_ )
         performanceEvaluation_.refresh();

      performanceEvaluation_.logResultOnRoot( interval_, getTiming( LAST ) );
      timer_.start();
   }

   ++timestep_;
}

template< typename FlagField_T >
void PerformanceLogger<FlagField_T>::logOverallResultsOnRoot() const
{
   WALBERLA_LOG_RESULT_ON_ROOT( "Min Performance:\n" << performanceEvaluation_.loggingString( interval_, getTiming( MIN ) ); );
   WALBERLA_LOG_RESULT_ON_ROOT( "Max Performance:\n" << performanceEvaluation_.loggingString( interval_, getTiming( MAX ) ); );
   WALBERLA_LOG_RESULT_ON_ROOT( "Avg Performance:\n" << performanceEvaluation_.loggingString( interval_, getTiming( AVG ) ); );
}

template< typename FlagField_T >
void PerformanceLogger<FlagField_T>::getBestResultsForSQLOnRoot( std::map< std::string, int > &         integerProperties,
                                                                 std::map< std::string, double > &      realProperties,
                                                                 std::map< std::string, std::string > & stringProperties )
{
   performanceEvaluation_.getResultsForSQLOnRoot( integerProperties, realProperties, stringProperties, interval_, getTiming( MAX ) );
}


} // namespace lbm
} // namespace walberla