/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** 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.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

/*
    jambiapiparser.cpp
*/

#include <QtXml>

#include "cppcodeparser.h"
#include "jambiapiparser.h"
#include "node.h"
#include "tree.h"

QT_BEGIN_NAMESPACE

static const char USED_INTERNALLY[] = "";

static Text textWithFixedBrief(const Text &text, const Text &beforeBrief,
                               const Text &afterBrief)
{
    Text result;

    const Atom *atom = text.firstAtom();
    while (atom) {
        if (atom->type() == Atom::BriefLeft) {
            result << Atom::ParaLeft << beforeBrief;
        } else if (atom->type() == Atom::BriefRight) {
            result << afterBrief << Atom::ParaRight;
        } else {
            result << *atom;
        }
        atom = atom->next();
    }

    return result;
}

static void setPass1JambifiedDoc(Node *javaNode, const Node *cppNode, const QString &qName = "")
{
    Doc newDoc(cppNode->doc());

    if (javaNode->type() == Node::Function) {
        const FunctionNode *javaFunc = static_cast<const FunctionNode *>(javaNode);
        if (cppNode->type() == Node::Function) {
            const FunctionNode *cppFunc = static_cast<const FunctionNode *>(cppNode);
            if (const PropertyNode *property = cppFunc->associatedProperty()) {
                newDoc = property->doc();
                Text text(newDoc.body());

                Node *mutableCppNode = const_cast<Node *>(cppNode);
                if (property->getters().contains(mutableCppNode)) {
                    text = textWithFixedBrief(text, Text("Returns "), Text("."));
                } else if (property->setters().contains(mutableCppNode)) {
                    Text afterBrief;
                    if (javaFunc->parameterNames().count() == 1
                            && !javaFunc->parameterNames().first().isEmpty()) {
                        afterBrief << " to "
                                   << Atom(Atom::FormattingLeft, ATOM_FORMATTING_PARAMETER)
                                   << javaFunc->parameterNames().first()
                                   << Atom(Atom::FormattingRight, ATOM_FORMATTING_PARAMETER);
                    }
                    afterBrief << ".";
                    text = textWithFixedBrief(text, Text("Sets "), afterBrief);
                } else if (property->resetters().contains(mutableCppNode)) {
                    text = textWithFixedBrief(text, Text("Resets "), Text("."));
                }

                newDoc.setBody(text);
            } else {
                QStringList javaParams = javaFunc->parameterNames();
                QStringList cppParams = cppFunc->parameterNames();
                newDoc.renameParameters(cppParams, javaParams);

                if (cppNode->access() == Node::Private) {
                    Text text;
                    text << Atom::ParaLeft;
                    if (cppFunc->reimplementedFrom()) {
                        text << "This function is reimplemented for internal reasons.";
                    } else {
                        text << USED_INTERNALLY;
                    }
                    text << Atom::ParaRight;
                    newDoc.setBody(text);
                }
            }
        } else if (cppNode->type() == Node::Variable) {
            Text text(newDoc.body());

            if (qName == "variablegetter") {
                text = textWithFixedBrief(text, Text("Returns "), Text("."));
            } else if (qName == "variablesetter") {
                Text afterBrief;
                if (javaFunc->parameterNames().count() == 1
                        && !javaFunc->parameterNames().first().isEmpty()) {
                    afterBrief << " to "
                               << Atom(Atom::FormattingLeft, ATOM_FORMATTING_PARAMETER)
                               << javaFunc->parameterNames().first()
                               << Atom(Atom::FormattingRight, ATOM_FORMATTING_PARAMETER);
                }
                afterBrief << ".";
                text = textWithFixedBrief(text, Text("Sets "), afterBrief);
            }

            newDoc.setBody(text);
        }
    } else {    // ### enum value names?

    }

    javaNode->setDoc(newDoc, true);
}

static void setStatus(Node *javaNode, const Node *cppNode)
{
    if (cppNode->status() == Node::Compat) {
        javaNode->setStatus(Node::Obsolete);
    } else {
        javaNode->setStatus(cppNode->status());
    }
}

static Text findEnumText(Node *javaEnum, const QString &enumItemName)
{
    const Text &body = javaEnum->doc().body();
    const Atom *atom = body.firstAtom();
    while (atom) {
        if (atom->type() == Atom::ListTagLeft && atom->string() == ATOM_LIST_VALUE) {
            atom = atom->next();
            if (atom) {
                // ### paras?
                if (atom->string() == enumItemName)
                    return body.subText(Atom::ListItemLeft, Atom::ListItemRight, atom);
            }
        } else {
            atom = atom->next();
        }
    }
    return Text();
}

JambiApiParser::JambiApiParser(Tree *cppTree)
    : cppTre(cppTree), javaTre(0), metJapiTag(false)
{
}

JambiApiParser::~JambiApiParser()
{
}

void JambiApiParser::initializeParser(const Config &config)
{
    CodeParser::initializeParser(config);
}

void JambiApiParser::terminateParser()
{
    CodeParser::terminateParser();
}

QString JambiApiParser::language()
{
    return "Java";
}

QString JambiApiParser::sourceFileNameFilter()
{
    return "*.japi";
}

void JambiApiParser::parseSourceFile(const Location &location, const QString &filePath, Tree *tree)
{
    javaTre = tree;
    metJapiTag = false;

    QXmlSimpleReader reader;
    reader.setContentHandler(this);
    reader.setErrorHandler(this);

    QFile file(filePath);
    if (!file.open(QFile::ReadOnly)) {
        location.warning(tr("Cannot open JAPI file '%1'").arg(filePath));
        return;
    }

    japiLocation = Location(filePath);
    QXmlInputSource xmlSource(&file);
    reader.parse(xmlSource);
}

void JambiApiParser::doneParsingSourceFiles(Tree * /* tree */)
{
    /*
        Also import the overview documents.
    */
    foreach (Node *cppNode, cppTre->root()->childNodes()) {
        if (cppNode->type() == Node::Fake) {
            FakeNode *cppFake = static_cast<FakeNode *>(cppNode);
            if (cppFake->subType() == Node::Page) {
                FakeNode *javaFake = new FakeNode(javaTre->root(),
                                                  cppFake->name(),
                                                  cppFake->subType());
                javaFake->setModuleName("com.trolltech.qt");    // ### hard-coded
                javaFake->setTitle(cppFake->title());
                javaFake->setSubTitle(cppFake->subTitle());
                setStatus(javaFake, cppFake);
                setPass1JambifiedDoc(javaFake, cppFake);
            }
        }
    }

    /*
        Fix the docs.
    */
    if (javaTre) {
        javaTre->resolveInheritance();
        jambifyDocsPass2(javaTre->root());
        javaTre = 0;
    }
}

bool JambiApiParser::startElement(const QString & /* namespaceURI */,
                                  const QString & /* localName */,
                                  const QString &qName,
                                  const QXmlAttributes &attributes)
{
    if (!metJapiTag && qName != "japi") {
        // ### The file is not a JAPI file.
        return true;
    }
    metJapiTag = true;

    EnumNode *javaEnum = 0;
    EnumNode *cppEnum = 0;
    InnerNode *javaParent = javaTre->root();
    InnerNode *cppParent = cppTre->root();

    for (int i = 0; i < classAndEnumStack.count(); ++i) {
        const ClassOrEnumInfo &info = classAndEnumStack.at(i);
        if (info.cppNode) {
            if (info.cppNode->type() == Node::Enum) {
                Q_ASSERT(info.javaNode->type() == Node::Enum);
                javaEnum = static_cast<EnumNode *>(info.javaNode);
                cppEnum = static_cast<EnumNode *>(info.cppNode);
            } else {
                Q_ASSERT(info.javaNode->type() == Node::Class
                         || info.javaNode->type() == Node::Namespace);
                javaParent = static_cast<InnerNode *>(info.javaNode);
                cppParent = static_cast<InnerNode *>(info.cppNode);
            }
        }
    }

    if (qName == "class" || qName == "enum") {
        Node::Type type = (qName == "class") ? Node::Class : Node::Enum;

        QString javaExtends = attributes.value("java-extends");
        QString javaImplements = attributes.value("javaimplements");

        ClassOrEnumInfo info;
        info.tag = qName;
        info.javaName = attributes.value("java");
        info.cppName = attributes.value("cpp");
        info.cppNode = cppTre->findNode(info.cppName.split("::"), type, cppParent);
        if (!info.cppNode && type == Node::Class) {
            type = Node::Namespace;
            info.cppNode = cppTre->findNode(info.cppName.split("::"), type, cppParent);
        }

        if (!info.cppNode) {
            japiLocation.warning(tr("Cannot find C++ class or enum '%1'").arg(info.cppName));
        } else {
            if (qName == "class") {
                ClassNode *javaClass = new ClassNode(javaParent, info.javaName);
                javaClass->setModuleName(attributes.value("package"));
                if (!javaExtends.isEmpty())
                    javaTre->addBaseClass(javaClass, Node::Public, javaExtends.split('.'),
                                          javaExtends);
                if (!javaImplements.isEmpty())
                    javaTre->addBaseClass(javaClass, Node::Public, javaImplements.split('.'),
                                          javaExtends);

                info.javaNode = javaClass;
            } else {
                info.javaNode = new EnumNode(javaParent, info.javaName);
            }
            info.javaNode->setLocation(japiLocation);
            setStatus(info.javaNode, info.cppNode);

            setPass1JambifiedDoc(info.javaNode, info.cppNode);
        }
        classAndEnumStack.push(info);
    } else if (qName == "method" || qName == "signal") {
        QString javaSignature = attributes.value("java");
        if (javaSignature.startsWith("private"))
            return true;

        QString cppSignature = attributes.value("cpp");

        CppCodeParser cppParser;
        const FunctionNode *cppNode = cppParser.findFunctionNode(cppSignature, cppTre,
                                                                 cppParent,
                                                                 true /* fuzzy */);
        if (!cppNode) {
            bool quiet = false;

            /*
                Default constructors sometimes don't exist in C++.
            */
            if (!quiet && javaSignature == "public " + javaParent->name() + "()")
                quiet = true;

            if (!quiet)
                japiLocation.warning(tr("Cannot find C++ function '%1' ('%2')")
                                     .arg(cppSignature).arg(cppParent->name()));
        }

        FunctionNode *javaNode;
        if (makeFunctionNode(javaParent, javaSignature, &javaNode)) {
            javaNode->setLocation(japiLocation);
            if (qName == "signal")
                javaNode->setMetaness(FunctionNode::Signal);

            if (cppNode) {
                setStatus(javaNode, cppNode);

                int overloadNo = cppNode->parameters().count() - javaNode->parameters().count() + 1;
                if (overloadNo == 1) {
                    setPass1JambifiedDoc(javaNode, cppNode);
                } else {
                    Text text;

                    text << Atom::ParaLeft << "Equivalent to "
                         << Atom(Atom::Link, javaNode->name() + "()")
                         << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
                         << javaNode->name()
                         << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
                         << "(";

                    for (int i = 0; i < cppNode->parameters().count(); ++i) {
                        if (i > 0)
                            text << ", ";
                        if (i < javaNode->parameters().count()) {
                            text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_PARAMETER)
                                 << javaNode->parameters().at(i).name()
                                 << Atom(Atom::FormattingRight, ATOM_FORMATTING_PARAMETER);
                        } else {
                            // ### convert to Java
                            text << cppNode->parameters().at(i).defaultValue();
                        }
                    }

                    text << ").";

                    Doc doc;
                    doc.setBody(text);
                    javaNode->setDoc(doc, true);
                }
                javaNode->setOverload(overloadNo > 1);
            }
        }
    } else if (qName == "variablesetter" || qName == "variablegetter") {
        QString javaSignature = attributes.value("java");
        if (javaSignature.startsWith("private"))
            return true;

        QString cppVariable = attributes.value("cpp");

        VariableNode *cppNode = static_cast<VariableNode *>(cppParent->findNode(cppVariable,
                                                                                Node::Variable));
        FunctionNode *javaNode;
        if (makeFunctionNode(javaParent, javaSignature, &javaNode)) {
            javaNode->setLocation(japiLocation);

            if (!cppNode) {
#if 0
                japiLocation.warning(tr("Cannot find C++ variable '%1' ('%2')")
                                     .arg(cppVariable).arg(cppParent->name()));
#endif
                javaNode->setDoc(Doc(japiLocation, japiLocation,
                                     USED_INTERNALLY,
                                     QSet<QString>()), true);
            } else {
                setPass1JambifiedDoc(javaNode, cppNode, qName);
                setStatus(javaNode, cppNode);
            }
        }
    } else if (qName == "enum-value") {
        QString javaName = attributes.value("java");
        QString cppName = attributes.value("cpp");
        QString value = attributes.value("value");

        if (javaEnum) {
            EnumItem item(javaName, value, findEnumText(javaEnum, javaName));
            javaEnum->addItem(item);
        }
    }

    return true;
}

bool JambiApiParser::endElement(const QString & /* namespaceURI */,
                                const QString & /* localName */,
                                const QString &qName)
{
    if (qName == "class" || qName == "enum")
        classAndEnumStack.pop();
    return true;
}

bool JambiApiParser::fatalError(const QXmlParseException &exception)
{
    japiLocation.setLineNo(exception.lineNumber());
    japiLocation.setColumnNo(exception.columnNumber());
    japiLocation.warning(tr("Syntax error in JAPI file (%1)").arg(exception.message()));
    return true;
}

void JambiApiParser::jambifyDocsPass2(Node *node)
{
    const Doc &doc = node->doc();
    if (!doc.isEmpty()) {
        if (node->type() == Node::Enum) {
            Doc newDoc(doc);
            newDoc.simplifyEnumDoc();
            node->setDoc(newDoc, true);
        }
    }

    if (node->isInnerNode()) {
        InnerNode *innerNode = static_cast<InnerNode *>(node);
        foreach (Node *child, innerNode->childNodes())
            jambifyDocsPass2(child);
    }
}

bool JambiApiParser::makeFunctionNode(InnerNode *parent, const QString &synopsis,
				      FunctionNode **funcPtr)
{
    Node::Access access = Node::Public;
    FunctionNode::Metaness metaness = FunctionNode::Plain;
    bool final = false;
    bool statique = false;

    QString mySynopsis = synopsis.simplified();
    int oldLen;
    do {
        oldLen = mySynopsis.length();

        if (mySynopsis.startsWith("public ")) {
            mySynopsis.remove(0, 7);
            access = Node::Public;
        }
        if (mySynopsis.startsWith("protected ")) {
            mySynopsis.remove(0, 10);
            access = Node::Protected;
        }
        if (mySynopsis.startsWith("private ")) {
            mySynopsis.remove(0, 8);
            access = Node::Private;
        }
        if (mySynopsis.startsWith("native ")) {
            mySynopsis.remove(0, 7);
            metaness = FunctionNode::Native;
        }
        if (mySynopsis.startsWith("final ")) {
            mySynopsis.remove(0, 6);
            final = true;
        }
        if (mySynopsis.startsWith("static ")) {
            mySynopsis.remove(0, 7);
            statique = true;
        }
    } while (oldLen != mySynopsis.length());

    // method or constructor
    QRegExp funcRegExp("(?:(.*) )?([A-Za-z_0-9]+)\\((.*)\\)");
    if (!funcRegExp.exactMatch(mySynopsis))
        return false;

    QString retType = funcRegExp.cap(1);
    QString funcName = funcRegExp.cap(2);
    QStringList params = funcRegExp.cap(3).split(",");

    FunctionNode *func = new FunctionNode(parent, funcName);
    func->setReturnType(retType);
    func->setAccess(access);
    func->setStatic(statique);
    func->setConst(final);
    func->setMetaness(metaness);

    QRegExp paramRegExp(" ?([^ ].*) ([A-Za-z_0-9]+) ?");

    foreach (const QString &param, params) {
        if (paramRegExp.exactMatch(param)) {
            func->addParameter(Parameter(paramRegExp.cap(1), "", paramRegExp.cap(2)));
        } else {
            // problem
        }
    }

    if (funcPtr) {
        *funcPtr = func;
    } else if (!parent) {
        delete func;
    }
    return true;
}

QT_END_NAMESPACE
