/*! \file piiodevice.h
 * \brief Abstract input/output device
*/
/*
	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 "picollection.h"
#include "pitimer.h"
#include "piqueue.h"


// function executed from threaded read, pass ThreadedReadData, readedData, sizeOfData
typedef bool (*ReadRetFunc)(void * , uchar * , int );


#ifdef DOXYGEN

//! \relatesalso PIIODevice \brief Use this macro to enable automatic creation instances of your class with \a createFromFullPath() function
#  define REGISTER_DEVICE(class)

//! \relatesalso PIIODevice \brief Use this macro instead of PIOBJECT when describe your own PIIODevice
#  define PIIODEVICE(class)

#else

#  define REGISTER_DEVICE(name) ADD_NEW_TO_COLLECTION_WITH_NAME(__PIIODevices__, name, __S__collection_##name##__)
#  define PIIODEVICE(name) PIOBJECT_SUBCLASS(name, PIIODevice) PIIODevice * copy() const {return new name();}

#endif


class PIP_EXPORT PIIODevice: public PIThread
{
	PIOBJECT_SUBCLASS(PIIODevice, PIThread)
	friend void __DevicePool_threadReadDP(void * ddp);
public:
	NO_COPY_CLASS(PIIODevice)
	
	//! Constructs a empty PIIODevice
	explicit PIIODevice();

	//! \brief Open modes for PIIODevice
	enum DeviceMode {
		ReadOnly /*! Device can only read */ = 0x01,
		WriteOnly /*! Device can only write */ = 0x02,
		ReadWrite /*! Device can both read and write */ = 0x03
	};

	//! \brief Options for PIIODevice, works with some devices
	enum DeviceOption {
		BlockingRead /*! \a read block until data is received, default off */ = 0x01,
		BlockingWrite /*! \a write block until data is sent, default off */ = 0x02
	};

	//! \brief Characteristics of PIIODevice subclass
	enum DeviceInfoFlag {
		Sequential /*! Continuous bytestream without datagrams */ = 0x01,
		Reliable /*! Channel without data errors / corruptions */ = 0x02
	};

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

	explicit PIIODevice(const PIString & path, DeviceMode mode = ReadWrite);
	virtual ~PIIODevice();

	//! Current open mode of device
	DeviceMode mode() const {return mode_;}
	
	//! Set open mode of device
	void setMode(DeviceMode m) {mode_ = m;}

	//! Current device options
	DeviceOptions options() const {return options_;}

	//! Current device option "o" state
	bool isOptionSet(DeviceOption o) const {return options_[o];}

	//! Set device options
	void setOptions(DeviceOptions o);

	//! Set device option "o" to "yes" and return previous state
	bool setOption(DeviceOption o, bool yes = true);

	//! Returns device characteristic flags
	DeviceInfoFlags infoFlags() const {return deviceInfoFlags();}

	//! Current path of device
	PIString path() const {return property(PIStringAscii("path")).toString();}
	
	//! Set path of device
	void setPath(const PIString & path) {setProperty(PIStringAscii("path"), path);}
	
	//! Return \b true if mode is ReadOnly or ReadWrite
	bool isReadable() const {return (mode_ & ReadOnly);}
	
	//! Return \b true if mode is WriteOnly or ReadWrite
	bool isWriteable() const {return (mode_ & WriteOnly);}
	
	//! Return \b true if device is successfully opened
	bool isOpened() const {return opened_;}
	
	//! Return \b true if device is closed
	bool isClosed() const {return !opened_;}
	
	//! Return \b true if device can read \b now
	virtual bool canRead() const {return opened_ && (mode_ & ReadOnly);}
	
	//! Return \b true if device can write \b now
	virtual bool canWrite() const {return opened_ && (mode_ & WriteOnly);}


	//! Set execution of \a open enabled while threaded read on closed device
	void setReopenEnabled(bool yes = true) {setProperty(PIStringAscii("reopenEnabled"), yes);}
	
	//! Set timeout in milliseconds between \a open tryings if reopen is enabled
	void setReopenTimeout(int msecs) {setProperty(PIStringAscii("reopenTimeout"), msecs);}

	
	//! Return reopen enable
	bool isReopenEnabled() const {return property(PIStringAscii("reopenEnabled")).toBool();}
	
	//! Return reopen timeout
	int reopenTimeout() {return property(PIStringAscii("reopenTimeout")).toInt();}


	/** \brief Set "threaded read slot"
	 * \details Set external static function of threaded read that will be executed
	 * at every successful threaded read. Function should have format
	 * "bool func(void * data, uchar * readed, int size)" */
	void setThreadedReadSlot(ReadRetFunc func) {ret_func_ = func;}
	
	//! Set custom data that will be passed to "threaded read slot"
	void setThreadedReadData(void * d) {ret_data_ = d;}
	
	/** \brief Set size of threaded read buffer
	 * \details Default size is 4096 bytes. If your device can read at single read
	 * more than 4096 bytes you should use this function to adjust buffer size */
	void setThreadedReadBufferSize(int new_size) {threaded_read_buffer_size = new_size; threadedReadBufferSizeChanged();}

	//! Return size of threaded read buffer
	int threadedReadBufferSize() const {return threaded_read_buffer_size;}
	
	//! Return content of threaded read buffer
	const uchar * threadedReadBuffer() const {return buffer_tr.data();}

	//! Return custom data that will be passed to "threaded read slot"
	void * threadedReadData() const {return ret_data_;}

	
	//! Return \b true if threaded read is started
	bool isThreadedRead() const {return isRunning();}
	
	//! Start threaded read
	void startThreadedRead() {if (!isRunning()) PIThread::start();}
	
	//! Start threaded read and assign "threaded read slot" to "func"
	void startThreadedRead(ReadRetFunc func) {ret_func_ = func; startThreadedRead();}
	
	//! Stop threaded read. Hard stop terminate thread, otherwise wait fo 10 seconds
	void stopThreadedRead(bool hard = true);
	
	
	//! Return \b true if threaded write is started
	bool isThreadedWrite() const {return write_thread.isRunning();}
	
	//! Start threaded write
	void startThreadedWrite() {if (!write_thread.isRunning()) write_thread.startOnce();}
	
	//! Stop threaded write. Hard stop terminate thread, otherwise wait fo 10 seconds
	void stopThreadedWrite(bool hard = true);
	
	//! Clear threaded write task queue
	void clearThreadedWriteQueue();

	
	//! Start both threaded read and threaded write
	void start();
	
	//! Stop both threaded read and threaded write. Hard stop terminate threads, otherwise wait fo 10 seconds
	void stop(bool hard = true);


	//! Read from device maximum "max_size" bytes to "read_to"
	int read(void * read_to, int max_size) {return readDevice(read_to, max_size);}

	//! Read from device maximum "max_size" bytes and return them as PIByteArray
	PIByteArray read(int max_size);

	//! Write maximum "max_size" bytes of "data" to device
	int write(const void * data, int max_size) {return writeDevice(data, max_size);}

	//! Read from device for "timeout_ms" milliseconds and return readed data as PIByteArray. Timeout should to be greater than 0
	PIByteArray readForTime(double timeout_ms);
	

	//! Add task to threaded write queue and return task ID
	ullong writeThreaded(const void * data, int max_size) {return writeThreaded(PIByteArray(data, uint(max_size)));}
	
	//! Add task to threaded write queue and return task ID
	ullong writeThreaded(const PIByteArray & data);
	
	
	//! Configure device from section "section" of file "config_file", if "parent_section" parent section also will be read
	bool configure(const PIString & config_file, const PIString & section, bool parent_section = false);
	
	
	//! Reimplement to construct full unambiguous string prefix. \ref PIIODevice_sec7
	virtual PIString fullPathPrefix() const {return PIString();}
	
	//! Returns full unambiguous string, describes this device, \a fullPathPrefix() + "://"
	PIString constructFullPath() const;

	//! Configure device with parameters of full unambiguous string
	void configureFromFullPath(const PIString & full_path);

	//! Returns PIVariantTypes::IODevice, describes this device
	PIVariantTypes::IODevice constructVariant() const;

	//! Configure device from PIVariantTypes::IODevice
	void configureFromVariant(const PIVariantTypes::IODevice & d);

	//! \brief Try to determine suitable device, create new one, configure it with \a configureFromFullPath() and returns it.
	//! \details To function \a configureFromFullPath() "full_path" passed without \a fullPathPrefix() + "://".
	//! See \ref PIIODevice_sec7
	static PIIODevice * createFromFullPath(const PIString & full_path);

	//! \brief Try to determine suitable device, create new one, configure it with \a configureFromVariant() and returns it.
	//! \details To function \a configureFromFullPath() "full_path" passed without \a fullPathPrefix() + "://".
	//! See \ref PIIODevice_sec7
	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);

	//! Returns fullPath prefixes of all registered devices
	static PIStringList availablePrefixes();

	
	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(int, write, PIByteArray, data) {return writeDevice(data.data(), data.size_s());}

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

//! \handlers
//! \{

	//! \fn bool open()
	//! \brief Open device
	
	//! \fn bool open(const PIString & path)
	//! \brief Open device with path "path"

	//! \fn bool open(const DeviceMode & mode)
	//! \brief Open device with mode "mode"

	//! \fn bool open(const PIString & path, const DeviceMode & mode)
	//! \brief Open device with path "path" and mode "mode"

	//! \fn bool close()
	//! \brief Close device

	//! \fn int write(PIByteArray data)
	//! \brief Write "data" to device

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

	//! \fn void flush()
	//! \brief Immediate write all buffers
	
//! \}
//! \events
//! \{

	//! \fn void opened()
	//! \brief Raise if succesfull open
	
	//! \fn void closed()
	//! \brief Raise if succesfull close

	//! \fn void threadedReadEvent(uchar * readed, int size)
	//! \brief Raise if read thread succesfull read some data

	//! \fn void threadedWriteEvent(ullong id, int written_size)
	//! \brief Raise if write thread successfull write some data of task with ID "id"
	
//! \}
//! \ioparams
//! \{
#ifdef DOXYGEN
	//! \brief setReopenEnabled, default "true"
	bool reopenEnabled;
	
	//! \brief setReopenTimeout in ms, default 1000
	int reopenTimeout;
	
	//! \brief setThreadedReadBufferSize in bytes, default 4096
	int threadedReadBufferSize;
#endif
//! \}

protected:
	
	//! Function executed before first \a openDevice() or from constructor
	virtual bool init() {return true;}
	
	//! Reimplement to configure device from entries "e_main" and "e_parent", cast arguments to \a PIConfig::Entry*
	virtual bool configureDevice(const void * e_main, const void * e_parent = 0) {return true;}
	
	//! Reimplement to open device, return value will be set to "opened_" variable; don't call this function in subclass, use open()
	virtual bool openDevice() = 0; // use path_, type_, opened_, init_ variables
	
	//! Reimplement to close device, inverse return value will be set to "opened_" variable
	virtual bool closeDevice() {return true;} // use path_, type_, opened_, init_ variables

	//! Reimplement this function to read from your device
	virtual int readDevice(void * read_to, int max_size) {piCoutObj << "\"read\" is not implemented!"; return -2;}

	//! Reimplement this function to write to your device
	virtual int writeDevice(const void * data, int max_size) {piCoutObj << "\"write\" is not implemented!"; return -2;}

	//! Function executed when thread read some data, default implementation execute external slot "ret_func_"
	virtual bool threadedRead(uchar * readed, int size);
	
	//! Reimplement to construct full unambiguous string, describes this device. Default implementation returns \a path()
	virtual PIString constructFullPathDevice() const {return path();}

	//! Reimplement to configure your device with parameters of full unambiguous string. Default implementation does nothing
	virtual void configureFromFullPathDevice(const PIString & full_path) {setPath(full_path);}

	//! Reimplement to construct device properties.
	//! Default implementation return PIPropertyStorage with \"path\" entry
	virtual PIPropertyStorage constructVariantDevice() const;

	//! Reimplement to configure your device from PIPropertyStorage. Options and mode already applied.
	//! Default implementation apply \"path\" entry
	virtual void configureFromVariantDevice(const PIPropertyStorage & d);

	//! Reimplement to apply new device options
	virtual void optionsChanged() {;}

	//! Reimplement to return correct \a DeviceInfoFlags. Default implementation returns 0
	virtual DeviceInfoFlags deviceInfoFlags() const {return 0;}

	//! Reimplement to apply new \a threadedReadBufferSize()
	virtual void threadedReadBufferSizeChanged() {;}

	//! Invoked after hard read thread stop
	virtual void threadedReadTerminated() {;}

	//! Invoked after hard write thread stop
	virtual void threadedWriteTerminated() {;}

	static PIIODevice * newDeviceByPrefix(const PIString & prefix);

	void terminate();
	
	
	DeviceMode mode_;
	DeviceOptions options_;
	ReadRetFunc ret_func_;
	bool opened_;
	void * ret_data_;

private:
	EVENT_HANDLER2(void, check_start, void * , data, int, delim);
	EVENT_HANDLER(void, write_func);

	virtual PIIODevice * copy() const {return 0;}
	PIString fullPathOptions() const;
	void _init();
	void begin();
	void run();
	void end() {terminate();}
	static void cacheFullPath(const PIString & full_path, const PIIODevice * d);

	PITimer timer;
	PITimeMeasurer tm;
	PIThread write_thread;
	PIByteArray buffer_in, buffer_tr;
	PIQueue<PIPair<PIByteArray, ullong> > write_queue;
	ullong tri;
	int readed_;
	uint threaded_read_buffer_size;
	bool init_, thread_started_, raise_threaded_read_;

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

};

#endif // PIIODEVICE_H
