/*
 * Cppcheck - A tool for static C/C++ code analysis
 * Copyright (C) 2007-2011 Daniel Marjamäki and Cppcheck team.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */
#include "cppcheck.h"

#include "preprocessor.h" // preprocessor.
#include "tokenize.h"   // <- Tokenizer

#include "check.h"
#include "path.h"

#include <algorithm>
#include <iostream>
#include <fstream>
#include <stdexcept>
#include <ctime>
#include "timer.h"

#ifndef __BORLANDC__
#define PCRE_STATIC
#include <pcre.h>
#endif

static TimerResults S_timerResults;

CppCheck::CppCheck(ErrorLogger &errorLogger)
    : _errorLogger(errorLogger)
{
    exitcode = 0;
}

CppCheck::~CppCheck()
{
    if (_settings._showtime != SHOWTIME_NONE)
        S_timerResults.ShowResults();
}

void CppCheck::settings(const Settings &currentSettings)
{
    _settings = currentSettings;
}

void CppCheck::addFile(const std::string &filepath)
{
    _filenames.push_back(Path::fromNativeSeparators(filepath));
}

void CppCheck::addFile(const std::string &path, const std::string &content)
{
    _filenames.push_back(Path::fromNativeSeparators(path));
    _fileContents[ path ] = content;
}

void CppCheck::clearFiles()
{
    _filenames.clear();
    _fileContents.clear();
}

const char * CppCheck::version()
{
    return "1.46";
}

unsigned int CppCheck::check()
{
    exitcode = 0;

    std::sort(_filenames.begin(), _filenames.end());

    // TODO: Should this be moved out to its own function so all the files can be
    // analysed before any files are checked?
    if (_settings.test_2_pass && _settings._jobs == 1)
    {
        for (unsigned int c = 0; c < _filenames.size(); c++)
        {
            const std::string fname = _filenames[c];
            if (_settings.terminated())
                break;

            std::string fixedname = Path::toNativeSeparators(fname);
            reportOut("Analysing " + fixedname + "..");

            std::ifstream f(fname.c_str());
            analyseFile(f, fname);
        }
    }

    for (unsigned int c = 0; c < _filenames.size(); c++)
    {
        _errout.str("");
        const std::string fname = _filenames[c];

        if (_settings.terminated())
            break;

        if (_settings._errorsOnly == false)
        {
            std::string fixedpath(fname);
            fixedpath = Path::simplifyPath(fixedpath.c_str());
            fixedpath = Path::toNativeSeparators(fixedpath);
            _errorLogger.reportOut(std::string("Checking ") + fixedpath + std::string("..."));
        }

        try
        {
            Preprocessor preprocessor(&_settings, this);
            std::list<std::string> configurations;
            std::string filedata = "";

            if (_fileContents.size() > 0 && _fileContents.find(_filenames[c]) != _fileContents.end())
            {
                // File content was given as a string
                std::istringstream iss(_fileContents[ _filenames[c] ]);
                preprocessor.preprocess(iss, filedata, configurations, fname, _settings._includePaths);
            }
            else
            {
                // Only file name was given, read the content from file
                std::ifstream fin(fname.c_str());
                Timer t("Preprocessor::preprocess", _settings._showtime, &S_timerResults);
                preprocessor.preprocess(fin, filedata, configurations, fname, _settings._includePaths);
            }

            _settings.ifcfg = bool(configurations.size() > 1);

            if (!_settings.userDefines.empty())
            {
                configurations.clear();
                configurations.push_back(_settings.userDefines);
            }

            int checkCount = 0;
            for (std::list<std::string>::const_iterator it = configurations.begin(); it != configurations.end(); ++it)
            {
                // Check only 12 first configurations, after that bail out, unless --force
                // was used.
                if (!_settings._force && checkCount > 11)
                {
                    if (_settings._errorsOnly == false)
                    {
                        const std::string fixedpath = Path::toNativeSeparators(fname);
                        _errorLogger.reportOut(std::string("Bailing out from checking ") + fixedpath +
                                               ": Too many configurations. Recheck this file with --force if you want to check them all.");
                    }

                    break;
                }

                cfg = *it;
                Timer t("Preprocessor::getcode", _settings._showtime, &S_timerResults);
                const std::string codeWithoutCfg = Preprocessor::getcode(filedata, *it, fname, &_settings, &_errorLogger);
                t.Stop();

                // If only errors are printed, print filename after the check
                if (_settings._errorsOnly == false && it != configurations.begin())
                {
                    std::string fixedpath = Path::simplifyPath(fname.c_str());
                    fixedpath = Path::toNativeSeparators(fixedpath);
                    _errorLogger.reportOut(std::string("Checking ") + fixedpath + ": " + cfg + std::string("..."));
                }

                std::string appendCode = _settings.append();
                if (!appendCode.empty())
                    Preprocessor::preprocessWhitespaces(appendCode);

                checkFile(codeWithoutCfg + appendCode, _filenames[c].c_str());
                ++checkCount;
            }
        }
        catch (std::runtime_error &e)
        {
            // Exception was thrown when checking this file..
            const std::string fixedpath = Path::toNativeSeparators(fname);
            _errorLogger.reportOut("Bailing out from checking " + fixedpath + ": " + e.what());
        }

        _errorLogger.reportStatus(c + 1, (unsigned int)_filenames.size());
    }

    // This generates false positives - especially for libraries
    const bool verbose_orig = _settings._verbose;
    _settings._verbose = false;
    if (_settings.isEnabled("unusedFunctions") && _settings._jobs == 1)
    {
        _errout.str("");
        if (_settings._errorsOnly == false)
            _errorLogger.reportOut("Checking usage of global functions..");

        _checkUnusedFunctions.check(this);
    }
    _settings._verbose = verbose_orig;

    _errorList.clear();
    return exitcode;
}

void CppCheck::analyseFile(std::istream &fin, const std::string &filename)
{
    // Preprocess file..
    Preprocessor preprocessor(&_settings, this);
    std::list<std::string> configurations;
    std::string filedata = "";
    preprocessor.preprocess(fin, filedata, configurations, filename, _settings._includePaths);
    const std::string code = Preprocessor::getcode(filedata, "", filename, &_settings, &_errorLogger);

    // Tokenize..
    Tokenizer tokenizer(&_settings, this);
    std::istringstream istr(code);
    tokenizer.tokenize(istr, filename.c_str(), "");
    tokenizer.simplifyTokenList();

    // Analyse the tokens..
    std::set<std::string> data;
    for (std::list<Check *>::const_iterator it = Check::instances().begin(); it != Check::instances().end(); ++it)
    {
        (*it)->analyse(tokenizer.tokens(), data);
    }

    // Save analysis results..
    // TODO: This loop should be protected by a mutex or something like that
    //       The saveAnalysisData must _not_ be called from many threads at the same time.
    for (std::list<Check *>::const_iterator it = Check::instances().begin(); it != Check::instances().end(); ++it)
    {
        (*it)->saveAnalysisData(data);
    }
}

//---------------------------------------------------------------------------
// CppCheck - A function that checks a specified file
//---------------------------------------------------------------------------

void CppCheck::checkFile(const std::string &code, const char FileName[])
{
    if (_settings.terminated())
        return;

    Tokenizer _tokenizer(&_settings, this);
    bool result;

    // Tokenize the file
    std::istringstream istr(code);

    Timer timer("Tokenizer::tokenize", _settings._showtime, &S_timerResults);
    result = _tokenizer.tokenize(istr, FileName, cfg);
    timer.Stop();
    if (!result)
    {
        // File had syntax errors, abort
        return;
    }

    Timer timer2("Tokenizer::fillFunctionList", _settings._showtime, &S_timerResults);
    _tokenizer.fillFunctionList();
    timer2.Stop();

    // call all "runChecks" in all registered Check classes
    for (std::list<Check *>::iterator it = Check::instances().begin(); it != Check::instances().end(); ++it)
    {
        if (_settings.terminated())
            return;

        Timer timerRunChecks((*it)->name() + "::runChecks", _settings._showtime, &S_timerResults);
        (*it)->runChecks(&_tokenizer, &_settings, this);
    }

    Timer timer3("Tokenizer::simplifyTokenList", _settings._showtime, &S_timerResults);
    result = _tokenizer.simplifyTokenList();
    timer3.Stop();
    if (!result)
        return;

    Timer timer4("Tokenizer::fillFunctionList", _settings._showtime, &S_timerResults);
    _tokenizer.fillFunctionList();
    timer4.Stop();

    if (_settings.isEnabled("unusedFunctions") && _settings._jobs == 1)
        _checkUnusedFunctions.parseTokens(_tokenizer);

    // call all "runSimplifiedChecks" in all registered Check classes
    for (std::list<Check *>::iterator it = Check::instances().begin(); it != Check::instances().end(); ++it)
    {
        if (_settings.terminated())
            return;

        Timer timerSimpleChecks((*it)->name() + "::runSimplifiedChecks", _settings._showtime, &S_timerResults);
        (*it)->runSimplifiedChecks(&_tokenizer, &_settings, this);
    }

#ifndef __BORLANDC__
    // Are there extra rules?
    if (!_settings.rules.empty())
    {
        std::ostringstream ostr;
        for (const Token *tok = _tokenizer.tokens(); tok; tok = tok->next())
            ostr << " " << tok->str();
        const std::string str(ostr.str());
        for (std::list<Settings::Rule>::const_iterator it = _settings.rules.begin(); it != _settings.rules.end(); ++it)
        {
            const Settings::Rule &rule = *it;
            if (rule.pattern.empty() || rule.id.empty() || rule.severity.empty())
                continue;

            const char *error = 0;
            int erroffset = 0;
            pcre *re = pcre_compile(rule.pattern.c_str(),0,&error,&erroffset,NULL);
            if (!re && error)
            {
                ErrorLogger::ErrorMessage errmsg(std::list<ErrorLogger::ErrorMessage::FileLocation>(),
                                                 Severity::error,
                                                 error,
                                                 "pcre_compile");

                reportErr(errmsg);
            }
            if (re)
            {
                int pos = 0;
                int ovector[30];
                if (0 <= pcre_exec(re, NULL, str.c_str(), (int)str.size(), pos, 0, ovector, 30))
                {
                    unsigned int pos1 = (unsigned int)ovector[0];
                    unsigned int pos2 = (unsigned int)ovector[1];

                    // determine location..
                    ErrorLogger::ErrorMessage::FileLocation loc;
                    loc.setfile(_tokenizer.getFiles()->front());
                    loc.line = 0;

                    unsigned int len = 0;
                    for (const Token *tok = _tokenizer.tokens(); tok; tok = tok->next())
                    {
                        len = len + 1 + tok->str().size();
                        if (len > pos1)
                        {
                            loc.setfile(_tokenizer.getFiles()->at(tok->fileIndex()));
                            loc.line = tok->linenr();
                            break;
                        }
                    }

                    const std::list<ErrorLogger::ErrorMessage::FileLocation> callStack(1, loc);

                    // Create error message
                    std::string summary;
                    if (rule.summary.empty())
                        summary = "found '" + str.substr(pos1, pos2 - pos1) + "'";
                    else
                        summary = rule.summary;
                    ErrorLogger::ErrorMessage errmsg(callStack, Severity::fromString(rule.severity), summary, rule.id);

                    // Report error
                    reportErr(errmsg);
                }

                pcre_free(re);
            }
        }
    }
#endif
}

Settings CppCheck::settings() const
{
    return _settings;
}

//---------------------------------------------------------------------------

void CppCheck::reportErr(const ErrorLogger::ErrorMessage &msg)
{
    std::string errmsg = msg.toString(_settings._verbose);

    // Alert only about unique errors
    if (std::find(_errorList.begin(), _errorList.end(), errmsg) != _errorList.end())
        return;

    std::string file;
    unsigned int line(0);
    if (!msg._callStack.empty())
    {
        file = msg._callStack.back().getfile(false);
        line = msg._callStack.back().line;
    }

    if (_settings.nomsg.isSuppressed(msg._id, file, line))
        return;

    if (!_settings.nofail.isSuppressed(msg._id, file, line))
        exitcode = 1;

    _errorList.push_back(errmsg);
    std::string errmsg2(errmsg);
    if (_settings._verbose)
    {
        errmsg2 += "\n    Defines=\'" + cfg + "\'\n";
    }

    _errorLogger.reportErr(msg);

    _errout << errmsg2 << std::endl;
}

void CppCheck::reportOut(const std::string &outmsg)
{
    _errorLogger.reportOut(outmsg);
}

const std::vector<std::string> &CppCheck::filenames() const
{
    return _filenames;
}

void CppCheck::reportProgress(const std::string &filename, const char stage[], const unsigned int value)
{
    _errorLogger.reportProgress(filename, stage, value);
}

void CppCheck::reportStatus(unsigned int /*index*/, unsigned int /*max*/)
{

}

void CppCheck::getErrorMessages()
{
    // call all "getErrorMessages" in all registered Check classes
    for (std::list<Check *>::iterator it = Check::instances().begin(); it != Check::instances().end(); ++it)
        (*it)->getErrorMessages(this, &_settings);

    Tokenizer tokenizer(&_settings, 0);
    tokenizer.getErrorMessages(this, &_settings);

    Preprocessor::getErrorMessages(this, &_settings);
}