/*
 * Cppcheck - A tool for static C/C++ code analysis
 * Copyright (C) 2007-2010 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 "mainwindow.h"
#include "ui_mainwindow.h"

#include "preprocessor.h"
#include "tokenize.h"

#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <set>

#include <QFileDialog>

static void arrayIndex(const Tokenizer &tokenizer, std::set<unsigned int> &errorlines);

static unsigned char readChar(std::istream &istr)
{
    unsigned char ch = (unsigned char)istr.get();

    // Handling of newlines..
    if (ch == '\r')
    {
        ch = '\n';
        if ((char)istr.peek() == '\n')
            (void)istr.get();
    }

    return ch;
}

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    connect(ui->actionOpen, SIGNAL(triggered()), this, SLOT(open()));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::open()
{
    const std::string fileName = QFileDialog::getOpenFileName(this,
                                 tr("Open File"),
                                 "",
                                 "cpp files (*.cpp)").toStdString();
    if (fileName.empty())
        return;

    setWindowTitle(fileName.c_str());

    Tokenizer tokenizer;

    {
        // Preprocess the file..
        Preprocessor preprocessor;
        std::ifstream fin(fileName.c_str());
        std::string filedata;
        std::list<std::string> configurations;
        std::list<std::string> includePaths;
        preprocessor.preprocess(fin,
                                filedata,
                                configurations,
                                fileName,
                                includePaths);
        filedata = Preprocessor::getcode(filedata, "", fileName, NULL, NULL);

        // Tokenize the preprocessed code..
        std::istringstream istr(filedata);
        tokenizer.tokenize(istr, fileName.c_str(), "");
    }

    // Check the tokens..
    std::set<unsigned int> errorlines;
    arrayIndex(tokenizer, errorlines);

    // show report..
    {
        std::ostringstream report;
        std::ifstream fin(fileName.c_str());
        for (unsigned char c = readChar(fin); fin.good(); c = readChar(fin))
        {
            if (c & 0x80)
                continue;
            report << c;
        }
        ui->codeEditor->setPlainText(QString::fromStdString(report.str()));

        QList<int> errorLines;
        for (std::set<unsigned int>::const_iterator it = errorlines.begin(); it != errorlines.end(); ++it)
            errorLines.push_back(*it);
        ui->codeEditor->highlightErrors(errorLines);
    }

}



/**
 * Check that array indexes are within bounds
 * 1. Locate array access through: [ .. ]
 * 2. Try to determine if index is within bounds.
 * 3. If it fails to determine that the index is within bounds then write warning
 * \param tokenizer The tokenizer
 * \param errout output stream to write warnings to
 */

static void arrayIndex(const Tokenizer &tokenizer, std::set<unsigned int> &errorlines)
{
    for (const Token *tok = tokenizer.tokens(); tok; tok = tok->next())
    {
        // 1. Locate array access through: [ .. ]
        if (tok->fileIndex() == 0 && tok->str() == "[")
        {
            // 2. try to determine if the array index is within bounds

            // array declaration
            if (Token::simpleMatch(tok, "[ ]"))
                continue;
            if (Token::Match(tok->tokAt(-2), "%type% %var% [ %num% ] ;|="))
                continue;

            // 3. If it fails to determine that the index is within bounds then write warning
            errorlines.insert(tok->linenr());
        }
    }
}