#ifndef SCRIPTENGINE_H
#define SCRIPTENGINE_H

#include <QObject>
#include <QJSEngine>
#include <QFileInfo>
#include <QThread>
#include <QThreadPool>
#include <QSemaphore>
#include "database.h"
#include "imageinfodata.h"
#include "libxisf.h"

class BatchProcessing;
class Solver;

namespace Script
{

class File;

class ScriptEngine : public QObject
{
    Q_OBJECT
    QJSEngine *_jsEngine;
    Database *_database;
    BatchProcessing *_parent;
    QThreadPool *_pool;
    QSemaphore _semaphore;
    QString _scriptPath;
    QString _outputDir;
    QList<QPair<QString, QString>> _paths;
    Solver *_solver = nullptr;
public:
    explicit ScriptEngine(Database *database, BatchProcessing *parent = nullptr);
    void setParams(const QString &scriptPath, const QList<QPair<QString, QString>> &paths, const QString &outputDir, const QString &arg);
    void reportError(const QString &message);
    const QString& outputDir() const;
    void interrupt();
    void logError(const QString &message);
    Q_INVOKABLE void log(const QString &message);
    Q_INVOKABLE void mark(File *file);
    Q_INVOKABLE void unmark(File *file);
    Q_INVOKABLE bool isMarked(const File *file);
    Q_INVOKABLE QJSValue getObjects(double ra, double dec, double distance);
    Q_INVOKABLE QJSValue getObjects(const QJSValue &bounds);
    Q_INVOKABLE void setMaxThread(int maxthread);
    Q_INVOKABLE void sync();
    Q_INVOKABLE QJSValue getString(const QString &label = QString(), const QString &text = QString()) const;
    Q_INVOKABLE QJSValue getInt(const QString &label = QString(), int value = 0);
    Q_INVOKABLE QJSValue getFloat(const QString &label = QString(), double value = 0, int decimals = 3) const;
    Q_INVOKABLE QJSValue getItem(const QStringList &items, const QString &label = "", int current = 0) const;
    Q_INVOKABLE QJSValue question(const QString &question, const QStringList &buttons = {"ok"}, const QString &title = "") const;
    Q_INVOKABLE void plot(const QJSValue &pointsArray);
    Q_INVOKABLE QJSValue openFile(const QString &fileName, const QString &mode = "r");
    bool convert(File *file, QString &outpath, const QString &format, const QVariantMap &params, bool async);
#ifdef PLATESOLVER
    Q_INVOKABLE void setSolverProfile(int index);
    Q_INVOKABLE void setSolverProfile(const QVariantMap &profile);
    Q_INVOKABLE QJSValue getSolverProfile() const;
    Q_INVOKABLE void setStartingSolution(const QJSValue &solution = QJSValue());
    QJSValue solveImage(File *file, bool updateHeader);
    QJSValue extractStars(File *file, bool hfr);
#endif // PLATESOLVER
    QJSValue newObject();
    QJSValue newArray(uint size);
    QJSValue eval(const QString &program);
    QStringList complete(const QString &line);
    void setPaths(const QList<QPair<QString, QString>> &paths);
public slots:
    void run();
signals:
    void newMessage(const QString &message, bool error);
    void finished();
};

class ScriptEngineThread : public QObject
{
    Q_OBJECT
    QThread *_thread;
    ScriptEngine *_engine;
public:
    ScriptEngineThread(Database *database, BatchProcessing *parent = nullptr);
    ~ScriptEngineThread();
    void setParams(const QString &scriptPath, const QList<QPair<QString, QString>> &paths, const QString &outputDir, const QString &arg);
    void start();
    void interrupt();
signals:
    void newMessage(const QString &message, bool error);
    void finished();
};

class FITSRecordModify;

class File : public QObject
{
    Q_OBJECT
    ScriptEngine *_engine = nullptr;
    QString _path;
    QString _root;
    QFileInfo _info;
    bool _fitsKeywordsLoaded = false;
    QStringList _fitsKeywords;
    QMultiHash<QString, FITSRecord> _fitsRecords;
    std::shared_ptr<WCSDataT> _wcs;
    void loadFitsKeywords();
    bool mkpath(const QString &path) const;
    QJSValue _stats;
    QJSValue _solution;
    QJSValue _stars;
public:
    explicit File(const QString &path, ScriptEngine *engine);
    explicit File(const QString &path, const QString &root, ScriptEngine *engine);
    Q_INVOKABLE QString fileName() const;
    Q_INVOKABLE QString absoluteFilePath() const;
    Q_INVOKABLE QString absolutePath() const;
    Q_INVOKABLE QString relativeFilePath() const;
    Q_INVOKABLE QString relativePath() const;
    Q_INVOKABLE QString baseName() const;
    Q_INVOKABLE QString completeBaseName() const;
    Q_INVOKABLE QString suffix() const;
    Q_INVOKABLE qint64 size() const;
    Q_INVOKABLE QStringList fitsKeywords();
    Q_INVOKABLE QString fitsValue(const QString &key);
    Q_INVOKABLE QJSValue fitsValues(const QString &key);
    Q_INVOKABLE QJSValue fitsRecords();
    Q_INVOKABLE bool modifyFITSRecords(const FITSRecordModify *modify);
    Q_INVOKABLE bool isMarked() const;
    Q_INVOKABLE File* copy(const QString &newpath) const;
    Q_INVOKABLE bool move(const QString &newpath);
    Q_INVOKABLE File* convert(const QString &outpath, const QString &format, const QVariantMap &params = QVariantMap());
    Q_INVOKABLE File* convertAsync(const QString &outpath, const QString &format, const QVariantMap &params = QVariantMap());
    Q_INVOKABLE QJSValue stats();
    Q_INVOKABLE QJSValue calculatedBounds();
#ifdef PLATESOLVER
    Q_INVOKABLE QJSValue solve(bool updateHeader = false);
    Q_INVOKABLE QJSValue extractStars(bool hfr);
#endif // PLATESOLVER
};

class FITSRecordModify : public QObject
{
    Q_OBJECT
    QStringList _remove;
    QVector<FITSRecord> _update;
    QVector<FITSRecord> _add;
    QVector<LibXISF::Property> _property;
    uint32_t _imageIdx = 0;

    friend class File;
public:
    Q_INVOKABLE FITSRecordModify(){};
    Q_INVOKABLE void removeKeyword(const QString &key);
    Q_INVOKABLE void updateKeyword(const QString &key, const QVariant &value, const QString &comment = QString());
    Q_INVOKABLE void addKeyword(const QString &key, const QVariant &value, const QString &comment = QString());
    Q_PROPERTY(uint32_t imageIndex READ imageIndex WRITE setImageIndex);
    void updateProperty(const QString &id, const LibXISF::Variant &value);
    uint32_t imageIndex() const;
    void setImageIndex(uint32_t idx);
};

class TextFile : public QObject
{
    Q_OBJECT
    QFile _fr;
public:
    bool open(const QString &path, const QString &mode);
    Q_INVOKABLE void write(const QString &data);
    Q_INVOKABLE QString read(int maxlen);
    Q_INVOKABLE QString readLine();
    Q_INVOKABLE QString readAll();
    Q_INVOKABLE bool seek(qint64 offset);
    Q_INVOKABLE qint64 pos();
};

}

#endif // SCRIPTENGINE_H
