//////////////////////////////////////////////////////////////////////////////
// Copyright 2005-2006 Andreas Huber Doenni
// Distributed under the Boost Software License, Version 1.0. (See accompany-
// ing file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//////////////////////////////////////////////////////////////////////////////



//////////////////////////////////////////////////////////////////////////////
// #define CUSTOMIZE_MEMORY_MANAGEMENT
// #define BOOST_STATECHART_USE_NATIVE_RTTI
//////////////////////////////////////////////////////////////////////////////
// This program measures event processing performance of the BitMachine
// (see BitMachine example for more information) with a varying number of
// states. Also, a varying number of transitions are replaced with in-state
// reactions. This allows us to calculate how much time is spent for state-
// entry and state-exit during a transition. All measurements are written to
// comma-separated-values files, one file for each individual BitMachine.
//////////////////////////////////////////////////////////////////////////////



#include <boost/statechart/event.hpp>
#include <boost/statechart/simple_state.hpp>
#include <boost/statechart/state_machine.hpp>
#include <boost/statechart/transition.hpp>
#include <boost/statechart/in_state_reaction.hpp>

#include <boost/mpl/list.hpp>
#include <boost/mpl/front_inserter.hpp>
#include <boost/mpl/transform_view.hpp>
#include <boost/mpl/copy.hpp>
#include <boost/mpl/range_c.hpp>
#include <boost/mpl/integral_c.hpp>
#include <boost/mpl/shift_left.hpp>
#include <boost/mpl/bitxor.hpp>
#include <boost/mpl/for_each.hpp>
#include <boost/mpl/placeholders.hpp>
#include <boost/mpl/if.hpp>
#include <boost/mpl/less.hpp>
#include <boost/mpl/aux_/lambda_support.hpp>

#include <boost/intrusive_ptr.hpp>
#include <boost/config.hpp>
#include <boost/assert.hpp>

#ifdef CUSTOMIZE_MEMORY_MANAGEMENT
#  ifdef BOOST_MSVC
#    pragma warning( push )
#    pragma warning( disable: 4127 ) // conditional expression is constant
#    pragma warning( disable: 4800 ) // forcing value to bool 'true' or 'false'
#  endif
#  define BOOST_NO_MT
#  include <boost/pool/pool_alloc.hpp>
#  ifdef BOOST_MSVC
#    pragma warning( pop )
#  endif
#endif

#include <vector>
#include <ctime>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>
#include <algorithm>

#ifdef BOOST_NO_STDC_NAMESPACE
namespace std
{
  using ::clock_t;
  using ::clock;
}
#endif

#ifdef BOOST_INTEL
#  pragma warning( disable: 304 ) // access control not specified
#  pragma warning( disable: 444 ) // destructor for base is not virtual
#  pragma warning( disable: 981 ) // operands are evaluated in unspecified order
#endif



namespace sc = boost::statechart;
namespace mpl = boost::mpl;



//////////////////////////////////////////////////////////////////////////////
typedef mpl::integral_c< unsigned int, 0 > uint0;
typedef mpl::integral_c< unsigned int, 1 > uint1;
typedef mpl::integral_c< unsigned int, 2 > uint2;
typedef mpl::integral_c< unsigned int, 3 > uint3;
typedef mpl::integral_c< unsigned int, 4 > uint4;
typedef mpl::integral_c< unsigned int, 5 > uint5;
typedef mpl::integral_c< unsigned int, 6 > uint6;
typedef mpl::integral_c< unsigned int, 7 > uint7;
typedef mpl::integral_c< unsigned int, 8 > uint8;
typedef mpl::integral_c< unsigned int, 9 > uint9;

//////////////////////////////////////////////////////////////////////////////
template< class BitNo >
struct EvFlipBit : sc::event< EvFlipBit< BitNo > > {};

boost::intrusive_ptr< const sc::event_base > pFlipBitEvents[] =
{
  new EvFlipBit< uint0 >,
  new EvFlipBit< uint1 >,
  new EvFlipBit< uint2 >,
  new EvFlipBit< uint3 >,
  new EvFlipBit< uint4 >,
  new EvFlipBit< uint5 >,
  new EvFlipBit< uint6 >,
  new EvFlipBit< uint7 >,
  new EvFlipBit< uint8 >,
  new EvFlipBit< uint9 >
};


//////////////////////////////////////////////////////////////////////////////
template<
  class StateNo,
  class NoOfBits,
  class FirstTransitionBit >
struct BitState;

template< class NoOfBits, class FirstTransitionBit >
struct BitMachine : sc::state_machine<
  BitMachine< NoOfBits, FirstTransitionBit >,
  BitState< uint0, NoOfBits, FirstTransitionBit >
  #ifdef CUSTOMIZE_MEMORY_MANAGEMENT
  , boost::fast_pool_allocator< int >
  #endif
>
{
  public:
    BitMachine() : inStateReactions_( 0 ), transitions_( 0 ) {}

    // GCC 3.4.2 doesn't seem to instantiate a function template despite the
    // fact that an address of the instantiation is passed as a non-type
    // template argument. This leads to linker errors when a function template
    // is defined instead of the overloads below.
    void InStateReaction( const EvFlipBit< uint0 > & )
    {
      ++inStateReactions_;
    }

    void InStateReaction( const EvFlipBit< uint1 > & )
    {
      ++inStateReactions_;
    }

    void InStateReaction( const EvFlipBit< uint2 > & )
    {
      ++inStateReactions_;
    }

    void InStateReaction( const EvFlipBit< uint3 > & )
    {
      ++inStateReactions_;
    }

    void InStateReaction( const EvFlipBit< uint4 > & )
    {
      ++inStateReactions_;
    }

    void InStateReaction( const EvFlipBit< uint5 > & )
    {
      ++inStateReactions_;
    }

    void InStateReaction( const EvFlipBit< uint6 > & )
    {
      ++inStateReactions_;
    }

    void InStateReaction( const EvFlipBit< uint7 > & )
    {
      ++inStateReactions_;
    }

    void InStateReaction( const EvFlipBit< uint8 > & )
    {
      ++inStateReactions_;
    }

    void InStateReaction( const EvFlipBit< uint9 > & )
    {
      ++inStateReactions_;
    }

    void Transition( const EvFlipBit< uint0 > & )
    {
      ++transitions_;
    }

    void Transition( const EvFlipBit< uint1 > & )
    {
      ++transitions_;
    }

    void Transition( const EvFlipBit< uint2 > & )
    {
      ++transitions_;
    }

    void Transition( const EvFlipBit< uint3 > & )
    {
      ++transitions_;
    }

    void Transition( const EvFlipBit< uint4 > & )
    {
      ++transitions_;
    }

    void Transition( const EvFlipBit< uint5 > & )
    {
      ++transitions_;
    }

    void Transition( const EvFlipBit< uint6 > & )
    {
      ++transitions_;
    }

    void Transition( const EvFlipBit< uint7 > & )
    {
      ++transitions_;
    }

    void Transition( const EvFlipBit< uint8 > & )
    {
      ++transitions_;
    }

    void Transition( const EvFlipBit< uint9 > & )
    {
      ++transitions_;
    }

    unsigned int GetNoOfInStateReactions() const
    {
      return inStateReactions_;
    }

    unsigned int GetNoOfTransitions() const
    {
      return transitions_;
    }

  private:
    unsigned int inStateReactions_;
    unsigned int transitions_;
};

//////////////////////////////////////////////////////////////////////////////
template<
  class BitNo, class StateNo, class NoOfBits, class FirstTransitionBit >
struct FlipTransition
{
  private:
    typedef typename mpl::bitxor_<
      StateNo,
      mpl::shift_left< uint1, BitNo >
    >::type NextStateNo;

  public:
    typedef typename mpl::if_<
      mpl::less< BitNo, FirstTransitionBit >,
      sc::in_state_reaction< 
        EvFlipBit< BitNo >,
        BitMachine< NoOfBits, FirstTransitionBit >,
        &BitMachine< NoOfBits, FirstTransitionBit >::InStateReaction >,
      sc::transition<
        EvFlipBit< BitNo >,
        BitState< NextStateNo, NoOfBits, FirstTransitionBit >,
        BitMachine< NoOfBits, FirstTransitionBit >,
        &BitMachine< NoOfBits, FirstTransitionBit >::Transition >
    >::type type;

    BOOST_MPL_AUX_LAMBDA_SUPPORT(
      3, FlipTransition, (BitNo, StateNo, FirstTransitionBit) );
};

//////////////////////////////////////////////////////////////////////////////
template<
  class StateNo,
  class NoOfBits,
  class FirstTransitionBit >
struct BitState : sc::simple_state<
  BitState< StateNo, NoOfBits, FirstTransitionBit >,
  BitMachine< NoOfBits, FirstTransitionBit > >
{
  typedef typename mpl::copy< 
    typename mpl::transform_view<
      mpl::range_c< unsigned int, 0, NoOfBits::value >,
      FlipTransition<
        mpl::placeholders::_, StateNo, NoOfBits, FirstTransitionBit >
    >::type,
    mpl::front_inserter< mpl::list<> >
  >::type reactions;
};

// GCC 3.4.2 doesn't seem to instantiate a class template member function
// despite the fact that an address of the function is passed as a non-type
// template argument. This leads to linker errors when the class template
// defining the functions is not explicitly instantiated.
template struct BitMachine< uint1, uint0 >;
template struct BitMachine< uint1, uint1 >;

template struct BitMachine< uint2, uint0 >;
template struct BitMachine< uint2, uint1 >;
template struct BitMachine< uint2, uint2 >;

template struct BitMachine< uint3, uint0 >;
template struct BitMachine< uint3, uint1 >;
template struct BitMachine< uint3, uint2 >;
template struct BitMachine< uint3, uint3 >;

template struct BitMachine< uint4, uint0 >;
template struct BitMachine< uint4, uint1 >;
template struct BitMachine< uint4, uint2 >;
template struct BitMachine< uint4, uint3 >;
template struct BitMachine< uint4, uint4 >;

template struct BitMachine< uint5, uint0 >;
template struct BitMachine< uint5, uint1 >;
template struct BitMachine< uint5, uint2 >;
template struct BitMachine< uint5, uint3 >;
template struct BitMachine< uint5, uint4 >;
template struct BitMachine< uint5, uint5 >;

template struct BitMachine< uint6, uint0 >;
template struct BitMachine< uint6, uint1 >;
template struct BitMachine< uint6, uint2 >;
template struct BitMachine< uint6, uint3 >;
template struct BitMachine< uint6, uint4 >;
template struct BitMachine< uint6, uint5 >;
template struct BitMachine< uint6, uint6 >;

template struct BitMachine< uint7, uint0 >;
template struct BitMachine< uint7, uint1 >;
template struct BitMachine< uint7, uint2 >;
template struct BitMachine< uint7, uint3 >;
template struct BitMachine< uint7, uint4 >;
template struct BitMachine< uint7, uint5 >;
template struct BitMachine< uint7, uint6 >;
template struct BitMachine< uint7, uint7 >;


////////////////////////////////////////////////////////////////////////////
struct PerfResult
{
  PerfResult( double inStateRatio, double nanoSecondsPerReaction ) :
    inStateRatio_( inStateRatio ),
    nanoSecondsPerReaction_( nanoSecondsPerReaction )
  {
  }

  double inStateRatio_;
  double nanoSecondsPerReaction_;
};

template< class NoOfBits, class FirstTransitionBit >
class PerformanceTester
{
  public:
    ////////////////////////////////////////////////////////////////////////
    static PerfResult Test()
    {
      eventsSent_ = 0;
      BitMachine< NoOfBits, FirstTransitionBit > machine;
      machine.initiate();
      const std::clock_t startTime = std::clock();

      const unsigned int laps = eventsToSend_ / ( GetNoOfStates() - 1 );

      for ( unsigned int lap = 0; lap < laps; ++lap )
      {
        VisitAllStatesImpl( machine, NoOfBits::value - 1 );
      }

      const std::clock_t elapsedTime = std::clock() - startTime;

      BOOST_ASSERT( eventsSent_ == eventsToSend_ );
      BOOST_ASSERT( 
        machine.GetNoOfInStateReactions() +
        machine.GetNoOfTransitions() == eventsSent_ );

      return PerfResult(
        static_cast< double >( machine.GetNoOfInStateReactions() ) /
          eventsSent_,
        static_cast< double >( elapsedTime ) /
          CLOCKS_PER_SEC * 1000.0 * 1000.0 * 1000.0 / eventsSent_ );
    }

    static unsigned int GetNoOfStates()
    {
      return 1 << NoOfBits::value;
    }

    static unsigned int GetNoOfReactions()
    {
      return GetNoOfStates() * NoOfBits::value;
    }

  private:
    ////////////////////////////////////////////////////////////////////////
    static void VisitAllStatesImpl(
      BitMachine< NoOfBits, FirstTransitionBit > & machine,
      unsigned int bit )
    {
      if ( bit > 0 )
      {
        PerformanceTester< NoOfBits, FirstTransitionBit >::
          VisitAllStatesImpl( machine, bit - 1 );
      }

      machine.process_event( *pFlipBitEvents[ bit ] );
      ++PerformanceTester< NoOfBits, FirstTransitionBit >::eventsSent_;

      if ( bit > 0 )
      {
        PerformanceTester< NoOfBits, FirstTransitionBit >::
          VisitAllStatesImpl( machine, bit - 1 );
      }
    }

    // common prime factors of 2^n-1 for n in [1,8]
    static const unsigned int eventsToSend_ = 3 * 3 * 5 * 7 * 17 * 31 * 127;
    static unsigned int eventsSent_;
};

template< class NoOfBits, class FirstTransitionBit >
unsigned int PerformanceTester< NoOfBits, FirstTransitionBit >::eventsSent_;


//////////////////////////////////////////////////////////////////////////////
typedef std::vector< PerfResult > PerfResultList;

template< class NoOfBits >
struct PerfResultBackInserter
{
  PerfResultBackInserter( PerfResultList & perfResultList ) :
    perfResultList_( perfResultList )
  {
  }

  template< class FirstTransitionBit >
  void operator()( const FirstTransitionBit & )
  {
    perfResultList_.push_back(
      PerformanceTester< NoOfBits, FirstTransitionBit >::Test() );
  }

  PerfResultList & perfResultList_;
};

template< class NoOfBits >
std::vector< PerfResult > TestMachine()
{
  PerfResultList result;

  mpl::for_each< mpl::range_c< unsigned int, 0, NoOfBits::value + 1 > >(
    PerfResultBackInserter< NoOfBits >( result ) );

  return result;
}

template< class NoOfBits >
void TestAndWriteResults()
{
  PerfResultList results = TestMachine< NoOfBits >();

  std::fstream output;
  output.exceptions(
    std::ios_base::badbit | std::ios_base::eofbit | std::ios_base::failbit );

  std::string prefix = std::string( BOOST_COMPILER ) + "__";
  std::replace( prefix.begin(), prefix.end(), ' ', '_' );

  output.open( 
    ( prefix + std::string( 1, '0' + static_cast< char >( NoOfBits::value ) )
      + ".txt" ).c_str(),
    std::ios_base::out );

  for ( PerfResultList::const_iterator pResult = results.begin();
    pResult != results.end(); ++pResult )
  {
    output << std::fixed << std::setprecision( 0 ) <<
      std::setw( 8 ) << pResult->inStateRatio_ * 100 << ',' <<
      std::setw( 8 ) << pResult->nanoSecondsPerReaction_ << "\n";
  }
}


//////////////////////////////////////////////////////////////////////////////
int main()
{
  std::cout <<
    "Boost.Statechart in-state reaction vs. transition performance test\n\n";
  std::cout << "Press <CR> to start the test: ";

  {
    std::string input;
    std::getline( std::cin, input );
  }

  TestAndWriteResults< uint1 >();
  TestAndWriteResults< uint2 >();
  TestAndWriteResults< uint3 >();

  return 0;
}
