#include "bu/profiler.h"
#include "bu/mutexlocker.h"
#include "bu/sio.h"

#include <sys/time.h>

Bu::Profiler::Profiler()
{
}

Bu::Profiler::~Profiler()
{
    printReport();
}

uint64_t Bu::Profiler::getMicroTime()
{
    struct timeval tv;
    gettimeofday( &tv, NULL );
    return ((uint64_t)tv.tv_sec)*1000000 + ((uint64_t)tv.tv_usec);
}

void Bu::Profiler::startEvent( const Bu::Blob &bName )
{
    Bu::MutexLocker l( mLock );
    if( hEvent.has( bName ) )
    {
        hEvent.get( bName ).append( Event() );
    }
    else
    {
        hEvent.insert( bName, EventList( Event() ) );
    }
}

void Bu::Profiler::endEvent( const Bu::Blob &bName )
{
    Bu::MutexLocker l( mLock );
    if( !hEvent.has( bName ) )
        return;

    if( hEvent.get( bName ).isEmpty() )
        return;

    hEvent.get( bName ).last().end();
}

void Bu::Profiler::printReport() const
{
    class Breakdown
    {
    public:
        Breakdown( const Bu::Blob &bName ) :
            bName( bName ), uCount( 0 ), uTotal( 0 ), uMin( 0 ), uMax( 0 ) { }
        Breakdown( const Breakdown &rSrc ) :
            bName( rSrc.bName ), uCount( rSrc.uCount ), uTotal( rSrc.uTotal ),
            uMin( rSrc.uMin ), uMax( rSrc.uMax ) { }
        ~Breakdown() { }

        bool operator<( const Breakdown &rhs ) const
        {
            return uTotal > rhs.uTotal;
        }
        Breakdown &operator=( const Breakdown &rhs )
        {
            bName = rhs.bName;
            uCount = rhs.uCount;
            uTotal = rhs.uTotal;
            uMin = rhs.uMin;
            uMax = rhs.uMax;
            return *this;
        }
        
        void add( uint64_t uSpan )
        {
            if( uCount == 0 )
            {
                uMin = uMax = uSpan;
            }
            else
            {
                if( uMin > uSpan )
                    uMin = uSpan;
                if( uMax < uSpan )
                    uMax = uSpan;

            }
            uCount++;
            uTotal += uSpan;
        }

        Bu::Blob bName;
        uint64_t uCount;
        uint64_t uTotal;
        uint64_t uMin;
        uint64_t uMax;
    };
    Bu::MutexLocker l( mLock );

    int iTitleLen = 0;
    Bu::List<Breakdown> lBreakdown;
    for( EventListHash::const_iterator iType = hEvent.begin(); iType; iType++ )
    {
        Breakdown b( iType.getKey() );
        if( iTitleLen < b.bName.getSize() )
            iTitleLen = b.bName.getSize();
        for( EventList::const_iterator iEv = iType.getValue().begin();
                iEv; iEv++ )
        {
            b.add( (*iEv).getSpan() );
        }
        lBreakdown.append( b );
    }

    lBreakdown.sort();

    Bu::println("%1 %2 %3 %4 %5 %6")
        .arg("Title",   Bu::Fmt(iTitleLen) )
        .arg("Cnt",     Bu::Fmt(5) )
        .arg("Total",   Bu::Fmt(9) )
        .arg("Avg",     Bu::Fmt(9) )
        .arg("Max",     Bu::Fmt(9) )
        .arg("Min",     Bu::Fmt(9) );
    for( Bu::List<Breakdown>::iterator i = lBreakdown.begin(); i; i++ )
    {
        Bu::println("%1 %2 %3.%4 %5.%6 %7.%8 %9.%10")
            .arg( (*i).bName.getData(), Bu::Fmt(iTitleLen) )
            .arg( (*i).uCount, Bu::Fmt(5) )
            .arg( ((*i).uTotal/1000000), Bu::Fmt(5).right() )
            .arg( ((*i).uTotal/1000)%1000, Bu::Fmt(3).fill('0').right() )
            .arg( ((*i).uTotal/(*i).uCount)/1000000, Bu::Fmt(5).right() )
            .arg( (((*i).uTotal/(*i).uCount)/1000)%1000, Bu::Fmt(3).right().fill('0') )
            .arg( (*i).uMax/1000000, Bu::Fmt(5).right() )
            .arg( ((*i).uMax/1000)%1000, Bu::Fmt(3).right().fill('0') )
            .arg( (*i).uMin/1000000, Bu::Fmt(5).right() )
            .arg( ((*i).uMin/1000)%1000, Bu::Fmt(3).right().fill('0') )
            ;
    }
}

Bu::Profiler::Event::Event() :
    iStart( Bu::Profiler::getMicroTime() ),
    iStop( 0 )
{
}

Bu::Profiler::Event::Event( const Event &rSrc ) :
    iStart( rSrc.iStart ),
    iStop( rSrc.iStop )
{
}

Bu::Profiler::Event::~Event()
{
}

void Bu::Profiler::Event::end()
{
    if( iStop == 0 )
        iStop = Bu::Profiler::getMicroTime();
}

bool Bu::Profiler::Event::hasEnded() const
{
    return iStop != 0;
}

uint64_t Bu::Profiler::Event::getStart() const
{
    return iStart;
}

uint64_t Bu::Profiler::Event::getEnd() const
{
    return iStop;
}

uint64_t Bu::Profiler::Event::getSpan() const
{
    if( iStop < iStart )
        return 0;
    return iStop-iStart;
}

