/*! \file piserial.h
 * \brief Serial device
*/
/*
    PIP - Platform Independent Primitives
    COM
    Copyright (C) 2014  Ivan Pelipenko peri4ko@gmail.com, Bychkov Andrey wapmobil@gmail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU 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 General Public License for more details.

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

#ifndef PISERIAL_H
#define PISERIAL_H

#include "pitimer.h"
#include "piiodevice.h"
#ifndef WINDOWS
#  include <termios.h>
#  include <fcntl.h>
#  include <sys/ioctl.h>
#  ifndef B50
#    define B50       0000001
#  endif
#  ifndef B75
#    define B75       0000002
#  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
#else
#  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
#  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 B128000   128000
#  define B256000   256000
#  define B1500000  1500000
#  define B2000000  2000000
#  define B2500000  2500000
#  define B3000000  3000000
#  define B3500000  3500000
#  define B4000000  4000000
#endif
#ifndef CRTSCTS
#  define CRTSCTS 020000000000
#endif


class PIP_EXPORT PISerial: public PIIODevice
{
	PIIODEVICE(PISerial)
public:
	
	//! Contructs an empty %PISerial
	PISerial();

	//! \brief Parameters of PISerial
	enum Parameters {
		ParityControl /*! Enable parity check and generate */ = 0x1,
		ParityOdd /*! Parity is odd instead of even */ = 0x2,
		TwoStopBits /*! Two stop bits instead of one */ = 0x4
	};

	//! \brief Speed of 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,
		S19200 /*! 19200 baud */ = 19200,
		S38400 /*! 38400 baud */ = 38400,
		S57600 /*! 57600 baud */ = 57600,
		S115200 /*! 115200 baud */ = 115200,
		S1500000 = 1500000, // Linux only
		S2000000 = 2000000, // Linux only
		S2500000 = 2500000, // Linux only
		S3000000 = 3000000, // Linux only
		S3500000 = 3500000, // Linux only
		S4000000 = 4000000  // Linux only
	};
	
	//! Contructs %PISerial with device name "device", speed "speed" and parameters "params"
	PISerial(const PIString & device, PISerial::Speed speed = S115200, PIFlags<PISerial::Parameters> params = 0);
	
	~PISerial();

	
	//! Set both input and output speed to "speed"
	void setSpeed(PISerial::Speed speed) {setProperty("outSpeed", (int)speed); setProperty("inSpeed", (int)speed); applySettings();}
	
	//! Set output speed to "speed"
	void setOutSpeed(PISerial::Speed speed) {setProperty("outSpeed", (int)speed); applySettings();}
	
	//! Set input speed to "speed"
	void setInSpeed(PISerial::Speed speed) {setProperty("inSpeed", (int)speed); applySettings();}
	
	//! Set device name to "dev"
	void setDevice(const PIString & dev) {setPath(dev); if (isOpened()) {close(); open();};}

	
	//! Set parameters to "parameters_"
	void setParameters(PIFlags<PISerial::Parameters> parameters_) {setProperty("parameters", (int)parameters_); applySettings();}
	
	//! Set parameter "parameter" to "on" state
	void setParameter(PISerial::Parameters parameter, bool on = true);
	
	//! Returns if parameter "parameter" is set
	bool isParameterSet(PISerial::Parameters parameter) const;
	
	//! Returns parameters
	PIFlags<PISerial::Parameters> parameters() const {return (PIFlags<Parameters>)(property("parameters").toInt());}

	
	//! Set data bits count. Valid range is from 5 to 8, befault is 8
	void setDataBitsCount(int bits) {setProperty("dataBitsCount", bits); applySettings();}
	
	//! Returns data bits count
	int dataBitsCount() const {return property("dataBitsCount").toInt();}

	
	//! Set pin number "number" to logic level "on". Valid numbers are 4 (DTR) and 7 (RTS)
	bool setPin(int number, bool on);
	
	//! Returns pin number "number" logic level. Valid numbers range is from 1 to 9
	bool isPin(int number) const;

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

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

	void setVTime(int t) {vtime = t; applySettings();}

	
	//! Set read is blocking for function read(void * read_to, int max_size)
	void setReadIsBlocking(bool yes);

	
	//! Returns device name
	PIString device() const {return path();}
	
	//! Returns output speed
	PISerial::Speed outSpeed() const {return (PISerial::Speed)(property("outSpeed").toInt());}
	
	//! Returns input speed
	PISerial::Speed inSpeed() const {return (PISerial::Speed)(property("inSpeed").toInt());}
	
	int VTime() const {return vtime;}

	
	//! Discard all buffered input and output data
	void flush() {
#ifndef WINDOWS
		if (fd != -1) tcflush(fd, TCIOFLUSH);
#endif
	}

	int read(void * read_to, int max_size);
	bool read(void * read_to, int max_size, double timeout_ms);
	PIString read(int size = -1, double timeout_ms = 1000.);
	PIByteArray readData(int size = -1, double timeout_ms = 1000.);


	//! \brief Write to device data "data" with maximum size "max_size" and wait for data written if "wait" is \b true.
	//! \returns sended bytes count
	int write(const void * data, int max_size, bool wait = false);

	//! \brief Write to device data "data" with maximum size "size" and wait for data written if "wait" is \b true.
	//! \returns \b true if sended bytes count = "size"
	bool send(const void * data, int size, bool wait = false) {return (write(data, size, wait) == size);}

	//! \brief Write to device string "data" and wait for data written if "wait" is \b true.
	//! \returns \b true if sended bytes count = size of string
	bool send(const PIString & data, bool wait = false) {return (write(data.data(), data.lengthAscii(), wait) == data.size_s());}

	//! \brief Write to device byte array "data" and wait for data written if "wait" is \b true.
	//! \returns \b true if sended bytes count = size of string
	bool send(const PIByteArray & data, bool wait = false) {return (write(data.data(), data.size_s(), wait) == data.size_s());}

	PIString constructFullPath() const;
	
	
	//! \brief Returns all available speeds for serial devices
	static PIVector<int> availableSpeeds();
	
	//! \brief Returns all available system devices. If "test" each device will be tried to open
	static PIStringList availableDevices(bool test = false);

//! \ioparams
//! \{
#ifdef DOXYGEN
	//! \brief device, default ""
	string device;
	
	//! \brief input/output speed, default 115200
	int speed;
	
	//! \brief dataBitsCount, default 8
	int dataBitsCount;
	
	//! \brief parityControl, default false
	bool parityControl;
	
	//! \brief parityOdd, default false
	bool parityOdd;
	
	//! \brief twoStopBits, default false
	bool twoStopBits;
#endif
//! \}
	
protected:
	PIString fullPathPrefix() const {return "ser";}
	void configureFromFullPath(const PIString & full_path);
	bool configureDevice(const void * e_main, const void * e_parent = 0);
	int write(const void * data, int max_size) {return write(data, max_size, true);}
	
	//! Executes when any read function was successful. Default implementation does nothing
	virtual void received(const void * data, int size) {;}

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

	bool openDevice();
	bool closeDevice();

#ifdef WINDOWS
	DCB desc, sdesc;
	void * hCom;
	DWORD readed, mask;
	bool block_write;
#else
	termios desc, sdesc;
	uint readed;
#endif
	int fd, vtime;
	bool block_read;
	PITimeMeasurer tm_;

};

#endif // PISERIAL_H
