#ifndef PROFILER_H
#define PROFILER_H


#ifndef TIMER_H
#include "Timer.h"
#endif

#ifndef EVENT_H
#include "Event.h"
#endif

namespace MM
{

class Profiler
{
    Timer               timer_;
    bool                on_;

public:
    Event               finish;

    int    interactions___add_force___start_count_;

    double interactions___add_force___start_;
    double interactions___add_force___make_list_;
    double interactions___add_force___make_sublist_;
    double interactions___add_force___calc_nonbonded_force_;
    double interactions___add_force___add_atom_force_;
    double interactions___add_force___add_bond_force_;
    double interactions___add_force___add_full_nonbonded_force_;
    double interactions___add_force___add_long_force_;
    double interactions___add_force___add_switching_force_;
    double interactions___add_force___add_water_force_;
    double interactions___add_force___flush_force_;
    double interactions___add_force___others_;
    double kick_;
    double drift_;
    double lock_;
    double update_;

    double CUDA_interactions___add_nonbonded_force___start_;
    double CUDA_interactions___add_nonbonded_force___set_atom_force_;
    double CUDA_interactions___add_nonbonded_force___atom_force_to_device_;
    double CUDA_interactions___add_nonbonded_force___nonbonded_force_;
    double CUDA_interactions___add_nonbonded_force___atom_force_from_device_;
    double CUDA_interactions___add_nonbonded_force___get_atom_force_;
    double CUDA_interactions___add_nonbonded_force___free_;

public:
    Profiler () : on_(false) { }
    static Profiler & singleton ();

    bool    is_on () const      {return on_;}
    void    set_on (bool on)    {on_ = on;}

    void    restart ();
    void    clear   ();
    void    report  ();

    //void interactions___add_force___start ()                     {}
    //void interactions___add_force___finish ()                     {}
    //void kick ()            {}
    //void drift ()              {} 
    //void lock ()               {}
    //void update ()               {}
    //void interactions___add_force___others ()              
    //void interactions___add_force___make_list ()                 {}
    //void interactions___add_force___make_sublist ()             
    //void interactions___add_force___calc_nonbonded_force ()      {}
    //void interactions___add_force___add_atom_force ()            {}
    //void interactions___add_force___add_bond_force ()            {}
    //void interactions___add_force___add_full_nonbonded_force ()  {}
    //void interactions___add_force___add_long_force () {}
    //void interactions___add_force___add_switching_force () {}
    //void interactions___add_force___add_water_force ()          {}
    //void List_interactions___add_force___flush_force ()          {}



    void interactions___add_force___start ()                    
    {
        clear ();
        interactions___add_force___start_ = timer_.elapsed();  timer_.restart();
        ++interactions___add_force___start_count_;
    }
    void interactions___add_force___finish ()                    
    {
        interactions___add_force___others ();
        finish.publish ();
    }

    void kick ()              
    {
        if (!on_) return;
        kick_  += timer_.elapsed();   timer_.restart();
    }
    void drift ()              
    {
        if (!on_) return;
        drift_ += timer_.elapsed();   timer_.restart();
    }
    void lock ()              
    {
        if (!on_) return;
        lock_ += timer_.elapsed();    timer_.restart();
    }
    void update ()              
    {
        if (!on_) return;
        update_ += timer_.elapsed();  timer_.restart();
    }

    void interactions___add_force___others ()              
    {
        if (!on_) return;
//#ifndef NDEBUG
//        double dT = timer_.elapsed();
//        interactions___add_force___others_                   += dT;  timer_.restart();
//        log() << dT << "\n";
//#endif

        interactions___add_force___others_                   += timer_.elapsed();  timer_.restart();
    }

    void interactions___add_force___make_list ()                
    {
        if (!on_) return;
        interactions___add_force___make_list_                += timer_.elapsed();  timer_.restart();
    }
    void interactions___add_force___make_sublist ()             
    {
        if (!on_) return;
        interactions___add_force___make_sublist_             += timer_.elapsed();  timer_.restart();
    }
    void interactions___add_force___calc_nonbonded_force ()     
    {
        if (!on_) return;
        interactions___add_force___calc_nonbonded_force_     += timer_.elapsed();  timer_.restart();
    }
    void interactions___add_force___add_atom_force ()           
    {
        if (!on_) return;
        interactions___add_force___add_atom_force_           += timer_.elapsed();  timer_.restart();
    }
    void interactions___add_force___add_bond_force ()           
    {
        if (!on_) return;
        interactions___add_force___add_bond_force_           += timer_.elapsed();  timer_.restart();
    }
    void interactions___add_force___add_full_nonbonded_force () 
    {
        if (!on_) return;
        interactions___add_force___add_full_nonbonded_force_ += timer_.elapsed();  timer_.restart();
    }
    void interactions___add_force___add_long_force ()
    {
        if (!on_) return;
        interactions___add_force___add_long_force_ += timer_.elapsed();  timer_.restart();
    }
    void interactions___add_force___add_switching_force ()
    {
        if (!on_) return;
        interactions___add_force___add_switching_force_ += timer_.elapsed();  timer_.restart();
    }
    void interactions___add_force___add_water_force ()         
    {
        if (!on_) return;
        interactions___add_force___add_water_force_              += timer_.elapsed();  timer_.restart();
    }
    void List_interactions___add_force___flush_force ()         
    {
        if (!on_) return;
        interactions___add_force___flush_force_              += timer_.elapsed();  timer_.restart();
    }


    void CUDA_interactions___add_nonbonded_force___start ()                     {CUDA_interactions___add_nonbonded_force___start_ = timer_.elapsed();  timer_.restart();}
    void CUDA_interactions___add_nonbonded_force___set_atom_force ()            {CUDA_interactions___add_nonbonded_force___set_atom_force_ = timer_.elapsed();  timer_.restart();}
    void CUDA_interactions___add_nonbonded_force___atom_force_to_device ()      {CUDA_interactions___add_nonbonded_force___atom_force_to_device_ = timer_.elapsed();  timer_.restart();}
    void CUDA_interactions___add_nonbonded_force___nonbonded_force ()           {CUDA_interactions___add_nonbonded_force___nonbonded_force_ = timer_.elapsed();  timer_.restart();}
    void CUDA_interactions___add_nonbonded_force___atom_force_from_device ()    {CUDA_interactions___add_nonbonded_force___atom_force_from_device_ = timer_.elapsed();  timer_.restart();}
    void CUDA_interactions___add_nonbonded_force___get_atom_force ()            {CUDA_interactions___add_nonbonded_force___get_atom_force_ = timer_.elapsed();  timer_.restart();}
    void CUDA_interactions___add_nonbonded_force___free ()                      {CUDA_interactions___add_nonbonded_force___free_ = timer_.elapsed();  timer_.restart();}
};

inline Profiler & profile () {return Profiler::singleton ();}

}//MM

#endif //PROFILER_H
