/*! \file piiodevice.h
 * \ingroup IO
 * \~\brief
 * \~english Abstract input/output device
 * \~russian Базовый класс утройств ввода/вывода
 */
/*
	PIP - Platform Independent Primitives
	Abstract input/output device
	Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@yandex.ru

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef PIIODEVICE_H
#define PIIODEVICE_H

#include "piinit.h"
#include "pitimer.h"
#include "piqueue.h"

/// TODO: написать документацию, тут ничего не понятно
// function executed from threaded read, pass readedData, sizeOfData, ThreadedReadData
typedef std::function<bool(const uchar *, int, void *)> ReadRetFunc;

#ifdef DOXYGEN

//! \relatesalso PIIODevice
//! \brief
//! \~english Enable device instances creation with \a PIIODevice::createFromFullPath() function.
//! \~russian Включить создание экземпляров устройства с помощью метода \a PIIODevice::createFromFullPath().
//! \~\details
//! \~english This macro may be placed in cpp or header file, but preferred place is header
//! \~russian Этот макрос может быть расположен в cpp или заголовочном файле, но предпочтительнее распологать в заголовочном
#  define REGISTER_DEVICE(class)

//! \relatesalso PIIODevice
//! \brief
//! \~english Use this macro instead of PIOBJECT when describe your own PIIODevice.
//! \~russian Используйте этот макрос вместо PIOBJECT при объявлении своего PIIODevice.
//! \~\param "prefix"
//! \~english Unique device prefix in quotes, may be ""
//! \~russian Уникальный префикс устройства в кавычках, может быть ""
#  define PIIODEVICE(class, "prefix")

#else

#  define REGISTER_DEVICE(name) \
	STATIC_INITIALIZER_BEGIN \
		PIIODevice::registerDevice(name::fullPathPrefixS(), #name, []()->PIIODevice*{return new name();});\
	STATIC_INITIALIZER_END

#  define PIIODEVICE(name, prefix) \
		PIOBJECT_SUBCLASS(name, PIIODevice) \
		PIIODevice * copy() const override {return new name();} \
	public: \
		PIConstChars fullPathPrefix() const override {return prefix;} \
		static PIConstChars fullPathPrefixS() {return prefix;} \
	private:


#endif

//! \ingroup IO
//! \~\brief
//! \~english Base class for input/output devices.
//! \~russian Базовый класс утройств ввода/вывода.
class PIP_EXPORT PIIODevice: public PIObject
{
	PIOBJECT_SUBCLASS(PIIODevice, PIObject);
	friend void __DevicePool_threadReadDP(void * ddp);
public:
	NO_COPY_CLASS(PIIODevice);
	
	//! \~english Constructs a empty %PIIODevice
	//! \~russian Создает пустой %PIIODevice
	explicit PIIODevice();

	//! \~english Open modes for PIIODevice
	//! \~russian Режимы открытия для PIIODevice
	enum DeviceMode {
		ReadOnly  /*! \~english Device can only read \~russian Устройство может только читать */ = 0x01,
		WriteOnly /*! \~english Device can only write \~russian Устройство может только писать */ = 0x02,
		ReadWrite /*! \~english Device can both read and write \~russian Устройство может читать и писать */ = 0x03
	};

	//! \~english Options for PIIODevice, works with some devices
	//! \~russian Опции для PIIODevice, работает для некоторых устройств
	enum DeviceOption {
		BlockingRead  /*! \~english \a read() block until data is received, default off \~russian \a read() блокируется, пока данные не поступят, по умолчанию выключено */ = 0x01,
		BlockingWrite /*! \~english \a write() block until data is sent, default off \~russian \a write() блокируется, пока данные не запишутся, по умолчанию выключено */ = 0x02
	};

	//! \~english Characteristics of PIIODevice channel
	//! \~russian Характеристики канала PIIODevice
	enum DeviceInfoFlag {
		Sequential /*! \~english Continuous bytestream without packets \~russian Непрерывный поток байт, без пакетирования */ = 0x01,
		Reliable   /*! \~english Channel without data errors or corruptions \~russian Канал без ошибок или повреждений данных */ = 0x02
	};

	struct FabricInfo {
		PIConstChars prefix;
		PIConstChars classname;
		PIIODevice*(*fabricator)() = nullptr;
	};

	typedef PIFlags<DeviceOption> DeviceOptions;
	typedef PIFlags<DeviceInfoFlag> DeviceInfoFlags;

	//! \~english Constructs %PIIODevice with path "path" and open mode "mode"
	//! \~russian Создает %PIIODevice с путём "path" и режимом открытия "mode"
	explicit PIIODevice(const PIString & path, DeviceMode mode = ReadWrite);

	virtual ~PIIODevice();

	//! \~english Returns current open mode of device
	//! \~russian Возвращает текущий режим открытия устройства
	DeviceMode mode() const {return mode_;}
	
	//! \~english Set open mode of device. Don`t reopen device
	//! \~russian Устанавливает режим открытия устройства. Не переоткрывает устройство
	void setMode(DeviceMode m) {mode_ = m;}

	//! \~english Returns current device options
	//! \~russian Возвращает текущие опции устройства
	DeviceOptions options() const {return options_;}

	//! \~english Returns current device option "o" state
	//! \~russian Возвращает текущее состояние опции "o"
	bool isOptionSet(DeviceOption o) const {return options_[o];}

	//! \~english Set device options
	//! \~russian Устанавливает опции устройства
	void setOptions(DeviceOptions o);

	//! \~english Set device option "o" to "yes" and returns previous state
	//! \~russian Устанавливает опцию "o" устройства в "yes" и возвращает предыдущее состояние опции
	bool setOption(DeviceOption o, bool yes = true);

	//! \~english Returns device characteristic flags
	//! \~russian Возвращает характеристики канала
	DeviceInfoFlags infoFlags() const {return deviceInfoFlags();}

	//! \~english Returns current path of device
	//! \~russian Возвращает текущий путь устройства
	PIString path() const {return property("path").toString();}
	
	//! \~english Set path of device. Don`t reopen device
	//! \~russian Устанавливает путь устройства. Не переоткрывает устройство
	void setPath(const PIString & path) {setProperty("path", path);}
	
	//! \~english Returns if mode is ReadOnly or ReadWrite
	//! \~russian Возвращает равен ли режим открытия ReadOnly или ReadWrite
	bool isReadable() const {return (mode_ & ReadOnly);}
	
	//! \~english Returns if mode is WriteOnly or ReadWrite
	//! \~russian Возвращает равен ли режим открытия WriteOnly или ReadWrite
	bool isWriteable() const {return (mode_ & WriteOnly);}
	
	//! \~english Returns if device is successfully opened
	//! \~russian Возвращает успешно ли открыто устройство
	bool isOpened() const {return opened_;}
	
	//! \~english Returns if device is closed
	//! \~russian Возвращает закрыто ли устройство
	bool isClosed() const {return !opened_;}
	
	//! \~english Returns if device can read \b now
	//! \~russian Возвращает может ли устройство читать \b сейчас
	virtual bool canRead() const {return opened_ && (mode_ & ReadOnly);}
	
	//! \~english Returns if device can write \b now
	//! \~russian Возвращает может ли устройство писать \b сейчас
	virtual bool canWrite() const {return opened_ && (mode_ & WriteOnly);}


	//! \~english Set calling of \a open() enabled while threaded read on closed device
	//! \~russian Устанавливает возможность вызова \a open() при потоковом чтении на закрытом устройстве
	void setReopenEnabled(bool yes = true);
	
	//! \~english Set timeout in milliseconds between \a open() tryings if reopen is enabled
	//! \~russian Устанавливает задержку в миллисекундах между вызовами \a open() если переоткрытие активно
	void setReopenTimeout(int msecs);

	//! \~english Returns reopen enable
	//! \~russian Возвращает активно ли переоткрытие
	bool isReopenEnabled() const {return property("reopenEnabled").toBool();}
	
	//! \~english Returns reopen timeout in milliseconds
	//! \~russian Возвращает задержку переоткрытия в миллисекундах
	int reopenTimeout() {return property("reopenTimeout").toInt();}


	//! \~english Set threaded read callback
	//! \~russian Устанавливает callback потокового чтения
	void setThreadedReadSlot(ReadRetFunc func);
	
	//! \~english Set custom data that will be passed to threaded read callback
	//! \~russian Устанавливает произвольный указатель, который будет передан в callback потокового чтения
	void setThreadedReadData(void * d) {ret_data_ = d;}
	
	//! \~english Set size of threaded read buffer
	//! \~russian Устанавливает размер буфера потокового чтения
	void setThreadedReadBufferSize(int new_size);

	//! \~english Returns size of threaded read buffer
	//! \~russian Возвращает размер буфера потокового чтения
	int threadedReadBufferSize() const {return threaded_read_buffer_size;}
	
	//! \~english Returns content of threaded read buffer
	//! \~russian Возвращает содержимое буфера потокового чтения
	const uchar * threadedReadBuffer() const {return buffer_tr.data();}

	//! \~english Returns custom data that will be passed to threaded read callback
	//! \~russian Возвращает произвольный указатель, который будет передан в callback потокового чтения
	void * threadedReadData() const {return ret_data_;}

	
	//! \~english Returns if threaded read is started
	//! \~russian Возвращает запущен ли поток чтения
	bool isThreadedRead() const;
	
	//! \~english Start threaded read
	//! \~russian Запускает потоковое чтение
	void startThreadedRead();
	
	//! \~english Start threaded read and assign threaded read callback to "func"
	//! \~russian Запускает потоковое чтение и устанавливает callback потокового чтения в "func"
	void startThreadedRead(ReadRetFunc func);
	
	//! \~english Stop threaded read.
	//! \~russian Останавливает потоковое чтение.
	void stopThreadedRead();

	//! \~english Wait for threaded read finish no longer than "timeout_ms" milliseconds.
	//! \~russian Ожидает завершения потокового чтения в течении не более "timeout_ms" миллисекунд.
	bool waitThreadedReadFinished(int timeout_ms = -1);
	
	
	//! \~english Returns if threaded write is started
	//! \~russian Возвращает запущен ли поток записи
	bool isThreadedWrite() const;
	
	//! \~english Start threaded write
	//! \~russian Запускает потоковую запись
	void startThreadedWrite();
	
	//! \~english Stop threaded write.
	//! \~russian Останавливает потоковую запись.
	void stopThreadedWrite();

	//! \~english Wait for threaded write finish no longer than "timeout_ms" milliseconds.
	//! \~russian Ожидает завершения потоковой записи в течении не более "timeout_ms" миллисекунд.
	bool waitThreadedWriteFinished(int timeout_ms = -1);

	//! \~english Clear threaded write task queue
	//! \~russian Очищает очередь потоковой записи
	void clearThreadedWriteQueue();

	
	//! \~english Start both threaded read and threaded write
	//! \~russian Запускает потоковое чтение и запись
	void start();
	
	//! \~english Stop both threaded read and threaded write.
	//! \~russian Останавливает потоковое чтение и запись.
	void stop();

	//! \~english Stop both threaded read and threaded write and wait for finish.
	//! \~russian Останавливает потоковое чтение и запись и ожидает завершения.
	void stopAndWait(int timeout_ms = -1);

	//! \~english Interrupt blocking operation.
	//! \~russian Прерывает блокирующую операцию.
	virtual void interrupt() {}


	//! \~english Read from device maximum "max_size" bytes to "read_to"
	//! \~russian Читает из устройства не более "max_size" байт в "read_to"
	ssize_t read(void * read_to, ssize_t max_size);

	//! \~english Read from device to memory block "mb"
	//! \~russian Читает из устройства в блок памяти "mb"
	ssize_t read(PIMemoryBlock mb);

	//! \~english Read from device maximum "max_size" bytes and returns them as PIByteArray
	//! \~russian Читает из устройства не более "max_size" байт и возвращает данные как PIByteArray
	PIByteArray read(ssize_t max_size);

	//! \~english Returns the number of bytes that are available for reading.
	//! \~russian Возвращает количество байт доступных для чтения
	//! \~\details
	//! \~english This function is commonly used with sequential devices
	//! to determine the number of bytes to allocate in a buffer before reading.
	//! If function returns -1 it mean that number of bytes undefined.
	//! \~russian Эта функция как правило используется чтобы знать какой
	//! размер буфера нужен в памяти для чтения.
	//! Если функция возвращает -1 это значит что количество байт для чтения не известно.
	virtual ssize_t bytesAvailable() const {return -1;}

	//! \~english Write maximum "max_size" bytes of "data" to device
	//! \~russian Пишет в устройство не более "max_size" байт из "data"
	ssize_t write(const void * data, ssize_t max_size);

	//! \~english Read from device for "timeout_ms" milliseconds and return readed data as PIByteArray.
	//! Timeout should to be greater than 0
	//! \~russian Читает из устройства в течении "timeout_ms" миллисекунд и возвращает данные как PIByteArray.
	//! Таймаут должен быть больше 0
	PIByteArray readForTime(double timeout_ms);
	

	//! \~english Add task to threaded write queue and return task ID
	//! \~russian Добавляет данные в очередь на потоковую запись и возвращает ID задания
	ullong writeThreaded(const void * data, ssize_t max_size) {return writeThreaded(PIByteArray(data, uint(max_size)));}
	
	//! \~english Add task to threaded write queue and return task ID
	//! \~russian Добавляет данные в очередь на потоковую запись и возвращает ID задания
	ullong writeThreaded(const PIByteArray & data);
	
	
	//! \~english Configure device from section "section" of file "config_file", if "parent_section" parent section also will be read
	//! \~russian
	bool configure(const PIString & config_file, const PIString & section, bool parent_section = false);
	
	
	//! \~english Returns full unambiguous string prefix. \ref PIIODevice_sec7
	//! \~russian Возвращает префикс устройства. \ref PIIODevice_sec7
	virtual PIConstChars fullPathPrefix() const {return "";}

	static PIConstChars fullPathPrefixS() {return "";}
	
	//! \~english Returns full unambiguous string, describes this device, \a fullPathPrefix() + "://" + ...
	//! \~russian Возвращает строку полного описания для этого устройства, \a fullPathPrefix() + "://" + ...
	PIString constructFullPath() const;

	//! \~english Configure device with parameters of full unambiguous string
	//! \~russian Настраивает устройство из параметров строки полного описания
	void configureFromFullPath(const PIString & full_path);

	//! \~english Returns PIVariantTypes::IODevice, describes this device
	//! \~russian Возвращает PIVariantTypes::IODevice, описывающий это устройство
	PIVariantTypes::IODevice constructVariant() const;

	//! \~english Configure device from PIVariantTypes::IODevice
	//! \~russian Настраивает устройство из PIVariantTypes::IODevice
	void configureFromVariant(const PIVariantTypes::IODevice & d);

	//! \~english Try to create new device by prefix, configure it with \a configureFromFullPath() and returns it.
	//! \~russian Пытается создать новое устройство по префиксу, настраивает с помощью \a configureFromFullPath() и возвращает его
	static PIIODevice * createFromFullPath(const PIString & full_path);

	//! \~english Try to create new device by prefix, configure it with \a configureFromVariant() and returns it.
	//! \~russian Пытается создать новое устройство по префиксу, настраивает с помощью \a configureFromVariant() и возвращает его
	static PIIODevice * createFromVariant(const PIVariantTypes::IODevice & d);
	
	static PIString normalizeFullPath(const PIString & full_path);

	static void splitFullPath(PIString fpwm, PIString * full_path, DeviceMode * mode = 0, DeviceOptions * opts = 0);

	//! \~english Returns fullPath prefixes of all registered devices
	//! \~russian Возвращает префиксы всех зарегистрированных устройств
	static PIStringList availablePrefixes();

	//! \~english Returns class names of all registered devices
	//! \~russian Возвращает имена классов всех зарегистрированных устройств
	static PIStringList availableClasses();

	static void registerDevice(PIConstChars prefix, PIConstChars classname, PIIODevice*(*fabric)());

	
	EVENT_HANDLER(bool, open);
	EVENT_HANDLER1(bool, open, const PIString &, _path);
	bool open(DeviceMode _mode);
	EVENT_HANDLER2(bool, open, const PIString &, _path, DeviceMode, _mode);
	EVENT_HANDLER(bool, close);
	EVENT_HANDLER1(ssize_t, write, PIByteArray, data);

	//! \~english Write memory block "mb" to device
	//! \~russian Пишет в устройство блок памяти "mb"
	ssize_t write(const PIMemoryBlock & mb) {return write(mb.data(), mb.size());}

	EVENT_VHANDLER(void, flush) {;}
	
	EVENT(opened);
	EVENT(closed);
	EVENT2(threadedReadEvent, const uchar * , readed, ssize_t, size);
	EVENT2(threadedWriteEvent, ullong, id, ssize_t, written_size);

//! \handlers
//! \{

	//! \fn bool open()
	//! \~english Open device
	//! \~russian Открывает устройство

	//! \fn bool open(const PIString & path)
	//! \~english Open device with path "path"
	//! \~russian Открывает устройство с путём "path"

	//! \fn bool open(const DeviceMode & mode)
	//! \~english Open device with mode "mode"
	//! \~russian Открывает устройство с режимом открытия "mode"

	//! \fn bool open(const PIString & path, const DeviceMode & mode)
	//! \~english Open device with path "path" and mode "mode"
	//! \~russian Открывает устройство с путём "path" и режимом открытия "mode"

	//! \fn bool close()
	//! \~english Close device
	//! \~russian Закрывает устройство

	//! \fn ssize_t write(PIByteArray data)
	//! \~english Write "data" to device
	//! \~russian Пишет "data" в устройство

//! \}
//! \vhandlers
//! \{

	//! \fn void flush()
	//! \~english Immediate write all buffers
	//! \~russian Немедленно записать все буферизированные данные

//! \}
//! \events
//! \{

	//! \fn void opened()
	//! \~english Raise if succesfull open
	//! \~russian Вызывается при успешном открытии

	//! \fn void closed()
	//! \~english Raise if succesfull close
	//! \~russian Вызывается при успешном закрытии

	//! \fn void threadedReadEvent(const uchar * readed, ssize_t size)
	//! \~english Raise if read thread succesfull read some data
	//! \~russian Вызывается при успешном потоковом чтении данных

	//! \fn void threadedWriteEvent(ullong id, ssize_t written_size)
	//! \~english Raise if write thread successfull write some data of task with ID "id"
	//! \~russian Вызывается при успешной потоковой записи данных с ID задания "id"

//! \}
//! \ioparams
//! \{
#ifdef DOXYGEN
	//! \~english setReopenEnabled, default "true"
	//! \~russian setReopenEnabled, по умолчанию "true"
	bool reopenEnabled;
	
	//! \~english setReopenTimeout in milliseconds, default 1000
	//! \~russian setReopenTimeout в миллисекундах, по умолчанию 1000
	int reopenTimeout;
	
	//! \~english setThreadedReadBufferSize in bytes, default 4096
	//! \~russian setThreadedReadBufferSize в байтах, по умолчанию 4096
	int threadedReadBufferSize;
#endif
//! \}

protected:
	//! \~english Reimplement to configure device from entries "e_main" and "e_parent", cast arguments to \a PIConfig::Entry*
	//! \~russian
	virtual bool configureDevice(const void * e_main, const void * e_parent = 0) {return true;}
	
	//! \~english Reimplement to open device, return value will be set to "opened_" variable.
	//! Don't call this function in subclass, use \a open()!
	//! \~russian Переопределите для открытия устройства, возвращаемое значение будет установлено в
	//! переменную "opened_". Не используйте напрямую, только через \a open()!
	virtual bool openDevice() = 0; // use path_, type_, opened_, init_ variables
	
	//! \~english Reimplement to close device, inverse return value will be set to "opened_" variable
	//! \~russian Переопределите для закрытия устройства, обратное возвращаемое значение будет установлено в переменную "opened_"
	virtual bool closeDevice() {return true;} // use path_, type_, opened_, init_ variables

	//! \~english Reimplement this function to read from your device
	//! \~russian Переопределите для чтения данных из устройства
	virtual ssize_t readDevice(void * read_to, ssize_t max_size) {piCoutObj << "\"read\" is not implemented!"; return -2;}

	//! \~english Reimplement this function to write to your device
	//! \~russian Переопределите для записи данных в устройство
	virtual ssize_t writeDevice(const void * data, ssize_t max_size) {piCoutObj << "\"write\" is not implemented!"; return -2;}

	//! \~english Function executed when thread read some data, default implementation execute external callback "ret_func_"
	//! \~russian Метод вызывается после каждого успешного потокового чтения, по умолчанию вызывает callback "ret_func_"
	virtual bool threadedRead(const uchar * readed, ssize_t size);
	
	//! \~english Reimplement to construct full unambiguous string, describes this device.
	//! Default implementation returns \a path()
	//! \~russian Переопределите для создания строки полного описания устройства.
	//! По умолчанию возвращает \a path()
	virtual PIString constructFullPathDevice() const {return path();}

	//! \~english Reimplement to configure your device with parameters of full unambiguous string.
	//! Default implementation call \a setPath()
	//! \~russian Переопределите для настройки устройства из строки полного описания.
	//! По умолчанию вызывает \a setPath()
	virtual void configureFromFullPathDevice(const PIString & full_path) {setPath(full_path);}

	//! \~english Reimplement to construct device properties.
	//! Default implementation return PIPropertyStorage with \"path\" entry
	//! \~russian Переопределите для создания свойств устройства.
	//! По умолчанию возвращает PIPropertyStorage со свойством \"path\"
	virtual PIPropertyStorage constructVariantDevice() const;

	//! \~english Reimplement to configure your device from PIPropertyStorage. Options and mode already applied.
	//! Default implementation apply \"path\" entry
	//! \~russian Переопределите для настройки устройства из PIPropertyStorage. Опции и режим уже применены.
	//! По умолчанию устанавливает свойство \"path\"
	virtual void configureFromVariantDevice(const PIPropertyStorage & d);

	//! \~english Reimplement to apply new device options
	//! \~russian Переопределите для применения новых опций устройства
	virtual void optionsChanged() {;}

	//! \~english Reimplement to return correct \a DeviceInfoFlags. Default implementation returns 0
	//! \~russian Переопределите для возврата правильных \a DeviceInfoFlags. По умолчанию возвращает 0
	virtual DeviceInfoFlags deviceInfoFlags() const {return 0;}

	//! \~english Reimplement to apply new \a threadedReadBufferSize()
	//! \~russian Переопределите для применения нового \a threadedReadBufferSize()
	virtual void threadedReadBufferSizeChanged() {;}

	static PIIODevice * newDeviceByPrefix(const char * prefix);

	bool isThreadedReadStopping() const {return read_thread.isStopping();}

	DeviceMode mode_;
	DeviceOptions options_;
	ReadRetFunc func_read = nullptr;
	std::atomic_bool opened_;
	void * ret_data_ = nullptr;

private:
	EVENT_HANDLER(void, read_func);
	EVENT_HANDLER(void, write_func);

	virtual PIIODevice * copy() const {return nullptr;}
	PIString fullPathOptions() const;
	void _init();
	static void cacheFullPath(const PIString & full_path, const PIIODevice * d);
	static PIMap<PIConstChars, FabricInfo> & fabrics();

	PITimeMeasurer tm, reopen_tm;
	PIThread read_thread, write_thread;
	PIByteArray buffer_in, buffer_tr;
	PIQueue<PIPair<PIByteArray, ullong> > write_queue;
	ullong tri = 0;
	uint threaded_read_buffer_size, reopen_timeout = 1000;
	bool reopen_enabled = true, destroying = false;

	static PIMutex nfp_mutex;
	static PIMap<PIString, PIString> nfp_cache;

};

#endif // PIIODEVICE_H
