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

#ifndef BORNAGAIN_GUI_MODEL_DATA_DATAITEM_H
#define BORNAGAIN_GUI_MODEL_DATA_DATAITEM_H

#include "Device/Coord/Tags.h"
#include "GUI/Model/Data/ComboProperty.h"
#include <QDateTime>
#include <mutex>

class AmplitudeAxisItem;
class BasicAxisItem;
class ComboProperty;
class Datafield;
class ICoordSystem;
class MessageService;

//! Abstract base class for IntensityDataItem and SpecularDataItem.
//! Owns one simulated data set of type Datafield.

//! For experimental data, use RealItem.

class DataItem : public QObject {
    Q_OBJECT
protected:
    explicit DataItem(const QString& modelType);

public:
    ~DataItem();

    const QString TYPE{"uninitialized"};

    // Returns datafield, owned by this class
    Datafield* p_field() { return m_datafield.get(); }
    const Datafield* c_field() const { return m_datafield.get(); }

    //! The given pointer becomes owned by this class!!
    virtual void setDatafield(Datafield* data);

    //! Sets the raw data vector from external source.
    //! Checks only the equality of data size; no dimension checks are applied.
    void setRawDataVector(const std::vector<double>& data);

    QString fileName() const;
    void setFileName(const QString& filename);
    QString dataFullPath(const QString& projectDir) const;

    QDateTime lastModified() const;
    void setLastModified(const QDateTime& dtime);

    QString loadDatafield(MessageService* messageService, const QString& projectDir);
    void saveDatafield(const QString& projectDir) const;

    // Number of bins in data
    int xSize() const;
    int ySize() const;

    // Returns min and max range of x-axis as given by data
    virtual double xMin() const = 0;
    virtual double xMax() const = 0;

    // Returns min and max range of y-axis as given by data
    virtual double yMin() const = 0;
    virtual double yMax() const = 0;

    // Lower and upper zoom ranges of x-axis
    double lowerX() const;
    double upperX() const;
    void setLowerX(double value);
    void setUpperX(double value);

    // Lower and upper zoom ranges of y-axis
    double lowerY() const;
    double upperY() const;
    void setLowerY(double value);
    void setUpperY(double value);

    // Adjust zoom range of this item to the other items range
    void copyXRangeFromItem(DataItem* sourceItem);
    void copyYRangeFromItem(DataItem* sourceItem);
    void copyXYRangesFromItem(DataItem* sourceItem);

    // Axes units
    Coords currentCoord() const;
    void setCurrentCoord(Coords coord);
    QString currentAxesUnits() const;
    void setCurrentAxesUnits(const QString& units);

    //! Updates data on the change of axes units
    virtual void updateCoords(const ICoordSystem& converter) = 0;

    const BasicAxisItem* xAxisItem() const;
    BasicAxisItem* xAxisItem();
    const AmplitudeAxisItem* yAxisItem() const;
    AmplitudeAxisItem* yAxisItem();

    // Axes titles
    QString XaxisTitle() const;
    QString YaxisTitle() const;
    void setXaxisTitle(const QString& title);
    void setYaxisTitle(const QString& title);

    //! Sets zoom range of x,y axes to axes of input data
    void setAxesRangeToData();

    //! Returns dimensions and axesbins of data
    virtual std::vector<int> shape() const = 0;

    //! Set axes viewport to original data.
    virtual void resetView() = 0;

    virtual void writeTo(QXmlStreamWriter* w) const;
    virtual void readFrom(QXmlStreamReader* r);

    void setSaveInBackground(bool b) { m_saveInBackground = b; }
    bool wasModifiedSinceLastSave() const;

signals:
    void datafieldChanged();
    void rawDataVectorChanged(const std::vector<double>& data);
    void fileNameChanged(const QString& filename);
    void axesUnitsChanged(DataItem* sender = nullptr);
    void axesUnitsReplotRequested();
    void itemAxesRangeChanged();
    void updateOtherPlots(DataItem* sender = nullptr);

protected:
    std::unique_ptr<Datafield> m_datafield; //!< simulation results
    mutable std::mutex m_update_data_mutex;

    QString m_fileName;
    QString m_fileDir;
    Coords m_currentCoord;
    std::unique_ptr<BasicAxisItem> m_xAxis;
    std::unique_ptr<AmplitudeAxisItem> m_yAxis;

    bool m_saveInBackground = true;
    QDateTime m_last_modified;
    mutable QDateTime m_last_saved;
};

#endif // BORNAGAIN_GUI_MODEL_DATA_DATAITEM_H
