/**
 * @file llmetricperformancetester.cpp
 * @brief LLMetricPerformanceTesterBasic and LLMetricPerformanceTesterWithSession classes implementation
 *
 * $LicenseInfo:firstyear=2004&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"

#include "indra_constants.h"
#include "llerror.h"
#include "llsdserialize.h"
#include "lltreeiterators.h"
#include "llmetricperformancetester.h"
#include "llfasttimer.h"

//----------------------------------------------------------------------------------------------
// LLMetricPerformanceTesterBasic : static methods and testers management
//----------------------------------------------------------------------------------------------

LLMetricPerformanceTesterBasic::name_tester_map_t LLMetricPerformanceTesterBasic::sTesterMap ;

/*static*/
void LLMetricPerformanceTesterBasic::cleanupClass()
{
    for (name_tester_map_t::value_type& pair : sTesterMap)
    {
        delete pair.second;
    }
    sTesterMap.clear() ;
}

/*static*/
bool LLMetricPerformanceTesterBasic::addTester(LLMetricPerformanceTesterBasic* tester)
{
    llassert_always(tester != NULL);
    std::string name = tester->getTesterName() ;
    if (getTester(name))
    {
        LL_ERRS() << "Tester name is already used by some other tester : " << name << LL_ENDL ;
        return false;
    }

    sTesterMap.insert(std::make_pair(name, tester));
    return true;
}

/*static*/
void LLMetricPerformanceTesterBasic::deleteTester(std::string name)
{
    name_tester_map_t::iterator tester = sTesterMap.find(name);
    if (tester != sTesterMap.end())
    {
        delete tester->second;
        sTesterMap.erase(tester);
    }
}

/*static*/
LLMetricPerformanceTesterBasic* LLMetricPerformanceTesterBasic::getTester(std::string name)
{
    // Check for the requested metric name
    name_tester_map_t::iterator found_it = sTesterMap.find(name) ;
    if (found_it != sTesterMap.end())
    {
        return found_it->second ;
    }
    return NULL ;
}

/*static*/
// Return true if this metric is requested or if the general default "catch all" metric is requested
bool LLMetricPerformanceTesterBasic::isMetricLogRequested(std::string name)
{
    return (LLTrace::BlockTimer::sMetricLog && ((LLTrace::BlockTimer::sLogName == name) || (LLTrace::BlockTimer::sLogName == DEFAULT_METRIC_NAME)));
}

/*static*/
LLSD LLMetricPerformanceTesterBasic::analyzeMetricPerformanceLog(std::istream& is)
{
    LLSD ret;
    LLSD cur;

    while (!is.eof() && LLSDParser::PARSE_FAILURE != LLSDSerialize::fromXML(cur, is))
    {
        for (LLSD::map_iterator iter = cur.beginMap(); iter != cur.endMap(); ++iter)
        {
            std::string label = iter->first;

            LLMetricPerformanceTesterBasic* tester = LLMetricPerformanceTesterBasic::getTester(iter->second["Name"].asString()) ;
            if(tester)
            {
                ret[label]["Name"] = iter->second["Name"] ;

                auto num_of_metrics = tester->getNumberOfMetrics() ;
                for(size_t index = 0 ; index < num_of_metrics ; index++)
                {
                    ret[label][ tester->getMetricName(index) ] = iter->second[ tester->getMetricName(index) ] ;
                }
            }
        }
    }

    return ret;
}

/*static*/
void LLMetricPerformanceTesterBasic::doAnalysisMetrics(std::string baseline, std::string target, std::string output)
{
    if(!LLMetricPerformanceTesterBasic::hasMetricPerformanceTesters())
    {
        return ;
    }

    // Open baseline and current target, exit if one is inexistent
    llifstream base_is(baseline.c_str());
    llifstream target_is(target.c_str());
    if (!base_is.is_open() || !target_is.is_open())
    {
        LL_WARNS() << "'-analyzeperformance' error : baseline or current target file inexistent" << LL_ENDL;
        base_is.close();
        target_is.close();
        return;
    }

    //analyze baseline
    LLSD base = analyzeMetricPerformanceLog(base_is);
    base_is.close();

    //analyze current
    LLSD current = analyzeMetricPerformanceLog(target_is);
    target_is.close();

    //output comparision
    llofstream os(output.c_str());

    os << "Label, Metric, Base(B), Target(T), Diff(T-B), Percentage(100*T/B)\n";
    for (LLMetricPerformanceTesterBasic::name_tester_map_t::value_type& pair : LLMetricPerformanceTesterBasic::sTesterMap)
    {
        LLMetricPerformanceTesterBasic* tester = ((LLMetricPerformanceTesterBasic*)pair.second);
        tester->analyzePerformance(&os, &base, &current) ;
    }

    os.flush();
    os.close();
}


//----------------------------------------------------------------------------------------------
// LLMetricPerformanceTesterBasic : Tester instance methods
//----------------------------------------------------------------------------------------------

LLMetricPerformanceTesterBasic::LLMetricPerformanceTesterBasic(std::string name) :
    mName(name),
    mCount(0)
{
    if (mName == std::string())
    {
        LL_ERRS() << "LLMetricPerformanceTesterBasic construction invalid : Empty name passed to constructor" << LL_ENDL ;
    }

    mValidInstance = LLMetricPerformanceTesterBasic::addTester(this) ;
}

LLMetricPerformanceTesterBasic::~LLMetricPerformanceTesterBasic()
{
}

void LLMetricPerformanceTesterBasic::preOutputTestResults(LLSD* sd)
{
    incrementCurrentCount() ;
}

void LLMetricPerformanceTesterBasic::postOutputTestResults(LLSD* sd)
{
    LLTrace::BlockTimer::pushLog(*sd);
}

void LLMetricPerformanceTesterBasic::outputTestResults()
{
    LLSD sd;

    preOutputTestResults(&sd) ;
    outputTestRecord(&sd) ;
    postOutputTestResults(&sd) ;
}

void LLMetricPerformanceTesterBasic::addMetric(std::string str)
{
    mMetricStrings.push_back(str) ;
}

/*virtual*/
void LLMetricPerformanceTesterBasic::analyzePerformance(llofstream* os, LLSD* base, LLSD* current)
{
    resetCurrentCount() ;

    std::string current_label = getCurrentLabelName();
    bool in_base = (*base).has(current_label) ;
    bool in_current = (*current).has(current_label) ;

    while(in_base || in_current)
    {
        LLSD::String label = current_label ;

        if(in_base && in_current)
        {
            *os << llformat("%s\n", label.c_str()) ;

            for(U32 index = 0 ; index < mMetricStrings.size() ; index++)
            {
                switch((*current)[label][ mMetricStrings[index] ].type())
                {
                case LLSD::TypeInteger:
                    compareTestResults(os, mMetricStrings[index],
                        (S32)((*base)[label][ mMetricStrings[index] ].asInteger()), (S32)((*current)[label][ mMetricStrings[index] ].asInteger())) ;
                    break ;
                case LLSD::TypeReal:
                    compareTestResults(os, mMetricStrings[index],
                        (F32)((*base)[label][ mMetricStrings[index] ].asReal()), (F32)((*current)[label][ mMetricStrings[index] ].asReal())) ;
                    break;
                default:
                    LL_ERRS() << "unsupported metric " << mMetricStrings[index] << " LLSD type: " << (S32)(*current)[label][ mMetricStrings[index] ].type() << LL_ENDL ;
                }
            }
        }

        incrementCurrentCount();
        current_label = getCurrentLabelName();
        in_base = (*base).has(current_label) ;
        in_current = (*current).has(current_label) ;
    }
}

/*virtual*/
void LLMetricPerformanceTesterBasic::compareTestResults(llofstream* os, std::string metric_string, S32 v_base, S32 v_current)
{
    *os << llformat(" ,%s, %d, %d, %d, %.4f\n", metric_string.c_str(), v_base, v_current,
                        v_current - v_base, (v_base != 0) ? 100.f * v_current / v_base : 0) ;
}

/*virtual*/
void LLMetricPerformanceTesterBasic::compareTestResults(llofstream* os, std::string metric_string, F32 v_base, F32 v_current)
{
    *os << llformat(" ,%s, %.4f, %.4f, %.4f, %.4f\n", metric_string.c_str(), v_base, v_current,
                        v_current - v_base, (fabs(v_base) > 0.0001f) ? 100.f * v_current / v_base : 0.f ) ;
}

//----------------------------------------------------------------------------------------------
// LLMetricPerformanceTesterWithSession
//----------------------------------------------------------------------------------------------

LLMetricPerformanceTesterWithSession::LLMetricPerformanceTesterWithSession(std::string name) :
    LLMetricPerformanceTesterBasic(name),
    mBaseSessionp(NULL),
    mCurrentSessionp(NULL)
{
}

LLMetricPerformanceTesterWithSession::~LLMetricPerformanceTesterWithSession()
{
    if (mBaseSessionp)
    {
        delete mBaseSessionp ;
        mBaseSessionp = NULL ;
    }
    if (mCurrentSessionp)
    {
        delete mCurrentSessionp ;
        mCurrentSessionp = NULL ;
    }
}

/*virtual*/
void LLMetricPerformanceTesterWithSession::analyzePerformance(llofstream* os, LLSD* base, LLSD* current)
{
    // Load the base session
    resetCurrentCount() ;
    mBaseSessionp = loadTestSession(base) ;

    // Load the current session
    resetCurrentCount() ;
    mCurrentSessionp = loadTestSession(current) ;

    if (!mBaseSessionp || !mCurrentSessionp)
    {
        LL_ERRS() << "Error loading test sessions." << LL_ENDL ;
    }

    // Compare
    compareTestSessions(os) ;

    // Release memory
    if (mBaseSessionp)
    {
        delete mBaseSessionp ;
        mBaseSessionp = NULL ;
    }
    if (mCurrentSessionp)
    {
        delete mCurrentSessionp ;
        mCurrentSessionp = NULL ;
    }
}


//----------------------------------------------------------------------------------------------
// LLTestSession
//----------------------------------------------------------------------------------------------

LLMetricPerformanceTesterWithSession::LLTestSession::~LLTestSession()
{
}