/*
    This file is part of KCachegrind.

    SPDX-FileCopyrightText: 2002-2016 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>

    SPDX-License-Identifier: GPL-2.0-only
*/

/*
 * Global configuration for KCachegrind
 */

#include "globalconfig.h"

#include <QtDebug>

#include "config.h"
#include "tracedata.h"


// GlobalConfig defaults
#define DEFAULT_SHOWPERCENTAGE   true
#define DEFAULT_SHOWEXPANDED     false
#define DEFAULT_SHOWCYCLES       true
#define DEFAULT_HIDETEMPLATES    false
#define DEFAULT_CYCLECUT         0.0
#define DEFAULT_PERCENTPRECISION 2
#define DEFAULT_MAXSYMBOLLENGTH  30
#define DEFAULT_MAXSYMBOLCOUNT   10
#define DEFAULT_MAXLISTCOUNT     100
#define DEFAULT_CONTEXT          3
#define DEFAULT_NOCOSTINSIDE     20


//
// GlobalConfig
//

// Some predefined event types...
QStringList GlobalConfig::knownTypes()
{
    QStringList l;

    l << QStringLiteral("Ir")   << QStringLiteral("Dr")   << QStringLiteral("Dw")
      << QStringLiteral("I1mr") << QStringLiteral("D1mr") << QStringLiteral("D1mw") << QStringLiteral("L1m");
    // Valgrind < 3.6.0
    l << QStringLiteral("I2mr") << QStringLiteral("D2mr") << QStringLiteral("D2mw") << QStringLiteral("L2m");
    // Valgrind 3.6.0: L2 events changed to LL (last level) events
    l << QStringLiteral("ILmr") << QStringLiteral("DLmr") << QStringLiteral("DLmw") << QStringLiteral("LLm");

    // branch simulation
    l << QStringLiteral("Bi") << QStringLiteral("Bim") << QStringLiteral("Bc") << QStringLiteral("Bcm") << QStringLiteral("Bm");

    // global bus events (e.g. CAS)
    l << QStringLiteral("Ge");

    l << QStringLiteral("Smp")  << QStringLiteral("Sys")  << QStringLiteral("User") << QStringLiteral("CEst");

    return l;
}


QString GlobalConfig::knownFormula(const QString& name)
{
    if (name == QLatin1String("L1m")) return QStringLiteral("I1mr + D1mr + D1mw");
    if (name == QLatin1String("L2m")) return QStringLiteral("I2mr + D2mr + D2mw");
    if (name == QLatin1String("LLm")) return QStringLiteral("ILmr + DLmr + DLmw");
    if (name == QLatin1String("Bm"))  return QStringLiteral("Bim + Bcm");
    if (name == QLatin1String("CEst"))
        return QStringLiteral("Ir + 10 Bm + 10 L1m + 20 Ge + 100 L2m + 100 LLm");

    return QString();
}

QString GlobalConfig::knownLongName(const QString& name)
{
    if (name == QLatin1String("Ir")) return QObject::tr("Instruction Fetch");
    if (name == QLatin1String("Dr")) return QObject::tr("Data Read Access");
    if (name == QLatin1String("Dw")) return QObject::tr("Data Write Access");
    if (name == QLatin1String("I1mr")) return QObject::tr("L1 Instr. Fetch Miss");
    if (name == QLatin1String("D1mr")) return QObject::tr("L1 Data Read Miss");
    if (name == QLatin1String("D1mw")) return QObject::tr("L1 Data Write Miss");
    if (name == QLatin1String("I2mr")) return QObject::tr("L2 Instr. Fetch Miss");
    if (name == QLatin1String("D2mr")) return QObject::tr("L2 Data Read Miss");
    if (name == QLatin1String("D2mw")) return QObject::tr("L2 Data Write Miss");
    if (name == QLatin1String("ILmr")) return QObject::tr("LL Instr. Fetch Miss");
    if (name == QLatin1String("DLmr")) return QObject::tr("LL Data Read Miss");
    if (name == QLatin1String("DLmw")) return QObject::tr("LL Data Write Miss");
    if (name == QLatin1String("L1m")) return QObject::tr("L1 Miss Sum");
    if (name == QLatin1String("L2m")) return QObject::tr("L2 Miss Sum");
    if (name == QLatin1String("LLm")) return QObject::tr("Last-level Miss Sum");
    if (name == QLatin1String("Bi"))  return QObject::tr("Indirect Branch");
    if (name == QLatin1String("Bim")) return QObject::tr("Mispredicted Ind. Branch");
    if (name == QLatin1String("Bc"))  return QObject::tr("Conditional Branch");
    if (name == QLatin1String("Bcm")) return QObject::tr("Mispredicted Cond. Branch");
    if (name == QLatin1String("Bm"))  return QObject::tr("Mispredicted Branch");
    if (name == QLatin1String("Ge"))  return QObject::tr("Global Bus Event");
    if (name == QLatin1String("Smp")) return QObject::tr("Samples");
    if (name == QLatin1String("Sys")) return QObject::tr("System Time");
    if (name == QLatin1String("User")) return QObject::tr("User Time");
    if (name == QLatin1String("CEst")) return QObject::tr("Cycle Estimation");

    return QString();
}


GlobalConfig* GlobalConfig::_config = nullptr;

GlobalConfig::GlobalConfig()
{
    _config = nullptr;

    // general presentation options
    _showPercentage   = DEFAULT_SHOWPERCENTAGE;
    _showExpanded     = DEFAULT_SHOWEXPANDED;
    _showCycles       = DEFAULT_SHOWCYCLES;
    _cycleCut         = DEFAULT_CYCLECUT;
    _percentPrecision = DEFAULT_PERCENTPRECISION;
    _hideTemplates    = DEFAULT_HIDETEMPLATES;

    // max symbol count/length in tooltip/popup
    _maxSymbolLength  = DEFAULT_MAXSYMBOLLENGTH;
    _maxSymbolCount   = DEFAULT_MAXSYMBOLCOUNT;
    _maxListCount     = DEFAULT_MAXLISTCOUNT;

    // annotation behaviour
    _context          = DEFAULT_CONTEXT;
    _noCostInside     = DEFAULT_NOCOSTINSIDE;
}

GlobalConfig::~GlobalConfig()
{
}

GlobalConfig* GlobalConfig::config()
{
    if (_config == nullptr)
        _config = new GlobalConfig();

    return _config;
}


void GlobalConfig::saveOptions()
{
    // source options
    ConfigGroup* sourceConfig = ConfigStorage::group(QStringLiteral("Source"));
    sourceConfig->setValue(QStringLiteral("Dirs"), _generalSourceDirs);
    QHashIterator<QString,QStringList> it( _objectSourceDirs );
    int count = 1;
    while( it.hasNext() ) {
        it.next();
        sourceConfig->setValue( QStringLiteral("Object%1").arg(count),
                                it.key() );
        sourceConfig->setValue( QStringLiteral("Dirs%1").arg(count),
                                it.value() );
        count++;
    }
    sourceConfig->setValue(QStringLiteral("Count"), count-1);
    delete sourceConfig;

    // general options
    ConfigGroup* generalConfig = ConfigStorage::group(QStringLiteral("GeneralSettings"));
    generalConfig->setValue(QStringLiteral("ShowPercentage"), _showPercentage,
                            DEFAULT_SHOWPERCENTAGE);
    generalConfig->setValue(QStringLiteral("ShowExpanded"), _showExpanded,
                            DEFAULT_SHOWEXPANDED);
    generalConfig->setValue(QStringLiteral("ShowCycles"), _showCycles,
                            DEFAULT_SHOWCYCLES);
    generalConfig->setValue(QStringLiteral("CycleCut"), _cycleCut,
                            DEFAULT_CYCLECUT);
    generalConfig->setValue(QStringLiteral("PercentPrecision"), _percentPrecision,
                            DEFAULT_PERCENTPRECISION);
    generalConfig->setValue(QStringLiteral("MaxSymbolLength"), _maxSymbolLength,
                            DEFAULT_MAXSYMBOLLENGTH);
    generalConfig->setValue(QStringLiteral("MaxSymbolCount"), _maxSymbolCount,
                            DEFAULT_MAXSYMBOLCOUNT);
    generalConfig->setValue(QStringLiteral("MaxListCount"), _maxListCount,
                            DEFAULT_MAXLISTCOUNT);
    generalConfig->setValue(QStringLiteral("Context"), _context,
                            DEFAULT_CONTEXT);
    generalConfig->setValue(QStringLiteral("NoCostInside"), _noCostInside,
                            DEFAULT_NOCOSTINSIDE);
    generalConfig->setValue(QStringLiteral("HideTemplates"), _hideTemplates,
                            DEFAULT_HIDETEMPLATES);
    delete generalConfig;

    // store known event types
    ConfigGroup* etConfig = ConfigStorage::group(QStringLiteral("EventTypes"));
    int etCount = EventType::knownTypeCount();
    int j = 0; // counter for config keys
    for (int i = 0; i<etCount; i++) {
        EventType* t = EventType::knownType(i);
        // do not store derived event types with empty formula
        // (these can exist when new type gets added with context menu)
        if (!t->isReal() && t->formula().isEmpty()) continue;

        etConfig->setValue( QStringLiteral("Name%1").arg(j+1), t->name());
        etConfig->setValue( QStringLiteral("Longname%1").arg(j+1),
                            t->longName(),
                            knownLongName(t->name()) );
        etConfig->setValue( QStringLiteral("Formula%1").arg(j+1),
                            t->formula(), knownFormula(t->name()) );
        j++;
    }
    etConfig->setValue( QStringLiteral("Count"), j);
    delete etConfig;
}

void GlobalConfig::readOptions()
{
    int i, count;

    // source options
    ConfigGroup* sourceConfig = ConfigStorage::group(QStringLiteral("Source"));
    QStringList dirs;
    dirs = sourceConfig->value(QStringLiteral("Dirs"), QStringList()).toStringList();
    if (dirs.count()>0) _generalSourceDirs = dirs;
    count = sourceConfig->value(QStringLiteral("Count"), 0).toInt();
    _objectSourceDirs.clear();
    for(i=1; i<=count; ++i) {
        QString n = sourceConfig->value(QStringLiteral("Object%1").arg(i),
                                        QString()).toString();
        dirs = sourceConfig->value(QStringLiteral("Dirs%1").arg(i),
                                   QStringList()).toStringList();

        if (n.isEmpty() || (dirs.isEmpty())) continue;

        _objectSourceDirs.insert(n, dirs);
    }
    delete sourceConfig;

    // general options
    ConfigGroup* generalConfig = ConfigStorage::group(QStringLiteral("GeneralSettings"));
    _showPercentage   = generalConfig->value(QStringLiteral("ShowPercentage"),
                                             DEFAULT_SHOWPERCENTAGE).toBool();
    _showExpanded     = generalConfig->value(QStringLiteral("ShowExpanded"),
                                             DEFAULT_SHOWEXPANDED).toBool();
    _showCycles       = generalConfig->value(QStringLiteral("ShowCycles"),
                                             DEFAULT_SHOWCYCLES).toBool();
    _cycleCut         = generalConfig->value(QStringLiteral("CycleCut"),
                                             DEFAULT_CYCLECUT).toDouble();
    _percentPrecision = generalConfig->value(QStringLiteral("PercentPrecision"),
                                             DEFAULT_PERCENTPRECISION).toInt();
    _maxSymbolLength  = generalConfig->value(QStringLiteral("MaxSymbolLength"),
                                             DEFAULT_MAXSYMBOLLENGTH).toInt();
    _maxSymbolCount   = generalConfig->value(QStringLiteral("MaxSymbolCount"),
                                             DEFAULT_MAXSYMBOLCOUNT).toInt();
    _maxListCount     = generalConfig->value(QStringLiteral("MaxListCount"),
                                             DEFAULT_MAXLISTCOUNT).toInt();
    _context          = generalConfig->value(QStringLiteral("Context"),
                                             DEFAULT_CONTEXT).toInt();
    _noCostInside     = generalConfig->value(QStringLiteral("NoCostInside"),
                                             DEFAULT_NOCOSTINSIDE).toInt();
    _hideTemplates    = generalConfig->value(QStringLiteral("HideTemplates"),
                                             DEFAULT_HIDETEMPLATES).toBool();
    delete generalConfig;

    // event types
    if (EventType::knownTypeCount() >0) return; // already read
    ConfigGroup* etConfig = ConfigStorage::group(QStringLiteral("EventTypes"));
    int etCount = etConfig->value(QStringLiteral("Count"), 0).toInt();

    for (int i=1;i<=etCount;i++) {
        QString n = etConfig->value(QStringLiteral("Name%1").arg(i),
                                    QString()).toString();
        QString l = etConfig->value(QStringLiteral("Longname%1").arg(i),
                                    QString()).toString();
        if (l.isEmpty()) l = knownLongName(n);
        QString f = etConfig->value(QStringLiteral("Formula%1").arg(i),
                                    QString()).toString();
        if (f.isEmpty()) f = knownFormula(n);

        EventType::add(new EventType(n, l, f));
    }

    // this does only add yet non-existing types
    addDefaultTypes();

    delete etConfig;
}

void GlobalConfig::addDefaultTypes()
{
    QString longName, formula;
    EventType* ct;
    QStringList l = knownTypes();
    for ( QStringList::const_iterator it = l.constBegin();
          it != l.constEnd(); ++it ) {
        longName = knownLongName(*it);
        formula  = knownFormula(*it);
        ct = new EventType(*it, longName, formula);
        EventType::add(ct, false);
    }
}

/* Gives back a list of all Source Base Directories of Objects in
 * current trace. If a special object is given in 2nd argument,
 * put its Source Base in front.
 */
QStringList GlobalConfig::sourceDirs(TraceData* data, TraceObject* o)
{
    QStringList l = config()->_generalSourceDirs, ol, ol2;
    TraceObjectMap::Iterator oit;
    for ( oit = data->objectMap().begin();
          oit != data->objectMap().end(); ++oit ) {
        ol = config()->_objectSourceDirs[(*oit).name()];
        if (&(*oit) == o) {
            ol2 = ol;
            continue;
        }

        for(int i=0;i<ol.count();i++)
            l.prepend( ol[i] );
    }
    for(int i=0;i<ol2.count();i++)
        l.prepend( ol2[i] );

    if (0) qDebug() << "GlobalConfig::sourceDirs: " << l.join(QLatin1Char(':'));

    return l;
}

bool GlobalConfig::showPercentage()
{
    return config()->_showPercentage;
}

bool GlobalConfig::showExpanded()
{
    return config()->_showExpanded;
}

bool GlobalConfig::showCycles()
{
    return config()->_showCycles;
}

bool GlobalConfig::hideTemplates()
{
    return config()->_hideTemplates;
}

void GlobalConfig::setShowPercentage(bool s)
{
    GlobalConfig* c = config();
    if (c->_showPercentage == s) return;

    c->_showPercentage = s;
}

void GlobalConfig::setShowExpanded(bool s)
{
    GlobalConfig* c = config();
    if (c->_showExpanded == s) return;

    c->_showExpanded = s;
}

void GlobalConfig::setShowCycles(bool s)
{
    GlobalConfig* c = config();
    if (c->_showCycles == s) return;

    c->_showCycles = s;
}

void GlobalConfig::setHideTemplates(bool s)
{
    GlobalConfig* c = config();
    if (c->_hideTemplates == s) return;

    c->_hideTemplates = s;
}

double GlobalConfig::cycleCut()
{
    return config()->_cycleCut;
}

int GlobalConfig::percentPrecision()
{
    return config()->_percentPrecision;
}

int GlobalConfig::maxSymbolLength()
{
    return config()->_maxSymbolLength;
}

QString GlobalConfig::shortenSymbol(const QString& s)
{
    if(s.length() > config()->_maxSymbolLength)
        return s.left(config()->_maxSymbolLength) + QStringLiteral("...");
    return s;
}

int GlobalConfig::maxListCount()
{
    return config()->_maxListCount;
}

int GlobalConfig::maxSymbolCount()
{
    return config()->_maxSymbolCount;
}

int GlobalConfig::context()
{
    return config()->_context;
}

int GlobalConfig::noCostInside()
{
    return config()->_noCostInside;
}

void GlobalConfig::setPercentPrecision(int v)
{
    if ((v<1) || (v >5)) return;
    _percentPrecision = v;
}

void GlobalConfig::setMaxSymbolLength(int v)
{
    if ((v<1) || (v >1000)) return;
    _maxSymbolLength = v;
}

void GlobalConfig::setMaxSymbolCount(int v)
{
    if ((v<1) || (v >50)) return;
    _maxSymbolCount = v;
}

void GlobalConfig::setMaxListCount(int v)
{
    if ((v<1) || (v >500)) return;
    _maxListCount = v;
}

void GlobalConfig::setContext(int v)
{
    if ((v<1) || (v >500)) return;
    _context = v;
}

const QStringList& GlobalConfig::generalSourceDirs()
{
    return _generalSourceDirs;
}

QStringList GlobalConfig::objectSourceDirs(QString obj)
{
    if (_objectSourceDirs.contains(obj))
        return _objectSourceDirs[obj];
    else
        return QStringList();
}

void GlobalConfig::setGeneralSourceDirs(QStringList dirs)
{
    _generalSourceDirs = dirs;
}

void GlobalConfig::setObjectSourceDirs(QString obj, QStringList dirs)
{
    if (dirs.isEmpty())
        _objectSourceDirs.remove(obj);
    else
        _objectSourceDirs.insert(obj, dirs);
}

void GlobalConfig::clearObjectSourceDirs()
{
    _objectSourceDirs.clear();
}
