/***************************************************************************
 *   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"

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

#include <sys/time.h>

#include "skgerror.h"
#include "skgservices.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) \
    QStringLiteral("\x1b[31m")%TEXT%"\x1b[39m"

// ===================================================================
int SKGTraces::SKGLevelTrace = SKGServices::stringToInt(SKGServices::getEnvVariable(QStringLiteral("SKGTRACE")));
bool SKGTraces::SKGPerfo = (!SKGServices::getEnvVariable(QStringLiteral("SKGTRACEPERFO")).isEmpty() ? true : false);
QString SKGTraces::SKGIndentTrace = QStringLiteral("##");
SKGPerfoMap     SKGTraces::m_SKGPerfoMethode;
SKGQStringStack     SKGTraces::m_SKGPerfoPathMethode;
QTextStream SKGTraces::SKGCout(stdout, QIODevice::WriteOnly);
// ===================================================================
SKGTraces::SKGTraces(int iLevel, const char* iName, SKGError* iRC)
{
    init(iLevel, QLatin1String(iName), iRC);
}

SKGTraces::SKGTraces(int iLevel, const QString& iName, SKGError* iRC)
{
    init(iLevel, iName, iRC);
}

void SKGTraces::init(int iLevel, const QString& iName, SKGError* iRC)
{
    IFSKGTRACEL(iLevel) {
        m_mame = iName;
        m_output = true;

        m_rc = iRC;
        SKGIndentTrace += QStringLiteral("  ");

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

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

        // Push the method 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 << QStringLiteral(" TIME=") << m_elapse << QStringLiteral(" ms");
        }
        SKGTRACESUITE << endl;
        SKGIndentTrace.resize(SKGIndentTrace.length() - 2);

        m_rc = nullptr;
    }

    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 method from stack
            SKGTraces::m_SKGPerfoPathMethode.pop();

            // Get previous method name
            if (!SKGTraces::m_SKGPerfoPathMethode.empty()) {
                QString previousMethode = qAsConst(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.at(i) << endl;
    }
}

QStringList SKGTraces::getProfilingStatistics()
{
    QStringList output;
    if (SKGPerfo) {
        output.push_back(QStringLiteral("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 == -1) {
                    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;
}
