/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** Contact: Qt Software Information (qt-info@nokia.com)
**
** This file is part of the Qt Linguist of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** 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.
**
** In addition, as a special exception, Nokia gives you certain
** additional rights. These rights are described in the Nokia Qt LGPL
** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
** package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at qt-sales@nokia.com.
** $QT_END_LICENSE$
**
****************************************************************************/

#include "profileevaluator.h"
#include "proparserutils.h"
#include "proitems.h"

#include <QtCore/QByteArray>
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QList>
#include <QtCore/QRegExp>
#include <QtCore/QSet>
#include <QtCore/QStack>
#include <QtCore/QString>
#include <QtCore/QStringList>
#include <QtCore/QTextStream>

#ifdef Q_OS_WIN32
#define QT_POPEN _popen
#else
#define QT_POPEN popen
#endif

QT_BEGIN_NAMESPACE

///////////////////////////////////////////////////////////////////////
//
// ProFileEvaluator::Private
//
///////////////////////////////////////////////////////////////////////

class ProFileEvaluator::Private : public AbstractProItemVisitor
{
public:
    Private(ProFileEvaluator *q_);

    bool read(ProFile *pro);

    ProBlock *currentBlock();
    void updateItem();
    bool parseLine(const QString &line);
    void insertVariable(const QString &line, int *i);
    void insertOperator(const char op);
    void insertComment(const QString &comment);
    void enterScope(bool multiLine);
    void leaveScope();
    void finalizeBlock();

    // implementation of AbstractProItemVisitor
    bool visitBeginProBlock(ProBlock *block);
    bool visitEndProBlock(ProBlock *block);
    bool visitBeginProVariable(ProVariable *variable);
    bool visitEndProVariable(ProVariable *variable);
    bool visitBeginProFile(ProFile *value);
    bool visitEndProFile(ProFile *value);
    bool visitProValue(ProValue *value);
    bool visitProFunction(ProFunction *function);
    bool visitProOperator(ProOperator *oper);
    bool visitProCondition(ProCondition *condition);

    QStringList valuesDirect(const QString &variableName) const { return m_valuemap[variableName]; }
    QStringList values(const QString &variableName) const;
    QStringList values(const QString &variableName, const ProFile *pro) const;
    QString propertyValue(const QString &val) const;

    bool isActiveConfig(const QString &config, bool regex = false);
    QStringList expandPattern(const QString &pattern);
    void expandPatternHelper(const QString &relName, const QString &absName,
        QStringList &sources_out);
    QStringList expandVariableReferences(const QString &value);
    QStringList evaluateExpandFunction(const QString &function, const QString &arguments);
    QString format(const char *format) const;

    QString currentFileName() const;
    QString getcwd() const;
    ProFile *currentProFile() const;

    bool evaluateConditionalFunction(const QString &function, const QString &arguments, bool *result);
    bool evaluateFile(const QString &fileName, bool *result);
    bool evaluateFeatureFile(const QString &fileName, bool *result);

    QStringList qmakeFeaturePaths();

    ProFileEvaluator *q;

    QStack<ProBlock *> m_blockstack;
    ProBlock *m_block;

    ProItem *m_commentItem;
    QString m_proitem;
    QString m_pendingComment;
    bool m_syntaxError;
    bool m_contNextLine;
    bool m_condition;
    bool m_invertNext;
    QString m_lastVarName;
    ProVariable::VariableOperator m_variableOperator;
    int m_lineNo;                                   // Error reporting
    QString m_oldPath;                              // To restore the current path to the path
    QStack<ProFile*> m_profileStack;                // To handle 'include(a.pri), so we can track back to 'a.pro' when finished with 'a.pri'

    QHash<QString, QStringList> m_valuemap;         // VariableName must be us-ascii, the content however can be non-us-ascii.
    QHash<const ProFile*, QHash<QString, QStringList> > m_filevaluemap; // Variables per include file
    QHash<QString, QString> m_properties;
    QString m_origfile;

    int m_prevLineNo;                               // Checking whether we're assigning the same TARGET
    ProFile *m_prevProFile;                         // See m_prevLineNo

    bool m_verbose;
};

ProFileEvaluator::Private::Private(ProFileEvaluator *q_)
  : q(q_)
{
    m_prevLineNo = 0;
    m_prevProFile = 0;
    m_verbose = true;
}

bool ProFileEvaluator::Private::read(ProFile *pro)
{
    QFile file(pro->fileName());
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        q->errorMessage(format("%1 not readable.").arg(pro->fileName()));
        return false;
    }

    m_block = 0;
    m_commentItem = 0;
    m_contNextLine = false;
    m_syntaxError = false;
    m_lineNo = 1;
    m_blockstack.clear();
    m_blockstack.push(pro);

    QTextStream ts(&file);
    while (!ts.atEnd()) {
        QString line = ts.readLine();
        if (!parseLine(line)) {
            q->errorMessage(format(".pro parse failure."));
            return false;
        }
        ++m_lineNo;
    }
    return true;
}

bool ProFileEvaluator::Private::parseLine(const QString &line0)
{
    if (m_blockstack.isEmpty())
        return false;

    ushort quote = 0;
    int parens = 0;
    bool contNextLine = false;
    QString line = line0.simplified();

    for (int i = 0; !m_syntaxError && i < line.length(); ++i) {
        ushort c = line.at(i).unicode();
        if (quote && c == quote)
            quote = 0;
        else if (c == '(')
            ++parens;
        else if (c == ')')
            --parens;
        else if (c == '"' && (i == 0 || line.at(i - 1).unicode() != '\\'))
            quote = c;
        else if (!parens && !quote) {
            if (c == '#') {
                insertComment(line.mid(i + 1));
                contNextLine = m_contNextLine;
                break;
            }
            if (c == '\\' && i >= line.count() - 1) {
                updateItem();
                contNextLine = true;
                continue;
            }
            if (m_block && (m_block->blockKind() & ProBlock::VariableKind)) {
                if (c == ' ')
                    updateItem();
                else
                    m_proitem += c;
                continue;
            }
            if (c == ':') {
                enterScope(false);
                continue;
            }
            if (c == '{') {
                enterScope(true);
                continue;
            }
            if (c == '}') {
                leaveScope();
                continue;
            }
            if (c == '=') {
                insertVariable(line, &i);
                continue;
            }
            if (c == '|' || c == '!') {
                insertOperator(c);
                continue;
            }
        }

        m_proitem += c;
    }
    m_contNextLine = contNextLine;

    if (!m_syntaxError) {
        updateItem();
        if (!m_contNextLine)
            finalizeBlock();
    }
    return !m_syntaxError;
}

void ProFileEvaluator::Private::finalizeBlock()
{
    if (m_blockstack.isEmpty()) {
        m_syntaxError = true;
    } else {
        if (m_blockstack.top()->blockKind() & ProBlock::SingleLine)
            leaveScope();
        m_block = 0;
        m_commentItem = 0;
    }
}

void ProFileEvaluator::Private::insertVariable(const QString &line, int *i)
{
    ProVariable::VariableOperator opkind;

    switch (m_proitem.at(m_proitem.length() - 1).unicode()) {
        case '+':
            m_proitem.chop(1);
            opkind = ProVariable::AddOperator;
            break;
        case '-':
            m_proitem.chop(1);
            opkind = ProVariable::RemoveOperator;
            break;
        case '*':
            m_proitem.chop(1);
            opkind = ProVariable::UniqueAddOperator;
            break;
        case '~':
            m_proitem.chop(1);
            opkind = ProVariable::ReplaceOperator;
            break;
        default:
            opkind = ProVariable::SetOperator;
    }

    ProBlock *block = m_blockstack.top();
    m_proitem = m_proitem.trimmed();
    ProVariable *variable = new ProVariable(m_proitem, block);
    variable->setLineNumber(m_lineNo);
    variable->setVariableOperator(opkind);
    block->appendItem(variable);
    m_block = variable;

    if (!m_pendingComment.isEmpty()) {
        m_block->setComment(m_pendingComment);
        m_pendingComment.clear();
    }
    m_commentItem = variable;

    m_proitem.clear();

    if (opkind == ProVariable::ReplaceOperator) {
        // skip util end of line or comment
        while (1) {
            ++(*i);

            // end of line?
            if (*i >= line.count())
                break;

            // comment?
            if (line.at(*i).unicode() == '#') {
                --(*i);
                break;
            }

            m_proitem += line.at(*i);
        }
        m_proitem = m_proitem.trimmed();
    }
}

void ProFileEvaluator::Private::insertOperator(const char op)
{
    updateItem();

    ProOperator::OperatorKind opkind;
    switch(op) {
        case '!':
            opkind = ProOperator::NotOperator;
            break;
        case '|':
            opkind = ProOperator::OrOperator;
            break;
        default:
            opkind = ProOperator::OrOperator;
    }

    ProBlock * const block = currentBlock();
    ProOperator * const proOp = new ProOperator(opkind);
    proOp->setLineNumber(m_lineNo);
    block->appendItem(proOp);
    m_commentItem = proOp;
}

void ProFileEvaluator::Private::insertComment(const QString &comment)
{
    updateItem();

    QString strComment;
    if (!m_commentItem)
        strComment = m_pendingComment;
    else
        strComment = m_commentItem->comment();

    if (strComment.isEmpty())
        strComment = comment;
    else {
        strComment += QLatin1Char('\n');
        strComment += comment.trimmed();
    }

    strComment = strComment.trimmed();

    if (!m_commentItem)
        m_pendingComment = strComment;
    else
        m_commentItem->setComment(strComment);
}

void ProFileEvaluator::Private::enterScope(bool multiLine)
{
    updateItem();

    ProBlock *parent = currentBlock();
    ProBlock *block = new ProBlock(parent);
    block->setLineNumber(m_lineNo);
    parent->setBlockKind(ProBlock::ScopeKind);

    parent->appendItem(block);

    if (multiLine)
        block->setBlockKind(ProBlock::ScopeContentsKind);
    else
        block->setBlockKind(ProBlock::ScopeContentsKind|ProBlock::SingleLine);

    m_blockstack.push(block);
    m_block = 0;
}

void ProFileEvaluator::Private::leaveScope()
{
    updateItem();
    m_blockstack.pop();
    finalizeBlock();
}

ProBlock *ProFileEvaluator::Private::currentBlock()
{
    if (m_block)
        return m_block;

    ProBlock *parent = m_blockstack.top();
    m_block = new ProBlock(parent);
    m_block->setLineNumber(m_lineNo);
    parent->appendItem(m_block);

    if (!m_pendingComment.isEmpty()) {
        m_block->setComment(m_pendingComment);
        m_pendingComment.clear();
    }

    m_commentItem = m_block;

    return m_block;
}

void ProFileEvaluator::Private::updateItem()
{
    m_proitem = m_proitem.trimmed();
    if (m_proitem.isEmpty())
        return;

    ProBlock *block = currentBlock();
    if (block->blockKind() & ProBlock::VariableKind) {
        m_commentItem = new ProValue(m_proitem, static_cast<ProVariable*>(block));
    } else if (m_proitem.endsWith(QLatin1Char(')'))) {
        m_commentItem = new ProFunction(m_proitem);
    } else {
        m_commentItem = new ProCondition(m_proitem);
    }
    m_commentItem->setLineNumber(m_lineNo);
    block->appendItem(m_commentItem);

    m_proitem.clear();
}


bool ProFileEvaluator::Private::visitBeginProBlock(ProBlock *block)
{
    if (block->blockKind() == ProBlock::ScopeKind) {
        m_invertNext = false;
        m_condition = false;
    }
    return true;
}

bool ProFileEvaluator::Private::visitEndProBlock(ProBlock *block)
{
    Q_UNUSED(block);
    return true;
}

bool ProFileEvaluator::Private::visitBeginProVariable(ProVariable *variable)
{
    m_lastVarName = variable->variable();
    m_variableOperator = variable->variableOperator();
    return true;
}

bool ProFileEvaluator::Private::visitEndProVariable(ProVariable *variable)
{
    Q_UNUSED(variable);
    m_lastVarName.clear();
    return true;
}

bool ProFileEvaluator::Private::visitProOperator(ProOperator *oper)
{
    m_invertNext = (oper->operatorKind() == ProOperator::NotOperator);
    return true;
}

bool ProFileEvaluator::Private::visitProCondition(ProCondition *cond)
{
    if (!m_condition) {
        if (m_invertNext)
            m_condition |= !isActiveConfig(cond->text(), true);
        else
            m_condition |= isActiveConfig(cond->text(), true);
    }
    return true;
}

bool ProFileEvaluator::Private::visitBeginProFile(ProFile * pro)
{
    PRE(pro);
    bool ok = true;
    m_lineNo = pro->lineNumber();
    if (m_oldPath.isEmpty()) {
        // change the working directory for the initial profile we visit, since
        // that is *the* profile. All the other times we reach this function will be due to
        // include(file) or load(file)
        m_oldPath = QDir::currentPath();
        m_profileStack.push(pro);
        ok = QDir::setCurrent(pro->directoryName());
    }

    if (m_origfile.isEmpty())
        m_origfile = pro->fileName();

    return ok;
}

bool ProFileEvaluator::Private::visitEndProFile(ProFile * pro)
{
    PRE(pro);
    bool ok = true;
    m_lineNo = pro->lineNumber();
    if (m_profileStack.count() == 1 && !m_oldPath.isEmpty()) {
        m_profileStack.pop();
        ok = QDir::setCurrent(m_oldPath);
    }
    return ok;
}

bool ProFileEvaluator::Private::visitProValue(ProValue *value)
{
    PRE(value);
    m_lineNo = value->lineNumber();
    QString val = value->value();

    QString varName = m_lastVarName;

    QStringList v = expandVariableReferences(val);

    // Since qmake combines different values for the TARGET variable, we join
    // TARGET values that are on the same line. We can't do this later with all
    // values because this parser isn't scope-aware, so we'd risk joining
    // scope-specific targets together.
    if (varName == QLatin1String("TARGET")
            && m_lineNo == m_prevLineNo
            && currentProFile() == m_prevProFile) {
        QStringList targets = m_valuemap.value(QLatin1String("TARGET"));
        m_valuemap.remove(QLatin1String("TARGET"));
        QStringList lastTarget(targets.takeLast());
        lastTarget << v.join(QLatin1String(" "));
        targets.push_back(lastTarget.join(QLatin1String(" ")));
        v = targets;
    }
    m_prevLineNo = m_lineNo;
    m_prevProFile = currentProFile();

    // The following two blocks fix bug 180128 by making all "interesting"
    // file name absolute in each .pro file, not just the top most one
    if (varName == QLatin1String("SOURCES")
            || varName == QLatin1String("HEADERS")
            || varName == QLatin1String("INTERFACES")
            || varName == QLatin1String("FORMS")
            || varName == QLatin1String("FORMS3")
            || varName == QLatin1String("RESOURCES")) {
        // matches only existent files, expand certain(?) patterns
        QStringList vv;
        for (int i = v.count(); --i >= 0; )
            vv << expandPattern(v[i]);
        v = vv;
    }

    if (varName == QLatin1String("TRANSLATIONS")) {
        // also matches non-existent files, but does not expand pattern
        QString dir = QFileInfo(currentFileName()).absolutePath();
        dir += QLatin1Char('/');
        for (int i = v.count(); --i >= 0; )
            v[i] = QFileInfo(dir, v[i]).absoluteFilePath();
    }

    switch (m_variableOperator) {
        case ProVariable::UniqueAddOperator:    // *
            insertUnique(&m_valuemap, varName, v, true);
            insertUnique(&m_filevaluemap[currentProFile()], varName, v, true);
            break;
        case ProVariable::SetOperator:          // =
        case ProVariable::AddOperator:          // +
            insertUnique(&m_valuemap, varName, v, false);
            insertUnique(&m_filevaluemap[currentProFile()], varName, v, false);
            break;
        case ProVariable::RemoveOperator:       // -
            // fix me: interaction between AddOperator and RemoveOperator
            insertUnique(&m_valuemap, varName.prepend(QLatin1Char('-')), v, false);
            insertUnique(&m_filevaluemap[currentProFile()],
                         varName.prepend(QLatin1Char('-')), v, false);
            break;
        case ProVariable::ReplaceOperator:      // ~
            {
                // DEFINES ~= s/a/b/?[gqi]

/*              Create a superset by executing replacement + adding items that have changed
                to original list. We're not sure if this is really the right approach, so for
                the time being we will just do nothing ...

                QChar sep = val.at(1);
                QStringList func = val.split(sep);
                if (func.count() < 3 || func.count() > 4) {
                    q->logMessage(format("'~= operator '(function s///) expects 3 or 4 arguments."));
                    return false;
                }
                if (func[0] != QLatin1String("s")) {
                    q->logMessage(format("~= operator can only handle s/// function."));
                    return false;
                }

                bool global = false, quote = false, case_sense = false;
                if (func.count() == 4) {
                    global = func[3].indexOf(QLatin1Char('g')) != -1;
                    case_sense = func[3].indexOf(QLatin1Char('i')) == -1;
                    quote = func[3].indexOf(QLatin1Char('q')) != -1;
                }
                QString pattern = func[1];
                QString replace = func[2];
                if (quote)
                    pattern = QRegExp::escape(pattern);

                QRegExp regexp(pattern, case_sense ? Qt::CaseSensitive : Qt::CaseInsensitive);

                QStringList replaceList = replaceInList(m_valuemap.value(varName), regexp, replace,
                                                        global);
                // Add changed entries to list
                foreach (const QString &entry, replaceList)
                    if (!m_valuemap.value(varName).contains(entry))
                        insertUnique(&m_valuemap, varName, QStringList() << entry, false);

                replaceList = replaceInList(m_filevaluemap[currentProFile()].value(varName), regexp,
                                      replace, global);
                foreach (const QString &entry, replaceList)
                    if (!m_filevaluemap[currentProFile()].value(varName).contains(entry))
                        insertUnique(&m_filevaluemap[currentProFile()], varName,
                                     QStringList() << entry, false); */
            }
            break;

    }
    return true;
}

bool ProFileEvaluator::Private::visitProFunction(ProFunction *func)
{
    m_lineNo = func->lineNumber();
    bool result = true;
    bool ok = true;
    QString text = func->text();
    int lparen = text.indexOf(QLatin1Char('('));
    int rparen = text.lastIndexOf(QLatin1Char(')'));
    Q_ASSERT(lparen < rparen);

    QString arguments = text.mid(lparen + 1, rparen - lparen - 1);
    QString funcName = text.left(lparen);
    ok &= evaluateConditionalFunction(funcName.trimmed(), arguments, &result);
    return ok;
}


QStringList ProFileEvaluator::Private::qmakeFeaturePaths()
{
    QStringList concat;
    {
        const QString base_concat = QDir::separator() + QString(QLatin1String("features"));
        concat << base_concat + QDir::separator() + QLatin1String("mac");
        concat << base_concat + QDir::separator() + QLatin1String("macx");
        concat << base_concat + QDir::separator() + QLatin1String("unix");
        concat << base_concat + QDir::separator() + QLatin1String("win32");
        concat << base_concat + QDir::separator() + QLatin1String("mac9");
        concat << base_concat + QDir::separator() + QLatin1String("qnx6");
        concat << base_concat;
    }
    const QString mkspecs_concat = QDir::separator() + QString(QLatin1String("mkspecs"));
    QStringList feature_roots;
    QByteArray mkspec_path = qgetenv("QMAKEFEATURES");
    if (!mkspec_path.isNull())
        feature_roots += splitPathList(QString::fromLocal8Bit(mkspec_path));
    /*
    if (prop)
        feature_roots += splitPathList(prop->value("QMAKEFEATURES"));
    if (!Option::mkfile::cachefile.isEmpty()) {
        QString path;
        int last_slash = Option::mkfile::cachefile.lastIndexOf(Option::dir_sep);
        if (last_slash != -1)
            path = Option::fixPathToLocalOS(Option::mkfile::cachefile.left(last_slash));
        foreach (const QString &concat_it, concat)
            feature_roots << (path + concat_it);
    }
    */

    QByteArray qmakepath = qgetenv("QMAKEPATH");
    if (!qmakepath.isNull()) {
        const QStringList lst = splitPathList(QString::fromLocal8Bit(qmakepath));
        foreach (const QString &item, lst) {
            foreach (const QString &concat_it, concat)
                feature_roots << (item + mkspecs_concat + concat_it);
        }
    }
    //if (!Option::mkfile::qmakespec.isEmpty())
    //    feature_roots << Option::mkfile::qmakespec + QDir::separator() + "features";
    //if (!Option::mkfile::qmakespec.isEmpty()) {
    //    QFileInfo specfi(Option::mkfile::qmakespec);
    //    QDir specdir(specfi.absoluteFilePath());
    //    while (!specdir.isRoot()) {
    //        if (!specdir.cdUp() || specdir.isRoot())
    //            break;
    //        if (QFile::exists(specdir.path() + QDir::separator() + "features")) {
    //            foreach (const QString &concat_it, concat)
    //                feature_roots << (specdir.path() + concat_it);
    //            break;
    //        }
    //    }
    //}
    foreach (const QString &concat_it, concat)
        feature_roots << (propertyValue(QLatin1String("QT_INSTALL_PREFIX")) +
                          mkspecs_concat + concat_it);
    foreach (const QString &concat_it, concat)
        feature_roots << (propertyValue(QLatin1String("QT_INSTALL_DATA")) +
                          mkspecs_concat + concat_it);
    return feature_roots;
}

QString ProFileEvaluator::Private::propertyValue(const QString &name) const
{
    if (m_properties.contains(name))
        return m_properties.value(name);
    if (name == QLatin1String("QT_INSTALL_PREFIX"))
        return QLibraryInfo::location(QLibraryInfo::PrefixPath);
    if (name == QLatin1String("QT_INSTALL_DATA"))
        return QLibraryInfo::location(QLibraryInfo::DataPath);
    if (name == QLatin1String("QT_INSTALL_DOCS"))
        return QLibraryInfo::location(QLibraryInfo::DocumentationPath);
    if (name == QLatin1String("QT_INSTALL_HEADERS"))
        return QLibraryInfo::location(QLibraryInfo::HeadersPath);
    if (name == QLatin1String("QT_INSTALL_LIBS"))
        return QLibraryInfo::location(QLibraryInfo::LibrariesPath);
    if (name == QLatin1String("QT_INSTALL_BINS"))
        return QLibraryInfo::location(QLibraryInfo::BinariesPath);
    if (name == QLatin1String("QT_INSTALL_PLUGINS"))
        return QLibraryInfo::location(QLibraryInfo::PluginsPath);
    if (name == QLatin1String("QT_INSTALL_TRANSLATIONS"))
        return QLibraryInfo::location(QLibraryInfo::TranslationsPath);
    if (name == QLatin1String("QT_INSTALL_CONFIGURATION"))
        return QLibraryInfo::location(QLibraryInfo::SettingsPath);
    if (name == QLatin1String("QT_INSTALL_EXAMPLES"))
        return QLibraryInfo::location(QLibraryInfo::ExamplesPath);
    if (name == QLatin1String("QT_INSTALL_DEMOS"))
        return QLibraryInfo::location(QLibraryInfo::DemosPath);
    if (name == QLatin1String("QMAKE_MKSPECS"))
        return qmake_mkspec_paths().join(Option::dirlist_sep);
    if (name == QLatin1String("QMAKE_VERSION"))
        return QLatin1String("1.0");        //### FIXME
        //return qmake_version();
#ifdef QT_VERSION_STR
    if (name == QLatin1String("QT_VERSION"))
        return QLatin1String(QT_VERSION_STR);
#endif
    return QLatin1String("UNKNOWN");        //###
}

ProFile *ProFileEvaluator::Private::currentProFile() const
{
    if (m_profileStack.count() > 0)
        return m_profileStack.top();
    return 0;
}

QString ProFileEvaluator::Private::currentFileName() const
{
    ProFile *pro = currentProFile();
    if (pro)
        return pro->fileName();
    return QString();
}

QString ProFileEvaluator::Private::getcwd() const
{
    ProFile *cur = m_profileStack.top();
    return cur->directoryName();
}

QStringList ProFileEvaluator::Private::expandVariableReferences(const QString &str)
{
    QStringList ret;
//    if (ok)
//        *ok = true;
    if (str.isEmpty())
        return ret;

    const ushort LSQUARE = '[';
    const ushort RSQUARE = ']';
    const ushort LCURLY = '{';
    const ushort RCURLY = '}';
    const ushort LPAREN = '(';
    const ushort RPAREN = ')';
    const ushort DOLLAR = '$';
    const ushort BACKSLASH = '\\';
    const ushort UNDERSCORE = '_';
    const ushort DOT = '.';
    const ushort SPACE = ' ';
    const ushort TAB = '\t';
    const ushort SINGLEQUOTE = '\'';
    const ushort DOUBLEQUOTE = '"';

    ushort unicode, quote = 0;
    const QChar *str_data = str.data();
    const int str_len = str.length();

    ushort term;
    QString var, args;

    int replaced = 0;
    QString current;
    for (int i = 0; i < str_len; ++i) {
        unicode = str_data[i].unicode();
        const int start_var = i;
        if (unicode == DOLLAR && str_len > i+2) {
            unicode = str_data[++i].unicode();
            if (unicode == DOLLAR) {
                term = 0;
                var.clear();
                args.clear();
                enum { VAR, ENVIRON, FUNCTION, PROPERTY } var_type = VAR;
                unicode = str_data[++i].unicode();
                if (unicode == LSQUARE) {
                    unicode = str_data[++i].unicode();
                    term = RSQUARE;
                    var_type = PROPERTY;
                } else if (unicode == LCURLY) {
                    unicode = str_data[++i].unicode();
                    var_type = VAR;
                    term = RCURLY;
                } else if (unicode == LPAREN) {
                    unicode = str_data[++i].unicode();
                    var_type = ENVIRON;
                    term = RPAREN;
                }
                forever {
                    if (!(unicode & (0xFF<<8)) &&
                       unicode != DOT && unicode != UNDERSCORE &&
                       //unicode != SINGLEQUOTE && unicode != DOUBLEQUOTE &&
                       (unicode < 'a' || unicode > 'z') && (unicode < 'A' || unicode > 'Z') &&
                       (unicode < '0' || unicode > '9'))
                        break;
                    var.append(QChar(unicode));
                    if (++i == str_len)
                        break;
                    unicode = str_data[i].unicode();
                    // at this point, i points to either the 'term' or 'next' character (which is in unicode)
                }
                if (var_type == VAR && unicode == LPAREN) {
                    var_type = FUNCTION;
                    int depth = 0;
                    forever {
                        if (++i == str_len)
                            break;
                        unicode = str_data[i].unicode();
                        if (unicode == LPAREN) {
                            depth++;
                        } else if (unicode == RPAREN) {
                            if (!depth)
                                break;
                            --depth;
                        }
                        args.append(QChar(unicode));
                    }
                    if (++i < str_len)
                        unicode = str_data[i].unicode();
                    else
                        unicode = 0;
                    // at this point i is pointing to the 'next' character (which is in unicode)
                    // this might actually be a term character since you can do $${func()}
                }
                if (term) {
                    if (unicode != term) {
                        q->logMessage(format("Missing %1 terminator [found %2]")
                            .arg(QChar(term))
                            .arg(unicode ? QString(unicode) : QString::fromLatin1(("end-of-line"))));
//                        if (ok)
//                            *ok = false;
                        return QStringList();
                    }
                } else {
                    // move the 'cursor' back to the last char of the thing we were looking at
                    --i;
                }
                // since i never points to the 'next' character, there is no reason for this to be set
                unicode = 0;

                QStringList replacement;
                if (var_type == ENVIRON) {
                    replacement = split_value_list(QString::fromLocal8Bit(qgetenv(var.toLatin1().constData())));
                } else if (var_type == PROPERTY) {
                    replacement << propertyValue(var);
                } else if (var_type == FUNCTION) {
                    replacement << evaluateExpandFunction(var, args);
                } else if (var_type == VAR) {
                    replacement = values(var);
                }
                if (!(replaced++) && start_var)
                    current = str.left(start_var);
                if (!replacement.isEmpty()) {
                    if (quote) {
                        current += replacement.join(QString(Option::field_sep));
                    } else {
                        current += replacement.takeFirst();
                        if (!replacement.isEmpty()) {
                            if (!current.isEmpty())
                                ret.append(current);
                            current = replacement.takeLast();
                            if (!replacement.isEmpty())
                                ret += replacement;
                        }
                    }
                }
            } else {
                if (replaced)
                    current.append(QLatin1Char('$'));
            }
        }
        if (quote && unicode == quote) {
            unicode = 0;
            quote = 0;
        } else if (unicode == BACKSLASH) {
            bool escape = false;
            const char *symbols = "[]{}()$\\'\"";
            for (const char *s = symbols; *s; ++s) {
                if (str_data[i+1].unicode() == (ushort)*s) {
                    i++;
                    escape = true;
                    if (!(replaced++))
                        current = str.left(start_var);
                    current.append(str.at(i));
                    break;
                }
            }
            if (escape || !replaced)
                unicode =0;
        } else if (!quote && (unicode == SINGLEQUOTE || unicode == DOUBLEQUOTE)) {
            quote = unicode;
            unicode = 0;
            if (!(replaced++) && i)
                current = str.left(i);
        } else if (!quote && (unicode == SPACE || unicode == TAB)) {
            unicode = 0;
            if (!current.isEmpty()) {
                ret.append(current);
                current.clear();
            }
        }
        if (replaced && unicode)
            current.append(QChar(unicode));
    }
    if (!replaced)
        ret = QStringList(str);
    else if (!current.isEmpty())
        ret.append(current);
    return ret;
}

bool ProFileEvaluator::Private::isActiveConfig(const QString &config, bool regex)
{
    // magic types for easy flipping
    if (config == QLatin1String("true"))
        return true;
    if (config == QLatin1String("false"))
        return false;

    // mkspecs
    if ((Option::target_mode == Option::TARG_MACX_MODE
            || Option::target_mode == Option::TARG_QNX6_MODE
            || Option::target_mode == Option::TARG_UNIX_MODE)
          && config == QLatin1String("unix"))
        return true;
    if (Option::target_mode == Option::TARG_MACX_MODE && config == QLatin1String("macx"))
        return true;
    if (Option::target_mode == Option::TARG_QNX6_MODE && config == QLatin1String("qnx6"))
        return true;
    if (Option::target_mode == Option::TARG_MAC9_MODE && config == QLatin1String("mac9"))
        return true;
    if ((Option::target_mode == Option::TARG_MAC9_MODE
            || Option::target_mode == Option::TARG_MACX_MODE)
          && config == QLatin1String("mac"))
        return true;
    if (Option::target_mode == Option::TARG_WIN_MODE && config == QLatin1String("win32"))
        return true;

    QRegExp re(config, Qt::CaseSensitive, QRegExp::Wildcard);
    QString spec = Option::qmakespec;
    if ((regex && re.exactMatch(spec)) || (!regex && spec == config))
        return true;

    return false;
}

QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &func, const QString &arguments)
{
    QStringList argumentsList = split_arg_list(arguments);

    QStringList args;
    for (int i = 0; i < argumentsList.count(); ++i)
        args += expandVariableReferences(argumentsList[i]).join(Option::field_sep);

    enum ExpandFunc { E_MEMBER=1, E_FIRST, E_LAST, E_CAT, E_FROMFILE, E_EVAL, E_LIST,
                      E_SPRINTF, E_JOIN, E_SPLIT, E_BASENAME, E_DIRNAME, E_SECTION,
                      E_FIND, E_SYSTEM, E_UNIQUE, E_QUOTE, E_ESCAPE_EXPAND,
                      E_UPPER, E_LOWER, E_FILES, E_PROMPT, E_RE_ESCAPE,
                      E_REPLACE };

    static QHash<QString, int> *expands = 0;
    if (!expands) {
        expands = new QHash<QString, int>;
        expands->insert(QLatin1String("member"), E_MEMBER);                //v (implemented)
        expands->insert(QLatin1String("first"), E_FIRST);                  //v
        expands->insert(QLatin1String("last"), E_LAST);                    //v
        expands->insert(QLatin1String("cat"), E_CAT);
        expands->insert(QLatin1String("fromfile"), E_FROMFILE);
        expands->insert(QLatin1String("eval"), E_EVAL);
        expands->insert(QLatin1String("list"), E_LIST);
        expands->insert(QLatin1String("sprintf"), E_SPRINTF);
        expands->insert(QLatin1String("join"), E_JOIN);                    //v
        expands->insert(QLatin1String("split"), E_SPLIT);                  //v
        expands->insert(QLatin1String("basename"), E_BASENAME);            //v
        expands->insert(QLatin1String("dirname"), E_DIRNAME);              //v
        expands->insert(QLatin1String("section"), E_SECTION);
        expands->insert(QLatin1String("find"), E_FIND);
        expands->insert(QLatin1String("system"), E_SYSTEM);                //v
        expands->insert(QLatin1String("unique"), E_UNIQUE);
        expands->insert(QLatin1String("quote"), E_QUOTE);                  //v
        expands->insert(QLatin1String("escape_expand"), E_ESCAPE_EXPAND);
        expands->insert(QLatin1String("upper"), E_UPPER);
        expands->insert(QLatin1String("lower"), E_LOWER);
        expands->insert(QLatin1String("re_escape"), E_RE_ESCAPE);
        expands->insert(QLatin1String("files"), E_FILES);
        expands->insert(QLatin1String("prompt"), E_PROMPT);
        expands->insert(QLatin1String("replace"), E_REPLACE);
    }
    ExpandFunc func_t = ExpandFunc(expands->value(func.toLower()));

    QStringList ret;

    switch (func_t) {
        case E_BASENAME:
        case E_DIRNAME:
        case E_SECTION: {
            bool regexp = false;
            QString sep, var;
            int beg = 0;
            int end = -1;
            if (func_t == E_SECTION) {
                if (args.count() != 3 && args.count() != 4) {
                    q->logMessage(format("%1(var) section(var, sep, begin, end) "
                        "requires three or four arguments.").arg(func));
                } else {
                    var = args[0];
                    sep = args[1];
                    beg = args[2].toInt();
                    if (args.count() == 4)
                        end = args[3].toInt();
                }
            } else {
                if (args.count() != 1) {
                    q->logMessage(format("%1(var) requires one argument.").arg(func));
                } else {
                    var = args[0];
                    regexp = true;
                    sep = QLatin1String("[\\\\/]");
                    if (func_t == E_DIRNAME)
                        end = -2;
                    else
                        beg = -1;
                }
            }
            if (!var.isNull()) {
                foreach (const QString str, values(var)) {
                    if (regexp)
                        ret += str.section(QRegExp(sep), beg, end);
                    else
                        ret += str.section(sep, beg, end);
                }
            }
            break;
        }
        case E_JOIN: {
            if (args.count() < 1 || args.count() > 4) {
                q->logMessage(format("join(var, glue, before, after) requires one to four arguments."));
            } else {
                QString glue, before, after;
                if (args.count() >= 2)
                    glue = args[1];
                if (args.count() >= 3)
                    before = args[2];
                if (args.count() == 4)
                    after = args[3];
                const QStringList &var = values(args.first());
                if (!var.isEmpty())
                    ret.append(before + var.join(glue) + after);
            }
            break;
        }
        case E_SPLIT: {
            if (args.count() != 2) {
                q->logMessage(format("split(var, sep) requires two arguments"));
            } else {
                QString sep = args.at(1);
                foreach (const QString &var, values(args.first()))
                    foreach (const QString &splt, var.split(sep))
                        ret.append(splt);
            }
            break;
        }
        case E_MEMBER: {
            if (args.count() < 1 || args.count() > 3) {
                q->logMessage(format("member(var, start, end) requires one to three arguments."));
            } else {
                bool ok = true;
                const QStringList var = values(args.first());
                int start = 0, end = 0;
                if (args.count() >= 2) {
                    QString start_str = args[1];
                    start = start_str.toInt(&ok);
                    if (!ok) {
                        if (args.count() == 2) {
                            int dotdot = start_str.indexOf(QLatin1String(".."));
                            if (dotdot != -1) {
                                start = start_str.left(dotdot).toInt(&ok);
                                if (ok)
                                    end = start_str.mid(dotdot+2).toInt(&ok);
                            }
                        }
                        if (!ok)
                            q->logMessage(format("member() argument 2 (start) '%2' invalid.")
                                .arg(start_str));
                    } else {
                        end = start;
                        if (args.count() == 3)
                            end = args[2].toInt(&ok);
                        if (!ok)
                            q->logMessage(format("member() argument 3 (end) '%2' invalid.\n")
                                .arg(args[2]));
                    }
                }
                if (ok) {
                    if (start < 0)
                        start += var.count();
                    if (end < 0)
                        end += var.count();
                    if (start < 0 || start >= var.count() || end < 0 || end >= var.count()) {
                        //nothing
                    } else if (start < end) {
                        for (int i = start; i <= end && var.count() >= i; i++)
                            ret.append(var[i]);
                    } else {
                        for (int i = start; i >= end && var.count() >= i && i >= 0; i--)
                            ret += var[i];
                    }
                }
            }
            break;
        }
        case E_FIRST:
        case E_LAST: {
            if (args.count() != 1) {
                q->logMessage(format("%1(var) requires one argument.").arg(func));
            } else {
                const QStringList var = values(args.first());
                if (!var.isEmpty()) {
                    if (func_t == E_FIRST)
                        ret.append(var[0]);
                    else
                        ret.append(var.last());
                }
            }
            break;
        }
        case E_SYSTEM: {
            if (m_condition) {
                if (args.count() < 1 || args.count() > 2) {
                    q->logMessage(format("system(execute) requires one or two arguments."));
                } else {
                    char buff[256];
                    FILE *proc = QT_POPEN(args[0].toLatin1(), "r");
                    bool singleLine = true;
                    if (args.count() > 1)
                        singleLine = (args[1].toLower() == QLatin1String("true"));
                    QString output;
                    while (proc && !feof(proc)) {
                        int read_in = int(fread(buff, 1, 255, proc));
                        if (!read_in)
                            break;
                        for (int i = 0; i < read_in; i++) {
                            if ((singleLine && buff[i] == '\n') || buff[i] == '\t')
                                buff[i] = ' ';
                        }
                        buff[read_in] = '\0';
                        output += QLatin1String(buff);
                    }
                    ret += split_value_list(output);
                }
            }
            break; }
        case E_QUOTE:
            for (int i = 0; i < args.count(); ++i)
                ret += QStringList(args.at(i));
            break;
        case 0:
            q->logMessage(format("'%1' is not a function").arg(func));
            break;
        default:
            q->logMessage(format("Function '%1' is not implemented").arg(func));
            break;
    }

    return ret;
}

bool ProFileEvaluator::Private::evaluateConditionalFunction(const QString &function,
    const QString &arguments, bool *result)
{
    QStringList argumentsList = split_arg_list(arguments);
    QString sep;
    sep.append(Option::field_sep);

    QStringList args;
    for (int i = 0; i < argumentsList.count(); ++i)
        args += expandVariableReferences(argumentsList[i]).join(sep);

    enum ConditionFunc { CF_CONFIG = 1, CF_CONTAINS, CF_COUNT, CF_EXISTS, CF_INCLUDE,
        CF_LOAD, CF_ISEMPTY, CF_SYSTEM, CF_MESSAGE};

    static QHash<QString, int> *functions = 0;
    if (!functions) {
        functions = new QHash<QString, int>;
        functions->insert(QLatin1String("load"), CF_LOAD);         //v
        functions->insert(QLatin1String("include"), CF_INCLUDE);   //v
        functions->insert(QLatin1String("message"), CF_MESSAGE);   //v
        functions->insert(QLatin1String("warning"), CF_MESSAGE);   //v
        functions->insert(QLatin1String("error"), CF_MESSAGE);     //v
    }

    bool cond = false;
    bool ok = true;

    ConditionFunc func_t = (ConditionFunc)functions->value(function);

    switch (func_t) {
        case CF_CONFIG: {
            if (args.count() < 1 || args.count() > 2) {
                q->logMessage(format("CONFIG(config) requires one or two arguments."));
                ok = false;
                break;
            }
            if (args.count() == 1) {
                //cond = isActiveConfig(args.first()); XXX
                break;
            }
            const QStringList mutuals = args[1].split(QLatin1Char('|'));
            const QStringList &configs = valuesDirect(QLatin1String("CONFIG"));
            for (int i = configs.size() - 1 && ok; i >= 0; i--) {
                for (int mut = 0; mut < mutuals.count(); mut++) {
                    if (configs[i] == mutuals[mut].trimmed()) {
                        cond = (configs[i] == args[0]);
                        goto done_T_CONFIG;
                    }
                }
            }
          done_T_CONFIG:
            break;
        }
        case CF_CONTAINS: {
            if (args.count() < 2 || args.count() > 3) {
                q->logMessage(format("contains(var, val) requires two or three arguments."));
                ok = false;
                break;
            }

            QRegExp regx(args[1]);
            const QStringList &l = values(args.first());
            if (args.count() == 2) {
                for (int i = 0; i < l.size(); ++i) {
                    const QString val = l[i];
                    if (regx.exactMatch(val) || val == args[1]) {
                        cond = true;
                        break;
                    }
                }
            } else {
                const QStringList mutuals = args[2].split(QLatin1Char('|'));
                for (int i = l.size() - 1; i >= 0; i--) {
                    const QString val = l[i];
                    for (int mut = 0; mut < mutuals.count(); mut++) {
                        if (val == mutuals[mut].trimmed()) {
                            cond = (regx.exactMatch(val) || val == args[1]);
                            goto done_T_CONTAINS;
                        }
                    }
                }
            }
          done_T_CONTAINS:
            break;
        }
        case CF_COUNT: {
            if (args.count() != 2 && args.count() != 3) {
                q->logMessage(format("count(var, count) requires two or three arguments."));
                ok = false;
                break;
            }
            if (args.count() == 3) {
                QString comp = args[2];
                if (comp == QLatin1String(">") || comp == QLatin1String("greaterThan")) {
                    cond = values(args.first()).count() > args[1].toInt();
                } else if (comp == QLatin1String(">=")) {
                    cond = values(args.first()).count() >= args[1].toInt();
                } else if (comp == QLatin1String("<") || comp == QLatin1String("lessThan")) {
                    cond = values(args.first()).count() < args[1].toInt();
                } else if (comp == QLatin1String("<=")) {
                    cond = values(args.first()).count() <= args[1].toInt();
                } else if (comp == QLatin1String("equals") || comp == QLatin1String("isEqual") || comp == QLatin1String("=") || comp == QLatin1String("==")) {
                    cond = values(args.first()).count() == args[1].toInt();
                } else {
                    ok = false;
                    q->logMessage(format("unexpected modifier to count(%2)").arg(comp));
                }
                break;
            }
            cond = values(args.first()).count() == args[1].toInt();
            break;
        }
        case CF_INCLUDE: {
            QString parseInto;
            if (args.count() == 2) {
                parseInto = args[1];
            } else if (args.count() != 1) {
                q->logMessage(format("include(file) requires one or two arguments."));
                ok = false;
                break;
            }
            QString fileName = args.first();
            // ### this breaks if we have include(c:/reallystupid.pri) but IMHO that's really bad style.
            QDir currentProPath(getcwd());
            fileName = QDir::cleanPath(currentProPath.absoluteFilePath(fileName));
            ok = evaluateFile(fileName, &ok);
            break;
        }
        case CF_LOAD: {
            QString parseInto;
            bool ignore_error = false;
            if (args.count() == 2) {
                QString sarg = args[1];
                ignore_error = (sarg.toLower() == QLatin1String("true") || sarg.toInt());
            } else if (args.count() != 1) {
                q->logMessage(format("load(feature) requires one or two arguments."));
                ok = false;
                break;
            }
            ok = evaluateFeatureFile( args.first(), &cond);
            break;
        }
        case CF_MESSAGE: {
            if (args.count() != 1) {
                q->logMessage(format("%1(message) requires one argument.").arg(function));
                ok = false;
                break;
            }
            QString msg = args.first();
            if (function == QLatin1String("error")) {
                QStringList parents;
                foreach (ProFile *proFile, m_profileStack)
                    parents.append(proFile->fileName());
                if (!parents.isEmpty())
                    parents.takeLast();
                if (parents.isEmpty())
                    q->fileMessage(format("Project ERROR: %1").arg(msg));
                else
                    q->fileMessage(format("Project ERROR: %1. File was included from: '%2'")
                        .arg(msg).arg(parents.join(QLatin1String("', '"))));
            } else {
                q->fileMessage(format("Project MESSAGE: %1").arg(msg));
            }
            break;
        }
        case CF_SYSTEM: {
            if (args.count() != 1) {
                q->logMessage(format("system(exec) requires one argument."));
                ok = false;
                break;
            }
            ok = system(args.first().toLatin1().constData()) == 0;
            break;
        }
        case CF_ISEMPTY: {
            if (args.count() != 1) {
                q->logMessage(format("isEmpty(var) requires one argument."));
                ok = false;
                break;
            }
            QStringList sl = values(args.first());
            if (sl.count() == 0) {
                cond = true;
            } else if (sl.count() > 0) {
                QString var = sl.first();
                cond = (var.isEmpty());
            }
            break;
        }
        case CF_EXISTS: {
            if (args.count() != 1) {
                q->logMessage(format("exists(file) requires one argument."));
                ok = false;
                break;
            }
            QString file = args.first();

            file = QDir::cleanPath(file);

            if (QFile::exists(file)) {
                cond = true;
                break;
            }
            //regular expression I guess
            QString dirstr = getcwd();
            int slsh = file.lastIndexOf(Option::dir_sep);
            if (slsh != -1) {
                dirstr = file.left(slsh+1);
                file = file.right(file.length() - slsh - 1);
            }
            cond = QDir(dirstr).entryList(QStringList(file)).count();

            break;
        }
    }

    if (result)
        *result = cond;

    return ok;
}

QStringList ProFileEvaluator::Private::values(const QString &variableName) const
{
    if (variableName == QLatin1String("TARGET")) {
        QStringList list = m_valuemap.value(variableName);
        if (!m_origfile.isEmpty())
            list.append(QFileInfo(m_origfile).baseName());
        return list;
    }
    if (variableName == QLatin1String("PWD")) {
        return QStringList(getcwd());
    }
    return m_valuemap.value(variableName);
}

QStringList ProFileEvaluator::Private::values(const QString &variableName, const ProFile *pro) const
{
    if (variableName == QLatin1String("TARGET")) {
        QStringList list = m_filevaluemap[pro].value(variableName);
        if (!m_origfile.isEmpty())
            list.append(QFileInfo(m_origfile).baseName());
        return list;
    }
    if (variableName == QLatin1String("PWD")) {
        return QStringList(QFileInfo(pro->fileName()).absoluteFilePath());
    }
    return m_filevaluemap[pro].value(variableName);
}

ProFile *ProFileEvaluator::parsedProFile(const QString &fileName)
{
    QFileInfo fi(fileName);
    if (fi.exists()) {
        ProFile *pro = new ProFile(fi.absoluteFilePath());
        if (d->read(pro))
            return pro;
        delete pro;
    }
    return 0;
}

void ProFileEvaluator::releaseParsedProFile(ProFile *proFile)
{
    delete proFile;
}

bool ProFileEvaluator::Private::evaluateFile(const QString &fileName, bool *result)
{
    bool ok = true;
    ProFile *pro = q->parsedProFile(fileName);
    if (pro) {
        m_profileStack.push(pro);
        ok = (currentProFile() ? pro->Accept(this) : false);
        m_profileStack.pop();
        q->releaseParsedProFile(pro);

        if (result)
            *result = true;
    } else {
        if (result)
            *result = false;
    }
/*    if (ok && readFeatures) {
        QStringList configs = values("CONFIG");
        QSet<QString> processed;
        foreach (const QString &fn, configs) {
            if (!processed.contains(fn)) {
                processed.insert(fn);
                evaluateFeatureFile(fn, 0);
            }
        }
    } */

    return ok;
}

bool ProFileEvaluator::Private::evaluateFeatureFile(const QString &fileName, bool *result)
{
    QString fn;
    foreach (const QString &path, qmakeFeaturePaths()) {
        QString fname = path + QLatin1Char('/') + fileName;
        if (QFileInfo(fname).exists()) {
            fn = fname;
            break;
        }
        fname += QLatin1String(".prf");
        if (QFileInfo(fname).exists()) {
            fn = fname;
            break;
        }
    }
    return fn.isEmpty() ? false : evaluateFile(fn, result);
}

void ProFileEvaluator::Private::expandPatternHelper(const QString &relName, const QString &absName,
        QStringList &sources_out)
{
    const QStringList vpaths = values(QLatin1String("VPATH"))
        + values(QLatin1String("QMAKE_ABSOLUTE_SOURCE_PATH"))
        + values(QLatin1String("DEPENDPATH"))
        + values(QLatin1String("VPATH_SOURCES"));

    QFileInfo fi(absName);
    bool found = fi.exists();
    // Search in all vpaths
    if (!found) {
        foreach (const QString &vpath, vpaths) {
            fi.setFile(vpath + QDir::separator() + relName);
            if (fi.exists()) {
                found = true;
                break;
            }
        }
    }

    if (found) {
        sources_out += fi.absoluteFilePath(); // Not resolving symlinks
    } else {
        QString val = relName;
        QString dir;
        QString wildcard = val;
        QString real_dir;
        if (wildcard.lastIndexOf(QLatin1Char('/')) != -1) {
            dir = wildcard.left(wildcard.lastIndexOf(QLatin1Char('/')) + 1);
            real_dir = dir;
            wildcard = wildcard.right(wildcard.length() - dir.length());
        }

        if (real_dir.isEmpty() || QFileInfo(real_dir).exists()) {
            QStringList files = QDir(real_dir).entryList(QStringList(wildcard));
            if (files.isEmpty()) {
                q->logMessage(format("Failure to find %1").arg(val));
            } else {
                QString a;
                for (int i = files.count() - 1; i >= 0; --i) {
                    if (files[i] == QLatin1String(".") || files[i] == QLatin1String(".."))
                        continue;
                    a = dir + files[i];
                    sources_out += a;
                }
            }
        } else {
            q->logMessage(format("Cannot match %1/%2, as %3 does not exist.")
                .arg(real_dir).arg(wildcard).arg(real_dir));
        }
    }
}


/*
 * Lookup of files are done in this order:
 *  1. look in pwd
 *  2. look in vpaths
 *  3. expand wild card files relative from the profiles folder
 **/

// FIXME: This code supports something that I'd consider a flaw in .pro file syntax
// which is not even documented. So arguably this can be ditched completely...
QStringList ProFileEvaluator::Private::expandPattern(const QString& pattern)
{
    if (!currentProFile())
        return QStringList();

    QStringList sources_out;
    const QString absName = QDir::cleanPath(QDir::current().absoluteFilePath(pattern));

    expandPatternHelper(pattern, absName, sources_out);
    return sources_out;
}

QString ProFileEvaluator::Private::format(const char *fmt) const
{
    ProFile *pro = currentProFile();
    QString fileName = pro ? pro->fileName() : QLatin1String("Not a file");
    int lineNumber = pro ? m_lineNo : 0;
    return QString::fromLatin1("%1(%2):").arg(fileName).arg(lineNumber) + QString::fromAscii(fmt);
}


///////////////////////////////////////////////////////////////////////
//
// ProFileEvaluator
//
///////////////////////////////////////////////////////////////////////

ProFileEvaluator::ProFileEvaluator()
  : d(new Private(this))
{
    Option::init();
}

ProFileEvaluator::~ProFileEvaluator()
{
    delete d;
}

bool ProFileEvaluator::contains(const QString &variableName) const
{
    return d->m_valuemap.contains(variableName);
}

QStringList ProFileEvaluator::values(const QString &variableName) const
{
    return d->values(variableName);
}

QStringList ProFileEvaluator::values(const QString &variableName, const ProFile *pro) const
{
    return d->values(variableName, pro);
}

ProFileEvaluator::TemplateType ProFileEvaluator::templateType()
{
    QStringList templ = values(QLatin1String("TEMPLATE"));
    if (templ.count() >= 1) {
        QString t = templ.last().toLower();
        if (t == QLatin1String("app"))
            return TT_Application;
        if (t == QLatin1String("lib"))
            return TT_Library;
        if (t == QLatin1String("subdirs"))
            return TT_Subdirs;
    }
    return TT_Unknown;
}

bool ProFileEvaluator::queryProFile(ProFile *pro)
{
    return d->read(pro);
}

bool ProFileEvaluator::accept(ProFile *pro)
{
    return pro->Accept(d);
}

QString ProFileEvaluator::propertyValue(const QString &name) const
{
    return d->propertyValue(name);
}

namespace {
    template<class K, class T> void insert(QHash<K,T> *out, const QHash<K,T> &in)
    {
        typename QHash<K,T>::const_iterator i = in.begin();
        while (i != in.end()) {
            out->insert(i.key(), i.value());
            ++i;
        }
    }
} // anon namespace

void ProFileEvaluator::addVariables(const QHash<QString, QStringList> &variables)
{
    insert(&(d->m_valuemap), variables);
}

void ProFileEvaluator::addProperties(const QHash<QString, QString> &properties)
{
    insert(&(d->m_properties), properties);
}

void ProFileEvaluator::logMessage(const QString &message)
{
    if (d->m_verbose)
        qWarning("%s", qPrintable(message));
}

void ProFileEvaluator::fileMessage(const QString &message)
{
    qWarning("%s", qPrintable(message));
}

void ProFileEvaluator::errorMessage(const QString &message)
{
    qWarning("%s", qPrintable(message));
}

void ProFileEvaluator::setVerbose(bool on)
{
    d->m_verbose = on;
}

void evaluateProFile(const ProFileEvaluator &visitor, QHash<QByteArray, QStringList> *varMap)
{
    QStringList sourceFiles;
    QString codecForTr;
    QString codecForSource;
    QStringList tsFileNames;

    // app/lib template
    sourceFiles += visitor.values(QLatin1String("SOURCES"));
    sourceFiles += visitor.values(QLatin1String("HEADERS"));
    tsFileNames = visitor.values(QLatin1String("TRANSLATIONS"));

    QStringList trcodec = visitor.values(QLatin1String("CODEC"))
        + visitor.values(QLatin1String("DEFAULTCODEC"))
        + visitor.values(QLatin1String("CODECFORTR"));
    if (!trcodec.isEmpty())
        codecForTr = trcodec.last();

    QStringList srccodec = visitor.values(QLatin1String("CODECFORSRC"));
    if (!srccodec.isEmpty())
        codecForSource = srccodec.last();

    QStringList forms = visitor.values(QLatin1String("INTERFACES"))
        + visitor.values(QLatin1String("FORMS"))
        + visitor.values(QLatin1String("FORMS3"));
    sourceFiles << forms;

    sourceFiles.sort();
    sourceFiles.removeDuplicates();
    tsFileNames.sort();
    tsFileNames.removeDuplicates();

    varMap->insert("SOURCES", sourceFiles);
    varMap->insert("CODECFORTR", QStringList() << codecForTr);
    varMap->insert("CODECFORSRC", QStringList() << codecForSource);
    varMap->insert("TRANSLATIONS", tsFileNames);
}

bool evaluateProFile(const QString &fileName, bool verbose, QHash<QByteArray, QStringList> *varMap)
{
    QFileInfo fi(fileName);
    if (!fi.exists())
        return false;

    ProFile pro(fi.absoluteFilePath());

    ProFileEvaluator visitor;
    visitor.setVerbose(verbose);

    if (!visitor.queryProFile(&pro))
        return false;

    if (!visitor.accept(&pro))
        return false;

    evaluateProFile(visitor, varMap);

    return true;
}

QT_END_NAMESPACE
