/****************************************************************************
**
** Copyright (C) 1992-2005 Trolltech AS. All rights reserved.
**
** This file is part of the porting application of the Qt Toolkit.
**
** This file may be distributed under the terms of the Q Public License
** as defined by Trolltech AS of Norway and appearing in the file
** LICENSE.QPL included in the packaging of this file.
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.
**
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
**   information about Qt Commercial License Agreements.
** See http://www.trolltech.com/qpl/ for QPL licensing information.
** See http://www.trolltech.com/gpl/ for GPL licensing information.
**
** Contact info@trolltech.com if any conditions of this licensing are
** not clear to you.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/
#include <iostream>
#include <QFile>
#include <QDir>
#include <QFileInfo>
#include "preprocessorcontrol.h"
#include "fileporter.h"
#include "replacetoken.h"
#include "logger.h"
#include "tokenizer.h"

using namespace TokenEngine;
using std::cout;
using std::endl;

/*
QByteArray FilePorter::loadFile(const QString &filePath)
{
    QFile f(filePath);
    f.open(QIODevice::ReadOnly);
    return f.readAll();
}
*/
FilePorter::FilePorter(PreprocessorCache &preprocessorCache)
:preprocessorCache(preprocessorCache)
,tokenReplacementRules(PortingRules::instance()->getTokenReplacementRules())
,replaceToken(tokenReplacementRules)
{
    int dummy=0;
    foreach(QString headerName, PortingRules::instance()->getHeaderList(PortingRules::Qt4)) {
        qt4HeaderNames.insert(headerName, dummy);
    }
}

/*
    Ports a file given by fileName, which should be an aboslute file path.
*/
void FilePorter::port(QString fileName)
{
    //Get file tokens from cache.
    TokenContainer sourceTokens = preprocessorCache.sourceTokens(fileName);
    if(sourceTokens.count() == 0)
        return;

    Logger::instance()->beginSection();

    //Perform token replacements.
    QByteArray portedContents =
        replaceToken.getTokenTextReplacements(sourceTokens).apply(sourceTokens.fullText());

    //This step needs to be done after the token replacements, since
    //we need to know which new class names that has been inserted in the source.
    portedContents = includeAnalyse(portedContents);

    //check if any changes has been made.
    if(portedContents == sourceTokens.fullText()) {
        Logger::instance()->addEntry(
            new PlainLogEntry("Info", "Porting",  QLatin1String("No changes made to file ") + fileName));
        Logger::instance()->commitSection();
        return;
    }

    //Write file, commit log if write was successful
    if (FileWriter::instance()->writeFileVerbously(fileName, portedContents)) {
        Logger::instance()->commitSection();
    } else {
        Logger::instance()->revertSection();
        Logger::instance()->addEntry(
            new PlainLogEntry("Error", "Porting",  QLatin1String("Error writing to file ") + fileName));
    }
}

QByteArray FilePorter::includeAnalyse(QByteArray fileContents)
{
    //tokenize file contents agein, since it has changed.
    QList<TokenEngine::Token> tokens  = tokenizer.tokenize(fileContents);
    TokenEngine::TokenContainer tokenContainer(fileContents, tokens);

    QMap<QByteArray, int> classes;
    QMap<QByteArray, int> headers;

    //Get list of used classes and included headers
    int t = 0;
    const int numTokens = tokenContainer.count();
    while(t < numTokens )
    {
        QByteArray tokenText = tokenContainer.text(t);
        if(tokenText[0] =='Q' && qt4HeaderNames.contains(tokenText))
            classes.insert(tokenText, 0);
        else if(tokenText.startsWith("#include")) {
            QString tokenString(tokenText);
            QStringList subTokenList = tokenString.split(QRegExp("<|>|\""), QString::SkipEmptyParts);
            foreach(QString subToken, subTokenList) {
                if(subToken[0] == 'Q' || subToken[0] == 'q')
                    headers.insert(subToken.toLatin1(), 0);
            }
        }
        ++t;
    }

    //compare class and header names, find classes that lacks a
    //curresponding include directive
    QStringList neededHeaders = PortingRules::instance()->getNeededHeaderList();
    QStringList headersToInsert;
    QMapIterator<QByteArray, int> c(classes);
    //loop through classes
    while (c.hasNext()) {
        c.next();
        bool foundHeader=false;
        QMapIterator<QByteArray, int> h(headers);
        //loop through headers
        while (h.hasNext() && !foundHeader) {
            h.next();
            if(h.key().toLower().startsWith(c.key().toLower()))
                foundHeader=true; //we found a header for class c
        }
        if(!foundHeader){
            //we have a class without a coresponding header.
            bool needHeader=false;
            //loop through list of headers
            foreach(QString header, neededHeaders) {
                //compare class name with header
                //TODO: assumes that only new-style headers are specified in the
                //rules. This is ok for now.
                if(header.toLatin1() == c.key()) {
                    needHeader=true;
                    break;
                }
            }
            if(needHeader) {
                headersToInsert.push_back(QString("#include <" + c.key() + ">"));
             //   printf("Must insert header file for class  %s \n ", c.key().constData());
            }
        }
    }

    //insert headers in files, at the end of the first block of
    //include files
    //TODO: make this more intelligent by not inserting inside #ifdefs
    bool includeEnd = false;
    bool includeStart = false;
    t = 0;
    while(t < numTokens && !includeEnd)
    {
        QByteArray tokenText = tokenContainer.text(t);
        if(tokenText.trimmed().startsWith("#include"))
            includeStart=true;
        else if(includeStart &&(!tokenText.trimmed().isEmpty() || tokenText == "\n" ))
            includeEnd=true;
        ++t;
    }
    /*
    //back up a bit to get just at the end of the last include
    while(inStream->currentTokenText().trimmed())  ) {
        inStream->rewind(inStream->cursor()-1);
    }
    */
    int insertPos=0;
    if(includeStart != false)
        insertPos = tokenContainer.token(t).start;

    int insertCount = headersToInsert.count();
    if(insertCount>0) {
        QByteArray insertText;
        QByteArray logText;

        insertText+="\n//Added by the Qt porting tool:\n";
        logText += "In file ";
        logText += Logger::instance()->globalState.value("currentFileName");
        logText += ": Added the following include directives:\n";
        foreach(QString headerName, headersToInsert) {
            insertText = insertText + headerName.toLatin1() + "\n";
            logText +="\t";
            logText += headerName.toLatin1() + " ";
        }
        insertText+="\n";
        fileContents.insert(insertPos, insertText);
        Logger::instance()->addEntry(new PlainLogEntry("Info", "Porting", logText));
    }

    return fileContents;
}
