/*
    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/>.
*/

#include "piserial.h"

#include "piconfig.h"
#include "pidir.h"
#include "piincludes_p.h"
#include "pipropertystorage.h"
#include "pitime.h"
#include "pitranslator.h"
#include "piwaitevent_p.h"

#include <errno.h>

#if defined(MICRO_PIP)
#  define PISERIAL_NO_PINS
#endif
#if defined(PISERIAL_NO_PINS) || defined(WINDOWS)
#  define TIOCM_LE  1
#  define TIOCM_DTR 4
#  define TIOCM_RTS 7
#  define TIOCM_CTS 8
#  define TIOCM_ST  3
#  define TIOCM_SR  2
#  define TIOCM_CAR 1
#  define TIOCM_RNG 9
#  define TIOCM_DSR 6
#endif
#ifdef WINDOWS
#  ifndef INITGUID
#	define INITGUID
#	include <guiddef.h>
#	undef INITGUID
#  else
#	include <guiddef.h>
#  endif
// clang-format off
#  include <ntddmodm.h>
#  include <winreg.h>
#  include <windows.h>
#  include <winioctl.h>
#  include <cfgmgr32.h>
#  include <setupapi.h>
// clang-format on
#  define B50      50
#  define B75      75
#  define B110     110
#  define B300     300
#  define B600     600
#  define B1200    1200
#  define B2400    2400
#  define B4800    4800
#  define B9600    9600
#  define B14400   14400
#  define B19200   19200
#  define B38400   38400
#  define B57600   57600
#  define B115200  115200
#  define B230400  230400
#  define B460800  460800
#  define B500000  500000
#  define B576000  576000
#  define B921600  921600
#  define B1000000 1000000
#  define B1152000 1152000
#  define B1500000 1500000
#  define B2000000 2000000
#  define B2500000 2500000
#  define B3000000 3000000
#  define B3500000 3500000
#  define B4000000 4000000
#else
#  include <fcntl.h>
#  include <sys/ioctl.h>
#  include <termios.h>
#  ifndef B50
#	define B50 0000001
#  endif
#  ifndef B75
#	define B75 0000002
#  endif
#  ifndef B230400
#	define B230400 0010003
#  endif
#  ifndef B460800
#	define B460800 0010004
#  endif
#  ifndef B500000
#	define B500000 0010005
#  endif
#  ifndef B576000
#	define B576000 0010006
#  endif
#  ifndef B921600
#	define B921600 0010007
#  endif
#  ifndef B1000000
#	define B1000000 0010010
#  endif
#  ifndef B1152000
#	define B1152000 0010011
#  endif
#  ifndef B1500000
#	define B1500000 0010012
#  endif
#  ifndef B2000000
#	define B2000000 0010013
#  endif
#  ifndef B2500000
#	define B2500000 0010014
#  endif
#  ifndef B3000000
#	define B3000000 0010015
#  endif
#  ifndef B3500000
#	define B3500000 0010016
#  endif
#  ifndef B4000000
#	define B4000000 0010017
#  endif
#endif
#ifndef CRTSCTS
#  define CRTSCTS 020000000000
#endif
#ifdef LINUX
#  include <linux/serial.h>
#endif


//! \class PISerial piserial.h
//! \details
//! \~english \section PISerial_sec0 Synopsis
//! \~russian \section PISerial_sec0 Краткий обзор
//! \~english
//! This class provide access to serial device, e.g. COM port. It can read,
//! write, wait for write. There are several read and write functions.
//!
//! \~russian
//! Этот класс предоставляет доступ к последовательному порту, например, COM-порт.
//!
//! \~english \section PISerial_sec1 FullPath
//! \~russian \section PISerial_sec1 Строка полного описания
//! \~english
//! Since version 1.16.0 you can use as \a path \a PISerial::DeviceInfo::id() USB identifier
//! for USB devices.
//!
//! \~russian
//! Начиная с версии 1.16.0 можно в качестве \a path использовать \a PISerial::DeviceInfo::id()
//! USB идентификатор для USB устройств.
//!
//! \~\code
//! PISerial * s = new PISerial("0403:6001");
//! PIIODevice * d = PIIODevice::createFromFullPath("ser://0403:6001:115200");
//! \endcode
//!


REGISTER_DEVICE(PISerial)


PRIVATE_DEFINITION_START(PISerial)
	PIWaitEvent event;
#ifdef WINDOWS
	PIWaitEvent event_write;
	DCB desc, sdesc;
	HANDLE hCom  = nullptr;
	DWORD readed = 0, mask = 0;
	OVERLAPPED overlap, overlap_write;
#else
	termios desc, sdesc;
	uint readed = 0;
#endif
PRIVATE_DEFINITION_END(PISerial)


PIString PISerial::DeviceInfo::id() const {
	return PIString::fromNumber(vID, 16).toLowerCase().expandLeftTo(4, '0') + ":" +
	       PIString::fromNumber(pID, 16).toLowerCase().expandLeftTo(4, '0');
}


PISerial::PISerial(): PIIODevice("", ReadWrite) {
	construct();
}


PISerial::PISerial(const PIString & device_, PISerial::Speed speed_, PIFlags<PISerial::Parameters> params_)
	: PIIODevice(device_, ReadWrite) {
	construct();
	setPath(device_);
	setSpeed(speed_);
	setParameters(params_);
}


PISerial::~PISerial() {
	stopAndWait();
	close();
	PRIVATE->event.destroy();
#ifdef WINDOWS
	PRIVATE->event_write.destroy();
#endif
}


void PISerial::construct() {
	sending = false;
	// setPriority(piHigh);
	setParameters(0);
	setSpeed(S115200);
	setDataBitsCount(8);
}


void PISerial::setParameter(PISerial::Parameters parameter, bool on) {
	PIFlags<Parameters> cp = (PIFlags<Parameters>)(property("parameters").toInt());
	cp.setFlag(parameter, on);
	setParameters(cp);
}


bool PISerial::isParameterSet(PISerial::Parameters parameter) const {
	PIFlags<Parameters> cp = (PIFlags<Parameters>)(property("parameters").toInt());
	return cp[parameter];
}


bool PISerial::setPin(int number, bool on) {
	switch (number) {
	case 1: return setCAR(on); break;
	case 2: return setSR(on); break;
	case 3: return setST(on); break;
	case 4: return setDTR(on); break;
	case 5: piCoutObj << "Pin number 5 is ground"; return false;
	case 6: return setDSR(on); break;
	case 7: return setRTS(on); break;
	case 8: return setCTS(on); break;
	case 9: return setRNG(on); break;
	default: piCoutObj << "Pin number " << number << " doesn`t exists!"; return false;
	}
	return false;
}


bool PISerial::isPin(int number) const {
	switch (number) {
	case 1: return isCAR(); break;
	case 2: return isSR(); break;
	case 3: return isST(); break;
	case 4: return isDTR(); break;
	case 5: return false;
	case 6: return isDSR(); break;
	case 7: return isRTS(); break;
	case 8: return isCTS(); break;
	case 9: return isRNG(); break;
	default: piCoutObj << "Pin number " << number << " doesn`t exists!"; return false;
	}
	return false;
}

bool PISerial::setLE(bool on) {
	return setBit(TIOCM_LE, on, "LE");
}
bool PISerial::setDTR(bool on) {
	return setBit(TIOCM_DTR, on, "DTR");
}
bool PISerial::setRTS(bool on) {
	return setBit(TIOCM_RTS, on, "RTS");
}
bool PISerial::setCTS(bool on) {
	return setBit(TIOCM_CTS, on, "CTS");
}
bool PISerial::setST(bool on) {
	return setBit(TIOCM_ST, on, "ST");
}
bool PISerial::setSR(bool on) {
	return setBit(TIOCM_SR, on, "SR");
}
bool PISerial::setCAR(bool on) {
	return setBit(TIOCM_CAR, on, "CAR");
}
bool PISerial::setRNG(bool on) {
	return setBit(TIOCM_RNG, on, "RNG");
}
bool PISerial::setDSR(bool on) {
	return setBit(TIOCM_DSR, on, "DSR");
}

bool PISerial::isLE() const {
	return isBit(TIOCM_LE, "LE");
}
bool PISerial::isDTR() const {
	return isBit(TIOCM_DTR, "DTR");
}
bool PISerial::isRTS() const {
	return isBit(TIOCM_RTS, "RTS");
}
bool PISerial::isCTS() const {
	return isBit(TIOCM_CTS, "CTS");
}
bool PISerial::isST() const {
	return isBit(TIOCM_ST, "ST");
}
bool PISerial::isSR() const {
	return isBit(TIOCM_SR, "SR");
}
bool PISerial::isCAR() const {
	return isBit(TIOCM_CAR, "CAR");
}
bool PISerial::isRNG() const {
	return isBit(TIOCM_RNG, "RNG");
}
bool PISerial::isDSR() const {
	return isBit(TIOCM_DSR, "DSR");
}


//! \~\details
//! \~english
//! If enabled, sends a continuous stream of zero bits.
//! Returns if state changed successfully.
//! \note The serial port has to be open before using this method
//!
//! \~russian
//! Если включено, отсылается непрерывный поток нулей.
//! Возвращает успешна ли смена состояния.
//! \note Порт должен быть открыт перед использованием этого метода
bool PISerial::setBreak(bool enabled) {
	if (fd < 0) {
		piCoutObj << "sendBreak error: \"" << path() << "\" is not opened!";
		return false;
	}
#ifdef WINDOWS
	if (enabled) {
		if (!SetCommBreak(PRIVATE->hCom)) {
			piCoutObj << "setBreak error: " << errorString();
			return false;
		} else {
			return true;
		}
	} else {
		if (!ClearCommBreak(PRIVATE->hCom)) {
			piCoutObj << "setBreak error: " << errorString();
			return false;
		} else {
			return true;
		}
	}
#else
	if (ioctl(fd, enabled ? TIOCSBRK : TIOCCBRK) < 0) {
		piCoutObj << "setBreak error: " << errorString();
		return false;
	} else {
		return true;
	}
#endif
	return false;
}


bool PISerial::setBit(int bit, bool on, const PIString & bname) {
	if (fd < 0) {
		piCoutObj << "setBit" << bname << " error: \"" << path() << "\" is not opened!";
		return false;
	}
#ifndef PISERIAL_NO_PINS
#  ifdef WINDOWS
	static int bit_map_on[]  = {0, 0, 0, 0, SETDTR, 0, 0, SETRTS, 0, 0, 0};
	static int bit_map_off[] = {0, 0, 0, 0, CLRDTR, 0, 0, CLRRTS, 0, 0, 0};
	int action               = (on ? bit_map_on : bit_map_off)[bit];
	if (action > 0) {
		if (EscapeCommFunction(PRIVATE->hCom, action) == 0) {
			piCoutObj << "setBit" << bname << " error: " << errorString();
			return false;
		}
		return true;
	}
#  else
	if (ioctl(fd, on ? TIOCMBIS : TIOCMBIC, &bit) < 0) {
		piCoutObj << "setBit" << bname << " error: " << errorString();
		return false;
	}
	return true;
#  endif
#endif
	piCoutObj << "setBit" << bname << " doesn`t implemented, sorry :-(";
	return false;
}


bool PISerial::isBit(int bit, const PIString & bname) const {
	if (fd < 0) {
		piCoutObj << "isBit" << bname << " error: \"" << path() << "\" is not opened!";
		return false;
	}
#ifndef PISERIAL_NO_PINS
#  ifdef WINDOWS
#  else
	int ret = 0;
	if (ioctl(fd, TIOCMGET, &ret) < 0) piCoutObj << "isBit" << bname << " error: " << errorString();
	return ret & bit;
#  endif
#endif
	piCoutObj << "isBit" << bname << " doesn`t implemented, sorry :-(";
	return false;
}


void PISerial::flush() {
#ifndef WINDOWS
	if (fd != -1) tcflush(fd, TCIOFLUSH);
#endif
}


int PISerial::convertSpeed(PISerial::Speed speed) {
	switch (speed) {
	case S50: return B50;
	case S75: return B75;
	case S110: return B110;
	case S300: return B300;
	case S600: return B600;
	case S1200: return B1200;
	case S2400: return B2400;
	case S4800: return B4800;
	case S9600: return B9600;
#ifdef WINDOWS
	case S14400: return B14400;
#endif
	case S19200: return B19200;
	case S38400: return B38400;
	case S57600: return B57600;
	case S115200: return B115200;
	case S230400: return B230400;
	case S460800: return B460800;
	case S500000: return B500000;
	case S576000: return B576000;
	case S921600: return B921600;
	case S1000000: return B1000000;
	case S1152000: return B1152000;
	case S1500000: return B1500000;
	case S2000000: return B2000000;
	case S2500000: return B2500000;
	case S3000000: return B3000000;
	case S3500000: return B3500000;
	case S4000000: return B4000000;
	default: break;
	}
#ifdef WINDOWS
	piCoutObj << "Warning: Custom speed %1"_tr("PISerial").arg((int)speed);
	return (int)speed;
#else
	piCoutObj << "Warning: Unknown speed %1, using 115200"_tr("PISerial").arg((int)speed);
	return B115200;
#endif
}


//! \details
//! \~english
//! Read to pointer "read_to" no more than "max_size" and no longer
//! than "timeout_ms" milliseconds.\n
//! If "timeout_ms" < 0 function will be wait forever until "max_size" will be readed.\n
//! If "size" <= 0 function immediate returns \b false.\n
//! For read data with unknown size use function \a readData().
//! \returns If readed bytes count = "max_size"
//!
//! \~russian
//! Читает в указатель "read_to" не более "max_size" байт и не дольше
//! чем "timeout_ms" миллисекунд.\n
//! Если "timeout_ms" < 0 метод ожидает бесконечно, пока не будет прочитано "max_size" байт.\n
//! Если "size" <= 0, то метод немедленно возвращает \b false.\n
//! Для чтения данных неизвестного размера используется метод \a readData().
//! \returns Если количество прочитанных байт = "max_size"
//!
//! \sa \a readString(), \a readData()
bool PISerial::read(void * data, int size, double timeout_ms) {
	if (data == 0 || size <= 0) return false;
	int ret, all = 0;
	if (timeout_ms > 0.) {
		bool br = setOption(BlockingRead, false);
		all     = readDevice(data, 1);
		tm_.reset();
		while (all < size && tm_.elapsed_m() < timeout_ms) {
			ret = readDevice(&((uchar *)data)[all], size - all);
			if (ret > 0)
				all += ret;
			else
				piMinSleep();
		}
		setOption(BlockingRead, br);
		received(data, all);
		return (all == size);
	} else {
		bool br = setOption(BlockingRead, true);
		all     = readDevice(data, 1);
		while (all < size) {
			ret = readDevice(&((uchar *)data)[all], size - all);
			if (ret > 0)
				all += ret;
			else
				break;
		}
		setOption(BlockingRead, br);
		received(data, all);
		return (all == size);
	}
	return false;
}


//! \details
//! \~english
//! Read all or no more than "size" bytes and no longer than "timeout_ms" milliseconds.\n
//! If "timeout_ms" < 0 function will be wait forever until "max_size" will be readed.\n
//! If "size" <= 0 function will be read all until "timeout_ms" elaped.\n
//! If "size" <= 0 and "timeout_ms" <= 0 function immediate returns empty string.\n
//! This function similar to \a readData() but returns data as string.
//! \returns If readed bytes count = "max_size"
//!
//! \~russian
//! Читает всё или не более "size" байт и не дольше чем "timeout_ms" миллисекунд.\n
//! Если "timeout_ms" < 0 метод ожидает бесконечно, пока не будет прочитано "max_size" байт.\n
//! Если "size" <= 0, то читает всё в течении "timeout_ms" миллисекунд.\n
//! Если "size" <= 0 и "timeout_ms" <= 0, то метод немедленно возвращает пустую строку.\n
//! Этот метод аналогичен \a readData(), но возвращает строку.
//! \returns Если количество прочитанных байт = "max_size"
//!
//! \sa \a readData()
PIString PISerial::readString(int size, double timeout_ms) {
	PIString str;
	if (size <= 0 && timeout_ms <= 0.) return str;
	int ret, all = 0;
	uchar td[1024];
	if (timeout_ms > 0.) {
		bool br = setOption(BlockingRead, false);
		tm_.reset();
		if (size <= 0) {
			while (tm_.elapsed_m() < timeout_ms) {
				ret = readDevice(td, 1024);
				if (ret <= 0)
					piMinSleep();
				else
					str += PIString((char *)td, ret);
			}
		} else {
			while (all < size && tm_.elapsed_m() < timeout_ms) {
				ret = readDevice(td, size - all);
				if (ret <= 0)
					piMinSleep();
				else {
					str += PIString((char *)td, ret);
					all += ret;
				}
			}
		}
		setOption(BlockingRead, br);
	} else {
		bool br = setOption(BlockingRead, true);
		all     = readDevice(td, 1);
		str += PIString((char *)td, all);
		while (all < size) {
			ret = readDevice(td, size - all);
			if (ret <= 0)
				piMinSleep();
			else {
				str += PIString((char *)td, ret);
				all += ret;
			}
		}
		setOption(BlockingRead, br);
	}
	received(str.data(), str.size_s());
	return str;
}


//! \details
//! \~english
//! Read all or no more than "size" bytes and no longer than "timeout_ms" milliseconds.\n
//! If "timeout_ms" < 0 function will be wait forever until "max_size" will be readed.\n
//! If "size" <= 0 function will be read all until "timeout_ms" elaped.\n
//! If "size" <= 0 and "timeout_ms" <= 0 function immediate returns empty string.\n
//! This function similar to \a readString() but returns data as byte array.
//! \returns If readed bytes count = "max_size"
//!
//! \~russian
//! Читает всё или не более "size" байт и не дольше чем "timeout_ms" миллисекунд.\n
//! Если "timeout_ms" < 0 метод ожидает бесконечно, пока не будет прочитано "max_size" байт.\n
//! Если "size" <= 0, то читает всё в течении "timeout_ms" миллисекунд.\n
//! Если "size" <= 0 и "timeout_ms" <= 0, то метод немедленно возвращает пустую строку.\n
//! Этот метод аналогичен \a readString(), но возвращает массив байт.
//! \returns Если количество прочитанных байт = "max_size"
//!
//! \sa \a readString()
PIByteArray PISerial::readData(int size, double timeout_ms) {
	PIByteArray str;
	if (size <= 0 && timeout_ms <= 0.) return str;
	int ret, all = 0;
	uchar td[1024];
	if (timeout_ms > 0.) {
		bool br = setOption(BlockingRead, false);
		tm_.reset();
		if (size <= 0) {
			while (tm_.elapsed_m() < timeout_ms) {
				ret = readDevice(td, 1024);
				if (ret <= 0)
					piMinSleep();
				else
					str.append(td, ret);
			}
		} else {
			while (all < size && tm_.elapsed_m() < timeout_ms) {
				ret = readDevice(td, size - all);
				if (ret <= 0)
					piMinSleep();
				else {
					str.append(td, ret);
					all += ret;
				}
			}
		}
		setOption(BlockingRead, br);
	} else {
		bool br = setOption(BlockingRead, true);
		all     = readDevice(td, 1);
		str.append(td, all);
		while (all < size) {
			ret = readDevice(td, size - all);
			if (ret <= 0)
				piMinSleep();
			else {
				str.append(td, ret);
				all += ret;
			}
		}
		setOption(BlockingRead, br);
	}
	received(str.data(), str.size_s());
	return str;
}


bool PISerial::send(const void * data, int size) {
	int ret = 0;
	int wsz = 0;
	do {
		ret = write(&(((uchar *)data)[wsz]), size - wsz);
		if (ret > 0) wsz += ret;
		// piCout << ret << wsz;
		else
			return false;
	} while (wsz < size);
	return (wsz == size);
}


void PISerial::interrupt() {
	// piCoutObj << "interrupt";
	PRIVATE->event.interrupt();
#ifdef WINDOWS
	PRIVATE->event_write.interrupt();
#endif
}


bool PISerial::openDevice() {
	PIString p  = path();
	// piCout << "ser open" << p;
	PIString pl = p.toLowerCase().removeAll(' ');
	if (!pl.startsWith("/") && !pl.startsWith("com")) {
		p.clear();
		PIVector<DeviceInfo> devs = availableDevicesInfo();
		for (const auto & d: devs) {
			if (d.id() == pl) {
				p = d.path;
				break;
			}
		}
		if (p.isEmpty()) {
			piCoutObj << "Unable to find device \"%1\""_tr("PISerial").arg(pl);
		}
	}
	if (p.isEmpty()) return false;
#ifdef WINDOWS
	DWORD ds = 0, sm = 0;
	if (isReadable()) {
		ds |= GENERIC_READ;
		sm |= FILE_SHARE_READ;
	}
	if (isWriteable()) {
		ds |= GENERIC_WRITE;
		sm |= FILE_SHARE_WRITE;
	}
	PIString wp   = "//./" + p;
	PRIVATE->hCom = CreateFileA(wp.dataAscii(), ds, sm, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);
	if (PRIVATE->hCom == INVALID_HANDLE_VALUE) {
		piCoutObj << "Unable to open \"%1\": %2"_tr("PISerial").arg(p).arg(errorString());
		fd = -1;
		return false;
	}
	fd = 0;
#else
	int om = 0;
	switch (mode()) {
	case PIIODevice::ReadOnly: om = O_RDONLY; break;
	case PIIODevice::WriteOnly: om = O_WRONLY; break;
	case PIIODevice::ReadWrite: om = O_RDWR; break;
	}
	fd = ::open(p.data(), O_NOCTTY | om);
	if (fd == -1) {
		piCoutObj << "Unable to open \"%1\": %2"_tr("PISerial").arg(p).arg(errorString());
		return false;
	}
	tcgetattr(fd, &PRIVATE->desc);
	PRIVATE->sdesc = PRIVATE->desc;
	// piCoutObj << "Initialized " << p;
#endif
	applySettings();
	PRIVATE->event.create();
#ifdef WINDOWS
	PRIVATE->event_write.create();
#endif

	return true;
}


bool PISerial::closeDevice() {
	if (isThreadedRead() && !isThreadedReadStopping()) {
		stopThreadedRead();
	}
	if (fd != -1) {
#ifdef WINDOWS
		SetCommState(PRIVATE->hCom, &PRIVATE->sdesc);
		SetCommMask(PRIVATE->hCom, PRIVATE->mask);
		// piCoutObj << "close" <<
		CloseHandle(PRIVATE->hCom);
		PRIVATE->hCom = 0;
#else
		tcsetattr(fd, TCSANOW, &PRIVATE->sdesc);
		::close(fd);
#endif
		fd = -1;
	}
	PRIVATE->event.destroy();
#ifdef WINDOWS
	PRIVATE->event_write.destroy();
#endif
	return true;
}


void PISerial::applySettings() {
#ifdef WINDOWS
	if (fd == -1) return;
	setTimeouts();
	GetCommMask(PRIVATE->hCom, &PRIVATE->mask);
	SetCommMask(PRIVATE->hCom, EV_RXCHAR);
	GetCommState(PRIVATE->hCom, &PRIVATE->sdesc);
	// piCoutObj << PRIVATE->sdesc.fBinary << PRIVATE->sdesc.fAbortOnError << PRIVATE->sdesc.fDsrSensitivity << PRIVATE->sdesc.fDtrControl
	//		  << PRIVATE->sdesc.fDummy2 << PRIVATE->sdesc.fErrorChar;
	PRIVATE->desc             = PRIVATE->sdesc;
	PRIVATE->desc.DCBlength   = sizeof(PRIVATE->desc);
	PRIVATE->desc.BaudRate    = convertSpeed(outSpeed());
	PRIVATE->desc.fDtrControl = DTR_CONTROL_ENABLE;
	if (dataBitsCount() >= 5 && dataBitsCount() <= 8)
		PRIVATE->desc.ByteSize = dataBitsCount();
	else
		PRIVATE->desc.ByteSize = 8;
	PIFlags<Parameters> params = parameters();
	if (params[PISerial::ParityControl]) {
		PRIVATE->desc.fParity = 1;
		PRIVATE->desc.Parity  = params[PISerial::ParityOdd] ? 1 : 2;
	}
	PRIVATE->desc.StopBits = params[PISerial::TwoStopBits] ? TWOSTOPBITS : ONESTOPBIT;
	if (SetCommState(PRIVATE->hCom, &PRIVATE->desc) == -1) {
		piCoutObj << "Unable to set comm state for \"%1\""_tr("PISerial").arg(path());
		return;
	}
#else
	if (fd == -1) return;
	tcgetattr(fd, &PRIVATE->desc);
	PRIVATE->desc.c_oflag = PRIVATE->desc.c_lflag = PRIVATE->desc.c_cflag = 0;
	PRIVATE->desc.c_iflag                                                 = IGNBRK;
	PRIVATE->desc.c_cflag                                                 = CLOCAL | HUPCL;
	switch (dataBitsCount()) {
	case 5: PRIVATE->desc.c_cflag |= (CSIZE & CS5); break;
	case 6: PRIVATE->desc.c_cflag |= (CSIZE & CS6); break;
	case 7: PRIVATE->desc.c_cflag |= (CSIZE & CS7); break;
	case 8:
	default: PRIVATE->desc.c_cflag |= (CSIZE & CS8); break;
	};
	if (isReadable()) PRIVATE->desc.c_cflag |= CREAD;
	PIFlags<Parameters> params = parameters();
	if (params[PISerial::TwoStopBits]) PRIVATE->desc.c_cflag |= CSTOPB;
	if (params[PISerial::ParityControl]) {
		PRIVATE->desc.c_iflag |= INPCK;
		PRIVATE->desc.c_cflag |= PARENB;
		if (params[PISerial::ParityOdd]) PRIVATE->desc.c_cflag |= PARODD;
	}
	PRIVATE->desc.c_cc[VMIN]  = 1;
	PRIVATE->desc.c_cc[VTIME] = vtime;

	cfsetispeed(&PRIVATE->desc, convertSpeed(inSpeed()));
	cfsetospeed(&PRIVATE->desc, convertSpeed(outSpeed()));

	tcflush(fd, TCIOFLUSH);
	setTimeouts();

	if (tcsetattr(fd, TCSANOW, &PRIVATE->desc) < 0) {
		piCoutObj << "Can`t set attributes for \"%1\""_tr("PISerial").arg(path());
		return;
	}
#endif
}


void PISerial::setTimeouts() {
#ifdef WINDOWS
	COMMTIMEOUTS times;
	if (isOptionSet(BlockingRead)) {
		times.ReadIntervalTimeout        = MAXDWORD;
		times.ReadTotalTimeoutConstant   = vtime;
		times.ReadTotalTimeoutMultiplier = MAXDWORD;
	} else {
		times.ReadIntervalTimeout        = MAXDWORD;
		times.ReadTotalTimeoutConstant   = 0;
		times.ReadTotalTimeoutMultiplier = 0;
	}
	times.WriteTotalTimeoutConstant   = isOptionSet(BlockingWrite) ? 0 : 1;
	times.WriteTotalTimeoutMultiplier = 0;
	if (SetCommTimeouts(PRIVATE->hCom, &times) == -1) piCoutObj << "Unable to set timeouts for \"" << path() << "\"";
#else
	fcntl(fd, F_SETFL, isOptionSet(BlockingRead) ? 0 : O_NONBLOCK);
#endif
}


//! \details
//! \~english
//! Read to pointer "read_to" no more than "max_size".
//! If \a PIIODevice::BlockingRead option set this function
//! will be wait at least one byte.
//! \returns Readed bytes count, -1 for error
//!
//! \~russian
//! Читает в указатель "read_to" не более "max_size" байт.
//! Если установлена опция \a PIIODevice::BlockingRead,
//! то этот метод ожидает хотя бы одного байта.
//! \returns Количество прочитанных байт, -1 при ошибке
//!
//! \~\sa \a readData(), \a readString()
ssize_t PISerial::readDevice(void * read_to, ssize_t max_size) {
#ifdef WINDOWS
	if (!canRead()) return -1;
	if (sending) return -1;
	// piCoutObj << "read ..." << PRIVATE->hCom << max_size;
	DWORD mask = 0;
	if (GetCommMask(PRIVATE->hCom, &mask) == FALSE) {
		piCoutObj << "Read error: %1"_tr("PISerial").arg(errorString());
		stop();
		close();
		return 0;
	}
	piZeroMemory(PRIVATE->overlap);
	PRIVATE->overlap.hEvent = PRIVATE->event.getEvent();
	PRIVATE->readed         = 0;
	ReadFile(PRIVATE->hCom, read_to, max_size, NULL, &(PRIVATE->overlap));
	DWORD err = GetLastError();
	// piCoutObj << "read" << err;
	if (err == ERROR_BAD_COMMAND || err == ERROR_ACCESS_DENIED) {
		piCoutObj << "Read error: %1"_tr("PISerial").arg(errorString());
		stop();
		close();
		return 0;
	}
	// piCout << "wait ...";
	if (PRIVATE->event.wait()) {
		GetOverlappedResult(PRIVATE->hCom, &(PRIVATE->overlap), &(PRIVATE->readed), FALSE);
	} else
		return -1;
	// piCoutObj << "read" << (PRIVATE->readed) << errorString();
	return PRIVATE->readed;
#else
	if (!canRead()) return -1;
	if (isOptionSet(PIIODevice::BlockingRead)) {
		if (!PRIVATE->event.wait(fd)) return -1;
	}
	ssize_t ret = ::read(fd, read_to, max_size);
	if (ret < 0) {
		int err = errno;
		if (err == EBADF || err == EFAULT || err == EINVAL || err == EIO) {
			stopThreadedRead();
			close();
			return 0;
		}
	}
	return ret;
#endif
}


ssize_t PISerial::writeDevice(const void * data, ssize_t max_size) {
	if (fd == -1 || !canWrite()) {
		// piCoutObj << "Can`t write to uninitialized COM";
		return -1;
	}
#ifdef WINDOWS
	DWORD wrote(0);
	// piCoutObj << "send ..." << max_size;// << ": " << PIString((char*)data, max_size);
	sending = true;
	piZeroMemory(PRIVATE->overlap_write);
	PRIVATE->overlap_write.hEvent = PRIVATE->event_write.getEvent();
	WriteFile(PRIVATE->hCom, data, max_size, NULL, &(PRIVATE->overlap_write));
	if (PRIVATE->event_write.wait()) {
		GetOverlappedResult(PRIVATE->hCom, &(PRIVATE->overlap_write), &wrote, FALSE);
	}
	sending = false;
	// piCoutObj << "send ok" << wrote;// << " bytes in " << path();
#else
	ssize_t wrote;
	wrote = ::write(fd, data, max_size);
	if (isOptionSet(BlockingWrite)) tcdrain(fd);
#endif
	return (ssize_t)wrote;
	// piCoutObj << "Error while sending";
}


bool PISerial::configureDevice(const void * e_main, const void * e_parent) {
	PIConfig::Entry * em = (PIConfig::Entry *)e_main;
	PIConfig::Entry * ep = (PIConfig::Entry *)e_parent;
	setDevice(readDeviceSetting<PIString>("device", device(), em, ep));
	setSpeed((PISerial::Speed)(readDeviceSetting<int>("speed", (int)outSpeed(), em, ep)));
	setDataBitsCount(readDeviceSetting<int>("dataBitsCount", dataBitsCount(), em, ep));
	setParameter(PISerial::ParityControl, readDeviceSetting<bool>("parityControl", isParameterSet(PISerial::ParityControl), em, ep));
	setParameter(PISerial::ParityOdd, readDeviceSetting<bool>("parityOdd", isParameterSet(PISerial::ParityOdd), em, ep));
	setParameter(PISerial::TwoStopBits, readDeviceSetting<bool>("twoStopBits", isParameterSet(PISerial::TwoStopBits), em, ep));
	return true;
}


PIString PISerial::constructFullPathDevice() const {
	PIString ret;
	ret += path() + ":" + PIString::fromNumber(int(inSpeed())) + ":" + PIString::fromNumber(dataBitsCount());
	if (parameters()[ParityControl]) {
		if (parameters()[ParityOdd])
			ret += ":O";
		else
			ret += ":E";
	} else
		ret += ":N";
	if (parameters()[TwoStopBits])
		ret += ":2";
	else
		ret += ":1";
	return ret;
}


void PISerial::configureFromFullPathDevice(const PIString & full_path) {
	PIStringList pl = full_path.split(":");
	if (pl.size_s() > 1) {
		PIString _s = pl[0].toLowerCase().removeAll(' ').trim();
		if (!_s.startsWith("/") && !_s.startsWith("com")) {
			pl[0] += ":" + pl[1];
			pl.remove(1);
		}
	}
	for (int i = 0; i < pl.size_s(); ++i) {
		PIString p(pl[i].trimmed());
		switch (i) {
		case 0: setProperty("path", p); break;
		case 1:
			setProperty("outSpeed", p.toInt());
			setProperty("inSpeed", p.toInt());
			break;
		case 2: setProperty("dataBitsCount", p.toInt()); break;
		case 3:
			p = p.left(1).toLowerCase();
			if (p != "n") setParameter(ParityControl);
			if (p == "o") setParameter(ParityOdd);
			break;
		case 4:
			if (p.toInt() == 2) setParameter(TwoStopBits);
			break;
		}
	}
	applySettings();
}


PIPropertyStorage PISerial::constructVariantDevice() const {
	PIPropertyStorage ret;
	ret.addProperty("path", path());
	PIVariantTypes::Enum e;

	PIVector<int> as = availableSpeeds();
	for (const auto s: as) {
		e << PIVariantTypes::Enumerator(s, PIString::fromNumber(s));
	}
	e.selectValue((int)inSpeed());
	ret.addProperty("speed", e);

	e = PIVariantTypes::Enum();
	for (int i = 5; i <= 8; ++i) {
		e << PIVariantTypes::Enumerator(i, PIString::fromNumber(i));
	}
	e.selectValue(dataBitsCount());
	ret.addProperty("dataBits", e);

	e = PIVariantTypes::Enum();
	e << "None"
	  << "Odd"
	  << "Even";
	if (parameters()[ParityControl]) {
		if (parameters()[ParityOdd])
			e.selectValue(1);
		else
			e.selectValue(2);
	} else
		e.selectValue(0);
	ret.addProperty("parity", e);

	e = PIVariantTypes::Enum();
	for (int i = 1; i <= 2; ++i) {
		e << PIVariantTypes::Enumerator(i, PIString::fromNumber(i));
	}
	e.selectValue(parameters()[TwoStopBits] ? 2 : 1);
	ret.addProperty("stopBits", e);
	return ret;
}


void PISerial::configureFromVariantDevice(const PIPropertyStorage & d) {
	setPath(d.propertyValueByName("path").toString());
	setSpeed((Speed)d.propertyValueByName("speed").toEnum().selectedValue());
	setDataBitsCount(d.propertyValueByName("dataBits").toEnum().selectedValue());
	PIVariantTypes::Enum e = d.propertyValueByName("parity").toEnum();
	setParameter(ParityControl, e.selectedValue() > 0);
	setParameter(ParityOdd, e.selectedValue() == 1);
	setParameter(TwoStopBits, d.propertyValueByName("stopBits").toEnum().selectedValue() == 2);
}


PIVector<int> PISerial::availableSpeeds() {
	PIVector<int> spds;
	spds << 50 << 75 << 110 << 300 << 600 << 1200 << 2400 << 4800 << 9600 <<
#ifdef WINDOWS
		14400 <<
#endif
		19200 << 38400 << 57600 << 115200 << 230400 << 460800 << 500000 << 576000 << 921600 << 1000000 << 1152000 << 1500000 << 2000000
		 << 2500000 << 3000000 << 3500000 << 4000000;
	return spds;
}


PIStringList PISerial::availableDevices(bool test) {
	PIVector<DeviceInfo> devs = availableDevicesInfo(test);
	PIStringList ret;
	for (const auto & d: devs)
		ret << d.path;
	return ret;
}


#ifdef WINDOWS
PIString devicePortName(HDEVINFO deviceInfoSet, PSP_DEVINFO_DATA deviceInfoData) {
	PIString ret;
	const HKEY key = SetupDiOpenDevRegKey(deviceInfoSet, deviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
	if (key == INVALID_HANDLE_VALUE) return ret;
	static const wchar_t * const keyTokens[] = {L"PortName\0", L"PortNumber\0"};
	static const int keys_count              = sizeof(keyTokens) / sizeof(keyTokens[0]);
	for (int i = 0; i < keys_count; ++i) {
		DWORD dataType      = 0;
		DWORD bytesRequired = MAX_PATH;
		PIVector<wchar_t> outputBuffer(MAX_PATH + 1);
		for (;;) {
			LONG res = RegQueryValueExW(key, keyTokens[i], NULL, &dataType, (LPBYTE)outputBuffer.data(), &bytesRequired);
			if (res == ERROR_MORE_DATA) {
				outputBuffer.resize(bytesRequired / sizeof(wchar_t) + 2);
				continue;
			} else if (res == ERROR_SUCCESS) {
				if (dataType == REG_SZ)
					ret = PIString(outputBuffer.data());
				else if (dataType == REG_DWORD)
					ret = PIStringAscii("COM") + PIString::fromNumber(*(PDWORD(&outputBuffer[0])));
			}
			break;
		}
		if (!ret.isEmpty()) break;
	}
	RegCloseKey(key);
	return ret;
}


PIString deviceRegistryProperty(HDEVINFO deviceInfoSet, PSP_DEVINFO_DATA deviceInfoData, DWORD property) {
	DWORD dataType      = 0;
	DWORD bytesRequired = MAX_PATH;
	PIVector<wchar_t> outputBuffer(MAX_PATH + 1);
	for (;;) {
		if (SetupDiGetDeviceRegistryPropertyW(deviceInfoSet,
		                                      deviceInfoData,
		                                      property,
		                                      &dataType,
		                                      (PBYTE)outputBuffer.data(),
		                                      bytesRequired,
		                                      &bytesRequired))
			break;
		if ((GetLastError() != ERROR_INSUFFICIENT_BUFFER) || (dataType != REG_SZ && dataType != REG_EXPAND_SZ)) return PIString();
		outputBuffer.resize(bytesRequired / sizeof(wchar_t) + 2, 0);
	}
	return PIString(outputBuffer.data());
}


PIString deviceInstanceIdentifier(DEVINST deviceInstanceNumber) {
	PIVector<wchar_t> outputBuffer(MAX_DEVICE_ID_LEN + 1);
	if (CM_Get_Device_IDW(deviceInstanceNumber, (PWCHAR)outputBuffer.data(), MAX_DEVICE_ID_LEN, 0) != CR_SUCCESS) {
		return PIString();
	}
	return PIString(outputBuffer.data());
}


bool parseID(PIString str, PISerial::DeviceInfo & di) {
	if (str.isEmpty()) return false;
	int i = str.find("VID_");
	if (i > 0) di.vID = str.mid(i + 4, 4).toInt(16);
	i = str.find("PID_");
	if (i > 0) di.pID = str.mid(i + 4, 4).toInt(16);
	return (di.vID > 0) && (di.pID > 0);
}
#endif


PIVector<PISerial::DeviceInfo> PISerial::availableDevicesInfo(bool test) {
	PIVector<DeviceInfo> ret;
	DeviceInfo di;
#ifdef WINDOWS
	static const GUID guids[]  = {GUID_DEVINTERFACE_MODEM, GUID_DEVINTERFACE_COMPORT};
	static const int guids_cnt = sizeof(guids) / sizeof(GUID);
	for (int i = 0; i < guids_cnt; ++i) {
		const HDEVINFO dis = SetupDiGetClassDevs(&(guids[i]), NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
		if (dis == INVALID_HANDLE_VALUE) continue;
		SP_DEVINFO_DATA did;
		piZeroMemory(did);
		did.cbSize  = sizeof(did);
		DWORD index = 0;
		while (SetupDiEnumDeviceInfo(dis, index++, &did)) {
			di      = DeviceInfo();
			di.path = devicePortName(dis, &did);
			if (!di.path.startsWith("COM")) continue;
			di.description  = deviceRegistryProperty(dis, &did, SPDRP_DEVICEDESC);
			di.manufacturer = deviceRegistryProperty(dis, &did, SPDRP_MFG);
			PIString id_str = deviceInstanceIdentifier(did.DevInst);
			if (!parseID(id_str, di)) {
				DEVINST pdev = 0;
				if (CM_Get_Parent(&pdev, did.DevInst, 0) == CR_SUCCESS) {
					id_str = deviceInstanceIdentifier(pdev);
					parseID(id_str, di);
				}
			}
			ret << di;
			// piCout << "dev" << did.DevInst << di;
		}
		SetupDiDestroyDeviceInfoList(dis);
	}
#else
#  ifndef ANDROID
	PIStringList prefixes;
#	ifdef QNX
	prefixes << "ser";
#	else
	prefixes << "ttyS"
			 << "ttyO"
			 << "ttyUSB"
			 << "ttyACM"
			 << "ttyGS"
			 << "ttyMI"
			 << "ttymxc"
			 << "ttyAMA"
			 << "rfcomm"
			 << "ircomm";
#	  ifdef FREE_BSD
	prefixes << "cu";
#	  endif
#	  ifdef MAC_OS
	prefixes.clear();
	prefixes << "cu."
			 << "tty.";
#	  endif
	PIFile file_prefixes("/proc/tty/drivers", PIIODevice::ReadOnly);
	if (file_prefixes.open()) {
		PIString fc = PIString::fromAscii(file_prefixes.readAll()), line, cpref;
		PIStringList words;
		file_prefixes.close();
		while (!fc.isEmpty()) {
			words.clear();
			line = fc.takeLine();
			if (line.isEmpty()) break;
			while (!line.isEmpty())
				words << line.takeWord();
			if (words.size_s() < 2) break;
			if (words.back() != "serial") continue;
			cpref  = words[1];
			int li = cpref.findLast("/");
			if (li > 0) cpref.cutLeft(li + 1);
			prefixes << cpref;
		}
		prefixes.removeDuplicates();
	}
#	endif
	PIDir dir("/dev");
	PIVector<PIFile::FileInfo> de = dir.entries();
#	ifdef LINUX
	char linkbuf[1024];
#	endif
	for (const auto & e: de) { // TODO changes in FileInfo
		for (const auto & p: prefixes) {
			if (e.name().startsWith(p)) {
				di          = DeviceInfo();
				di.path     = e.path;
#	ifdef LINUX
				ssize_t lsz = readlink(("/sys/class/tty/" + e.name()).dataAscii(), linkbuf, 1024);
				if (lsz > 0) {
					PIString fpath = "/sys/class/tty/" + PIString(linkbuf, lsz) + "/";
					PIFile _f;
					for (int i = 0; i < 5; ++i) {
						fpath += "../";
						// piCout << "try" << fpath;
						if (_f.open(fpath + "idVendor", PIIODevice::ReadOnly)) di.vID = PIString::fromAscii(_f.readAll()).trim().toInt(16);
						if (_f.open(fpath + "idProduct", PIIODevice::ReadOnly)) di.pID = PIString::fromAscii(_f.readAll()).trim().toInt(16);
						if (_f.open(fpath + "product", PIIODevice::ReadOnly)) di.description = PIString::fromUTF8(_f.readAll()).trim();
						if (_f.open(fpath + "manufacturer", PIIODevice::ReadOnly))
							di.manufacturer = PIString::fromUTF8(_f.readAll()).trim();
						if (di.pID > 0) break;
					}
				}
#	endif
				ret << di;
			}
		}
	}
#  endif
#endif
	if (test) {
		for (int i = 0; i < ret.size_s(); ++i) {
#ifdef WINDOWS
			void * hComm = CreateFileA(ret[i].path.dataAscii(),
			                           GENERIC_READ,
			                           FILE_SHARE_READ,
			                           0,
			                           OPEN_EXISTING,
			                           FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED,
			                           0);
			if (hComm == INVALID_HANDLE_VALUE) {
#else
			int fd = ::open(ret[i].path.dataAscii(), O_NOCTTY | O_RDONLY);
			if (fd == -1) {
#endif
				ret.remove(i);
				--i;
				continue;
			}
			bool rok = true;
#ifndef WINDOWS
			int void_ = 0;
			fcntl(fd, F_SETFL, O_NONBLOCK);
			if (::read(fd, &void_, 1) == -1) rok = errno != EIO;

#endif
			if (!rok) {
				ret.remove(i);
				--i;
#ifdef WINDOWS
				CloseHandle(hComm);
#else
				::close(fd);
#endif
				continue;
			}
#ifdef WINDOWS
			CloseHandle(hComm);
#else
			::close(fd);
#endif
		}
	}
	return ret;
}


void PISerial::optionsChanged() {
	if (isOpened()) setTimeouts();
}


void PISerial::threadedReadBufferSizeChanged() {
	if (!isOpened()) return;
#if defined(LINUX)
	serial_struct ss;
	ioctl(fd, TIOCGSERIAL, &ss);
	// piCoutObj << "b" << ss.xmit_fifo_size;
	ss.xmit_fifo_size = piMaxi(threadedReadBufferSize(), 4096);
	ioctl(fd, TIOCSSERIAL, &ss);
	// piCoutObj << "a" << ss.xmit_fifo_size;
#endif
}
