#ifndef SCRIPT_ENGINE_H
#define SCRIPT_ENGINE_H

#include <QObject>
#include <QThread>
#include <QQueue>
#include <QMutex>
#include <QQmlEngine>
#include <QJSValue>
#include <QPlainTextEdit>
#include "script_connection.h"
#include "variables_database.h"

class LogView;
class SHSScriptSingleTimer;
class SHSScriptQuery;
class SHSScriptTimeMeasurer;
class SHSScriptEngine;


class SHSScriptProxy: public QObject {
	Q_OBJECT
public:
	SHSScriptProxy(SHSScriptEngine * e);
private:
	void timerEvent(QTimerEvent * e);
	SHSScriptEngine * engine;
	QMap<int, QJSValue> delayed;
public slots:
	void __sendCommand(int id, QVariantList args);
	void __processCommand(int id, uint pid, QVariantList args);
	QVariant __varGet(int id);
	QVariant __varSet(int id, QVariant v);
	QVariant __propGet(int oid, QString prop);
	QVariant __propSet(int oid, QString prop, QVariant v);
	QVariant __wpropGet(QVariant oid, QString prop);
	QVariant __wpropSet(QVariant oid, QString prop, QVariant v);

	void log(QString s, int type = 0);
	void message(QString s, int type = 0);
	void usleep(int us);
	void msleep(int ms) {usleep(ms*1000);}
	void sleep(double s) {usleep(piRound(s*1000000.));}
	QDateTime lastDate(int y = 0, int M = 0, int d = 0, int h = 0, int m = 0, int s = 0, int z = 0);

	uint randomInt(uint max = 0); // [0..max], or [0..inf] if max == 0
	double randomFloat(); // [0..1]

	// Variables methods
	QVariant getVariable(QString name);
	void setVariable(QString name, QVariant v);

	// Memory methods
	QVariant load(int id);
	void save(int id, QVariant v);

	// Project files and getters
	QString formJSON();
	QString getScript(QString name);
	QByteArray getResource(QString name);
	QObject * getWidget(QString name);

	// New object methods
	QTimer * newTimer();
	SHSScriptSingleTimer * newSingleTimer(QJSValue func);
	SHSScriptQuery * newQuery(QVariantMap props = QVariantMap());
	SHSScriptTimeMeasurer * newTimeMeasurer();

	// Delay call methods
	int delayCall(int msec, QJSValue func);
	bool cancelDelayCall(int id);

	bool isTest() const;
	bool isRelease() const {return !isTest();}

};


class SHSScriptSingleTimer: public QObject {
	Q_OBJECT
	friend class SHSScriptProxy;
private:
	SHSScriptSingleTimer(SHSScriptEngine * e);
	void timerEvent(QTimerEvent * e);
	SHSScriptEngine * engine;
	QJSValue func_;
	QElapsedTimer tm;
	int id = -1, msec_ = 1000;
public slots:
	void start();
	void stop();
	void setDelay(int msec);
	void setFunction(QJSValue func) {func_ = func;}
	int delay() const {return msec_;}
	bool isStarted() const {return id >= 0;}
};


class SHSScriptQuery: public QObject {
	Q_OBJECT
	friend class SHSScriptProxy;
private:
	SHSScriptQuery(SHSScriptEngine * e);
	void timerEvent(QTimerEvent * e);
	void destroyTimer(int & id);
	void query();
	void startQuery();
	void setMissed(bool yes);
	SHSScriptEngine * engine;
	QVector<QJSValue> qfuncs_;
	int max_tries = 5, cur_try = 0, try_timeout = 500, query_timeout = 5000;
	int max_miss = 2, cur_miss = 0, cur_func = 0;
	int timer_query = -1, timer_try = -1;
	bool is_missed = true, cur_accepted = false;
public slots:
	void start();
	void startOnce();
	void stop();
	void accept();
	void setQueryFunction(QJSValue func) {qfuncs_.clear(); qfuncs_ << func;}
	void addQueryFunction(QJSValue func) {qfuncs_ << func;}
	void setQueryTimeout(int msec) {query_timeout = msec;}
	void setTryTimeout(int msec) {try_timeout = msec;}
	void setMaxTries(int max) {max_tries = max;}
	void setMaxMissQueries(int max) {max_miss = max;}
	int queryTimeout() const {return query_timeout;}
	int tryTimeout() const {return try_timeout;}
	int maxTries() const {return max_tries;}
	int maxMissQueries() const {return max_miss;}
	bool isStarted() const {return timer_query >= 0;}
	bool isAccepted() const {return !is_missed;}
	bool isMissed() const {return is_missed;}
signals:
	void accepted();
	void missed();
};


class SHSScriptTimeMeasurer: public QObject {
	Q_OBJECT
	friend class SHSScriptProxy;
public:
	SHSScriptTimeMeasurer(SHSScriptEngine * e);
private:
	SHSScriptEngine * engine;
	PITimeMeasurer tm;
public slots:
	int elapsed();
	void reset();
};


class SHSScriptConsole: public QObject {
	Q_OBJECT
public:
	SHSScriptConsole(SHSScriptEngine * e);
private:
	SHSScriptEngine * engine;
public slots:
	void log(QString msg) {}
	void warn(QString msg) {}
	void _def_assert(QVariant condition, QString msg) {}
	void time(QString msg) {}
	void count(QString msg) {}
	void exception(QString msg) {}
	void clear();
};


class SHSDebugHandler: public QObject {
	Q_OBJECT
public:
	SHSDebugHandler();
	static SHSDebugHandler * instance();
private:
	static void handler(QtMsgType type, const QMessageLogContext & ctx, const QString & msg);
signals:
	void printed(QString msg);
};




class SHSScriptEngine: public QObject, public SHSScriptEngineBase {
	Q_OBJECT
	friend class SHSScriptProxy;
	friend class SHSScriptConsole;
	friend class SHSScriptSingleTimer;
	friend class SHSScriptQuery;
	friend class SHSVariablesDatabase;
public:
	explicit SHSScriptEngine(QObject * parent = 0);
	~SHSScriptEngine();
	
	void create(bool force = false);
	void remove();
	void abort();
	
	QJSEngine * engine() {return engine_;}
	SHSScriptConnection * connection() {return &conn_;}
	QString fullScript() const {return script_content;}
	
	void assignForm(QWidget * form);
	void assignConnection(QPIConnection * conn);
	void assignVariables(QList<SHS::ScriptVariable> vars);
	void setDebugWidget(LogView * w) {logview = w;}
	bool loadFromText(const QString & text, const QString & filename = QString());
	bool loadFromProject(const SHS::ServerProject & p);
	bool loadFromFile(const QString & file);
	bool start();
	void preparePlugins();
	void setDir(const QDir & d) {dir = d;}

	PIVector<PIPair<ullong, double>> getVariableHistory(const QString & vname) {return db.getVariableHistory(vname);}
	PIPair<ullong, double> getVariableValue(const QString & vname) {return db.getVariableValue(vname);}

	QJSValue createScriptObject(SHS::Object & o, uint script_plugin_id = 0);
	void createScriptObjectProperties(const SHS::Object & o, const QString & path);
	void createBlockObjects();
	QList<QMetaObject> creatableObjects() const;

	void sendCommand(int id, const QVector<QVariant> & args);
	void processCommand(int id, uint pid, const QVector<QVariant> & args);

	void prepare();
	void started();
	void prepareForRole(SHS::UserRole role);

	static QString prepareScript(const QString & t);
	static QString updateScript(const QString & content);

	bool no_graphics;

	struct IncPart {
		int start;
		int reduce;
		QString file;
	};

private:
	void timerEvent(QTimerEvent * e) override;
	void clearLogWidget();
	void connectToWidgets();
	bool checkJSValue(QJSValue v);
	void emitError(int line, const QString & msg, const QString & stack);
	QVariant call(const QString & function, const QVariantList & args = QVariantList()) override;
	QString processScriptIncludes(const QString & t, const QString & filename, bool & ok);

	QObject project;
	QQmlEngine * engine_;
	QJSValue svalue;
	LogView * logview;
	SHSScriptConnection conn_;
	SHSVariablesDatabase db;
	QList<QMetaObject> new_mol;
	QMap<QString, QString> files_content;
	QMap<QString, QWidget*> widgets;
	QMap<quintptr, QWidget*> widgets_by_ptr;
	QVector<IncPart> inc_lines;
	QString script_content, script_file;
	QDir dir;
	SHS::Object * _cur_root;
	
private slots:
	void logToWidget(const QString & msg);
	void eventReceived(SHS::Command * cmd, QVariantMap args);
	void invokeCommand(SHS::Command * cmd, QVariantList args, QVariant * ret);
	void sendBroadcast(const QByteArray & data);
	void buttonClicked();
	void engineWarnings(const QList<QQmlError> & warnings);
	
signals:
	void error(int line, QString file, QString text, QString stack);
	void log(QString text, int type);
	void logIO(QString msg);
	void debug(QString text);
	void message(QString text, int type);
	void variableHistoryAdded(QString vname, QPair<QDateTime, double> value);
	void variableHistoryRemoved(QString vname, int count);
	
};

inline QDebug operator<<(QDebug debug, const SHSScriptEngine::IncPart & v) {
	QDebugStateSaver saver(debug);
	debug.nospace() << '(' << v.start << ", " << v.reduce << ", " << v.file << ')';
	return debug;
}

#endif // SCRIPT_ENGINE_H
