/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact:  Qt Software Information (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 qt-sales@nokia.com.
**
**************************************************************************/

#include "gdbmi.h"

#include <utils/qtcassert.h>

#include <QtCore/QByteArray>
#include <QtCore/QTextStream>

namespace Debugger {
namespace Internal {

QTextStream & operator<<(QTextStream & os, const GdbMi & mi)
{
    return os << mi.toString();
}

//static void skipSpaces(const char *&from, const char *to)
//{
//    while (from != to && QChar(*from).isSpace())
//        ++from;
//}


void GdbMi::parseResultOrValue(const char *&from, const char *to)
{
    //skipSpaces(from, to);
    while (from != to && QChar(*from).isSpace())
        ++from;

    //qDebug() << "parseResultOrValue: " << QByteArray(from, to - from);
    parseValue(from, to);
    if (isValid()) {
        //qDebug() << "no valid result in " << QByteArray::fromLatin1(from, to - from);
        return;
    }
    if (from == to || *from == '(')
        return;
    const char *ptr = from;
    while (ptr < to && *ptr != '=') {
        //qDebug() << "adding" << QChar(*ptr) << "to name";
        ++ptr;
    }
    m_name = QByteArray(from, ptr - from);
    from = ptr;
    if (from < to && *from == '=') {
        ++from;
        parseValue(from, to);
    }
}

QByteArray GdbMi::parseCString(const char *&from, const char *to)
{
    QByteArray result;
    //qDebug() << "parseCString: " << QByteArray::fromUtf16(from, to - from);
    if (*from != '"') {
        qDebug() << "MI Parse Error, double quote expected";
        return QByteArray();
    }
    const char *ptr = from;
    ++ptr;
    while (ptr < to) {
        if (*ptr == '"') {
            ++ptr;
            result = QByteArray(from + 1, ptr - from - 2);
            break;
        }
        if (*ptr == '\\' && ptr < to - 1)
            ++ptr;
        ++ptr;
    }

    if (result.contains('\\')) {
        if (result.contains("\\032\\032"))
            result.clear();
        else {
            result = result.replace("\\n", "\n");
            result = result.replace("\\t", "\t");
            result = result.replace("\\\"", "\"");
        }
    }

    from = ptr;
    return result;
}

void GdbMi::parseValue(const char *&from, const char *to)
{
    //qDebug() << "parseValue: " << QByteArray::fromUtf16(from, to - from);
    switch (*from) {
    case '{':
        parseTuple(from, to);
        break;
    case '[':
        parseList(from, to);
        break;
    case '"':
        m_type = Const;
        m_data = parseCString(from, to);
        break;
    default:
        break;
    }
}


void GdbMi::parseTuple(const char *&from, const char *to)
{
    //qDebug() << "parseTuple: " << QByteArray::fromUtf16(from, to - from);
    QTC_ASSERT(*from == '{', /**/);
    ++from;
    parseTuple_helper(from, to);
}

void GdbMi::parseTuple_helper(const char *&from, const char *to)
{
    //qDebug() << "parseTuple_helper: " << QByteArray::fromUtf16(from, to - from);
    m_type = Tuple;
    while (from < to) {
        if (*from == '}') {
            ++from;
            break;
        }
        GdbMi child;
        child.parseResultOrValue(from, to);
        //qDebug() << "\n=======\n" << qPrintable(child.toString()) << "\n========\n";
        if (!child.isValid())
            return;
        m_children += child;
        if (*from == ',')
            ++from;
    }
}

void GdbMi::parseList(const char *&from, const char *to)
{
    //qDebug() << "parseList: " << QByteArray::fromUtf16(from, to - from);
    QTC_ASSERT(*from == '[', /**/);
    ++from;
    m_type = List;
    while (from < to) {
        if (*from == ']') {
            ++from;
            break;
        }
        GdbMi child;
        child.parseResultOrValue(from, to);
        if (child.isValid())
            m_children += child;
        if (*from == ',')
            ++from;
    }
}

void GdbMi::setStreamOutput(const QByteArray &name, const QByteArray &content)
{
    if (content.isEmpty())
        return;
    GdbMi child;
    child.m_type = Const;
    child.m_name = name;
    child.m_data = content;
    m_children += child;
    if (m_type == Invalid)
        m_type = Tuple;
}

static QByteArray ind(int indent)
{
    return QByteArray(2 * indent, ' ');
}

void GdbMi::dumpChildren(QByteArray * str, bool multiline, int indent) const
{
    for (int i = 0; i < m_children.size(); ++i) {
        if (i != 0) {
            *str += ',';
            if (multiline)
                *str += '\n';
        }
        if (multiline)
            *str += ind(indent);
        *str += m_children.at(i).toString(multiline, indent);
    }
}

QByteArray GdbMi::toString(bool multiline, int indent) const
{
    QByteArray result;
    switch (m_type) {
    case Invalid:
        if (multiline) {
            result += ind(indent) + "Invalid\n";
        } else {
        result += "Invalid";
        }
        break;
    case Const:
        if (!m_name.isEmpty())
            result += m_name + "=";
        if (multiline) {
        result += "\"" + m_data + "\"";
        } else {
            result += "\"" + m_data + "\"";
        }
        break;
    case Tuple:
        if (!m_name.isEmpty())
            result += m_name + "=";
        if (multiline) {
            result += "{\n";
            dumpChildren(&result, multiline, indent + 1);
            result += '\n' + ind(indent) + "}";
        } else {
        result += "{";
            dumpChildren(&result, multiline, indent + 1);
        result += "}";
        }
        break;
    case List:
        if (!m_name.isEmpty())
            result += m_name + "=";
        if (multiline) {
            result += "[\n";
            dumpChildren(&result, multiline, indent + 1);
            result += "]";
        } else {
            result += "[";
            dumpChildren(&result, multiline, indent + 1);
            result += '\n' + ind(indent) + "]";
        }
        break;
    }
    return result;
}

void GdbMi::fromString(const QByteArray &ba)
{
    const char *from = ba.constBegin();
    const char *to = ba.constEnd();
    parseResultOrValue(from, to);
}

GdbMi GdbMi::findChild(const QByteArray &name) const
{
    for (int i = 0; i < m_children.size(); ++i)
        if (m_children.at(i).m_name == name)
            return m_children.at(i);
    return GdbMi();
}


GdbMi GdbMi::findChild(const QByteArray &name, const QByteArray &defaultData) const
{
    for (int i = 0; i < m_children.size(); ++i)
        if (m_children.at(i).m_name == name)
            return m_children.at(i);
    GdbMi result;
    result.m_data = defaultData;
    return result;
}


//////////////////////////////////////////////////////////////////////////////////
//
// GdbResultRecord
//
//////////////////////////////////////////////////////////////////////////////////

QByteArray stringFromResultClass(GdbResultClass resultClass)
{
    switch (resultClass) {
        case GdbResultDone: return "done";
        case GdbResultRunning: return "running";
        case GdbResultConnected: return "connected";
        case GdbResultError: return "error";
        case GdbResultExit: return "exit";
        default: return "unknown";
    }
};

QByteArray GdbResultRecord::toString() const
{
    QByteArray result;
    if (token != -1)
        result = QByteArray::number(token);
    result += '^';
    result += stringFromResultClass(resultClass);
    if (data.isValid())
        result += ',' + data.toString();
    result += '\n';
    return result;
}


//////////////////////////////////////////////////////////////////////////////////
//
// GdbStreamOutput
//
//////////////////////////////////////////////////////////////////////////////////

#if 0

static const char test1[] =
    "1^done,stack=[frame={level=\"0\",addr=\"0x00000000004061ca\","
    "func=\"main\",file=\"test1.cpp\","
    "fullname=\"/home/apoenitz/work/test1/test1.cpp\",line=\"209\"}]\n"
    "(gdb)\n";

static const char test2[] =
    "2^done,stack=[frame={level=\"0\",addr=\"0x00002ac058675840\","
    "func=\"QApplication\",file=\"/home/apoenitz/dev/qt/src/gui/kernel/qapplication.cpp\","
    "fullname=\"/home/apoenitz/dev/qt/src/gui/kernel/qapplication.cpp\",line=\"592\"},"
    "frame={level=\"1\",addr=\"0x00000000004061e0\",func=\"main\",file=\"test1.cpp\","
    "fullname=\"/home/apoenitz/work/test1/test1.cpp\",line=\"209\"}]\n"
    "(gdb)\n";

static const char test3[] =
    "3^done,stack=[frame={level=\"0\",addr=\"0x00000000004061ca\","
    "func=\"main\",file=\"test1.cpp\","
    "fullname=\"/home/apoenitz/work/test1/test1.cpp\",line=\"209\"}]\n"
    "(gdb)\n";

static const char test4[] =
    "&\"source /home/apoenitz/dev/ide/main/bin/gdb/qt4macros\\n\"\n"
    "4^done\n"
    "(gdb)\n";


static const char test5[] =
    "1*stopped,reason=\"breakpoint-hit\",bkptno=\"1\",thread-id=\"1\","
    "frame={addr=\"0x0000000000405738\",func=\"main\","
    "args=[{name=\"argc\",value=\"1\"},{name=\"argv\",value=\"0x7fff1ac78f28\"}],"
    "file=\"test1.cpp\",fullname=\"/home/apoenitz/work/test1/test1.cpp\","
    "line=\"209\"}\n"
    "(gdb)\n";

static const char test6[] =
    "{u = {u = 2048, v = 16788279, w = -689265400}, a = 1, b = -689265424, c = 11063, s = {static null = {<No data fields>}, static shared_null = {ref = {value = 2}, alloc = 0, size = 0, data = 0x6098da, clean = 0, simpletext = 0, righttoleft = 0, asciiCache = 0, capacity = 0, reserved = 0, array = {0}}, static shared_empty = {ref = {value = 1}, alloc = 0, size = 0, data = 0x2b37d84f8fba, clean = 0, simpletext = 0, righttoleft = 0, asciiCache = 0, capacity = 0, reserved = 0, array = {0}}, d = 0x6098c0, static codecForCStrings = 0x0}}";

static const char test8[] =
    "8^done,data={locals={{name=\"a\"},{name=\"w\"}}}\n"
    "(gdb)\n";

static const char test9[] =
    "9^done,data={locals=[name=\"baz\",name=\"urgs\",name=\"purgs\"]}\n"
    "(gdb)\n";


static const char test10[] =
    "16^done,name=\"urgs\",numchild=\"1\",type=\"Urgs\"\n"
    "(gdb)\n"
    "17^done,name=\"purgs\",numchild=\"1\",type=\"Urgs *\"\n"
    "(gdb)\n"
    "18^done,name=\"bar\",numchild=\"0\",type=\"int\"\n"
    "(gdb)\n"
    "19^done,name=\"z\",numchild=\"0\",type=\"int\"\n"
    "(gdb)\n";

static const char test11[] =
    "[{name=\"size\",value=\"1\",type=\"size_t\",readonly=\"true\"},"
     "{name=\"0\",value=\"one\",type=\"QByteArray\"}]";

static const char test12[] =
    "{iname=\"local.hallo\",value=\"\\\"\\\"\",type=\"QByteArray\",numchild=\"0\"}";

static struct Tester {

    Tester() {
        //test(test10);
        test2(test12);
        //test(test4);
        //apple();
        exit(0);
    }

    void test(const char* input)
    {
        //qDebug("\n<<<<\n%s\n====\n%s\n>>>>\n", input,
           //qPrintable(GdbResponse(input).toString()));
    }

    void test2(const char* input)
    {
        GdbMi mi(input);
        qDebug("\n<<<<\n%s\n====\n%s\n>>>>\n", input,
            qPrintable(mi.toString()));
    }

    void apple()
    {
        QByteArray input(test9);
/*
        qDebug() << "input: " << input;
        input = input.replace("{{","[");
        input = input.replace("},{",",");
        input = input.replace("}}","]");
        qDebug() << "input: " << input;
        GdbResponse response(input);
        qDebug() << "read: " << response.toString();
        GdbMi list = response.results[0].data.findChild("data").findChild("locals");
        QByteArrayList locals;
        foreach (const GdbMi &item, list.children())
            locals.append(item.string());
        qDebug() << "Locals (new): " << locals;
*/
    }
    void parse(const QByteArray &str)
    {
        QByteArray result;
        result += "\n ";
        int indent = 0;
        int from = 0;
        int to = str.size();
        if (str.size() && str[0] == '{' /*'}'*/) {
            ++from;
            --to;
        }
        for (int i = from; i < to; ++i) {
            if (str[i] == '{')
                result += "{\n" + QByteArray(2*++indent + 1, ' ');
            else if (str[i] == '}') {
                if (!result.isEmpty() && result[result.size() - 1] != '\n')
                    result += "\n";
                result += QByteArray(2*--indent + 1, ' ') + "}\n";
            }
            else if (str[i] == ',') {
                if (true || !result.isEmpty() && result[result.size() - 1] != '\n')
                    result += "\n";
                result += QByteArray(2*indent, ' ');
            }
            else
                result += str[i];
        }
        qDebug() << "result:\n" << result;
    }

} dummy;

#endif

} // namespace Internal
} // namespace Debugger