/***************************************************************************
 *   Copyright (C) 2008 by S. MANKOWSKI / G. DE BURE support@mankowski.fr  *
 *                                                                         *
 *   This program 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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>  *
 ***************************************************************************/
/** @file
* This file implements classes SKGTraces.
*
* @author Stephane MANKOWSKI / Guillaume DE BURE
*/
#include "skgtraces.h"
#include "skgerror.h"
#include "skgservices.h"

#ifdef Q_OS_WIN
#include <windows.h>
#endif

#include <sys/time.h>
/**
  * To generate a colorized string
  */
#define COLORED(TEXT) \
    "\x1b[3"%QString::number(1+(((SKGTraces::SKGIndentTrace.count()-2)/2)%7))%'m'%TEXT%"\x1b[39m"
/**
  * To generate a red string
  */
#define COLOREDRED(TEXT) \
    QString("\x1b[31m")%TEXT%"\x1b[39m"

//===================================================================
int SKGTraces::SKGLevelTrace = SKGServices::stringToInt(SKGServices::getenv("SKGTRACE"));
bool SKGTraces::SKGPerfo = (!SKGServices::getenv("SKGTRACEPERFO").isEmpty() ? true : false);
QString SKGTraces::SKGIndentTrace = "##";
SKGPerfoMap     SKGTraces::m_SKGPerfoMethode;
SKGQStringStack     SKGTraces::m_SKGPerfoPathMethode;
//===================================================================
SKGTraces::SKGTraces(int iLevel, const QString& iName, SKGError* iRC)
{
    IFSKGTRACEL(iLevel) {
        m_mame = iName;
        m_output = true;

        m_rc = iRC;
        SKGIndentTrace += "  ";

        SKGTRACE << COLORED('>' % m_mame) << endl;
    }
    else m_output = false;

    if (SKGPerfo) {
        m_profiling = true;
        m_mame = iName;

        //Push the methode in the stack
        SKGTraces::m_SKGPerfoPathMethode.push(m_mame);

        //Pour les mesures de perfos
#ifdef Q_OS_WIN
        m_elapse = static_cast<double>(GetTickCount());
#else
        struct timeval tv;
        struct timezone tz;
        gettimeofday(&tv, &tz);
        m_elapse = (static_cast<double>(1000.0 * tv.tv_sec)) + (static_cast<double>(tv.tv_usec / 1000.0));
#endif

        //Searching the key in the map
        m_it = SKGTraces::m_SKGPerfoMethode.find(m_mame);
        if (m_it == SKGTraces::m_SKGPerfoMethode.end()) {
            //Not found ==> initialisation
            SKGPerfoInfo init;
            init.NbCall = 0;
            init.Time = 0;
            init.TimePropre = 0;
            init.TimeMin = 99999999;
            init.TimeMax = -1;

            //Add the line
            SKGTraces::m_SKGPerfoMethode[m_mame] = init;

            //find again
            m_it = SKGTraces::m_SKGPerfoMethode.find(m_mame);
        }
    } else {
        m_profiling = false;
        m_elapse = -1;
    }
}

SKGTraces::~SKGTraces()
{
    //Get delta time
    if (m_elapse >= 0) {
#ifdef Q_OS_WIN
        m_elapse = static_cast<double>(GetTickCount());
#else
        struct timeval tv;
        struct timezone tz;
        gettimeofday(&tv, &tz);
        m_elapse = (static_cast<double>(1000.0 * tv.tv_sec)) + (static_cast<double>(tv.tv_usec / 1000.0)) - m_elapse;
#endif
    }

    if (m_output) {
        SKGTRACESUITE << SKGTraces::SKGIndentTrace << COLORED('<' % m_mame);
        if (m_rc) {
            SKGTRACESUITE << (m_rc->isSucceeded() ? QString(COLORED(" RC=" % m_rc->getFullMessage())) : QString(COLOREDRED(" RC=" % m_rc->getFullMessage())));
        }
        if (m_profiling) {
            SKGTRACESUITE << " TIME=" << m_elapse << " ms";
        }
        SKGTRACESUITE << endl;
        SKGIndentTrace.resize(SKGIndentTrace.length() - 2);

        m_rc = NULL;
    }

    if (m_profiling) {
        //Update values
        ++ (m_it.value().NbCall);
        m_it.value().Time += m_elapse;
        m_it.value().TimePropre += m_elapse;
        if (m_elapse > m_it.value().TimeMax) m_it.value().TimeMax = m_elapse;
        if (m_elapse < m_it.value().TimeMin) m_it.value().TimeMin = m_elapse;

        if (!SKGTraces::m_SKGPerfoPathMethode.empty() && SKGTraces::m_SKGPerfoPathMethode.top() == m_mame) {

            //Remove current methode from stack
            SKGTraces::m_SKGPerfoPathMethode.pop();

            //Get previous methode name
            if (!SKGTraces::m_SKGPerfoPathMethode.empty()) {
                QString previousMethode = SKGTraces::m_SKGPerfoPathMethode.top();

                //Searching the key in the map
                m_it = SKGTraces::m_SKGPerfoMethode.find(previousMethode);
                if (m_it != SKGTraces::m_SKGPerfoMethode.end()) {
                    m_it.value().TimePropre -= m_elapse;
                }
            }
        }
    }
}

void SKGTraces::cleanProfilingStatistics()
{
    SKGTraces::m_SKGPerfoMethode.clear();
}

void SKGTraces::dumpProfilingStatistics()
{
    QStringList dump = getProfilingStatistics();

    int nbl = dump.count();
    for (int i = 0; i < nbl; ++i) {
        SKGTRACE << dump[i] << endl;
    }
}

QStringList SKGTraces::getProfilingStatistics()
{
    QStringList output;
    if (SKGPerfo) {
        output.push_back("method , nb call , millisecondes , average , min , max , own time , average own time");

        SKGPerfoMap SKGPerfoMethodeCopy = m_SKGPerfoMethode;
        while (!SKGPerfoMethodeCopy.empty()) {

            //Recheche du temps propre maximal
            double maxtime = -1;
            SKGPerfoMapIterator max;
            SKGPerfoMapIterator it2;
            for (it2 = SKGPerfoMethodeCopy.begin() ; it2 != SKGPerfoMethodeCopy.end(); ++it2) {
                if (it2.value().TimePropre > maxtime) {
                    maxtime = it2.value().TimePropre;
                    max = it2;
                }
            }

            //dump max
            if (maxtime != -1) {
                output.push_back(max.key()
                                 % " , " % SKGServices::intToString(max.value().NbCall)
                                 % " , " % SKGServices::doubleToString(max.value().Time)
                                 % " , " % SKGServices::doubleToString((max.value().Time) / (static_cast<double>(max.value().NbCall)))
                                 % " , " % SKGServices::doubleToString(max.value().TimeMin)
                                 % " , " % SKGServices::doubleToString(max.value().TimeMax)
                                 % " , " % SKGServices::doubleToString(max.value().TimePropre)
                                 % " , " % SKGServices::doubleToString((max.value().TimePropre) / (static_cast<double>(max.value().NbCall))));

                //Remove it
                SKGPerfoMethodeCopy.erase(max);
            }
        }
    }

    return output;
}
