/***************************************************************************
    File                 : AsciiTableImportFilter.cpp
    Project              : SciDAVis
    --------------------------------------------------------------------
    Copyright            : (C) 2008-2009 Knut Franke
    Email (use @ for *)  : Knut.Franke*gmx.net
    Description          : Import an ASCII file as Table.

 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *  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 "table/AsciiTableImportFilter.h"
#include "table/future_Table.h"
#include "lib/IntervalAttribute.h"
#include "core/column/Column.h"
#include "core/datatypes/String2DoubleFilter.h"

#include <QTextStream>
#include <QStringList>
#include <QLocale>

#include <vector>
#include <iostream>
using namespace std;

QStringList AsciiTableImportFilter::fileExtensions() const
{
    return QStringList() << "txt"
                         << "csv"
                         << "dat";
}

namespace {
// redirect to QIODevice's readLine so that we can override it to handle '\r' line terminators
struct SciDaVisTextStream
{
    QIODevice &input;
    bool good;
    operator bool() const { return good; }
    enum { none, simplify, trim } whiteSpaceTreatment;
    QString separator;
    SciDaVisTextStream(QIODevice &inp, const QString &sep)
        : input(inp), good(true), whiteSpaceTreatment(none), separator(sep)
    {
    }

    QStringList readRow()
    {
        char c;
        QString r;

        while ((good = input.getChar(&c)))
            switch (c) {
            case '\r':
                if (input.getChar(&c) && c != '\n') // eat \n following \r
                    input.ungetChar(c);
                goto breakLoop;
            case '\n':
                goto breakLoop;
            default:
                r += c;
            };
    breakLoop:
        switch (whiteSpaceTreatment) {
        case none:
            return r.split(separator);
        case simplify:
            return r.simplified().split(separator);
        case trim:
            return r.trimmed().split(separator);
        default:
            return QStringList();
        }
    }
};

template<class C>
C conv(const QString &x);
template<>
QString conv<QString>(const QString &x)
{
    return x;
}
template<>
qreal conv<qreal>(const QString &x)
{
    return (qreal)(QLocale().toDouble(x));
}

template<class T>
struct AP : public std::unique_ptr<T>
{
    AP() : std::unique_ptr<T>(new T) { }
    AP(const AP &x) : std::unique_ptr<T>(new T(*x)) { }
};

template<class C>
void readCols(QList<Column *> &cols, SciDaVisTextStream &stream, bool readColNames)
{
    QStringList row, column_names;
    int i;

    // read first row
    row = stream.readRow();

    int dataSize = row.size();
    vector<AP<C>> data(dataSize);
    vector<IntervalAttribute<bool>> invalid_cells(row.size());

    if (readColNames)
        column_names = row;
    else
        for (i = 0; i < row.size(); ++i) {
            column_names << QString::number(i + 1);
            *data[i] << conv<typename C::value_type>(row[i]);
        }

    // read rest of data
    while (stream) {
        row = stream.readRow();
        if (stream || (row != QStringList(""))) {
            for (i = 0; i < row.size() && i < dataSize; ++i)
                *data[i] << conv<typename C::value_type>(row[i]);
            // some rows might have too few columns (re-use value of i from above loop)
            for (; i < dataSize; ++i) {
                invalid_cells[i].setValue(data[i]->size(), true);
                *data[i] << conv<typename C::value_type>("");
            }
        }
    }

    for (i = 0; i < dataSize; ++i) {
        cols << new Column(std::move(column_names[i]), unique_ptr<C>(std::move(data[i])),
                           std::move(invalid_cells[i]));
        if (i == 0)
            cols.back()->setPlotDesignation(SciDAVis::X);
        else
            cols.back()->setPlotDesignation(SciDAVis::Y);
    }
}

}

AbstractAspect *AsciiTableImportFilter::importAspect(QIODevice &input)
{
    SciDaVisTextStream stream(input, d_separator);
    if (d_simplify_whitespace)
        stream.whiteSpaceTreatment = SciDaVisTextStream::simplify;
    else if (d_trim_whitespace)
        stream.whiteSpaceTreatment = SciDaVisTextStream::trim;

    // skip ignored lines
    for (int i = 0; i < d_ignored_lines; i++)
        stream.readRow();

    // build a Table from the gathered data
    QList<Column *> cols;
    if (d_convert_to_numeric)
        readCols<QVector<qreal>>(cols, stream, d_first_row_names_columns);
    else
        readCols<QStringList>(cols, stream, d_first_row_names_columns);

    // renaming will be done by the kernel
    future::Table *result = new future::Table(0, 0, tr("Table"));
    result->appendColumns(cols);
    return result;
}
