#include "mainwindow.h"
#include "graphview.h"
#include "tablemodel.h"
#include <QtWidgets>

//
// Public Functions
//
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent)
{
    createMenus();
    createInterface();
    layoutInterface();

    origModel = new TableModel;
    resultModel = new TableModel;

    origTableView->setModel(origModel);
    origGraphView->setModel(origModel);
    resultTableView->setModel(resultModel);
    resultGraphView->setModel(resultModel);

    setWindowTitle(tr("Basic WEKA Prepocessing"));
}

//
// Private Slots
//
/**
 * Prompts the user for a file to open
 * and loads it.
 */
void MainWindow::open()
{
    QString fileName = QFileDialog::getOpenFileName(this,
                            tr("Open..."), ".",
                            tr("CSV file (*.csv)"));
    if (!fileName.isEmpty())
        loadFile(fileName);
}

/**
 * Prompts the user for a file name
 * and directory to save the file.
 */
void MainWindow::saveAs()
{
    QString fileName = QFileDialog::getSaveFileName(this,
                            tr("Save As..."), ".",
                            tr("CSV (*.csv)"));

    if (!fileName.isEmpty())
        writeFile(fileName);
}

/**
 * Prompts the user for a file name
 * and directory to save the file
 * in a WEKA-oriented format.
 */
void MainWindow::exportForWeka()
{
    QString fileName = QFileDialog::getSaveFileName(this,
                            tr("Save As..."), ".",
                            tr("ARFF (*.arff);;CSV (*.csv)"));


    if (!fileName.isEmpty()) {
        if (fileName.endsWith(".csv", Qt::CaseInsensitive))
            writeCsvWekaFile(fileName);
        else if (fileName.endsWith(".arff", Qt::CaseInsensitive))
            writeArffWekaFile(fileName);
    }
/*
    if (!fileName.isEmpty())
        writeCsvWekaFile(fileName);
*/
}

/**
 * Saves a set of ARFF files with identical information
 * except different numbers of data points.
 */
void MainWindow::saveSet()
{
    QString saveDir = QFileDialog::getExistingDirectory(this,
                            tr("Choose directory to save set in..."), ".");

    if (!saveDir.isEmpty()) {
        bool ok;
        QString numPointsString = QInputDialog::getText(this, tr("Number of points in set..."),
                                                  tr("Number of points:"), QLineEdit::Normal,
                                                  "", &ok);

        if (ok && !numPointsString.isEmpty()) {
            QStringList numPoints = numPointsString.split(",");
            foreach (QString p, numPoints) {
                int points = p.toInt();
                numOfPointsSpinBox->setValue(points);
                QString fileName = saveDir;
                fileName.append("/").append(p).append("_points.arff");
                writeArffWekaFile(fileName);
            }
        }
    }
}

/**
 * Updates the range spin boxes so that
 * the smaller cannot be larger than the
 * larger.
 */
void MainWindow::updateRanges()
{
    rangeSmallSpinBox->setRange(0, rangeBigSpinBox->value() - numOfPointsSpinBox->value());
    rangeBigSpinBox->setRange(rangeSmallSpinBox->value() + numOfPointsSpinBox->value(), origModel->getContents().length());
}

/**
 * Called when the data is altered. Updates the
 * result model and data.
 */
void MainWindow::updateResults()
{
    if (origModel->getContents().isEmpty())
        return;

    // Indicates that the function performed already reduces
    // the number of points without the need to reduce after.
    // For example, a function like Log ( 1 / r ) is performed
    // on each point and so that number of points remains the
    // same after the function is performed. Taking the average
    // in an interval, however, already reduces the data to the
    // number of points desired.
    bool simplifiedInInterval = avgRadioButton->isChecked()
            || sumRadioButton->isChecked()
            || minRadioButton->isChecked()
            || maxRadioButton->isChecked()
            || rangeRadioButton->isChecked();

    // The result defaults to the same as the original.
    QList<QList<double> > result = origModel->getContents();
    QStringList rowTitles = origModel->getRowTitles();

    // Shrink to the correct range.
    result = List2DOperations::shrinkToRange(result, rangeSmallSpinBox->value(), rangeBigSpinBox->value());
    rowTitles = List2DOperations::titleRange(rowTitles, rangeSmallSpinBox->value(), rangeBigSpinBox->value());

    // Perform a function if a radio button is checked.
    if (derivRadioButton->isChecked()) {
        result = List2DOperations::derivative(result, derivSpinBox->value());
        rowTitles = List2DOperations::titleDeriv(rowTitles, derivSpinBox->value());
    } else if (avgRadioButton->isChecked()) {
        result = List2DOperations::averageInInterval(result, numOfPointsSpinBox->value());
        rowTitles = List2DOperations::titleShrink(rowTitles, numOfPointsSpinBox->value());
    } else if (sumRadioButton->isChecked()) {
        result = List2DOperations::sumInInterval(result, numOfPointsSpinBox->value());
        rowTitles = List2DOperations::titleShrink(rowTitles, numOfPointsSpinBox->value());
    } else if (minRadioButton->isChecked()) {
        result = List2DOperations::minInInterval(result, numOfPointsSpinBox->value());
        rowTitles = List2DOperations::titleShrink(rowTitles, numOfPointsSpinBox->value());
    } else if (maxRadioButton->isChecked()) {
        result = List2DOperations::maxInInterval(result, numOfPointsSpinBox->value());
        rowTitles = List2DOperations::titleShrink(rowTitles, numOfPointsSpinBox->value());
    } else if (rangeRadioButton->isChecked()) {
        result = List2DOperations::rangeInInterval(result, numOfPointsSpinBox->value());
        rowTitles = List2DOperations::titleShrink(rowTitles, numOfPointsSpinBox->value());
    }

    // Only decrease the number of points if the user wants to simplify.
    if (simplifyCheckBox->isChecked() && !simplifiedInInterval) {
        // Shrink to the correct number of points.
        result = List2DOperations::shrinkToNumPoints(result, numOfPointsSpinBox->value());
        rowTitles = List2DOperations::titleShrink(rowTitles, numOfPointsSpinBox->value());
    }

    // If the log radio button is checked, perform that
    // operation. Preferably, we would have this as an
    // else if clause under the derivRadioButton check
    // state. However, for accuracy, the derivative should
    // be taken before reducting points, and for performance,
    // the log should be taken after.
    if (logRadioButton->isChecked())
        result = List2DOperations::logOneOverR(result);
    else if (squareRadioButton->isChecked())
        result = List2DOperations::rSquared(result);
    else if (powerRadioButton->isChecked())
        result = List2DOperations::rToTheN(result, powSpinBox->value());

    //
    int colToSubtract = subtractSpinBox->value() - 1;
    if (colToSubtract > -1) {
        QList<double> minusList;
        for (int i = 0; i < result.length(); i++)
            minusList << result.at(i).at(colToSubtract);
        result = List2DOperations::subtractList(result, minusList);
    }

    // Update the model and graph view.
    resultModel->setContents(result);
    resultModel->setRowTitles(rowTitles);
    resultGraphView->setModel(resultModel);
    resultGraphView->update();
}

/**
 * Sets the range from 0 to the length of
 * the data list.
 */
void MainWindow::setToFullRange()
{
    rangeSmallSpinBox->setValue(0);
    rangeBigSpinBox->setValue(origModel->getContents().length());
}

//
// Private Functions
//
/**
 * Loads a file into the table views and graphs.
 * @param {const QString&} fileName - the name
 *      of the file to load
 * @return {bool} true if the file was loaded,
 *      false otherwise
 */
bool MainWindow::loadFile(const QString &fileName)
{
    // If the file cannot be opened, return false to let
    // the caller function know.
    QFile file(fileName);
    if (!file.open(QIODevice::ReadOnly))
        return false;

    QString fileContents = file.readAll();

    // Break the content into a list of strings, each element
    // being one row.
    QStringList rows;
    rows = fileContents.split("\n", QString::SkipEmptyParts);
    for (int i = 0; i < rows.length(); i++)
        rows[i] = rows.at(i).simplified();

    // Get a list of column headers (the first row broken apart
    // by commas).
    QStringList columnHeaders = rows.takeFirst().split(",");
    // Remove the first element. (This is "Class" and should be
    // in the corner -- it's not an actual header.)
    columnHeaders.takeFirst();

    // Create a 2D list with all the content and row headers.
    // Column headers are gone at this point.
    QList<QStringList> contentsWithRowTitles;
    for (int i = 0; i < rows.length(); i++)
        contentsWithRowTitles.append(rows.at(i).split(","));

    // Take the first element from each row and add it to
    // the list of row headers.
    QStringList rowHeaders;
    for (int i = 0; i < contentsWithRowTitles.length(); i++)
        rowHeaders.append(contentsWithRowTitles.at(i).at(0));

    // Now we get all the numbers into a 2D list.
    QList<QList<double> > contents;
    for (int i = 0; i < contentsWithRowTitles.length(); i++) {
        // First we'll gather all the numbers in one row.
        QList<double> row;
        // Star with 1 because at index 0, we still have the row header.
        for (int j = 1; j < contentsWithRowTitles.at(i).length(); j++) {
            // Convert the value at the current position to a double
            // before adding it to the list.
            row.append(contentsWithRowTitles.at(i).at(j).toDouble());
        }
        // Add on the whole row to the 2D array.
        contents.append(row);
    }

    // All the data has been saved. Now update the models.
    origModel->setRowTitles(rowHeaders);
    origModel->setColumnTitles(columnHeaders);
    origModel->setContents(contents);
    resultModel->setRowTitles(rowHeaders);
    resultModel->setColumnTitles(columnHeaders);
    resultModel->setContents(contents);

    rangeBigSpinBox->setValue(origModel->getContents().length());
    subtractSpinBox->setRange(0, origModel->getContents().at(0).length());
    subtractSpinBox->setValue(0);
    updateRanges();
    setToFullRange();
///////////////////////////
    // Once GraphView is updated, this is no longer needed.
    origGraphView->setModel(origModel);
    origGraphView->update();
    updateResults();
///////////////////////////

    return true;
}

/**
 * Saves the results into a CSV file.
 * @param {const QString&} fileName - the name
 *      of the file to write
 * @return {bool} true if the file was saved,
 *      false otherwise
 */
bool MainWindow::writeFile(const QString &fileName) const
{
    // If the file cannot be opened, return
    // false to let the caller function know.
    QFile file(fileName);
    if (!file.open(QFile::WriteOnly))
        return false;

    QStringList colTitles = resultModel->getColumnTitles();
    QStringList rowTitles = resultModel->getRowTitles();
    QList<QList<double> > contents = resultModel->getContents();

    QByteArray outText;
    // Output header.
    outText.append(List2DOperations::getCsvHeaderString(colTitles, false));
    // Now output the actual contents. Columns separated
    // by commas, rows by newlines.
    outText.append(List2DOperations::getContentsString(contents, rowTitles, false));

    // Write the text to the file and close.
    file.write(outText);
    file.close();
    return true;
}

/**
 * Saves the results into a CSV file in WEKA format.
 * @param {const QString&} fileName - the name
 *      of the file to write
 * @return {bool} true if the file was saved,
 *      false otherwise
 */
bool MainWindow::writeCsvWekaFile(const QString &fileName) const
{
    // If the file cannot be opened, return
    // false to let the caller function know.
    QFile file(fileName);
    if (!file.open(QFile::WriteOnly))
        return false;

    QStringList colTitles = resultModel->getColumnTitles();
    QStringList rowTitles = resultModel->getRowTitles();
    QList<QList<double > > contents = resultModel->getContents();

    // Swap axes of contents.
    contents = List2DOperations::swapAxes(contents);

    QByteArray outText;
    outText.append(List2DOperations::getCsvHeaderString(rowTitles, true));
    outText.append(List2DOperations::getContentsString(contents, colTitles, true));

    // Write the text to the file and close.
    file.write(outText);
    file.close();
    return true;
}

/**
 * Saves the results into a ARFF file in WEKA format.
 * @param {const QString&} fileName - the name
 *      of the file to write
 * @return {bool} true if the file was saved,
 *      false otherwise
 */
bool MainWindow::writeArffWekaFile(const QString &fileName) const
{
    // If the file cannot be opened, return
    // false to let the caller function know.
    QFile file(fileName);
    if (!file.open(QFile::WriteOnly))
        return false;

    QStringList colTitles = resultModel->getColumnTitles();
    QStringList rowTitles = resultModel->getRowTitles();
    QList<QList<double > > contents = resultModel->getContents();

    QByteArray outText;

    outText.append("% " + QDateTime::currentDateTime().toString() + "\n%\n");
    outText.append("% Preprocessing function: " + functionGroup->checkedButton()->text() + "\n");

    // Output information about operations done on the file.
    if (derivRadioButton->isChecked())
        outText.append("% Derivative " + QString::number(derivSpinBox->value()) + "\n");

    if (powerRadioButton->isChecked())
        outText.append("% n = " + QString::number(powSpinBox->value()) + "\n");

    outText.append("% Range " + QString::number(rangeSmallSpinBox->value())
                   + " to " + QString::number(rangeBigSpinBox->value()) + "\n");

    bool isSimplifiedInInterval = avgRadioButton->isChecked()
            || sumRadioButton->isChecked()
            || minRadioButton->isChecked()
            || maxRadioButton->isChecked()
            || rangeRadioButton->isChecked();

    if (simplifyCheckBox->isChecked() && !isSimplifiedInInterval)
        outText.append("%\n% Simplified\n");

    if (simplifyCheckBox->isChecked() || isSimplifiedInInterval)
        outText.append("% Number of points: " + QString::number(numOfPointsSpinBox->value()) + "\n%\n");

    if (subtractSpinBox->value() > 0)
        outText.append("% Subtracted instance " + QString::number(subtractSpinBox->value()) + "\n%\n");

    outText.append("% Number of instances: " + QString::number(colTitles.length()) + "\n%\n");


    // Get file name without path or extension and
    // use that as the relation.
    QStringList filePath = fileName.split("/");
    QString relation = filePath.last();
    relation.chop(5);
    // Output data information.
    outText.append("@relation '" + relation + "'\n");
    foreach (QString title, rowTitles)
        outText.append("@attribute " + title + " real\n");
    //outText.append("@attribute class { " + List2DOperations::getArffHeaderString(resultModel->getUniqueColumnTitles()) + " }\n\n");
    outText.append("@attribute class real\n\n");

    // Output actual data.
    outText.append("@data\n");
    for (int i = 0; i < contents.at(0).length(); i++) {
        for (int j = 0; j < contents.length(); j++) {
            outText.append(QString::number(contents.at(j).at(i)));
            outText.append(",");
        }
        outText.append("'" + colTitles.at(i) + "'");
        outText.append("\n");
    }

    // Write the text to the file and close.
    file.write(outText);
    file.close();
    return true;
}


/**
 * Creates all menus in the GUI.
 */
void MainWindow::createMenus()
{
    fileMenu = menuBar()->addMenu(tr("&File"));

    openAction = new QAction(tr("&Open"), this);
    openAction->setShortcut(tr("Ctrl+O"));
    connect(openAction, SIGNAL(triggered()), this, SLOT(open()));
    fileMenu->addAction(openAction);

    saveAsAction = new QAction(tr("&Save As..."), this);
    saveAsAction->setShortcut((tr("Ctrl+S")));
    connect(saveAsAction, SIGNAL(triggered()), this, SLOT(saveAs()));
    fileMenu->addAction(saveAsAction);

    exportForWekaAction = new QAction(tr("Export for WEKA"), this);
    exportForWekaAction->setShortcut(tr("Ctrl+E"));
    connect(exportForWekaAction, SIGNAL(triggered()), this, SLOT(exportForWeka()));
    fileMenu->addAction(exportForWekaAction);

    saveSetAction = new QAction(tr("Save set"), this);
    saveSetAction->setShortcut(tr("Ctrl+T"));
    connect(saveSetAction, SIGNAL(triggered()), this, SLOT(saveSet()));
    fileMenu->addAction(saveSetAction);

    quitAction = new QAction(tr("&Quit"), this);
    quitAction->setShortcut(tr("Ctrl+Q"));
    connect(quitAction, SIGNAL(triggered()), this, SLOT(close()));
    fileMenu->addAction(quitAction);
}

/**
 * Creates all the widgets for the GUI
 * and sets their default parameters.
 */
void MainWindow::createInterface()
{
    // Radio Buttons
    functionGroup = new QButtonGroup;

    noneRadioButton = new QRadioButton(tr("None"));
    noneRadioButton->setChecked(true);
    connect(noneRadioButton, SIGNAL(clicked()), this, SLOT(updateResults()));
    logRadioButton = new QRadioButton(tr("Log ( 1 / r )"));
    connect(logRadioButton, SIGNAL(clicked()), this, SLOT(updateResults()));
    squareRadioButton = new QRadioButton(tr("r ^ 2"));
    connect(squareRadioButton, SIGNAL(clicked()), this, SLOT(updateResults()));
    powerRadioButton = new QRadioButton(tr("r ^ n"));
    connect(powerRadioButton, SIGNAL(clicked()), this, SLOT(updateResults()));
    derivRadioButton  = new QRadioButton(tr("Derivative"));
    connect(derivRadioButton, SIGNAL(clicked()), this, SLOT(updateResults()));

    avgRadioButton = new QRadioButton(tr("Average in interval"));
    connect(avgRadioButton, SIGNAL(clicked()), this, SLOT(updateResults()));
    sumRadioButton = new QRadioButton(tr("Sum in interval"));
    connect(sumRadioButton, SIGNAL(clicked()), this, SLOT(updateResults()));
    minRadioButton = new QRadioButton(tr("Minimum in interval"));
    connect(minRadioButton, SIGNAL(clicked()), this, SLOT(updateResults()));
    maxRadioButton = new QRadioButton(tr("Maximum in interval"));
    connect(maxRadioButton, SIGNAL(clicked()), this, SLOT(updateResults()));
    rangeRadioButton = new QRadioButton(tr("Range in interval"));
    connect(rangeRadioButton, SIGNAL(clicked()), this, SLOT(updateResults()));

    functionGroup->addButton(noneRadioButton);
    functionGroup->addButton(logRadioButton);
    functionGroup->addButton(squareRadioButton);
    functionGroup->addButton(powerRadioButton);
    functionGroup->addButton(derivRadioButton);
    functionGroup->addButton(avgRadioButton);
    functionGroup->addButton(sumRadioButton);
    functionGroup->addButton(minRadioButton);
    functionGroup->addButton(maxRadioButton);
    functionGroup->addButton(rangeRadioButton);

    // Labels
    rangeLabel = new QLabel(tr("Range"));
    toLabel = new QLabel(tr("to"));
    numOfPointsLabel = new QLabel(tr("Number of points"));
    subtractLabel = new QLabel(tr("Subtract row"));

    // Spin boxes
    derivSpinBox = new QSpinBox;
    derivSpinBox->setRange(1, 3);
    derivSpinBox->setEnabled(false);
    connect(derivRadioButton, SIGNAL(clicked(bool)), derivSpinBox, SLOT(setEnabled(bool)));
    connect(logRadioButton, SIGNAL(clicked(bool)), derivSpinBox, SLOT(setDisabled(bool)));
    connect(squareRadioButton, SIGNAL(clicked(bool)), derivSpinBox, SLOT(setDisabled(bool)));
    connect(powerRadioButton, SIGNAL(clicked(bool)), derivSpinBox, SLOT(setDisabled(bool)));
    connect(noneRadioButton, SIGNAL(clicked(bool)), derivSpinBox, SLOT(setDisabled(bool)));
    connect(avgRadioButton, SIGNAL(clicked(bool)), derivSpinBox, SLOT(setDisabled(bool)));
    connect(sumRadioButton, SIGNAL(clicked(bool)), derivSpinBox, SLOT(setDisabled(bool)));
    connect(minRadioButton, SIGNAL(clicked(bool)), derivSpinBox, SLOT(setDisabled(bool)));
    connect(maxRadioButton, SIGNAL(clicked(bool)), derivSpinBox, SLOT(setDisabled(bool)));
    connect(rangeRadioButton, SIGNAL(clicked(bool)), derivSpinBox, SLOT(setDisabled(bool)));
    connect(derivSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateResults()));

    powSpinBox = new QDoubleSpinBox;
    powSpinBox->setRange(0.1, 20.0);
    powSpinBox->setEnabled(false);
    connect(powerRadioButton, SIGNAL(clicked(bool)), powSpinBox, SLOT(setEnabled(bool)));
    connect(derivRadioButton, SIGNAL(clicked(bool)), powSpinBox, SLOT(setDisabled(bool)));
    connect(logRadioButton, SIGNAL(clicked(bool)), powSpinBox, SLOT(setDisabled(bool)));
    connect(squareRadioButton, SIGNAL(clicked(bool)), powSpinBox, SLOT(setDisabled(bool)));
    connect(noneRadioButton, SIGNAL(clicked(bool)), powSpinBox, SLOT(setDisabled(bool)));
    connect(avgRadioButton, SIGNAL(clicked(bool)), powSpinBox, SLOT(setDisabled(bool)));
    connect(sumRadioButton, SIGNAL(clicked(bool)), powSpinBox, SLOT(setDisabled(bool)));
    connect(minRadioButton, SIGNAL(clicked(bool)), powSpinBox, SLOT(setDisabled(bool)));
    connect(maxRadioButton, SIGNAL(clicked(bool)), powSpinBox, SLOT(setDisabled(bool)));
    connect(rangeRadioButton, SIGNAL(clicked(bool)), powSpinBox, SLOT(setDisabled(bool)));
    connect(powSpinBox, SIGNAL(valueChanged(double)), this, SLOT(updateResults()));

    subtractSpinBox = new QSpinBox;
    subtractSpinBox->setRange(0, 0);
    connect(subtractSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateResults()));

    // Check boxes
    groupCheckBox = new QCheckBox(tr("Group with color"));
    groupCheckBox->setChecked(false);
    simplifyCheckBox = new QCheckBox(tr("Simplify"));
    simplifyCheckBox->setChecked(true);
    connect(simplifyCheckBox, SIGNAL(clicked()), this, SLOT(updateResults()));

    // More spin boxes
    rangeSmallSpinBox = new QSpinBox;
    rangeSmallSpinBox->setValue(0);
    connect(rangeSmallSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateRanges()));
    connect(rangeSmallSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateResults()));
    rangeBigSpinBox = new QSpinBox;
    rangeBigSpinBox->setValue(10);
    connect(rangeBigSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateRanges()));
    connect(rangeBigSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateResults()));

    // Range
    fullRangeButton = new QPushButton(tr("Full Range"));
    connect(fullRangeButton, SIGNAL(clicked()), this, SLOT(setToFullRange()));

    // Number of Points
    numOfPointsSpinBox = new QSpinBox;
    //numOfPointsSpinBox->setRange(10, 500);
    numOfPointsSpinBox->setRange(10, 2203);
    connect(numOfPointsSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateResults()));

    // Extra connections
    connect(simplifyCheckBox, SIGNAL(clicked(bool)), rangeSmallSpinBox, SLOT(setEnabled(bool)));
    connect(simplifyCheckBox, SIGNAL(clicked(bool)), rangeBigSpinBox, SLOT(setEnabled(bool)));
    connect(simplifyCheckBox, SIGNAL(clicked(bool)), numOfPointsSpinBox, SLOT(setEnabled(bool)));

    // Main tab widget
    tabWidget = new QTabWidget;

    // Views
    origTableView = new QTableView;
    resultTableView = new QTableView;

    origGraphView = new GraphView;
    resultGraphView = new GraphView;

    // And the last connections
    connect(groupCheckBox, SIGNAL(clicked(bool)), origGraphView, SLOT(setGrouping(bool)));
    connect(groupCheckBox, SIGNAL(clicked(bool)), origGraphView, SLOT(update()));
    connect(groupCheckBox, SIGNAL(clicked(bool)), resultGraphView, SLOT(setGrouping(bool)));
    connect(groupCheckBox, SIGNAL(clicked(bool)), resultGraphView, SLOT(update()));
}

/**
 * Lays out the widgets for the GUI.
 */
void MainWindow::layoutInterface()
{
    QVBoxLayout *functionLayout = new QVBoxLayout;
    functionLayout->addWidget(noneRadioButton);
    functionLayout->addWidget(logRadioButton);
    functionLayout->addWidget(squareRadioButton);
    QHBoxLayout *powLayout = new QHBoxLayout;
        powLayout->addWidget(powerRadioButton);
        powLayout->addWidget(powSpinBox);
        powLayout->addStretch();
    functionLayout->addLayout(powLayout);
    QHBoxLayout *derivLayout = new QHBoxLayout;
        derivLayout->addWidget(derivRadioButton);
        derivLayout->addWidget(derivSpinBox);
        derivLayout->addStretch();
    functionLayout->addLayout(derivLayout);

    QVBoxLayout *intervalFunctionLayout = new QVBoxLayout;
    intervalFunctionLayout->addWidget(avgRadioButton);
    intervalFunctionLayout->addWidget(sumRadioButton);
    intervalFunctionLayout->addWidget(minRadioButton);
    intervalFunctionLayout->addWidget(maxRadioButton);
    intervalFunctionLayout->addWidget(rangeRadioButton);

    QVBoxLayout *pointsLayout = new QVBoxLayout;
    pointsLayout->addWidget(groupCheckBox);
    pointsLayout->addWidget(simplifyCheckBox);
    QHBoxLayout *rangeLayout = new QHBoxLayout;
        rangeLayout->addWidget(rangeLabel);
        rangeLayout->addWidget(rangeSmallSpinBox);
        rangeLayout->addWidget(toLabel);
        rangeLayout->addWidget(rangeBigSpinBox);
        rangeLayout->addWidget(fullRangeButton);
        rangeLayout->addStretch();
    pointsLayout->addLayout(rangeLayout);
    QHBoxLayout *numOfPointsLayout = new QHBoxLayout;
        numOfPointsLayout->addWidget(numOfPointsLabel);
        numOfPointsLayout->addWidget(numOfPointsSpinBox);
        numOfPointsLayout->addStretch();
    pointsLayout->addLayout(numOfPointsLayout);
    QHBoxLayout *subtractLayout = new QHBoxLayout;
        subtractLayout->addWidget(subtractLabel);
        subtractLayout->addWidget(subtractSpinBox);
        subtractLayout->addStretch();
    pointsLayout->addLayout(subtractLayout);

    QHBoxLayout *preprocessingLayout = new QHBoxLayout;
    preprocessingLayout->addLayout(functionLayout);
    preprocessingLayout->addLayout(intervalFunctionLayout);
    preprocessingLayout->addStretch();
    preprocessingLayout->addLayout(pointsLayout);

    QSplitter *origSplitter = new QSplitter;
    origSplitter->addWidget(origTableView);
    origSplitter->addWidget(origGraphView);
    QSplitter *resultSplitter = new QSplitter;
    resultSplitter->addWidget(resultTableView);
    resultSplitter->addWidget(resultGraphView);
    tabWidget->addTab(origSplitter, tr("Original"));
    tabWidget->addTab(resultSplitter, tr("Result"));

    QVBoxLayout *mainLayout = new QVBoxLayout;
    mainLayout->addLayout(preprocessingLayout);
    mainLayout->addWidget(tabWidget);

    QWidget *centralWidget = new QWidget;
    centralWidget->setLayout(mainLayout);
    setCentralWidget(centralWidget);
}


/******************************************
 *
 * Methods for List2DOperations structure
 *
*******************************************/
/**
 * Converts the list of column titles to a string
 * in the "format String1,String2,String3\n"
 * without quotes.
 * @param {const QList<QString>} colTitles - a list
 *      of column titles
 * @param {bool} isSwapped - indicates whether or not
 *      the axes of the table has been swapped (would
 *      be if we're saving for WEKA)
 * @return {QString} the header
 */
QString List2DOperations::getCsvHeaderString(const QStringList colTitles, bool isSwapped)
{
    QString text;
    // Output the column titles preceded by the entry "Class."
    if (!isSwapped)
        text.append("Class,");
    for (int i = 0; i < colTitles.length(); i++) {
        text.append(colTitles.at(i));
        // Don't put a comma if it's the last
        // column in the row.
        if (i < colTitles.length() - 1)
            text.append(",");
    }
    if (isSwapped)
        text.append(",Class");
    text.append("\n");
    return text;
}

/**
 * Converts the list of column titles to a string
 * in the format 'String 1', 'String2', 'String 3'.
 * @param {const QList<QString>} colTitles - a list
 *      of column titles
 * @return {QString} the header
 */
QString List2DOperations::getArffHeaderString(const QStringList colTitles)
{
    QString text;
    // Output the column titles.
    for (int i = 0; i < colTitles.length(); i++) {
        text.append("'" + colTitles.at(i) + "'");
        // Don't put a comma if it's the last
        // column in the row.
        if (i < colTitles.length() - 1)
            text.append(", ");
    }
    return text;
}

/**
 * Creates a string containing the contents of the table
 * separating columns by commas and rows by newlines.
 * @param {const QList<QList<double> >} contents - the table
 * @param {const QList<QString>} rowTitles - the titles of the rows
 * @param {bool} isSwapped - indicates whether or not
 *      the axes of the table has been swapped (would
 *      be if we're saving for WEKA)
 * @return {QString} the contents converted to a string
 */
QString List2DOperations::getContentsString(const QList<QList<double> > contents, const QList<QString> rowTitles, bool isSwapped)
{
    QString text;
    for (int i = 0; i < contents.length(); i++) {
        // Output the row title at the beginning of the row.
        if (!isSwapped)
            text.append(rowTitles.at(i)).append(",");
        for (int j = 0; j < contents.at(i).length(); j++) {
            text.append(QString::number(contents.at(i).at(j)));
            // Don't put a comma if it's the last
            // column in the row.
            if (j < contents.at(i).length() - 1)
                text.append(",");
        }
        if (isSwapped)
            text.append(",").append(rowTitles.at(i));
        text.append("\n");
    }
    return text;
}

/**
 * Takes the derivative of each list in the 2D list.
 * @param {QList<QList<double> >} origList - the list to
 *      take the derivative of
 * @return {QList<QList<double> >} the derivative of the
 *      original array
 */
QList<QList<double> > List2DOperations::derivative(QList<QList<double> > origList, int times)
{
    // Take the derivative.
    QList<QList<double> > derivList;
    for (int i = 0; i < origList.length() - 1; i++) {
        QList<double> row;
        for (int j = 0; j < (origList.at(i).length()); j++) {
            // Get the slope by subtracting the current value from
            // the next value.
            double value = origList.at(i+1).at(j) - origList.at(i).at(j);
            row.append(value);
        }
        derivList.append(row);
    }

    // If more than the first derivative is needed,
    // perform the function again.
    if (times > 1)
        return derivative(derivList, times - 1);
    else
        return derivList;
}

/**
 * Performs the log function on each value in a 2D list.
 * @param {QList<QList<double> >} origList - the list to
 *      perform the operation on
 * @return {QList<QList<double> >} the list with the function
 *      log( 1 / r ) performed on each value
 */
QList<QList<double> > List2DOperations::logOneOverR(QList<QList<double> > origList)
{
    // Perform the operation log( 1 / r ) on each point
    // in the original list.
    QList<QList<double> > logList;
    for (int i = 0; i < origList.length(); i++) {
        QList<double> row;
        for (int j = 0; j < origList.at(i).length(); j++) {
            double r = origList.at(i).at(j);
            row.append(log10(1 / r));
        }
        logList.append(row);
    }
    return logList;
}

/**
 * Performs the square function on each value in a 2D list.
 * @param {QList<QList<double> >} origList - the list to
 *      perform the operation on
 * @return {QList<QList<double> >} the list with the function
 *      r^2 performed on each value
 */
QList<QList<double> > List2DOperations::rSquared(QList<QList<double> > origList)
{
    // Perform the operation r^2 on each point
    // in the original list.
    QList<QList<double> > squareList;
    for (int i = 0; i < origList.length(); i++) {
        QList<double> row;
        for (int j = 0; j < origList.at(i).length(); j++) {
            double r = origList.at(i).at(j);
            row.append(r * r);
        }
        squareList.append(row);
    }
    return squareList;
}

/**
 * Performs the power function on each value in a 2D list.
 * @param {QList<QList<double> >} origList - the list to
 *      perform the operation on
 * @return {QList<QList<double> >} the list with the function
 *      r^n performed on each value
 */
QList<QList<double> > List2DOperations::rToTheN(QList<QList<double> > origList, double n)
{
    // Perform the operation r^n on each point
    // in the original list.
    QList<QList<double> > powerList;
    for (int i = 0; i < origList.length(); i++) {
        QList<double> row;
        for (int j = 0; j < origList.at(i).length(); j++) {
            double r = origList.at(i).at(j);
            row.append(pow(r, n));
        }
        powerList.append(row);
    }
    return powerList;
}

/**
 * Takes the average of each interval in a 2D list.
 * @param {QList<QList<double> >} origList - the list
 *      to perform the operation on
 * @param {int} numIntervals - the number of intervals
 * @param {QList<QListdouble> the list of averages
 */
QList<QList<double> > List2DOperations::averageInInterval(QList<QList<double> > origList, int numIntervals)
{
    QList<QList<double> > avgList;

    // Make the list the right size.
    for (int i = 0; i < numIntervals; i++) {
        QList<double> row;
        for (int j = 0; j < origList.at(0).length(); j++) {
            row << 0;
        }
        avgList << row;
    }

    // Calculate the interval.
    double interval = origList.length() / (double)numIntervals;

    // Loop through the list and average the values of columns.
    for (int col = 0; col < origList.at(0).length(); col++) {
        for (int intervalNum = 0; intervalNum < numIntervals; intervalNum++) {
            double sum = 0;
            for (int row = intervalNum * interval; row < (intervalNum + 1) * interval; row++) {
                sum += origList.at(row).at(col);
            }
            avgList[intervalNum][col] = sum / interval;
        }
    }
    return avgList;
}

/**
 * Takes the sum of each interval in a 2D list.
 * @param {QList<QList<double> >} origList - the list
 *      to perform the operation on
 * @param {int} numIntervals - the number of intervals
 * @param {QList<QListdouble> the list of sums
 */
QList<QList<double> > List2DOperations::sumInInterval(QList<QList<double> > origList, int numIntervals)
{
    QList<QList<double> > sumList;

    // Make the list the right size.
    for (int i = 0; i < numIntervals; i++) {
        QList<double> row;
        for (int j = 0; j < origList.at(0).length(); j++) {
            row << 0;
        }
        sumList << row;
    }

    // Calculate the interval.
    double interval = origList.length() / (double)numIntervals;

    // Loop through the list and average the values of columns.
    for (int col = 0; col < origList.at(0).length(); col++) {
        for (int intervalNum = 0; intervalNum < numIntervals; intervalNum++) {
            double sum = 0;
            for (int row = intervalNum * interval; row < (intervalNum + 1) * interval; row++) {
                sum += origList.at(row).at(col);
            }
            sumList[intervalNum][col] = sum;
        }
    }
    return sumList;
}

/**
 * Takes the minimum of each interval in a 2D list.
 * @param {QList<QList<double> >} origList - the list
 *      to perform the operation on
 * @param {int} numIntervals - the number of intervals
 * @param {QList<QListdouble> the list of sums
 */
QList<QList<double> > List2DOperations::minInInterval(QList<QList<double> > origList, int numIntervals)
{
    QList<QList<double> > minList;

    // Make the list the right size.
    for (int i = 0; i < numIntervals; i++) {
        QList<double> row;
        for (int j = 0; j < origList.at(0).length(); j++) {
            row << 0;
        }
        minList << row;
    }

    // Calculate the interval.
    double interval = origList.length() / (double)numIntervals;

    // Loop through the list and take the minimum of each column.
    for (int col = 0; col < origList.at(0).length(); col++) {
        for (int intervalNum = 0; intervalNum < numIntervals; intervalNum++) {
            double min = origList.at(intervalNum * interval).at(col);
            for (int row = intervalNum * interval; row < (intervalNum + 1) * interval; row++) {
                min = qMin(min, origList.at(row).at(col));
            }
            minList[intervalNum][col] = min;
        }
    }
    return minList;
}

/**
 * Takes the maximum of each interval in a 2D list.
 * @param {QList<QList<double> >} origList - the list
 *      to perform the operation on
 * @param {int} numIntervals - the number of intervals
 * @param {QList<QListdouble> the list of sums
 */
QList<QList<double> > List2DOperations::maxInInterval(QList<QList<double> > origList, int numIntervals)
{
    QList<QList<double> > maxList;

    // Make the list the right size.
    for (int i = 0; i < numIntervals; i++) {
        QList<double> row;
        for (int j = 0; j < origList.at(0).length(); j++) {
            row << 0;
        }
        maxList << row;
    }

    // Calculate the interval.
    double interval = origList.length() / (double)numIntervals;

    // Loop through the list and take the maximum of each column.
    for (int col = 0; col < origList.at(0).length(); col++) {
        for (int intervalNum = 0; intervalNum < numIntervals; intervalNum++) {
            double max = origList.at(intervalNum * interval).at(col);
            for (int row = intervalNum * interval; row < (intervalNum + 1) * interval; row++) {
                max = qMax(max, origList.at(row).at(col));
            }
            maxList[intervalNum][col] = max;
        }
    }
    return maxList;
}

/**
 * Takes the range of each interval in a 2D list.
 * @param {QList<QList<double> >} origList - the list
 *      to perform the operation on
 * @param {int} numIntervals - the number of intervals
 * @param {QList<QListdouble> the list of sums
 */
QList<QList<double> > List2DOperations::rangeInInterval(QList<QList<double> > origList, int numIntervals)
{
    QList<QList<double> > rangeList;

    // Make the list the right size.
    for (int i = 0; i < numIntervals; i++) {
        QList<double> row;
        for (int j = 0; j < origList.at(0).length(); j++) {
            row << 0;
        }
        rangeList << row;
    }

    // Calculate the interval.
    double interval = origList.length() / (double)numIntervals;

    // Loop through the list and take the range of each column.
    for (int col = 0; col < origList.at(0).length(); col++) {
        for (int intervalNum = 0; intervalNum < numIntervals; intervalNum++) {
            double max = origList.at(intervalNum * interval).at(col);
            double min = max;
            for (int row = intervalNum * interval; row < (intervalNum + 1) * interval; row++) {
                max = qMax(max, origList.at(row).at(col));
                min = qMin(min, origList.at(row).at(col));
            }
            rangeList[intervalNum][col] = max - min;
        }
    }
    return rangeList;
}

/**
 * Accepts a 2D list and shrinks each column to the given number
 * of points by only taking points at an even interval.
 * @param {QList<QList<double> >} origList - the list to shrink
 * @param {int} numPoints - the number of points in each column
 *      of the final 2D list
 * @return {QList<QList<double> >} the 2D list after each column
 *      has been shrunk to the correct size
 */
QList<QList<double> > List2DOperations::shrinkToNumPoints(QList<QList<double> > origList, int numPoints)
{
    if (numPoints > origList.length())
        return QList<QList<double> >();

    QList<QList<double> > smallList;
    // Perform the operation on each row of the table.
    for (int i = 0; i < numPoints; i++) {
        // Make the interval double to make the interval more even.
        // Eventually it will have to be converted to an integer.
        double interval = origList.length() / (double)numPoints;
        QList<double> row;
        // Populate the row with the correct number of points.
        for (int j = 0; j < origList.at(i).length(); j++)
            row << origList.at((int)(i * interval)).at(j);
        // Add the row to the table.
        smallList << row;
    }
    return smallList;
}

/**
 * Reduces each column in the table to the indexes between
 * minIndex and maxIndex (inclusive).
 * @param {QList<QList<double> > origList - the original list
 * @param {int} minIndex - the smallest index
 * @param {int} maxIndex - the largest index
 * @return {QList<QList<double> >} the edited list
 */
QList<QList<double> > List2DOperations::shrinkToRange(QList<QList<double> > origList, int minIndex, int maxIndex)
{
    QList<QList<double> > rangeList;
    for (int i = minIndex; i < maxIndex; i++) {
        QList<double> row;
        for (int j = 0; j < origList.at(i).length(); j++) {
            row << origList.at(i).at(j);
        }
        rangeList << row;
    }
    return rangeList;
}

/**
 * Subtracts a 1D list from each list in a 2D list.
 * @param {QList<QList<double> > origList - the original list
 * @param {QList<QList<double> > otherList - the list to subtract
 * @return {QList<QList<double> >} the edited list
 */
QList<QList<double> > List2DOperations::subtractList(QList<QList<double> > origList, QList<double> otherList)
{
    QList<QList<double> > newList;
    for (int i = 0; i < origList.length(); i++) {
        QList<double> row;
        for (int j = 0; j < origList.at(i).length(); j++) {
            row << origList.at(i).at(j) - otherList.at(i);
        }
        newList << row;
    }
    return newList;
}

/**
 * Trims a string list based on how the derivative function
 * trims a list. For use with row titles.
 * @param {QStringList} origList - the list to edit
 * @param {int} times - the number of derivatives to take
 * @return {QStringList} the edited list
 */
QStringList List2DOperations::titleDeriv(QStringList origList, int times)
{
    for (int i = 0; i < times; i++)
        origList.removeLast();
    return origList;
}

/**
 * Decreases the number of elements to a certain number using a
 * constant interval.
 * @param {QStringList} origList - the list to reduce
 * @param {int} numPoints - the number of points in the result
 * @return {QStringList} the edited list
 */
QStringList List2DOperations::titleShrink(QStringList origList, int numPoints)
{
    QStringList simpleList;
    double interval = origList.length() / (double)numPoints;
    for (int i = 0; i < numPoints; i++)
        simpleList << origList.at((int)(i * interval));
    return simpleList;
}

/**
 * Returns a list with only the elements
 * from index minIndex to maxIndex.
 * @param {QStringList} origList - the original list
 * @param {int} minIndex - the smallest index
 * @param {int} maxIndex - the largest index
 * @return {QStringList} the edited list
 */
QStringList List2DOperations::titleRange(QStringList origList, int minIndex, int maxIndex)
{
    QStringList rangeList;
    for (int i = minIndex; i < maxIndex; i++)
        rangeList << origList.at(i);
    return rangeList;
}

/**
 * Swaps every value in a table so that the value at (x,y) in the new
 * table is the value of (y,x) in the old table.
 * @param {QList<QList<double> >} origList - the original table
 * @return {QList<QList<double> >} the table with the swapped axes
 */
QList<QList<double> > List2DOperations::swapAxes(QList<QList<double> > origList)
{
    QList<QList<double> > swappedList;
    for (int i = 0; i < origList.at(0).length(); i++) {
        QList<double> row;
        for (int j = 0; j < origList.length(); j++) {
            row << origList.at(j).at(i);
        }
        swappedList << row;
    }
    return swappedList;
}
