//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/coregui/DataLoaders/QREDataLoader.h
//! @brief     Defines class QREDataLoader
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2021
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#ifndef GUI_COREGUI_DATALOADERS_QREDATALOADER_H
#define GUI_COREGUI_DATALOADERS_QREDATALOADER_H

#include "GUI/coregui/DataLoaders/AbstractDataLoader1D.h"
#include <QVector>
#include <variant>

class QString;
class QREDataLoaderProperties;

//! Real data loader for Q/R/E reflectometry CSV files

class QREDataLoader : public AbstractDataLoader1D {
public:
    QREDataLoader();
    virtual QString name() const override;
    virtual QString persistentClassName() const override;
    virtual void populateImportSettingsWidget(QWidget* parent) override;
    virtual void initWithDefaultImportSettings() override;
    virtual void applyImportSettings() override;
    virtual QByteArray serialize() const override;
    virtual void deserialize(const QByteArray& data) override;
    virtual AbstractDataLoader* clone() const override;
    virtual QByteArray fileContent() const override;
    virtual void setFileContents(const QByteArray& fileContent) override;
    virtual void guessSettings() override;
    virtual void processContents() override;
    virtual int numErrors() const override;
    virtual int numLineRelatedErrors() const override;
    virtual QStringList lineUnrelatedErrors() const override;
    virtual AbstractDataLoaderResultModel* createResultModel() const override;

private:
    void parseFileContent() const;
    void calculateFromParseResult() const;
    void createOutputDataFromParsingResult(RealDataItem* item) const;

private:
    enum class UnitInFile { none, perNanoMeter, perAngstrom, other };

    struct ColumnDefinition {
        bool enabled;    //< shall data type be used
        int column;      //< read the value from this raw column
        UnitInFile unit; //< the unit of the data in the file
        double factor;   //< in case the raw data shall be multiplied

        bool operator==(const ColumnDefinition& other) const;
    };

    enum class DataType { Q, R, dR };

    //! Settings for importing the file
    struct ImportSettings {
        QString separator;    //!< column separator
        QString headerPrefix; //!< prefix denoting header line
        QString linesToSkip;  //!< pattern denoting line to skip (i.e. '1,10-12,42')
        QMap<DataType, ColumnDefinition> columnDefinitions;

        bool operator!=(const ImportSettings& other) const;
        QByteArray serialize() const;
        void deserialize(const QByteArray& data);
    } m_importSettings;

    //! Contains a line related error (stored in the import result). Used for showing
    //! line related errors in the import data table.
    struct ErrorDefinition {
        // Attention: numbers are serialized! Do not change them!
        enum Type {
            none = 0,
            columnDoesNotContainValidNumber = 1,
            duplicateQ = 2,
            wrongQOrder = 3,
            RGreaterOne = 4,
            RLessZero = 5
        };

        ErrorDefinition(Type t = none, int d = 0);
        ErrorDefinition(Type t, double d);

        //! Human readable error text
        QString toString() const;

        Type type;
        std::variant<int, double> data; //!< Additional data; meaning depends on the error type (see
                                        //!< implementation of toString() for more information)
    };

    //! Result of the file import. Some of the contained data is only relevant for showing the
    //! results in the result table. The usage of vectors which cover also invalid or skipped
    //! lines has its reason also in this result showing (to improve presentation performance).
    struct ImportResult {
        void clear();
        void clearCalculatedValues();
        void addError(int line, ErrorDefinition::Type type, int data = 0);
        void addError(int line, ErrorDefinition::Type type, double data);
        QString errorText(int line) const;

        QVector<QPair<bool, QString>> lines; //!< bool describes whether line is skipped
                                             //!< index is 0-based line number
        QVector<QVector<double>> rawValues;  //!< index is 0-based line number
        QVector<double> qValues;             //!< index is 0-based line number
        QVector<double> rValues;             //!< index is 0-based line number
        QVector<double> eValues;             //!< index is 0-based line number
        int validCalculatedLines;            //!< number of valid resulting data rows
        int maxColumnCount;                  //!< max found columns in raw data
        QMap<int, ErrorDefinition>
            calculationErrors;         //!< calculation error per line; line is 0-based
        QString error;                 //!< error unrelated to lines
        ImportSettings importSettings; //!< Settings used for the import
    };
    mutable ImportResult m_importResult;
    QByteArray m_fileContent;

    QPointer<QREDataLoaderProperties> m_propertiesWidget;

    friend QDataStream& operator<<(QDataStream& stream, const QREDataLoader::ImportSettings& s);
    friend QDataStream& operator>>(QDataStream& stream, QREDataLoader::ImportSettings& s);
    friend QDataStream& operator<<(QDataStream& stream, const QREDataLoader::ErrorDefinition& s);
    friend QDataStream& operator>>(QDataStream& stream, QREDataLoader::ErrorDefinition& s);
    friend class QREDataLoaderResultModel;
};

QDataStream& operator<<(QDataStream& stream, const QREDataLoader::ImportSettings& s);
QDataStream& operator>>(QDataStream& stream, QREDataLoader::ImportSettings& s);

#endif // GUI_COREGUI_DATALOADERS_QREDATALOADER_H
