/* KDevelop CMake Support
 *
 * Copyright 2006 Matt Rogers <mattr@kde.org>
 * Copyright 2008 Aleix Pol <aleixpol@gmail.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

#include "cmakelistsparser.h"
// #include "cmakeprojectvisitor.h"
#include "astfactory.h"
#include "../debug.h"

#include <QStack>
#include <QDir>

QMap<QChar, QChar> whatToScape()
{
    //Only add those where we're not scaping the next character
    QMap<QChar, QChar> ret;
    ret['n']='\n';
    ret['r']='\r';
    ret['t']='\t';
    return ret;
}

const QMap<QChar, QChar> CMakeFunctionArgument::scapings=whatToScape();

static const QChar scapingChar='\\';
QString CMakeFunctionArgument::unescapeValue(const QString& value)
{
    int firstScape=value.indexOf(scapingChar);
    if (firstScape<0)
    {
        return value;
    }

    QString newValue;
    int last=0;
    QMap<QChar, QChar>::const_iterator itEnd = scapings.constEnd();
    for(int i=firstScape; i<value.size()-1 && i>=0; i=value.indexOf(scapingChar, i+2))
    {
        newValue+=value.mid(last, i-last);
        const QChar current=value[i+1];
        QMap<QChar, QChar>::const_iterator it = scapings.constFind(current);

        if(it!=itEnd)
            newValue += *it;
        else
            newValue += current;

        last=i+2;
    }
    newValue+=value.mid(last, value.size());
//     qCDebug(CMAKE) << "escapiiiiiiiiing" << value << newValue;
    return newValue;
}

void CMakeFunctionDesc::addArguments( const QStringList& args, bool addEvenIfEmpty )
{
    if(addEvenIfEmpty && args.isEmpty())
        arguments += CMakeFunctionArgument();
    else foreach( const QString& arg, args )
    {
        CMakeFunctionArgument cmakeArg( arg );
        arguments.append( cmakeArg );
    }
}

QString CMakeFunctionDesc::writeBack() const
{
    QString output=name+"( ";
    foreach(const CMakeFunctionArgument& arg, arguments)
    {
        QString o = arg.value;
        if(arg.quoted)
            o='"'+o+'"';
        output += o+' ';
    }
    output += ')';
    return output;
}

namespace CMakeListsParser
{

static bool readCMakeFunction( cmListFileLexer* lexer, CMakeFunctionDesc& func);

CMakeFileContent readCMakeFile(const QString & _fileName)
{
    cmListFileLexer* lexer = cmListFileLexer_New();
    if ( !lexer )
        return CMakeFileContent();
    if ( !cmListFileLexer_SetFileName( lexer, qPrintable( _fileName ), nullptr ) ) {
        qCDebug(CMAKE) << "cmake read error. could not read " << _fileName;
        cmListFileLexer_Delete(lexer);
        return CMakeFileContent();
    }

    CMakeFileContent ret;
    QString fileName = QDir::cleanPath(_fileName);

    bool readError = false, haveNewline = true;
    cmListFileLexer_Token* token;

    while(!readError && (token = cmListFileLexer_Scan(lexer)))
    {
        readError=false;
        if(token->type == cmListFileLexer_Token_Newline)
        {
            readError=false;
            haveNewline = true;
        }
        else if(token->type == cmListFileLexer_Token_Identifier)
        {
            if(haveNewline)
            {
                haveNewline = false;
                CMakeFunctionDesc function;
                function.name = QString::fromLocal8Bit(token->text).toLower();
                function.filePath = fileName;
                function.line = token->line;
                function.column = token->column;

                readError = !readCMakeFunction( lexer, function);
                ret.append(function);

                if(readError)
                {
                    qCDebug(CMAKE) << "Error while parsing:" << function.name << "at" << function.line;
                }
            }
        }
    }
    cmListFileLexer_Delete(lexer);

    return ret;
}

}

bool CMakeListsParser::readCMakeFunction(cmListFileLexer *lexer, CMakeFunctionDesc &func)
{
        // Command name has already been parsed.  Read the left paren.
    cmListFileLexer_Token* token;
    if(!(token = cmListFileLexer_Scan(lexer)))
    {
        return false;
    }
    if(token->type != cmListFileLexer_Token_ParenLeft)
    {
        return false;
    }

    // Arguments.
    int parenthesis=1;
    while((token = cmListFileLexer_Scan(lexer)))
    {
        switch(token->type)
        {
            case cmListFileLexer_Token_ParenRight:
                parenthesis--;
                if(parenthesis==0) {
                    func.endLine=token->line;
                    func.endColumn=token->column;
                    return true;
                } else if(parenthesis<0)
                    return false;
                else
                    func.arguments << CMakeFunctionArgument( QString::fromLocal8Bit(token->text), false, token->line, token->column );
                break;
            case cmListFileLexer_Token_ParenLeft:
                parenthesis++;
                func.arguments << CMakeFunctionArgument( QString::fromLocal8Bit(token->text), false, token->line, token->column );
                break;
            case cmListFileLexer_Token_Identifier:
            case cmListFileLexer_Token_ArgumentUnquoted:
                func.arguments << CMakeFunctionArgument( QString::fromLocal8Bit(token->text), false, token->line, token->column );
                break;
            case cmListFileLexer_Token_ArgumentQuoted:
                func.arguments << CMakeFunctionArgument( QString::fromLocal8Bit(token->text), true, token->line, token->column+1 );
                break;
            case cmListFileLexer_Token_Newline:
                break;
            default:
                return false;
        }
    }

    return false;

}

CMakeFunctionDesc::CMakeFunctionDesc(const QString& name, const QStringList& args)
    : name(name)
    , line(0)
    , column(0)
    , endLine(0)
    , endColumn(0)
{
    addArguments(args);
}

CMakeFunctionDesc::CMakeFunctionDesc()
    : line(0)
    , column(0)
    , endLine(0)
    , endColumn(0)
{}

bool CMakeFunctionDesc::operator==(const CMakeFunctionDesc & other) const
{
    if(other.arguments.count()!=arguments.count() || name!=other.name)
        return false;

    QList<CMakeFunctionArgument>::const_iterator it=arguments.constBegin();
    QList<CMakeFunctionArgument>::const_iterator itOther=other.arguments.constBegin();
    for(;it!=arguments.constEnd(); ++it, ++itOther)
    {
        if(*it!=*itOther)
            return false;
    }
    return true;
}


/*CMakeFunctionArgument::CMakeFunctionArgument(const CMakeFunctionArgument & r)
    : value(r.value), quoted(r.quoted), filePath(r.filePath), line(r.line), column(r.column)
{
    value=unescapeValue(value);
}*/

CMakeFunctionArgument::CMakeFunctionArgument(const QString& v, bool q, quint32 l, quint32 c)
    : value(unescapeValue(v)), quoted(q), line(l), column(c)
{
}

CMakeFunctionArgument::CMakeFunctionArgument(const QString& v)
    : value(v), quoted(false), line(0), column(0)
{
}

