//! \~\file piiodevice.h
//! \~\ingroup IO
//! \~\brief
//! \~english Core abstraction for configurable input/output devices
//! \~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 "piqueue.h"
#include "pithread.h"

//! \~english Callback used by \a setThreadedReadSlot().
//! \~russian Callback, используемый методом \a setThreadedReadSlot().
//! \~\details
//! \~english Receives pointer to data read by the background thread, number of bytes and user data set by \a setThreadedReadData().
//! \~russian Принимает указатель на данные, прочитанные фоновым потоком, количество байт и пользовательские данные, заданные через \a
//! setThreadedReadData().
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.
//! \~\details
//! \~english "prefix" is a unique device prefix used in \a createFromFullPath().
//! \~russian "prefix" это уникальный префикс устройства, используемый в \a createFromFullPath().
#  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 an 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 Generic options supported by some devices.
	//! \~russian Общие опции, поддерживаемые некоторыми устройствами.
	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 the device channel.
	//! \~russian Характеристики канала устройства.
	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;
	};

	//! \~english Bitmask of \a DeviceOption values.
	//! \~russian Битовая маска значений \a DeviceOption.
	typedef PIFlags<DeviceOption> DeviceOptions;

	//! \~english Bitmask of \a DeviceInfoFlag values.
	//! \~russian Битовая маска значений \a DeviceInfoFlag.
	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);

	//! \~english Destroys the device base object.
	//! \~russian Уничтожает базовый объект устройства.
	virtual ~PIIODevice();

	//! \~english Returns current open mode.
	//! \~russian Возвращает текущий режим открытия.
	DeviceMode mode() const { return mode_; }

	//! \~english Sets open mode without reopening the device.
	//! \~russian Устанавливает режим открытия без переоткрытия устройства.
	void setMode(DeviceMode m) { mode_ = m; }

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

	//! \~english Returns whether option "o" is enabled.
	//! \~russian Возвращает, включена ли опция "o".
	bool isOptionSet(DeviceOption o) const { return options_[o]; }

	//! \~english Replaces all current device options with "o".
	//! \~russian Полностью заменяет текущие опции устройства на "o".
	void setOptions(DeviceOptions o);

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

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

	//! \~english Returns current device path.
	//! \~russian Возвращает текущий путь устройства.
	PIString path() const { return property("path").toString(); }

	//! \~english Sets device path without reopening the device.
	//! \~russian Устанавливает путь устройства без его переоткрытия.
	void setPath(const PIString & path) { setProperty("path", path); }

	//! \~english Returns whether the current mode allows reading.
	//! \~russian Возвращает, разрешает ли текущий режим чтение.
	bool isReadable() const { return (mode_ & ReadOnly); }

	//! \~english Returns whether the current mode allows writing.
	//! \~russian Возвращает, разрешает ли текущий режим запись.
	bool isWriteable() const { return (mode_ & WriteOnly); }

	//! \~english Returns whether the device is currently opened.
	//! \~russian Возвращает, открыто ли сейчас устройство.
	bool isOpened() const { return opened_; }

	//! \~english Returns whether the device is currently closed.
	//! \~russian Возвращает, закрыто ли сейчас устройство.
	bool isClosed() const { return !opened_; }

	//! \~english Returns whether reading is possible right now.
	//! \~russian Возвращает, возможно ли чтение прямо сейчас.
	virtual bool canRead() const { return opened_ && (mode_ & ReadOnly); }

	//! \~english Returns whether writing is possible right now.
	//! \~russian Возвращает, возможна ли запись прямо сейчас.
	virtual bool canWrite() const { return opened_ && (mode_ & WriteOnly); }


	//! \~english Enables or disables automatic reopen attempts during threaded read.
	//! \~russian Включает или выключает автоматические попытки переоткрытия при потоковом чтении.
	void setReopenEnabled(bool yes = true);

	//! \~english Sets delay between automatic reopen attempts.
	//! \~russian Устанавливает задержку между автоматическими попытками переоткрытия.
	void setReopenTimeout(PISystemTime timeout);

	//! \~english Returns whether automatic reopen is enabled.
	//! \~russian Возвращает, включено ли автоматическое переоткрытие.
	bool isReopenEnabled() const { return property("reopenEnabled").toBool(); }

	//! \~english Returns delay between automatic reopen attempts.
	//! \~russian Возвращает задержку между автоматическими попытками переоткрытия.
	PISystemTime reopenTimeout() { return property("reopenTimeout").toSystemTime(); }


	//! \~english Sets callback invoked after successful threaded reads.
	//! \~russian Устанавливает callback, вызываемый после успешного потокового чтения.
	void setThreadedReadSlot(ReadRetFunc func);

	//! \~english Sets custom user data passed to threaded read callback.
	//! \~russian Устанавливает пользовательские данные, передаваемые в callback потокового чтения.
	void setThreadedReadData(void * d) { ret_data_ = d; }

	//! \~english Sets background read buffer size in bytes.
	//! \~russian Устанавливает размер буфера фонового чтения в байтах.
	void setThreadedReadBufferSize(int new_size);

	//! \~english Returns background read buffer size in bytes.
	//! \~russian Возвращает размер буфера фонового чтения в байтах.
	int threadedReadBufferSize() const { return threaded_read_buffer_size; }

	//! \~english Returns pointer to the internal threaded-read buffer.
	//! \~russian Возвращает указатель на внутренний буфер потокового чтения.
	const uchar * threadedReadBuffer() const { return buffer_tr.data(); }

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


	//! \~english Returns whether threaded read is running.
	//! \~russian Возвращает, запущено ли потоковое чтение.
	bool isThreadedRead() const;

	//! \~english Returns whether threaded read is stopping.
	//! \~russian Возвращает, находится ли потоковое чтение в процессе остановки.
	bool isThreadedReadStopping() const { return read_thread.isStopping(); }

	//! \~english Starts threaded read.
	//! \~russian Запускает потоковое чтение.
	void startThreadedRead();

	//! \~english Sets threaded read callback to "func" and starts threaded read.
	//! \~russian Устанавливает callback потокового чтения в "func" и запускает потоковое чтение.
	void startThreadedRead(ReadRetFunc func);

	//! \~english Requests threaded read stop.
	//! \~russian Запрашивает остановку потокового чтения.
	void stopThreadedRead();

	//! \~english Terminate threaded read.
	//! \~russian Прерывает потоковое чтение.
	//! \~\warning
	//! \~english Try not to use! This method may cause memory corruption!
	//! \~russian Старайтесь не использовать! Этот метод может привести к повреждению памяти!
	void terminateThreadedRead();

	//! \~english Waits until threaded read finishes or "timeout" expires.
	//! \~russian Ожидает завершения потокового чтения, но не дольше "timeout".
	bool waitThreadedReadFinished(PISystemTime timeout = {});


	//! \~english Returns delay between unsuccessful threaded read attempts in milliseconds.
	//! \~russian Возвращает задержку между безуспешными попытками потокового чтения в миллисекундах.
	uint threadedReadTimeout() const { return threaded_read_timeout_ms; }

	//! \~english Sets delay between unsuccessful threaded read attempts in milliseconds.
	//! \~russian Устанавливает задержку между безуспешными попытками потокового чтения в миллисекундах.
	void setThreadedReadTimeout(uint ms) { threaded_read_timeout_ms = ms; }


	//! \~english Returns whether threaded write is running.
	//! \~russian Возвращает, запущена ли потоковая запись.
	bool isThreadedWrite() const;

	//! \~english Starts threaded write.
	//! \~russian Запускает потоковую запись.
	void startThreadedWrite();

	//! \~english Requests threaded write stop.
	//! \~russian Запрашивает остановку потоковой записи.
	void stopThreadedWrite();

	//! \~english Terminate threaded write.
	//! \~russian Прерывает потоковую запись.
	//! \~\warning
	//! \~english Try not to use! This method may cause memory corruption!
	//! \~russian Старайтесь не использовать! Этот метод может привести к повреждению памяти!
	void terminateThreadedWrite();

	//! \~english Waits until threaded write finishes or "timeout" expires.
	//! \~russian Ожидает завершения потоковой записи, но не дольше "timeout".
	bool waitThreadedWriteFinished(PISystemTime timeout = {});

	//! \~english Clears queued threaded-write tasks.
	//! \~russian Очищает очередь заданий потоковой записи.
	void clearThreadedWriteQueue();


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

	//! \~english Requests stop for both threaded read and threaded write.
	//! \~russian Запрашивает остановку потокового чтения и потоковой записи.
	void stop();

	//! \~english Stops both background threads and waits for completion.
	//! \~russian Останавливает оба фоновых потока и ожидает их завершения.
	void stopAndWait(PISystemTime timeout = {});

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


	//! \~english Reads at most "max_size" bytes into "read_to".
	//! \~russian Читает в "read_to" не более "max_size" байт.
	ssize_t read(void * read_to, ssize_t max_size);

	//! \~english Reads data into memory block "mb".
	//! \~russian Читает данные в блок памяти "mb".
	ssize_t read(PIMemoryBlock mb);

	//! \~english Reads at most "max_size" bytes and returns them as \a PIByteArray.
	//! \~russian Читает не более "max_size" байт и возвращает их как \a 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 Writes at most "max_size" bytes from "data".
	//! \~russian Записывает из "data" не более "max_size" байт.
	ssize_t write(const void * data, ssize_t max_size);

	//! \~english Reads data for up to "timeout" and returns collected bytes.
	//! \~russian Читает данные в течение "timeout" и возвращает накопленные байты.
	PIByteArray readForTime(PISystemTime timeout);


	//! \~english Queues "data" for threaded write and returns task ID.
	//! \~russian Помещает "data" в очередь потоковой записи и возвращает ID задания.
	ullong writeThreaded(const void * data, ssize_t max_size) { return writeThreaded(PIByteArray(data, uint(max_size))); }

	//! \~english Queues byte array "data" for threaded write and returns task ID.
	//! \~russian Помещает массив байт "data" в очередь потоковой записи и возвращает ID задания.
	ullong writeThreaded(const PIByteArray & data);


	//! \~english Configures the device from section "section" of file "config_file".
	//! \~russian Настраивает устройство из секции "section" файла "config_file".
	//! \~\details
	//! \~english
	//! If "parent_section" is true, inherited parameters are also read from the parent section.
	//! \~russian
	//! Если "parent_section" равно true, то дополнительные параметры также читаются из родительской секции.
	bool configure(const PIString & config_file, const PIString & section, bool parent_section = false);


	//! \~english Returns device prefix used in full-path notation. \ref PIIODevice_sec7
	//! \~russian Возвращает префикс устройства, используемый в полной строке пути. \ref PIIODevice_sec7
	virtual PIConstChars fullPathPrefix() const { return ""; }

	static PIConstChars fullPathPrefixS() { return ""; }

	//! \~english Returns full-path representation of this device, \a fullPathPrefix() + "://" + ...
	//! \~russian Возвращает полную строку описания этого устройства, \a fullPathPrefix() + "://" + ...
	PIString constructFullPath() const;

	//! \~english Configures the device from full-path parameters.
	//! \~russian Настраивает устройство из параметров полной строки описания.
	void configureFromFullPath(const PIString & full_path);

	//! \~english Builds \a PIVariantTypes::IODevice description for this device.
	//! \~russian Создает описание \a PIVariantTypes::IODevice для этого устройства.
	PIVariantTypes::IODevice constructVariant() const;

	//! \~english Configures the device from \a PIVariantTypes::IODevice.
	//! \~russian Настраивает устройство из \a 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);

	//! \~english Returns normalized full-path representation for "full_path".
	//! \~russian Возвращает нормализованную полную строку пути для "full_path".
	static PIString normalizeFullPath(const PIString & full_path);

	//! \~english Splits full-path string into path, mode and options.
	//! \~russian Разбирает полную строку пути на путь, режим и опции.
	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)());


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


	//! \handlers
	//! \{

	//! \fn bool open()
	//! \~english Opens the device with current path and mode.
	//! \~russian Открывает устройство с текущими путём и режимом.
	EVENT_HANDLER(bool, open);

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

	//! \fn bool open(DeviceMode mode)
	//! \~english Opens the device with mode "mode".
	//! \~russian Открывает устройство с режимом "mode".
	bool open(DeviceMode _mode);

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

	//! \fn bool close()
	//! \~english Closes the device.
	//! \~russian Закрывает устройство.
	EVENT_HANDLER(bool, close);

	//! \fn ssize_t write(PIByteArray data)
	//! \~english Writes "data" to the device.
	//! \~russian Записывает "data" в устройство.
	EVENT_HANDLER1(ssize_t, write, PIByteArray, data);

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

	//! \fn void flush()
	//! \~english Immediately flushes device buffers.
	//! \~russian Немедленно сбрасывает буферы устройства.
	EVENT_VHANDLER(void, flush) { ; }

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

	//! \fn void opened()
	//! \~english Raised after successful opening.
	//! \~russian Вызывается после успешного открытия.
	EVENT(opened);

	//! \fn void closed()
	//! \~english Raised after successful closing.
	//! \~russian Вызывается после успешного закрытия.
	EVENT(closed);

	//! \fn void threadedReadEvent(const uchar * readed, ssize_t size)
	//! \~english Raised after threaded read receives some data.
	//! \~russian Вызывается после того, как потоковое чтение получило данные.
	EVENT2(threadedReadEvent, const uchar *, readed, ssize_t, size);

	//! \fn void threadedWriteEvent(ullong id, ssize_t written_size)
	//! \~english Raised after threaded write processes task with ID "id".
	//! \~russian Вызывается после того, как потоковая запись обработала задание с ID "id".
	EVENT2(threadedWriteEvent, ullong, id, ssize_t, written_size);

//! \}
//! \ioparams
//! \{
#ifdef DOXYGEN
	//! \~english setReopenEnabled, default "true".
	//! \~russian setReopenEnabled, по умолчанию "true".
	bool reopenEnabled;

	//! \~english setReopenTimeout, default 1_s.
	//! \~russian setReopenTimeout, по умолчанию 1_s.
	int reopenTimeout;

	//! \~english setThreadedReadBufferSize in bytes, default 4096.
	//! \~russian setThreadedReadBufferSize в байтах, по умолчанию 4096.
	int threadedReadBufferSize;
#endif
	//! \}

protected:
	//! \~english Reimplement to configure the device from "e_main" and optional "e_parent" entries cast to \a PIConfig::Entry*.
	//! \~russian Переопределите для настройки устройства из записей "e_main" и необязательной "e_parent", приведённых к \a
	//! PIConfig::Entry*.
	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 the device; inverse return value is stored into "opened_".
	//! \~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 << "\"readDevice\" 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 << "\"writeDevice\" is not implemented!";
		return -2;
	}

	//! \~english Called after threaded read receives data; default implementation calls the external callback set by \a
	//! setThreadedReadSlot().
	//! \~russian Вызывается после успешного потокового чтения; по умолчанию вызывает внешний callback, заданный через \a
	//! setThreadedReadSlot().
	virtual bool threadedRead(const uchar * readed, ssize_t size);

	//! \~english Reimplement to build device-specific part of full-path string. Default implementation returns \a path().
	//! \~russian Переопределите для построения device-specific части полной строки пути. По умолчанию возвращает \a path().
	virtual PIString constructFullPathDevice() const { return path(); }

	//! \~english Reimplement to configure the device from device-specific full-path parameters. Default implementation calls \a setPath().
	//! \~russian Переопределите для настройки устройства из device-specific параметров полной строки пути. По умолчанию вызывает \a
	//! setPath().
	virtual void configureFromFullPathDevice(const PIString & full_path) { setPath(full_path); }

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

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

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

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

	//! \~english Reimplement to react to new \a threadedReadBufferSize().
	//! \~russian Переопределите для реакции на новое значение \a threadedReadBufferSize().
	virtual void threadedReadBufferSizeChanged() { ; }

	static PIIODevice * newDeviceByPrefix(const char * prefix);

	DeviceMode mode_ = ReadOnly;
	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;
	PISystemTime reopen_timeout;
	ullong tri = 0;
	uint threaded_read_buffer_size, threaded_read_timeout_ms = 10;
	bool reopen_enabled = true, destroying = false;

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

#endif // PIIODEVICE_H
