//! \~\addtogroup IO
//! \~\{
//! \~\file piserial.h
//! \~\brief
//! \~english Serial device
//! \~russian Последовательный порт
//! \~\}
/*
    PIP - Platform Independent Primitives
    COM
    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 PISERIAL_H
#define PISERIAL_H

#include "piiodevice.h"
#include "pitimer.h"


//! \~\ingroup IO
//! \~\brief
//! \~english Serial device.
//! \~russian Последовательный порт.
class PIP_EXPORT PISerial: public PIIODevice {
	PIIODEVICE(PISerial, "ser");

public:
	//! \~english Contructs an empty %PISerial
	//! \~russian Создает пустой %PISerial
	explicit PISerial();

	virtual ~PISerial();

	//! \~english Parameters of PISerial
	//! \~russian Параметры PISerial
	enum Parameters {
		ParityControl /*! \~english Enable parity check and generate \~russian Включить генерацию и проверку контроля чётности */ = 0x1,
		ParityOdd /*! \~english Parity is odd instead of even \~russian Нечётный контроль чётности вместо чётного */              = 0x2,
		TwoStopBits /*! \~english Two stop bits instead of one \~russian Два стоповых бита вместо одного */                       = 0x4
	};

	//! \~english Speed of PISerial
	//! \~russian Скорость PISerial
	enum Speed {
		S50 /*! 50 baud */           = 50,
		S75 /*! 75 baud */           = 75,
		S110 /*! 110 baud */         = 110,
		S300 /*! 300 baud */         = 300,
		S600 /*! 600 baud */         = 600,
		S1200 /*! 1200 baud */       = 1200,
		S2400 /*! 2400 baud */       = 2400,
		S4800 /*! 4800 baud */       = 4800,
		S9600 /*! 9600 baud */       = 9600,
		S14400 /*! 14400 baud */     = 14400,
		S19200 /*! 19200 baud */     = 19200,
		S38400 /*! 38400 baud */     = 38400,
		S57600 /*! 57600 baud */     = 57600,
		S115200 /*! 115200 baud */   = 115200,
		S230400 /*! 230400 baud */   = 230400,
		S460800 /*! 460800 baud */   = 460800,
		S500000 /*! 500000 baud */   = 500000,
		S576000 /*! 576000 baud */   = 576000,
		S921600 /*! 921600 baud */   = 921600,
		S1000000 /*! 1000000 baud */ = 1000000,
		S1152000 /*! 1152000 baud */ = 1152000,
		S1500000 /*! 1500000 baud */ = 1500000,
		S2000000 /*! 2000000 baud */ = 2000000,
		S2500000 /*! 2500000 baud */ = 2500000,
		S3000000 /*! 3000000 baud */ = 3000000,
		S3500000 /*! 3500000 baud */ = 3500000,
		S4000000 /*! 4000000 baud */ = 4000000
	};

	//! \~\ingroup IO
	//! \~\brief
	//! \~english Information about serial device
	//! \~russian Информация о последовательном устройстве
	struct PIP_EXPORT DeviceInfo {
		//! \~english Returns string representation of USB ID in format "xxxx:xxxx" (vID:pID)
		//! \~russian Возвращает строковое представление USB ID в формате "xxxx:xxxx" (vID:pID)
		PIString id() const;

		//! \~english USB Vendor ID
		//! \~russian USB Vendor ID
		uint vID = 0;

		//! \~english USB Product ID
		//! \~russian USB Product ID
		uint pID = 0;

		//! \~english Path to device, e.g. "COM2" or "/dev/ttyUSB0"
		//! \~russian Путь к устройству, например "COM2" или "/dev/ttyUSB0"
		PIString path;

		//! \~english Device description
		//! \~russian Описание устройства
		PIString description;

		//! \~english Device manufacturer
		//! \~russian Производитель устройства
		PIString manufacturer;
	};

	//! \~english Contructs %PISerial with device name "device", speed "speed" and parameters "params"
	//! \~russian Создает %PISerial с именем устройства "device", скоростью "speed" и параметрами "params"
	explicit PISerial(const PIString & device, PISerial::Speed speed = S115200, PIFlags<PISerial::Parameters> params = 0);


	//! \~english Set both input and output speed to "speed"
	//! \~russian Устанавливает скорости приема и передачи в "speed"
	void setSpeed(PISerial::Speed speed) {
		setProperty("outSpeed", (int)speed);
		setProperty("inSpeed", (int)speed);
		applySettings();
	}

	//! \~english Set output speed to "speed"
	//! \~russian Устанавливает скорость передачи в "speed"
	void setOutSpeed(PISerial::Speed speed) {
		setProperty("outSpeed", (int)speed);
		applySettings();
	}

	//! \~english Set input speed to "speed"
	//! \~russian Устанавливает скорость приема в "speed"
	void setInSpeed(PISerial::Speed speed) {
		setProperty("inSpeed", (int)speed);
		applySettings();
	}

	//! \~english Set device name to "dev"
	//! \~russian Устанавливает имя устройства в "dev"
	void setDevice(const PIString & dev) {
		setPath(dev);
		if (isOpened()) {
			close();
			open();
		};
	}


	//! \~english Set parameters to "parameters_"
	//! \~russian Устанавливает параметры в "parameters_"
	void setParameters(PIFlags<PISerial::Parameters> parameters_) {
		setProperty("parameters", (int)parameters_);
		applySettings();
	}

	//! \~english Set parameter "parameter" to "on" state
	//! \~russian Устанавливает параметр "parameter" в "on"
	void setParameter(PISerial::Parameters parameter, bool on = true);

	//! \~english Returns if parameter "parameter" is set
	//! \~russian Возвращает установлен ли параметр "parameter"
	bool isParameterSet(PISerial::Parameters parameter) const;

	//! \~english Returns parameters
	//! \~russian Возвращает параметры
	PIFlags<PISerial::Parameters> parameters() const { return (PIFlags<Parameters>)(property("parameters").toInt()); }


	//! \~english Set data bits count. Valid range is from 5 to 8, befault is 8
	//! \~russian Устанавливает количество бит данных. Разрешены значения от 5 до 8, по умолчанию 8
	void setDataBitsCount(int bits) {
		setProperty("dataBitsCount", bits);
		applySettings();
	}

	//! \~english Returns data bits count
	//! \~russian Возвращает количество бит данных
	int dataBitsCount() const { return property("dataBitsCount").toInt(); }


	//! \~english Set pin number "number" to logic level "on". Valid numbers are 4 (DTR) and 7 (RTS)
	//! \~russian Устанавливает пин с номером "number" в логический уровень "on". Разрешены номера 4 (DTR) и 7 (RTS)
	bool setPin(int number, bool on);

	//! \~english Returns pin number "number" logic level. Valid numbers range is from 1 to 9
	//! \~russian Возвращает логический уровень пина с номером "number". Разрешены номера от 1 до 9
	bool isPin(int number) const;

	bool setLE(bool on); // useless function, just formally
	bool setDTR(bool on);
	bool setRTS(bool on);
	bool setCTS(bool on); // useless function, just formally
	bool setST(bool on);  // useless function, just formally
	bool setSR(bool on);  // useless function, just formally
	bool setCAR(bool on); // useless function, just formally
	bool setRNG(bool on); // useless function, just formally
	bool setDSR(bool on); // useless function, just formally

	bool isLE() const;
	bool isDTR() const;
	bool isRTS() const;
	bool isCTS() const;
	bool isST() const;
	bool isSR() const;
	bool isCAR() const;
	bool isRNG() const;
	bool isDSR() const;

	//! \~english Switch transmission line in break
	//! \~russian Переключает состояние передачи в break.
	bool setBreak(bool enabled);

	//! \~english Set VTime parameter
	//! \~russian Устанавливает параметр VTime
	void setVTime(int t) {
		vtime = t;
		applySettings();
	}

	//! \~english Returns device name
	//! \~russian Возвращает имя устройства.
	PIString device() const { return path(); }

	//! \~english Returns output speed
	//! \~russian Возвращает скорость передачи
	PISerial::Speed outSpeed() const { return (PISerial::Speed)(property("outSpeed").toInt()); }

	//! \~english Returns input speed
	//! \~russian Возвращает скорость приема
	PISerial::Speed inSpeed() const { return (PISerial::Speed)(property("inSpeed").toInt()); }

	int VTime() const { return vtime; }

	//! \~english Discard all buffered input and output data
	//! \~russian Откидывает все буферизированные данные для передачи и приема.
	virtual void flush() override;

	int read(void * read_to, int max_size) { return readDevice(read_to, max_size); }

	//! \~english Read from device no more "max_size" bytes into "read_to" with "timeout_ms" timeout
	//! \~russian Читает из устройства не более "max_size" байт в "read_to" с таймаутом "timeout_ms"
	bool read(void * read_to, int max_size, double timeout_ms);

	//! \~english Read from device for "timeout_ms" timeout or for "size" bytes
	//! \~russian Читает из устройства в течение таймаута "timeout_ms" или до "size" байт.
	PIString readString(int size = -1, double timeout_ms = 1000.);

	//! \~english Read from device for "timeout_ms" timeout or for "size" bytes
	//! \~russian Читает из устройства в течение таймаута "timeout_ms" или до "size" байт.
	PIByteArray readData(int size = -1, double timeout_ms = 1000.);

	//! \~english Write to device data "data" with maximum size "size". Returns if sent bytes count = "size"
	//! \~russian Пишет в порт не более "size" байт данных "data". Возвращает если количество отправленных байт = "size"
	bool send(const void * data, int size);

	//! \~english Write to device byte array "data". Returns if sent bytes count = size of "data"
	//! \~russian Пишет в порт байтовый массив "data". Возвращает если количество отправленных байт = размер "data"
	bool send(const PIByteArray & data) { return send(data.data(), data.size_s()); }

	void interrupt() override;

	//! \~english Returns all available speeds for serial devices
	//! \~russian Возвращает все возможные скорости для последовательных портов.
	static PIVector<int> availableSpeeds();

	//! \~english Returns all available system devices path. If "test" each device will be tried to open
	//! \~russian Возвращает пути всех доступных устройств в системе. Если "test", то каждое устройство будет опробовано на открытие
	static PIStringList availableDevices(bool test = false);

	//! \~english Returns all available system devices. If "test" each device will be tried to open
	//! \~russian Возвращает все доступные устройства в системе. Если "test", то каждое устройство будет опробовано на открытие
	static PIVector<DeviceInfo> availableDevicesInfo(bool test = false);

//! \ioparams
//! \{
#ifdef DOXYGEN
	//! \~english device, default ""
	//! \~russian устройство, по умолчанию ""
	string device;

	//! \~english input/output speed, default 115200
	//! \~russian скорость чтения/записи, по умолчанию 115200
	int speed;

	//! \~english dataBitsCount, default 8
	//! \~russian количество бит данных, по умолчанию 8
	int dataBitsCount;

	//! \~english parityControl, default false
	//! \~russian контроль четности, по умолчанию false
	bool parityControl;

	//! \~english parityOdd, default false
	//! \~russian нечётный контроль четности, по умолчанию false
	bool parityOdd;

	//! \~english twoStopBits, default false
	//! \~russian два стоповых бита, по умолчанию false
	bool twoStopBits;
#endif
	//! \}

protected:
	PIString constructFullPathDevice() const override;
	void configureFromFullPathDevice(const PIString & full_path) override;
	PIPropertyStorage constructVariantDevice() const override;
	void configureFromVariantDevice(const PIPropertyStorage & d) override;
	bool configureDevice(const void * e_main, const void * e_parent = 0) override;
	void optionsChanged() override;
	void threadedReadBufferSizeChanged() override;

	//! \~english Basic read function
	//! \~russian Базовое чтение
	ssize_t readDevice(void * read_to, ssize_t max_size) override;
	ssize_t writeDevice(const void * data, ssize_t max_size) override;
	DeviceInfoFlags deviceInfoFlags() const override { return PIIODevice::Sequential; }

	//! \~english Executes when any read function was successful. Default implementation does nothing
	//! \~russian Выполняется при успешном вызове любой функции чтения. Реализация по умолчанию ничего не делает
	virtual void received(const void * data, int size) { ; }

	void construct();
	void applySettings();
	void setTimeouts();
	int convertSpeed(PISerial::Speed speed);
	bool setBit(int bit, bool on, const PIString & bname);
	bool isBit(int bit, const PIString & bname) const;

	bool openDevice() override;
	bool closeDevice() override;

	PRIVATE_DECLARATION(PIP_EXPORT)
	int fd = -1, vtime = 10;
	std::atomic_bool sending;
	PITimeMeasurer tm_;
};


//! \relatesalso PICout
//! \~english Output operator to \a PICout
//! \~russian Оператор вывода в \a PICout
inline PICout operator<<(PICout s, const PISerial::DeviceInfo & v) {
	s << v.path << " (" << v.id() << ", \"" << v.manufacturer << "\", \"" << v.description << "\")";
	return s;
}

//! \~english Compare operator
//! \~russian Оператор сравнения
inline bool operator==(const PISerial::DeviceInfo & v0, const PISerial::DeviceInfo & v1) {
	return v0.path == v1.path;
}

//! \~english Compare operator
//! \~russian Оператор сравнения
inline bool operator!=(const PISerial::DeviceInfo & v0, const PISerial::DeviceInfo & v1) {
	return v0.path != v1.path;
}


//! \relatesalso PIBinaryStream
//! \~english Store operator.
//! \~russian Оператор сохранения.
BINARY_STREAM_WRITE(PISerial::DeviceInfo) {
	s << v.vID << v.pID << v.path << v.description << v.manufacturer;
	return s;
}

//! \relatesalso PIBinaryStream
//! \~english Restore operator.
//! \~russian Оператор извлечения.
BINARY_STREAM_READ(PISerial::DeviceInfo) {
	s >> v.vID >> v.pID >> v.path >> v.description >> v.manufacturer;
	return s;
}


#endif // PISERIAL_H
