#ifndef SH_TYPES_H
#define SH_TYPES_H

#include <QDataStream>
#include <QIcon>
#include <QDir>
#include <qpiconfig.h>
#include <blockitem.h>
#include "SH_base.h"

const char SH_Block_Property_ID[]    = "__id__";
const char SH_Block_Property_Flags[] = "__flags__";
const int  SH_Block_ID               = 2000; // int
const int  SH_Block_Source           = 2001; // int
const int  SH_Block_Traced           = 2003; // bool
const char SH_Pin_Plugin_name[]      = "__plugin__";
const char SH_Pin_Bus_type[]         = "__bus__";

class SHPluginBase;
class SHPluginsLoader;
class SHPluginsWeb;
class SHPluginsManager;
class SHRuntime;

class SHS: public QObject {
	Q_OBJECT
	Q_ENUMS(BlockFlag PacketFlag PropertyFlag)
	Q_FLAGS(BlockFlag BlockFlags)
	Q_FLAGS(PacketFlag PacketFlags)
	Q_FLAGS(PropertyFlag PropertyFlags)
public:

	class Object;

	enum BlockFlag {
		NoFlag            = 0,
		Gateway           = 0x04,
		Computer          = 0x08,
		AlwaysConnected   = 0x10,
		All               = 0xFFFF
	};

	enum {
		Plugin = 0x1000
	};

	enum PacketFlag {
		// NoFlag = BlockFlag.NoFlag,
		Input = 0x1,
		Output = 0x2,
	};
	
	enum PropertyFlag {
		// NoFlag = BlockFlag.NoFlag,
		Fixed = 0x1, // User can` edit property in block property dialog
		Hidden = 0x2, // User can` see property in block property dialog
		Internal = 0x4, // User can`t edit this property even in database window
		Label = 0x8 | Fixed, // Property shown as label
		InstallFileWatcher = 0x10 //it use for set filewatcher for auto update packets on file change
	};
	
	enum MessageClass {
		mcInfo,
		mcWarning,
		mcError
	};

	Q_DECLARE_FLAGS(PacketFlags, PacketFlag)
	Q_DECLARE_FLAGS(PropertyFlags, PropertyFlag)
	Q_DECLARE_FLAGS(BlockFlags, BlockFlag)


	struct DeviceParameters {
		bool isValid() const {return !name.isEmpty();}
		QString name;
		bool send_time_grid = false;
		double send_time_grid_period_ms = 20.;
	};

	struct UserRole {
		bool isValid() const {return !name.isEmpty();}
		void prepare();
		QString displayName() const;
		QString name;
		QString display_name;
		QString password;
		QByteArray password_hash;
		int privilege = 0;
		int index = 0;
	};

	struct ScriptVariable {
		enum {Version = 1};
		ScriptVariable(const QString & n = QString(), const QString & s = QString(), const QString & d = QString()):
			name(n), store(s), description(d) {id = -1; parseStore();}
		void parseStore();
		int id;
		QString name, store, description, display_name;
		quint64 store_msecs;
	};

	struct ServerProject {
		ServerProject();
		bool load(const QString & path, bool messages = true);
		bool save(const QString & path) const;
		void makeEmptyNames(QString base_filename);
		QStringList getAssFiles() const;
		bool addScriptFile(const QString & p);
		void removeScriptFile(const QString & p);
		QString mainScriptText(QDir dir = QDir::current()) const;
		UserRole roleByIndex(int index) const;
		DeviceParameters deviceParameterByName(QString n) const;
		int version;
		QStringList qs;
		QStringList res;
		QString shs;
		QString shs_db;
		QString ui;
		QString dat;
		QString var;
		QString cm;
		QString qs_main;
		QVector<DeviceParameters> device_parameters;
		QVector<UserRole> roles;
		QSet<QString> used_plugins; // manifest.name
	};


	/// Server - Client structures

	struct PacketGraphicData {
		PacketGraphicData(const QString & n = QString()) {name = Q2PIString(n);}
		PIString name;
		PIVector<PIPair<ullong, double>> data;
	};


	static QByteArray blockConnectionModel(const BlockItem * block);



	/// NEW >>>>

	class ScriptObjectBase {
	public:
		uint id = 0;
		QString desc;
		QStringList object_path;
	};

	class PluginCommand {
	public:
		QVariantMap arguments;
		uint plugin_id = 0;
		SHPluginBase * plugin = nullptr;
	};

	class Command: public ScriptObjectBase {
	public:
		bool isValid() const {return flags != 0;}
		void swapIOFlag();
		void setDirectionFlag(SHS::PacketFlag f);
		QVariant invoke(QVariantMap args);
		QVector<QVariant> obtainUserArguments() const;
		void applyUserArguments(const QVector<QVariant> & ua);
		QStringList user_arguments; // filled in plugin
		QVariantMap arguments; // filled in plugin

		PacketFlags flags;
		QString binded_property;
		PIStringList devices;
		QVector<PluginCommand> cmd; // filled by sh-designer
		QString pin;

		/// engine run-time
		QString name;
		QVector<PluginCommand> gw_cmd;
		PluginCommand packet_cmd;
		SHPluginBase * block_plugin = nullptr;
		Object * root = nullptr, * parent = nullptr;
		SHRuntime * _rt = nullptr;
	};

	class Object: public ScriptObjectBase {
	public:
		Object & addProperty(const QString & n, const QVariant & v);
		Object & addObject  (const QString & n, const Object   & v);
		Object & addCommand (const QString & n, const Command  & v);
		Object & addCommand (const QString & n, const QStringList & user_args, const Command  & v);
		Object & addEvent   (const QString & n, const Command  & v);
		Object & addEvent   (const QString & n, const QStringList & user_args, const Command  & v);
		QVariant property(const QString & n);
		Object   object  (const QString & n);
		Command  command (const QString & n);
		Command  event   (const QString & n);
		void bindProperty(const QString & prop, const QString & cmd);
		void applyPathes(const PIStringList & p, PacketFlag flag);
		void applyPluginCommands(const QVector<PluginCommand> & c, PacketFlag flag);
		void applyBlockPlugin(SHPluginBase * p);
		void applyRuntime(SHRuntime * rt);
		void swapIOFlags();
		void renameObject(const QString & name, const QString & new_name);
		QString type;
		QVariantMap properties;
		QMap<QString, Object > objects ;
		QMap<QString, Command> commands;
		uint block_plugin_id = 0;

		/// engine run-time
		QString name;
		QMap<QString, Command*> binded_properties;
	private:
		Command & command(const QString & n, const Command & v, PacketFlag dir);

	};

	class ServerData {
	public:
		Object blocks;
		QString res_path;
		PropertyStorage project, server;
		QMap<uint, PropertyStorage> plugins;
		QSet<QString> used_plugins; // manifest.name
	};

	/// NEW <<<<

};


inline QDataStream & operator <<(QDataStream & s, const SHS::DeviceParameters & p) {
	ChunkStream cs;
	cs.add(1, p.name).add(2, p.send_time_grid).add(2, p.send_time_grid_period_ms);
	s << cs.data();
	return s;
}
inline QDataStream & operator >>(QDataStream & s, SHS::DeviceParameters & p) {
	ChunkStream cs(s);
	cs.readAll();
	cs.get(1, p.name).get(2, p.send_time_grid).get(2, p.send_time_grid_period_ms);
	return s;
}


inline QDataStream & operator <<(QDataStream & s, const SHS::UserRole & p) {
	ChunkStream cs;
	cs.add(1, p.name).add(2, p.display_name).add(3, p.privilege).add(4, p.password);
	s << cs.data();
	return s;
}
inline QDataStream & operator >>(QDataStream & s, SHS::UserRole & p) {
	ChunkStream cs(s);
	cs.readAll();
	cs.get(1, p.name).get(2, p.display_name).get(3, p.privilege).get(4, p.password);
	p.prepare();
	return s;
}


inline QDataStream & operator <<(QDataStream & s, const SHS::ScriptVariable & p) {
	ChunkStream cs;
	cs.add(1, p.name).add(2, p.store).add(3, p.description).add(4, p.store_msecs).add(5, p.display_name);
	s << cs.data();
	return s;
}
inline QDataStream & operator >>(QDataStream & s, SHS::ScriptVariable & p) {
	ChunkStream cs(s);
	cs.readAll();
	cs.get(1, p.name).get(2, p.store).get(3, p.description).get(4, p.store_msecs).get(5, p.display_name);
	p.parseStore();
	return s;
}


inline QDataStream & operator <<(QDataStream & s, const SHS::ServerProject & p) {
	ChunkStream cs;
	cs.add(1, p.version).add(2, p.qs).add(3, p.ui).add(4, p.dat).add(5, p.var)
	  .add(6, p.cm).add(7, p.qs_main).add(8, p.res).add(9, p.shs).add(10, p.shs_db)
	  .add(11, p.device_parameters).add(12, p.roles).add(13, p.used_plugins);
	s << cs.data();
	return s;
}
inline QDataStream & operator >>(QDataStream & s, SHS::ServerProject & p) {
	p = SHS::ServerProject();
	p.version = 0;
	ChunkStream cs(s);
	cs.readAll();
	cs.get(1, p.version).get(2, p.qs).get(3, p.ui).get(4, p.dat).get(5, p.var)
	  .get(6, p.cm).get(7, p.qs_main).get(8, p.res).get(9, p.shs).get(10, p.shs_db)
	  .get(11, p.device_parameters).get(12, p.roles).get(13, p.used_plugins);
	return s;
}


inline QDataStream & operator <<(QDataStream & s, const SHS::PluginCommand & p) {
	ChunkStream cs;
	cs.add(1, p.arguments).add(2, p.plugin_id);
	s << cs.data();
	return s;
}
inline QDataStream & operator >>(QDataStream & s, SHS::PluginCommand & p) {
	p = SHS::PluginCommand();
	ChunkStream cs(s);
	cs.readAll();
	cs.get(1, p.arguments).get(2, p.plugin_id);
	return s;
}


inline QDataStream & operator <<(QDataStream & s, const SHS::Command & p) {
	ChunkStream cs;
	cs.add(1, p.flags).add(2, p.desc).add(3, p.cmd).add(4, PI2QStringList(p.devices))
	  .add(5, p.user_arguments).add(6, p.arguments).add(7, p.binded_property).add(8, p.pin);
	s << cs.data();
	return s;
}
inline QDataStream & operator >>(QDataStream & s, SHS::Command & p) {
	p = SHS::Command();
	ChunkStream cs(s);
	cs.readAll();
	QStringList devices;
	cs.get(1, p.flags).get(2, p.desc).get(3, p.cmd).get(4, devices)
	  .get(5, p.user_arguments).get(6, p.arguments).get(7, p.binded_property).get(8, p.pin);
	p.devices = Q2PIStringList(devices);
	return s;
}


inline QDataStream & operator <<(QDataStream & s, const SHS::Object & p) {
	ChunkStream cs;
	cs.add(1, p.desc).add(2, p.type).add(3, p.properties).add(4, p.objects).add(5, p.commands).add(6, p.block_plugin_id);
	s << cs.data();
	return s;
}
inline QDataStream & operator >>(QDataStream & s, SHS::Object & p) {
	p = SHS::Object();
	ChunkStream cs(s);
	cs.readAll();
	cs.get(1, p.desc).get(2, p.type).get(3, p.properties).get(4, p.objects).get(5, p.commands).get(6, p.block_plugin_id);
	return s;
}


inline QDataStream & operator <<(QDataStream & s, const SHS::ServerData & p) {
	ChunkStream cs;
	cs.add(1, p.blocks).add(2, p.project).add(3, p.plugins).add(4, p.server).add(5, p.used_plugins);
	s << cs.data();
	return s;
}
inline QDataStream & operator >>(QDataStream & s, SHS::ServerData & p) {
	p = SHS::ServerData();
	ChunkStream cs(s);
	cs.readAll();
	cs.get(1, p.blocks).get(2, p.project).get(3, p.plugins).get(4, p.server).get(5, p.used_plugins);
	return s;
}


BINARY_STREAM_WRITE(SHS::PacketGraphicData) {s << v.name << v.data; return s;}
BINARY_STREAM_READ (SHS::PacketGraphicData) {s >> v.name >> v.data; return s;}

BINARY_STREAM_WRITE(SHS::Object) {s << qSerialize(v); return s;}
BINARY_STREAM_READ (SHS::Object) {QByteArray ba; s >> ba; v = qDeserialize<SHS::Object>(ba); return s;}


#endif // SH_TYPES_H
