/*
    This file is part of KCachegrind.

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

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

#include "loader.h"

#include <QIODevice>
#include <QVector>
#include <QDebug>

#include "addr.h"
#include "tracedata.h"
#include "utils.h"
#include "fixcost.h"


#define TRACE_LOADER 0

/*
 * Loader for Callgrind Profile data (format based on Cachegrind format).
 * See Callgrind documentation for the file format.
 */

class CachegrindLoader: public Loader
{
public:
    CachegrindLoader();

    bool canLoad(QIODevice* file) override;
    int  load(TraceData*, QIODevice* file, const QString& filename) override;

private:
    void error(QString);
    void warning(QString);

    int loadInternal(TraceData*, QIODevice* file, const QString& filename);

    enum lineType { SelfCost, CallCost, BoringJump, CondJump };

    bool parsePosition(FixString& s, PositionSpec& newPos);

    // position setters
    void clearPosition();
    void ensureObject();
    void ensureFile();
    void ensureFunction();
    void setObject(const QString&);
    void setCalledObject(const QString&);
    void setFile(const QString&);
    void setCalledFile(const QString&);
    void setFunction(const QString&);
    void setCalledFunction(const QString&);

    void prepareNewPart();

    QString _emptyString;

    // current line in file to read in
    QString _filename;
    int _lineNo;

    EventTypeMapping* mapping;
    TraceData* _data;
    TracePart* _part;
    int partsAdded;

    // current position
    lineType nextLineType;
    bool hasLineInfo, hasAddrInfo;
    PositionSpec currentPos;

    // current function/line
    TraceObject* currentObject;
    TracePartObject* currentPartObject;
    TraceFile* currentFile;
    TraceFile* currentFunctionFile;
    TracePartFile* currentPartFile;
    TraceFunction* currentFunction;
    TracePartFunction* currentPartFunction;
    TraceFunctionSource* currentFunctionSource;
    TraceInstr* currentInstr;
    TracePartInstr* currentPartInstr;
    TraceLine* currentLine;
    TracePartLine* currentPartLine;

    // current call
    TraceObject* currentCalledObject;
    TracePartObject* currentCalledPartObject;
    TraceFile* currentCalledFile;
    TracePartFile* currentCalledPartFile;
    TraceFunction* currentCalledFunction;
    TracePartFunction* currentCalledPartFunction;
    SubCost currentCallCount;

    // current jump
    TraceFile* currentJumpToFile;
    TraceFunction* currentJumpToFunction;
    PositionSpec targetPos;
    SubCost jumpsFollowed, jumpsExecuted;

    /** Support for compressed string format
   * This uses the following string compression model
   * for objects, files, functions:
   * If the name matches
   *   "(<Integer>) Name": this is a compression specification,
   *                       mapping the integer number to Name and using Name.
   *   "(<Integer>)"     : this is a compression reference.
   *                       Assumes previous compression specification of the
   *                       integer number to a name, uses this name.
   *   "Name"            : Regular name
   */
    void clearCompression();
    const QString& checkUnknown(const QString& n);
    TraceObject* compressedObject(const QString& name);
    TraceFile* compressedFile(const QString& name);
    TraceFunction* compressedFunction(const QString& name,
                                      TraceFile*, TraceObject*);

    QVector<TraceCostItem*> _objectVector, _fileVector, _functionVector;
};



/**********************************************************
 * Loader
 */


CachegrindLoader::CachegrindLoader()
    : Loader(QStringLiteral("Callgrind"),
             QObject::tr( "Import filter for Cachegrind/Callgrind generated profile data files") )
{
}

bool CachegrindLoader::canLoad(QIODevice* file)
{
    if (!file) return false;

    Q_ASSERT(file->isOpen());

    /*
     * We recognize this as cachegrind/callgrind format if
     * - it starts with a line "# callgrind format", or
     * - if the first 2047 bytes contain either "\nevents:" or "\ncreator:"
     */
    char buf[2048];
    int read = file->read(buf,2047);
    if (read < 0)
        return false;
    buf[read] = 0;

    QByteArray s = QByteArray::fromRawData(buf, read+1);

    if (s.indexOf("# callgrind format\n") == 0)
        return true;

    int pos = s.indexOf("events:");
    if (pos>0 && buf[pos-1] != '\n') pos = -1;
    if (pos>=0) return true;

    // callgrind puts a "cmd:" line before "events:", and with big command
    // lines, we need another way to detect such callgrind files...
    pos = s.indexOf("creator:");
    if (pos>0 && buf[pos-1] != '\n') pos = -1;

    return (pos>=0);
}

int CachegrindLoader::load(TraceData* d,
                           QIODevice* file, const QString& filename)
{
    /* do the loading in a new object so parallel load
   * operations do not interfere each other.
   */
    CachegrindLoader l;

    l.setLogger(_logger);

    return l.loadInternal(d, file, filename);
}

Loader* createCachegrindLoader()
{
    return new CachegrindLoader();
}

void CachegrindLoader::error(QString msg)
{
    loadError(_lineNo, msg);
}

void CachegrindLoader::warning(QString msg)
{
    loadWarning(_lineNo, msg);
}

/**
 * Return false if this is no position specification
 */
bool CachegrindLoader::parsePosition(FixString& line,
                                     PositionSpec& newPos)
{
    char c;
    uint diff;

    if (hasAddrInfo) {

        if (!line.first(c)) return false;

        if (c == '*') {
            // nothing changed
            line.stripFirst(c);
            newPos.fromAddr = currentPos.fromAddr;
            newPos.toAddr = currentPos.toAddr;
        }
        else if (c == '+') {
            line.stripFirst(c);
            line.stripUInt(diff, false);
            newPos.fromAddr = currentPos.fromAddr + diff;
            newPos.toAddr = newPos.fromAddr;
        }
        else if (c == '-') {
            line.stripFirst(c);
            line.stripUInt(diff, false);
            newPos.fromAddr = currentPos.fromAddr - diff;
            newPos.toAddr = newPos.fromAddr;
        }
        else if (c >= '0') {
            uint64 v;
            line.stripUInt64(v, false);
            newPos.fromAddr = Addr(v);
            newPos.toAddr = newPos.fromAddr;
        }
        else return false;

        // Range specification
        if (line.first(c)) {
            if (c == '+') {
                line.stripFirst(c);
                line.stripUInt(diff);
                newPos.toAddr = newPos.fromAddr + diff;
            }
            else if ((c == '-') || (c == ':')) {
                line.stripFirst(c);
                uint64 v;
                line.stripUInt64(v);
                newPos.toAddr = Addr(v);
            }
        }
        line.stripSpaces();

#if TRACE_LOADER
        if (newPos.fromAddr == newPos.toAddr)
            qDebug() << " Got Addr " << newPos.fromAddr.toString();
        else
            qDebug() << " Got AddrRange " << newPos.fromAddr.toString()
                     << ":" << newPos.toAddr.toString();
#endif

    }

    if (hasLineInfo) {

        if (!line.first(c)) return false;

        if (c > '9') return false;
        else if (c == '*') {
            // nothing changed
            line.stripFirst(c);
            newPos.fromLine = currentPos.fromLine;
            newPos.toLine   = currentPos.toLine;
        }
        else if (c == '+') {
            line.stripFirst(c);
            line.stripUInt(diff, false);
            newPos.fromLine = currentPos.fromLine + diff;
            newPos.toLine = newPos.fromLine;
        }
        else if (c == '-') {
            line.stripFirst(c);
            line.stripUInt(diff, false);
            if (currentPos.fromLine < diff) {
                error(QStringLiteral("Negative line number %1")
                      .arg((int)currentPos.fromLine - (int)diff));
                diff = currentPos.fromLine;
            }
            newPos.fromLine = currentPos.fromLine - diff;
            newPos.toLine = newPos.fromLine;
        }
        else if (c >= '0') {
            line.stripUInt(newPos.fromLine, false);
            newPos.toLine = newPos.fromLine;
        }
        else return false;

        // Range specification
        if (line.first(c)) {
            if (c == '+') {
                line.stripFirst(c);
                line.stripUInt(diff);
                newPos.toLine = newPos.fromLine + diff;
            }
            else if ((c == '-') || (c == ':')) {
                line.stripFirst(c);
                line.stripUInt(newPos.toLine);
            }
        }
        line.stripSpaces();

#if TRACE_LOADER
        if (newPos.fromLine == newPos.toLine)
            qDebug() << " Got Line " << newPos.fromLine;
        else
            qDebug() << " Got LineRange " << newPos.fromLine
                     << ":" << newPos.toLine;
#endif

    }

    return true;
}

// Support for compressed strings
void CachegrindLoader::clearCompression()
{
    // this does not delete previous contained objects
    _objectVector.clear();
    _fileVector.clear();
    _functionVector.clear();

    // reset to reasonable init size. We double lengths if needed.
    _objectVector.resize(100);
    _fileVector.resize(1000);
    _functionVector.resize(10000);
}

const QString& CachegrindLoader::checkUnknown(const QString& n)
{
    if (n == QLatin1String("???")) return _emptyString;
    return n;
}

TraceObject* CachegrindLoader::compressedObject(const QString& name)
{
    if ((name[0] != '(') || !name[1].isDigit()) return _data->object(checkUnknown(name));

    // compressed format using _objectVector
    int p = name.indexOf(')');
    if (p<2) {
        error(QStringLiteral("Invalid compressed ELF object ('%1')").arg(name));
        return nullptr;
    }
    int index = name.mid(1, p-1).toInt();
    TraceObject* o = nullptr;
    p++;
    while((name.length()>p) && name.at(p).isSpace()) p++;
    if (name.length()>p) {
        if (_objectVector.size() <= index) {
            int newSize = index * 2;
#if TRACE_LOADER
            qDebug() << " CachegrindLoader: objectVector enlarged to "
                     << newSize;
#endif
            _objectVector.resize(newSize);
        }

        QString realName = checkUnknown(name.mid(p));
        o = (TraceObject*) _objectVector.at(index);
        if (o && (o->name() != realName)) {
            error(QStringLiteral("Redefinition of compressed ELF object index %1 (was '%2') to %3")
                  .arg(index).arg(o->name()).arg(realName));
        }

        o = _data->object(realName);
        _objectVector.replace(index, o);
    }
    else {
        if ((_objectVector.size() <= index) ||
            ( (o=(TraceObject*)_objectVector.at(index)) == nullptr)) {
            error(QStringLiteral("Undefined compressed ELF object index %1").arg(index));
            return nullptr;
        }
    }

    return o;
}


// Note: Callgrind sometimes gives different IDs for same file
// (when references to same source file come from different ELF objects)
TraceFile* CachegrindLoader::compressedFile(const QString& name)
{
    if ((name[0] != '(') || !name[1].isDigit()) return _data->file(checkUnknown(name));

    // compressed format using _fileVector
    int p = name.indexOf(')');
    if (p<2) {
        error(QStringLiteral("Invalid compressed file ('%1')").arg(name));
        return nullptr;
    }
    int index = name.mid(1, p-1).toUInt();
    TraceFile* f = nullptr;
    p++;
    while((name.length()>p) && name.at(p).isSpace()) p++;
    if (name.length()>p) {
        if (_fileVector.size() <= index) {
            int newSize = index * 2;
#if TRACE_LOADER
            qDebug() << " CachegrindLoader::fileVector enlarged to "
                     << newSize;
#endif
            _fileVector.resize(newSize);
        }

        QString realName = checkUnknown(name.mid(p));
        f = (TraceFile*) _fileVector.at(index);
        if (f && (f->name() != realName)) {
            error(QStringLiteral("Redefinition of compressed file index %1 (was '%2') to %3")
                  .arg(index).arg(f->name()).arg(realName));
        }

        f = _data->file(realName);
        _fileVector.replace(index, f);
    }
    else {
        if ((_fileVector.size() <= index) ||
            ( (f=(TraceFile*)_fileVector.at(index)) == nullptr)) {
            error(QStringLiteral("Undefined compressed file index %1").arg(index));
            return nullptr;
        }
    }

    return f;
}

// Note: Callgrind gives different IDs even for same function
// when parts of the function are from different source files.
// Thus, it is no error when multiple indexes map to same function.
TraceFunction* CachegrindLoader::compressedFunction(const QString& name,
                                                    TraceFile* file,
                                                    TraceObject* object)
{
    if (name.size() < 2 || (name[0] != '(') || !name[1].isDigit())
        return _data->function(checkUnknown(name), file, object);

    // compressed format using _functionVector
    int p = name.indexOf(')');
    if (p<2) {
        error(QStringLiteral("Invalid compressed function ('%1')").arg(name));
        return nullptr;
    }


    int index = name.mid(1, p-1).toUInt();
    TraceFunction* f = nullptr;
    p++;
    while((name.length()>p) && name.at(p).isSpace()) p++;
    if (name.length()>p) {
        if (_functionVector.size() <= index) {
            int newSize = index * 2;
#if TRACE_LOADER
            qDebug() << " CachegrindLoader::functionVector enlarged to "
                     << newSize;
#endif
            _functionVector.resize(newSize);
        }

        QString realName = checkUnknown(name.mid(p));
        f = (TraceFunction*) _functionVector.at(index);
        if (f && (f->name() != realName)) {
            error(QStringLiteral("Redefinition of compressed function index %1 (was '%2') to %3")
                  .arg(index).arg(f->name()).arg(realName));
        }

        f = _data->function(realName, file, object);
        _functionVector.replace(index, f);

#if TRACE_LOADER
        qDebug() << "compressedFunction: Inserted at Index " << index
                 << "\n  " << f->fullName()
                 << "\n  in " << f->cls()->fullName()
                 << "\n  in " << f->file()->fullName()
                 << "\n  in " << f->object()->fullName();
#endif
    }
    else {
        if ((_functionVector.size() <= index) ||
            ( (f=(TraceFunction*)_functionVector.at(index)) == nullptr)) {
            error(QStringLiteral("Undefined compressed function index %1").arg(index));
            return nullptr;
        }

        // there was a check if the used function (returned from KCachegrinds
        // model) has the same object and file as here given to us, but that was wrong:
        // that holds only if we make this assumption on the model...
    }


    return f;
}


// make sure that a valid object is set, at least dummy with empty name
void CachegrindLoader::ensureObject()
{
    if (currentObject) return;

    currentObject = _data->object(_emptyString);
    currentPartObject = currentObject->partObject(_part);
}

void CachegrindLoader::setObject(const QString& name)
{
    currentObject = compressedObject(name);
    if (!currentObject) {
        error(QStringLiteral("Invalid ELF object specification, setting to unknown"));

        currentObject = _data->object(_emptyString);
    }

    currentPartObject = currentObject->partObject(_part);
    currentFunction = nullptr;
    currentPartFunction = nullptr;
}

void CachegrindLoader::setCalledObject(const QString& name)
{
    currentCalledObject = compressedObject(name);

    if (!currentCalledObject) {
        error(QStringLiteral("Invalid specification of called ELF object, setting to unknown"));

        currentCalledObject = _data->object(_emptyString);
    }

    currentCalledPartObject = currentCalledObject->partObject(_part);
}


// make sure that a valid file is set, at least dummy with empty name
void CachegrindLoader::ensureFile()
{
    if (currentFile) return;

    currentFile = _data->file(_emptyString);
    currentPartFile = currentFile->partFile(_part);
}

void CachegrindLoader::setFile(const QString& name)
{
    currentFile = compressedFile(name);

    if (!currentFile) {
        error(QStringLiteral("Invalid file specification, setting to unknown"));

        currentFile = _data->file(_emptyString);
    }

    currentPartFile = currentFile->partFile(_part);
    currentLine = nullptr;
    currentPartLine = nullptr;
}

void CachegrindLoader::setCalledFile(const QString& name)
{
    currentCalledFile = compressedFile(name);

    if (!currentCalledFile) {
        error(QStringLiteral("Invalid specification of called file, setting to unknown"));

        currentCalledFile = _data->file(_emptyString);
    }

    currentCalledPartFile = currentCalledFile->partFile(_part);
}

// make sure that a valid function is set, at least dummy with empty name
void CachegrindLoader::ensureFunction()
{
    if (currentFunction) return;

    error(QStringLiteral("Function not specified, setting to unknown"));

    ensureFile();
    ensureObject();

    currentFunction = _data->function(_emptyString,
                                      currentFile,
                                      currentObject);
    currentPartFunction = currentFunction->partFunction(_part,
                                                        currentPartFile,
                                                        currentPartObject);
}

void CachegrindLoader::setFunction(const QString& name)
{
    ensureFile();
    ensureObject();

    currentFunction = compressedFunction( name,
                                          currentFile,
                                          currentObject);

    if (!currentFunction) {
        error(QStringLiteral("Invalid function specification, setting to unknown"));

        currentFunction = _data->function(_emptyString,
                                          currentFile,
                                          currentObject);
    }

    currentPartFunction = currentFunction->partFunction(_part,
                                                        currentPartFile,
                                                        currentPartObject);

    currentFunctionSource = nullptr;
    currentLine = nullptr;
    currentPartLine = nullptr;
}

void CachegrindLoader::setCalledFunction(const QString& name)
{
    // if called object/file not set, use current object/file
    if (!currentCalledObject) {
        currentCalledObject = currentObject;
        currentCalledPartObject = currentPartObject;
    }

    if (!currentCalledFile) {
        // !=0 as functions needs file
        currentCalledFile = currentFile;
        currentCalledPartFile = currentPartFile;
    }

    currentCalledFunction = compressedFunction(name,
                                               currentCalledFile,
                                               currentCalledObject);
    if (!currentCalledFunction) {
        error(QStringLiteral("Invalid called function, setting to unknown"));

        currentCalledFunction = _data->function(_emptyString,
                                                currentCalledFile,
                                                currentCalledObject);
    }

    currentCalledPartFunction =
            currentCalledFunction->partFunction(_part,
                                                currentCalledPartFile,
                                                currentCalledPartObject);
}


void CachegrindLoader::clearPosition()
{
    currentPos = PositionSpec();

    // current function/line
    currentFunction = nullptr;
    currentPartFunction = nullptr;
    currentFunctionSource = nullptr;
    currentFile = nullptr;
    currentFunctionFile = nullptr;
    currentPartFile = nullptr;
    currentObject = nullptr;
    currentPartObject = nullptr;
    currentLine = nullptr;
    currentPartLine = nullptr;
    currentInstr = nullptr;
    currentPartInstr = nullptr;

    // current call
    currentCalledObject = nullptr;
    currentCalledPartObject = nullptr;
    currentCalledFile = nullptr;
    currentCalledPartFile = nullptr;
    currentCalledFunction = nullptr;
    currentCalledPartFunction = nullptr;
    currentCallCount = 0;

    // current jump
    currentJumpToFile = nullptr;
    currentJumpToFunction = nullptr;
    targetPos = PositionSpec();
    jumpsFollowed = 0;
    jumpsExecuted = 0;

    mapping = nullptr;
}

void CachegrindLoader::prepareNewPart()
{
    if (_part) {
        // really new part needed?
        if (mapping == nullptr) return;

        // yes
        _part->invalidate();
        _part->totals()->clear();
        _part->totals()->addCost(_part);
        _data->addPart(_part);
        partsAdded++;
    }

    clearCompression();
    clearPosition();

    _part = new TracePart(_data);
    _part->setName(_filename);
}

/**
 * The main import function...
 */
int CachegrindLoader::loadInternal(TraceData* data,
                                   QIODevice* device, const QString& filename)
{
    if (!data || !device) return 0;

    _data = data;
    _filename = filename;
    _lineNo = 0;

    loadStart(_filename);

    FixFile file(device, _filename);
    if (!file.exists()) {
        loadFinished(QStringLiteral("File does not exist"));
        return 0;
    }

    int statusProgress = 0;

#if USE_FIXCOST
    // FixCost Memory Pool
    FixPool* pool = _data->fixPool();
#endif

    _part = nullptr;
    partsAdded = 0;
    prepareNewPart();

    FixString line;
    char c;

    // current position
    nextLineType  = SelfCost;
    // default if there is no "positions:" line
    hasLineInfo = true;
    hasAddrInfo = false;

    while (file.nextLine(line)) {

        _lineNo++;

#if TRACE_LOADER
        qDebug() << "[CachegrindLoader] " << _filename << ":" << _lineNo
                 << " - '" << QString(line) << "'";
#endif

        // if we cannot strip a character, this was an empty line
        if (!line.first(c)) continue;

        if (c <= '9') {

            if (c == '#') continue;

            // parse position(s)
            if (!parsePosition(line, currentPos)) {
                error(QStringLiteral("Invalid position specification '%1'").arg(line));
                continue;
            }

            // go through after big switch
        }
        else { // if (c > '9')

            line.stripFirst(c);

            /* in order of probability */
            switch(c) {

            case 'f':

                // fl=
                if (line.stripPrefix("l=")) {

                    setFile(line);
                    // this is the default for new functions
                    currentFunctionFile = currentFile;
                    continue;
                }

                // fi=, fe=
                if (line.stripPrefix("i=") ||
                    line.stripPrefix("e=")) {

                    setFile(line);
                    continue;
                }

                // fn=
                if (line.stripPrefix("n=")) {

                    if (currentFile != currentFunctionFile)
                        currentFile = currentFunctionFile;
                    setFunction(line);

                    // on a new function, update status
                    int progress = (int)(100.0 * file.current() / file.len() +.5);
                    if (progress != statusProgress) {
                        statusProgress = progress;

                        /* When this signal is connected, it most probably
         * should lead to GUI update. Thus, when multiple
         * "long operations" (like file loading) are in progress,
         * this can temporarily switch to another operation.
         */
                        loadProgress(statusProgress);
                    }

                    continue;
                }

                break;

            case 'c':
                // cob=
                if (line.stripPrefix("ob=")) {
                    setCalledObject(line);
                    continue;
                }

                // cfi= / cfl=
                if (line.stripPrefix("fl=") ||
                    line.stripPrefix("fi=")) {
                    setCalledFile(line);
                    continue;
                }

                // cfn=
                if (line.stripPrefix("fn=")) {

                    setCalledFunction(line);
                    continue;
                }

                // calls=
                if (line.stripPrefix("alls=")) {
                    // ignore long lines...
                    line.stripUInt64(currentCallCount);
                    nextLineType = CallCost;
                    continue;
                }

                // cmd:
                if (line.stripPrefix("md:")) {
                    QString command = QString(line).trimmed();
                    if (!_data->command().isEmpty() &&
                        _data->command() != command) {

                        error(QStringLiteral("Redefined command, was '%1'").arg(_data->command()));
                    }
                    _data->setCommand(command);
                    continue;
                }

                // creator:
                if (line.stripPrefix("reator:")) {
                    // ignore ...
                    continue;
                }

                break;

            case 'j':

                // jcnd=
                if (line.stripPrefix("cnd=")) {
                    bool valid;

                    valid = line.stripUInt64(jumpsFollowed) &&
                            line.stripPrefix("/") &&
                            line.stripUInt64(jumpsExecuted) &&
                            parsePosition(line, targetPos);

                    if (!valid) {
                        error(QStringLiteral("Invalid line after 'jcnd'"));
                    }
                    else
                        nextLineType = CondJump;
                    continue;
                }

                if (line.stripPrefix("ump=")) {
                    bool valid;

                    valid = line.stripUInt64(jumpsExecuted) &&
                            parsePosition(line, targetPos);

                    if (!valid) {
                        error(QStringLiteral("Invalid line after 'jump'"));
                    }
                    else
                        nextLineType = BoringJump;
                    continue;
                }

                // jfi=
                if (line.stripPrefix("fi=")) {
                    currentJumpToFile = compressedFile(line);
                    continue;
                }

                // jfn=
                if (line.stripPrefix("fn=")) {

                    if (!currentJumpToFile) {
                        // !=0 as functions needs file
                        currentJumpToFile = currentFile;
                    }

                    currentJumpToFunction =
                            compressedFunction(line,
                                               currentJumpToFile,
                                               currentObject);
                    continue;
                }

                break;

            case 'o':

                // ob=
                if (line.stripPrefix("b=")) {
                    setObject(line);
                    continue;
                }

                break;

            case '#':
                continue;

            case 'a':
                // "arch: arm"
                if (line.stripPrefix("rch: arm")) {
                    TraceData::Arch a = _data->architecture();
                    if ((a != TraceData::ArchUnknown) &&
                        (a != TraceData::ArchARM)) {
                        error(QStringLiteral("Redefined architecture!"));
                    }
                    _data->setArchitecture(TraceData::ArchARM);
                    continue;
                }
                break;

            case 't':

                // totals:
                if (line.stripPrefix("otals:")) continue;

                // thread:
                if (line.stripPrefix("hread:")) {
                    prepareNewPart();
                    _part->setThreadID(QString(line).toInt());
                    continue;
                }

                // timeframe (BB):
                if (line.stripPrefix("imeframe (BB):")) {
                    _part->setTimeframe(line);
                    continue;
                }

                break;

            case 'd':

                // desc:
                if (line.stripPrefix("esc:")) {

                    line.stripSurroundingSpaces();

                    // desc: Trigger:
                    if (line.stripPrefix("Trigger:")) {
                        _part->setTrigger(line);
                    }

                    continue;
                }
                break;

            case 'e':

                // events:
                if (line.stripPrefix("vents:")) {
                    prepareNewPart();
                    mapping = _data->eventTypes()->createMapping(line);
                    _part->setEventMapping(mapping);
                    continue;
                }

                // event:<name>[=<formula>][:<long name>]
                if (line.stripPrefix("vent:")) {
                    line.stripSurroundingSpaces();

                    FixString e, f, l;
                    if (!line.stripName(e)) {
                        error(QStringLiteral("Invalid event"));
                        continue;
                    }
                    line.stripSpaces();
                    if (!line.stripFirst(c)) continue;

                    if (c=='=') f = line.stripUntil(':');
                    line.stripSpaces();

                    // add to known cost types
                    if (line.isEmpty()) line = e;
                    EventType::add(new EventType(e,line,f));
                    continue;
                }
                break;

            case 'p':

                // part:
                if (line.stripPrefix("art:")) {
                    prepareNewPart();
                    _part->setPartNumber(QString(line).toInt());
                    continue;
                }

                // pid:
                if (line.stripPrefix("id:")) {
                    prepareNewPart();
                    _part->setProcessID(QString(line).toInt());
                    continue;
                }

                // positions:
                if (line.stripPrefix("ositions:")) {
                    prepareNewPart();
                    QString positions(line);
                    hasLineInfo = positions.contains(QLatin1String("line"));
                    hasAddrInfo = positions.contains(QLatin1String("instr"));
                    continue;
                }
                break;

            case 'v':

                // version:
                if (line.stripPrefix("ersion:")) {
                    // ignore for now
                    continue;
                }
                break;

            case 's':

                // summary:
                if (line.stripPrefix("ummary:")) {
                    if (!mapping) {
                        error(QStringLiteral("Invalid format: summary before data. Skipping file"));
                        delete _part;
                        return false;
                    }

                    _part->totals()->set(mapping, line);
                    continue;
                }
                break;

            case 'r':

                // rcalls= (deprecated)
                if (line.stripPrefix("calls=")) {
                    // handle like normal calls: we need the sum of call count
                    // recursive cost is discarded in cycle detection
                    line.stripUInt64(currentCallCount);
                    nextLineType = CallCost;

                    warning(QStringLiteral("Old file format using deprecated 'rcalls'"));
                    continue;
                }
                break;

            default:
                break;
            }

            error(QStringLiteral("Invalid line '%1%2'").arg(c).arg(line));
            continue;
        }

        if (!mapping) {
            error(QStringLiteral("Invalid format: data found before 'events' line. Skipping file"));
            delete _part;
            return false;
        }

        // for a cost line, we always need a current function
        ensureFunction();



        if (!currentFunctionSource ||
            (currentFunctionSource->file() != currentFile)) {
            currentFunctionSource = currentFunction->sourceFile(currentFile,
                                                                true);
        }

#if !USE_FIXCOST
        if (hasAddrInfo) {
            if (!currentInstr ||
                (currentInstr->addr() != currentPos.fromAddr)) {
                currentInstr = currentFunction->instr(currentPos.fromAddr,
                                                      true);

                if (!currentInstr) {
                    error(QString("Invalid address '%1'").arg(currentPos.fromAddr.toString()));

                    continue;
                }

                currentPartInstr = currentInstr->partInstr(_part,
                                                           currentPartFunction);
            }
        }

        if (hasLineInfo) {
            if (!currentLine ||
                (currentLine->lineno() != currentPos.fromLine)) {

                currentLine = currentFunctionSource->line(currentPos.fromLine,
                                                          true);
                currentPartLine = currentLine->partLine(_part,
                                                        currentPartFunction);
            }
            if (hasAddrInfo && currentInstr)
                currentInstr->setLine(currentLine);
        }
#endif

#if TRACE_LOADER
        qDebug() << _filename << ":" << _lineNo;
        qDebug() << "  currentInstr "
                 << (currentInstr ? qPrintable(currentInstr->toString()) : ".");
        qDebug() << "  currentLine "
                 << (currentLine ? qPrintable(currentLine->toString()) : ".")
                 << "( file " << currentFile->name() << ")";
        qDebug() << "  currentFunction "
                 << qPrintable(currentFunction->prettyName());
        qDebug() << "  currentCalled "
                 << (currentCalledFunction ? qPrintable(currentCalledFunction->prettyName()) : ".");
#endif

        // create cost item

        if (nextLineType == SelfCost) {

#if USE_FIXCOST
            new (pool) FixCost(_part, pool,
                               currentFunctionSource,
                               currentPos,
                               currentPartFunction,
                               line);
#else
            if (hasAddrInfo) {
                TracePartInstr* partInstr;
                partInstr = currentInstr->partInstr(_part, currentPartFunction);

                if (hasLineInfo) {
                    // we need to set <line> back after reading for the line
                    int l = line.len();
                    const char* s = line.ascii();

                    partInstr->addCost(mapping, line);
                    line.set(s,l);
                }
                else
                    partInstr->addCost(mapping, line);
            }

            if (hasLineInfo) {
                TracePartLine* partLine;
                partLine = currentLine->partLine(_part, currentPartFunction);
                partLine->addCost(mapping, line);
            }
#endif

            if (!line.isEmpty()) {
                error(QStringLiteral("Garbage at end of cost line ('%1')").arg(line));
            }
        }
        else if (nextLineType == CallCost) {
            nextLineType = SelfCost;

            TraceCall* calling = currentFunction->calling(currentCalledFunction);
            TracePartCall* partCalling =
                    calling->partCall(_part, currentPartFunction,
                                      currentCalledPartFunction);

#if USE_FIXCOST
            FixCallCost* fcc;
            fcc = new (pool) FixCallCost(_part, pool,
                                         currentFunctionSource,
                                         hasLineInfo ? currentPos.fromLine : 0,
                                         hasAddrInfo ? currentPos.fromAddr : Addr(0),
                                         partCalling,
                                         currentCallCount, line);
            fcc->setMax(_data->callMax());
            _data->updateMaxCallCount(fcc->callCount());
#else
            if (hasAddrInfo) {
                TraceInstrCall* instrCall;
                TracePartInstrCall* partInstrCall;

                instrCall = calling->instrCall(currentInstr);
                partInstrCall = instrCall->partInstrCall(_part, partCalling);
                partInstrCall->addCallCount(currentCallCount);

                if (hasLineInfo) {
                    // we need to set <line> back after reading for the line
                    int l = line.len();
                    const char* s = line.ascii();

                    partInstrCall->addCost(mapping, line);
                    line.set(s,l);
                }
                else
                    partInstrCall->addCost(mapping, line);

                // update maximum of call cost
                _data->callMax()->maxCost(partInstrCall);
                _data->updateMaxCallCount(partInstrCall->callCount());
            }

            if (hasLineInfo) {
                TraceLineCall* lineCall;
                TracePartLineCall* partLineCall;

                lineCall = calling->lineCall(currentLine);
                partLineCall = lineCall->partLineCall(_part, partCalling);

                partLineCall->addCallCount(currentCallCount);
                partLineCall->addCost(mapping, line);

                // update maximum of call cost
                _data->callMax()->maxCost(partLineCall);
                _data->updateMaxCallCount(partLineCall->callCount());
            }
#endif
            currentCalledFile = nullptr;
            currentCalledPartFile = nullptr;
            currentCalledObject = nullptr;
            currentCalledPartObject = nullptr;
            currentCallCount = 0;

            if (!line.isEmpty()) {
                error(QStringLiteral("Garbage at end of call cost line ('%1')").arg(line));
            }
        }
        else { // (nextLineType == BoringJump || nextLineType == CondJump)

            TraceFunctionSource* targetSource;

            if (!currentJumpToFunction)
                currentJumpToFunction = currentFunction;

            targetSource = (currentJumpToFile) ?
                               currentJumpToFunction->sourceFile(currentJumpToFile, true) :
                               currentFunctionSource;

#if USE_FIXCOST
            new (pool) FixJump(_part, pool,
                               /* source */
                               hasLineInfo ? currentPos.fromLine : 0,
                               hasAddrInfo ? currentPos.fromAddr : 0,
                               currentPartFunction,
                               currentFunctionSource,
                               /* target */
                               hasLineInfo ? targetPos.fromLine : 0,
                               hasAddrInfo ? targetPos.fromAddr : Addr(0),
                               currentJumpToFunction,
                               targetSource,
                               (nextLineType == CondJump),
                               jumpsExecuted, jumpsFollowed);
#else
            if (hasAddrInfo) {
                TraceInstr* jumpToInstr;
                TraceInstrJump* instrJump;
                TracePartInstrJump* partInstrJump;

                jumpToInstr = currentJumpToFunction->instr(targetPos.fromAddr,
                                                           true);
                instrJump = currentInstr->instrJump(jumpToInstr,
                                                    (nextLineType == CondJump));
                partInstrJump = instrJump->partInstrJump(_part);
                partInstrJump->addExecutedCount(jumpsExecuted);
                if (nextLineType == CondJump)
                    partInstrJump->addFollowedCount(jumpsFollowed);
            }

            if (hasLineInfo) {
                TraceLine* jumpToLine;
                TraceLineJump* lineJump;
                TracePartLineJump* partLineJump;

                jumpToLine = targetSource->line(targetPos.fromLine, true);
                lineJump = currentLine->lineJump(jumpToLine,
                                                 (nextLineType == CondJump));
                partLineJump = lineJump->partLineJump(_part);

                partLineJump->addExecutedCount(jumpsExecuted);
                if (nextLineType == CondJump)
                    partLineJump->addFollowedCount(jumpsFollowed);
            }
#endif

            if (0) {
                qDebug() << _filename << ":" << _lineNo
                         << " - jump from 0x" << currentPos.fromAddr.toString()
                         << " (line " << currentPos.fromLine
                         << ") to 0x" << targetPos.fromAddr.toString()
                         << " (line " << targetPos.fromLine << ")";

                if (nextLineType == BoringJump)
                    qDebug() << " Boring Jump, count " << jumpsExecuted.pretty();
                else
                    qDebug() << " Cond. Jump, followed " << jumpsFollowed.pretty()
                             << ", executed " << jumpsExecuted.pretty();
            }

            nextLineType = SelfCost;
            currentJumpToFunction = nullptr;
            currentJumpToFile = nullptr;

            if (!line.isEmpty()) {
                error(QStringLiteral("Garbage at end of jump cost line ('%1')").arg(line));
            }

        }
    }

    loadFinished();

    if (mapping) {
        _part->invalidate();
        _part->totals()->clear();
        _part->totals()->addCost(_part);
        data->addPart(_part);
        partsAdded++;
    }
    else {
        error(QStringLiteral("No data found. Skipping file"));
        delete _part;
    }

    device->close();

    return partsAdded;
}

