/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://www.qtsoftware.com/contact.
**
**************************************************************************/

#include "cdbassembler.h"
#include "cdbdebugoutput.h"
#include "cdbdebugengine_p.h"
#include "cdbsymbolgroupcontext.h"

#include "disassemblerhandler.h"
#include "registerhandler.h"

#include <QtCore/QVector>

namespace Debugger {
namespace Internal {

typedef QList<DisassemblerLine> DisassemblerLineList;

bool getRegisters(CIDebugControl *ctl,
                  CIDebugRegisters *ireg,
                  QList<Register> *registers,
                  QString *errorMessage, int base)
{
    registers->clear();
    ULONG count;
    HRESULT hr = ireg->GetNumberRegisters(&count);
    if (FAILED(hr)) {
        *errorMessage= msgComFailed("GetNumberRegisters", hr);
        return false;
    }
    if (!count)
        return true;
    // Retrieve names
    WCHAR wszBuf[MAX_PATH];
    for (ULONG r = 0; r < count; r++) {
        hr = ireg->GetDescriptionWide(r, wszBuf, MAX_PATH - 1, 0, 0);
        if (FAILED(hr)) {
            *errorMessage= msgComFailed("GetDescriptionWide", hr);
            return false;
        }
        Register reg;
        reg.name = QString::fromUtf16(reinterpret_cast<const ushort *>(wszBuf));
        registers->push_back(reg);
    }
    // get values
    QVector<DEBUG_VALUE> values(count);
    DEBUG_VALUE *valuesPtr = &(*values.begin());
    memset(valuesPtr, 0, count * sizeof(DEBUG_VALUE));
    hr = ireg->GetValues(count, 0, 0, valuesPtr);
    if (FAILED(hr)) {
        *errorMessage= msgComFailed("GetValues", hr);
        return false;
    }
    if (base < 2)
        base = 10;
    for (ULONG r = 0; r < count; r++)
        (*registers)[r].value = CdbSymbolGroupContext::debugValueToString(values.at(r), ctl, 0, base);
    return true;
}

// Output parser for disassembler lines.
// It uses the source file lines as symbol until it encounters
// a C++ symbol (function entered), from which then on
// it uses that symbol.
class DisassemblerOutputParser
{
public:
    explicit DisassemblerOutputParser(DisassemblerLineList *list);

    void parse(const QStringList &l);

private:
    enum ParseResult { ParseOk, ParseIgnore, ParseFailed };
    ParseResult parseDisassembled(const QString &in, DisassemblerLine* l);

    DisassemblerLineList *m_list;
    QString m_sourceSymbol;
    int m_sourceSymbolOffset;
};

DisassemblerOutputParser::DisassemblerOutputParser(DisassemblerLineList *list) :
    m_list(list),
    m_sourceSymbolOffset(0)
{
}

// Parse a disassembler line:
//  module!class::foo:
//                          004017cf cc int 3
//  77 mainwindow.cpp       004018ff 8d4da8           lea     ecx,[ebp-0x58]
DisassemblerOutputParser::ParseResult
    DisassemblerOutputParser::parseDisassembled(const QString &in, DisassemblerLine* l)
{
    l->clear();

    // Check if there is a source file
    if (in.size() < 7)
        return ParseIgnore;
    const bool hasSourceFile = !in.at(6).isSpace();

    // Sometimes, empty lines occur
    const QString simplified = in.simplified();
    if (simplified.isEmpty())
        return ParseIgnore;

    QStringList tokens = simplified.split(QLatin1Char(' '), QString::SkipEmptyParts);
    // Check for symbols as 'module!class::foo:' (start of function encountered)
    if (tokens.size() == 1) {
        QString symbol = tokens.front();
        if (symbol.endsWith(QLatin1Char(':'))  && symbol.contains(QLatin1Char('!'))) {
            symbol.truncate(symbol.size() - 1);
            m_sourceSymbol = symbol;
            m_sourceSymbolOffset = 0;
        }
        return ParseIgnore;
    }
    if (tokens.size() < 2)
        return ParseIgnore;
    // Symbol display: Do we know a symbol?
    if (!m_sourceSymbol.isEmpty()) {
        l->symbol = QString(QLatin1Char('<'));
        l->symbol += m_sourceSymbol;
        if (m_sourceSymbolOffset) {
            l->symbol += QLatin1Char('+');
            l->symbol += QString::number(m_sourceSymbolOffset);
        }
        l->symbol += QLatin1Char('>');
        m_sourceSymbolOffset++;
    }
    // Read source file information: If we don't know a symbol yet,
    // use the source file.
    if (hasSourceFile) {
        if (l->symbol.isEmpty()) {
            l->symbol = tokens.at(1);
            l->symbol += QLatin1Char('+');
            l->symbol += tokens.front();
        }
        tokens.pop_front();
        tokens.pop_front();
    }
    l->symbolDisplay = l->symbol;
    // Get offset address and instruction
    if (tokens.size() < 3)
        return ParseFailed;
    l->addressDisplay = l->address = tokens.front();    
    tokens.pop_front();
    // The rest is effective address & instructions
    if (tokens.size() > 1)
        tokens.pop_front();
    l->mnemonic = tokens.join(QString(QLatin1Char(' ')));     
    return ParseOk;
}

void DisassemblerOutputParser::parse(const QStringList &l)
{
    DisassemblerLine dLine;
    foreach(const QString &line, l) {
        switch (parseDisassembled(line, &dLine)) {
        case ParseOk:
            m_list->push_back(dLine);
            break;
        case ParseIgnore:
            break;
        case ParseFailed:
            qWarning("Failed to parse '%s'\n", qPrintable(line));
            break;
        }
    }
}

bool dissassemble(CIDebugClient *client,
                  CIDebugControl *ctl,
                  ULONG64 offset,
                  unsigned long beforeLines,
                  unsigned long afterLines,
                  QList<DisassemblerLine> *lines,
                  QString *errorMessage)
{
    if (debugCDB)
        qDebug() << Q_FUNC_INFO << offset;
    lines->clear();
    const ULONG flags = DEBUG_DISASM_MATCHING_SYMBOLS|DEBUG_DISASM_SOURCE_LINE_NUMBER|DEBUG_DISASM_SOURCE_FILE_NAME;
    // Catch the output by temporarily setting another handler.
    // We use the method that outputs to the output handler as it
    // conveniently provides the 'beforeLines' context (stepping back
    // in assembler code). We build a complete string first as line breaks
    // may occur in-between messages.
    StringOutputHandler stringHandler;
    OutputRedirector redir(client, &stringHandler);
    // For some reason, we need to output to "all clients"
    const HRESULT hr =  ctl->OutputDisassemblyLines(DEBUG_OUTCTL_ALL_CLIENTS,
                                                    beforeLines, beforeLines + afterLines,
                                                    offset, flags, 0, 0, 0, 0);
    if (FAILED(hr)) {
        *errorMessage= QString::fromLatin1("Unable to dissamble at 0x%1: %2").
                       arg(QString::number(offset, 16), msgComFailed("OutputDisassemblyLines", hr));
        return false;
    }
    DisassemblerOutputParser parser(lines);
    parser.parse(stringHandler.result().split(QLatin1Char('\n')));
    return true;
}

} // namespace Internal
} // namespace Debugger
