// Copyright (C) 1999-2005 Open Source Telecom Corporation.
// Copyright (C) 2006-2014 David Sugar, Tycho Softworks.
//
// 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 2 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 Lesser General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// As a special exception, you may use this file as part of a free software
// library without restriction.  Specifically, if other files instantiate
// templates or use macros or inline functions from this file, or you compile
// this file and link it with other files to produce an executable, this
// file does not by itself cause the resulting executable to be covered by
// the GNU General Public License.  This exception does not however
// invalidate any other reasons why the executable file might be covered by
// the GNU General Public License.
//
// This exception applies only to the code released under the name GNU
// Common C++.  If you copy code from other releases into a copy of GNU
// Common C++, as the General Public License permits, the exception does
// not apply to the code that you add in this way.  To avoid misleading
// anyone as to the status of such modified files, you must delete
// this exception notice from them.
//
// If you write modifications of your own for GNU Common C++, it is your choice
// whether to permit this exception to apply to your modifications.
// If you do not wish that, delete this exception notice.
//

/**
 * @file serial.h
 * @short Serial I/O services.
 **/

#ifndef COMMONCPP_SERIAL_H_
#define COMMONCPP_SERIAL_H_

#ifndef COMMONCPP_CONFIG_H_
#include <commoncpp/config.h>
#endif

#ifndef COMMONCPP_THREAD_H_
#include <commoncpp/thread.h>
#endif

#ifndef COMMMONCPP_EXCEPTION_H_
#include <commoncpp/exception.h>
#endif

namespace ost {

/**
 * The Serial class is used as the base for all serial I/O services
 * under APE.  A serial is a system serial port that is used either
 * for line or packet based data input.  Serial ports may also be
 * "streamable" in a derived form.
 *
 *  Common C++ serial I/O classes are used to manage serial devices and
 *  implement serial device protocols.  From the point of view of Common C++,
 *  serial devices are supported by the underlying Posix specified "termios"
 *  call interface.
 *
 *  The serial I/O base class is used to hold a descriptor to a serial device
 *  and to provide an exception handling interface for all serial I/O classes.
 *  The base class is also used to specify serial I/O properties such as
 *  communication speed, flow control, data size, and parity.  The "Serial"
 *  base class is not itself directly used in application development,
 *  however.
 *
 *  Common C++ Serial I/O is itself divided into two conceptual modes; frame
 *  oriented and line oriented I/O.  Both frame and line oriented I/O makes
 *  use of the ability of the underlying tty driver to buffer data and return
 *  "ready" status from when select either a specified number of bytes or
 *  newline record has been reached by manipulating termios c_cc fields
 *  appropriately.  This provides some advantage in that a given thread
 *  servicing a serial port can block and wait rather than have to continually
 *  poll or read each and every byte as soon as it appears at the serial port.
 *
 * @author David Sugar <dyfet@ostel.com>
 * @short base class for all serial I/O services.
 */
class __EXPORT Serial
{
public:
    enum Error {
        errSuccess = 0,
        errOpenNoTty,
        errOpenFailed,
        errSpeedInvalid,
        errFlowInvalid,
        errParityInvalid,
        errCharsizeInvalid,
        errStopbitsInvalid,
        errOptionInvalid,
        errResourceFailure,
        errOutput,
        errInput,
        errTimeout,
        errExtended
    };
    typedef enum Error Error;

    enum Flow {
        flowNone,
        flowSoft,
        flowHard,
        flowBoth
    };
    typedef enum Flow Flow;

    enum Parity {
        parityNone,
        parityOdd,
        parityEven
    };
    typedef enum Parity Parity;

    enum Pending {
        pendingInput,
        pendingOutput,
        pendingError
    };
    typedef enum Pending Pending;

private:
    Error errid;
    char *errstr;

    struct {
        bool thrown: 1;
        bool linebuf: 1;
    } flags;

    void *original;
    void *current;

    /**
     * Used to properly initialize serial object.
     */
    void initSerial(void);

protected:
    fd_t dev;
    int bufsize;

    /**
     * Opens the serial device.
     *
     * @param fname Pathname of device to open
     */
    void open(const char *fname);

    /**
     * Closes the serial device.
     *
     */
    void close(void);

    /**
     * Reads from serial device.
     *
     * @param Data  Point to character buffer to receive data.  Buffers MUST
     *              be at least Length + 1 bytes in size.
     * @param Length Number of bytes to read.
     */
    virtual int aRead(char * Data, const int Length);

    /**
     * Writes to serial device.
     *
     * @param Data  Point to character buffer containing data to write.  Buffers MUST
     * @param Length Number of bytes to write.
     */
    virtual int aWrite(const char * Data, const int Length);

    /**
     * This service is used to throw all serial errors which usually
     * occur during the serial constructor.
     *
     * @param error defined serial error id.
     * @param errstr string or message to optionally pass.
     */
    Error error(Error error, char *errstr = NULL);

    /**
     * This service is used to thow application defined serial
     * errors where the application specific error code is a string.
     *
     * @param err string or message to pass.
     */
    inline void error(char *err)
        {error(errExtended, err);}


    /**
     * This method is used to turn the error handler on or off for
     * "throwing" execptions by manipulating the thrown flag.
     *
     * @param enable true to enable handler.
     */
    inline void setError(bool enable)
        {flags.thrown = !enable;}

    /**
     * Set packet read mode and "size" of packet read buffer.
     * This sets VMIN to x.  VTIM is normally set to "0" so that
     * "isPending()" can wait for an entire packet rather than just
     * the first byte.
     *
     * @return actual buffer size set.
     * @param size of packet read request.
     * @param btimer optional inter-byte data packet timeout.
     */
    int setPacketInput(int size, unsigned char btimer = 0);

    /**
     * Set "line buffering" read mode and specifies the newline
     * character to be used in seperating line records.  isPending
     * can then be used to wait for an entire line of input.
     *
     * @param newline newline character.
     * @param nl1 EOL2 control character.
     * @return size of conical input buffer.
     */
    int setLineInput(char newline = 13, char nl1 = 0);

    /**
     * Restore serial device to the original settings at time of open.
     */
    void restore(void);

    /**
     * Used to flush the input waiting queue.
     */
    void flushInput(void);

    /**
     * Used to flush any pending output data.
     */
    void flushOutput(void);

    /**
     * Used to wait until all output has been sent.
     */
    void waitOutput(void);

    /**
     * Used as the default destructor for ending serial I/O
     * services.  It will restore the port to it's original state.
     */
    void endSerial(void);

    /**
     * Used to initialize a newly opened serial file handle.  You
     * should set serial properties and DTR manually before first
     * use.
     */
    void initConfig(void);

    /**
     * This allows later ttystream class to open and close a serial
     * device.
     */
    Serial()
        {initSerial();}

    /**
     * A serial object may be constructed from a named file on the
     * file system.  This named device must be "isatty()".
     *
     * @param name of file.
     */
    Serial(const char *name);


public:

    /**
     * The serial base class may be "thrown" as a result on an error,
     * and the "catcher" may then choose to destory the object.  By
     * assuring the socket base class is a virtual destructor, we
     * can assure the full object is properly terminated.
     */
    virtual ~Serial();

    /**
     * Serial ports may also be duplecated by the assignment
     * operator.
     */
    Serial &operator=(const Serial &from);

    /**
     * Set serial port speed for both input and output.
     *
     * @return 0 on success.
     * @param speed to select. 0 signifies modem "hang up".
     */
    Error setSpeed(unsigned long speed);

    /**
     * Set character size.
     *
     * @return 0 on success.
     * @param bits character size to use (usually 7 or 8).
     */
    Error setCharBits(int bits);

    /**
     * Set parity mode.
     *
     * @return 0 on success.
     * @param parity mode.
     */
    Error setParity(Parity parity);

    /**
     * Set number of stop bits.
     *
     * @return 0 on success.
     * @param bits stop bits.
     */
    Error setStopBits(int bits);

    /**
     * Set flow control.
     *
     * @return 0 on success.
     * @param flow control mode.
     */
    Error setFlowControl(Flow flow);

    /**
     * Set the DTR mode off momentarily.
     *
     * @param millisec number of milliseconds.
     */
    void toggleDTR(timeout_t millisec);

    /**
     * Send the "break" signal.
     */
    void sendBreak(void);

    /**
     * Often used by a "catch" to fetch the last error of a thrown
     * serial.
     *
     * @return error numbr of last Error.
     */
    inline Error getErrorNumber(void)
        {return errid;}

    /**
     * Often used by a "catch" to fetch the user set error string
     * of a thrown serial.
     *
     * @return string for error message.
     */
    inline char *getErrorString(void)
        {return errstr;}

    /**
     * Get the "buffer" size for buffered operations.  This can
     * be used when setting packet or line read modes to determine
     * how many bytes to wait for in a given read call.
     *
     * @return number of bytes used for buffering.
     */
    inline int getBufferSize(void)
        {return bufsize;}

    /**
     * Get the status of pending operations.  This can be used to
     * examine if input or output is waiting, or if an error has
     * occured on the serial device.
     *
     * @return true if ready, false if timeout.
     * @param pend ready check to perform.
     * @param timeout in milliseconds.
     */
    virtual bool isPending(Pending pend, timeout_t timeout = TIMEOUT_INF);
};

/**
 * TTY streams are used to represent serial connections that are fully
 * "streamable" objects using C++ stream classes and friends.
 *
 * The first application relevant serial I/O class is the TTYStream class.
 * TTYStream offers a linearly buffered "streaming" I/O session with the
 * serial device.  Furthermore, traditional C++ "stream" operators (<< and
 * >>) may be used with the serial device.  A more "true" to ANSI C++ library
 * format "ttystream" is also available, and this supports an "open" method
 * in which one can pass initial serial device parameters immediately
 * following the device name in a single string, as in
 * "/dev/tty3a:9600,7,e,1", as an example.
 *
 * The TTYSession aggragates a TTYStream and a Common C++ Thread which is
 * assumed to be the execution context that will be used to perform actual
 * I/O operations.  This class is very anagolous to TCPSession.
 *
 *
 * @author David Sugar <dyfet@ostel.com>
 * @short streamable tty serial I/O class.
 */
class __EXPORT TTYStream : protected std::streambuf, public Serial, public std::iostream
{
private:
    int doallocate();

    friend TTYStream& crlf(TTYStream&);
    friend TTYStream& lfcr(TTYStream&);

protected:
    char *gbuf, *pbuf;
    timeout_t timeout;

    /**
     * This constructor is used to derive "ttystream", a more
     * C++ style version of the TTYStream class.
     */
    TTYStream();

    /**
     * Used to allocate the buffer space needed for iostream
     * operations.  This is based on MAX_INPUT.
     */
    void allocate(void);

    /**
     * Used to terminate the buffer space and clean up the tty
     * connection.  This function is called by the destructor.
     */
    void endStream(void);

    /**
     * This streambuf method is used to load the input buffer
     * through the established tty serial port.
     *
     * @return char from get buffer, EOF also possible.
     */
    int underflow(void);

    /**
     * This streambuf method is used for doing unbuffered reads
     * through the establish tty serial port when in interactive mode.
     * Also this method will handle proper use of buffers if not in
     * interative mode.
     *
     * @return char from tty serial port, EOF also possible.
     */
    int uflow(void);

    /**
     * This streambuf method is used to write the output
     * buffer through the established tty port.
     *
     * @param ch char to push through.
     * @return char pushed through.
     */
    int overflow(int ch);

public:
    /**
     * Create and open a tty serial port.
     *
     * @param filename char name of device to open.
     * @param to default timeout.
     */
    TTYStream(const char *filename, timeout_t to = 0);

    /**
     * End the tty stream and cleanup.
     */
    virtual ~TTYStream();

    /**
     * Set the timeout control.
     *
     * @param to timeout to use.
     */
    inline void setTimeout(timeout_t to)
        {timeout = to;}

    /**
     * Set tty mode to buffered or "interactive".  When interactive,
     * all streamed I/O is directly sent to the serial port
     * immediately.
     *
     * @param flag bool set to true to make interactive.
     */
    void interactive(bool flag);

    /**
     * Flushes the stream input and out buffers, writes
     * pending output.
     *
     * @return 0 on success.
     */
    int sync(void);

    /**
     * Get the status of pending operations.  This can be used to
     * examine if input or output is waiting, or if an error has
     * occured on the serial device.  If read buffer contains data
     * then input is ready and if write buffer contains data it is
     * first flushed then checked.
     *
     * @return true if ready, false if timeout.
     * @param pend ready check to perform.
     * @param timeout in milliseconds.
     */
    bool isPending(Pending pend, timeout_t timeout = TIMEOUT_INF);
};

/**
 * A more natural C++ "ttystream" class for use by non-threaded
 * applications.  This class behaves a lot more like fstream and
 * similar classes.
 *
 * @author David Sugar <dyfet@ostel.com>
 * @short C++ "fstream" style ttystream class.
 */

class __EXPORT ttystream : public TTYStream
{
public:
    /**
     * Construct an unopened "ttystream" object.
     */
    ttystream();

    /**
     * Construct and "open" a tty stream object.  A filename in
     * the form "device:options[,options]" may be used to pass
     * device options as part of the open.
     *
     * @param name of file and serial options.
     */
    ttystream(const char *name);

    /**
     * Open method for a tty stream.
     *
     * @param name filename to open.
     */
    void open(const char *name);

    /**
     * Close method for a tty stream.
     */
    void close(void);

    /**
     * Test to see if stream is opened.
     */
    inline bool operator!()
        {return (dev < 0);}
};

/**
 *
 * The TTYSession aggragates a TTYStream and a Common C++ Thread which is
 * assumed to be the execution context that will be used to perform actual
 * I/O operations.  This class is very anagolous to TCPSession.
 *
 * @author David Sugar <dyfet@ostel.com>
 * @short This class is very anagolous to TCPSession.
 */

class __EXPORT TTYSession : public Thread, public TTYStream
{
public:
    /**
     * Create TTY stream that will be managed by it's own thread.
     *
     * @param name of tty device to open.
     * @param pri execution priority.
     * @param stack allocation needed on some platforms.
     */
    TTYSession(const char *name, int pri = 0, int stack = 0);

    virtual ~TTYSession();
};

#ifndef _MSWINDOWS_

//  Not support this right now.......
//
class SerialPort;
class SerialService;

/**
 * The serial port is an internal class which is attached to and then
 * serviced by a specified SerialService thread.  Derived versions of
 * this class offer specific functionality such as serial integration
 * protocols.
 *
 * The TTYPort and TTYService classes are used to form thread-pool serviced
 * serial I/O protocol sets.  These can be used when one has a large number
 * of serial devices to manage, and a single (or limited number of) thread(s)
 * can then be used to service the tty port objects present.  Each tty port
 * supports a timer control and several virtual methods that the service
 * thread can call when events occur.  This model provides for "callback"
 * event management, whereby the service thread performs a "callback" into
 * the port object when events occur.  Specific events supported include the
 * expiration of a TTYPort timer, pending input data waiting to be read, and
 * "sighup" connection breaks.
 *
 *
 * @author David Sugar <dyfet@ostel.com>
 * @short base class for thread pool serviced serial I/O.
 */
class __EXPORT SerialPort: public Serial, public TimerPort
{
private:
    SerialPort *next, *prev;
    SerialService *service;
#ifdef  USE_POLL
    struct pollfd *ufd;
#endif
    bool detect_pending;
    bool detect_output;
    bool detect_disconnect;

    friend class SerialService;

protected:
    /**
     * Construct a tty serial port for a named serial device.
     *
     * @param svc pool thread object.
     * @param name of tty port.
     */
    SerialPort(SerialService *svc, const char *name);

    /**
     * Disconnect the Serial Port from the service pool thread
     * and shutdown the port.
     */
    virtual ~SerialPort();

    /**
     * Used to indicate if the service thread should monitor pending
     * data for us.
     */
    void setDetectPending( bool );

    /**
     * Get the current state of the DetectPending flag.
     */
    inline bool getDetectPending( void ) const
        { return detect_pending; }

    /**
     * Used to indicate if output ready monitoring should be performed
     * by the service thread.
     */
    void setDetectOutput( bool );

    /**
     * Get the current state of the DetectOutput flag.
     */
    inline bool getDetectOutput( void ) const
        { return detect_output; }

    /**
     * Called by the service thread when the objects timer
     * has expired.
     */
    virtual void expired(void);

    /**
     * Called by the service thread when input data is pending
     * for this tty port.  Effected by setPacketInput and by
     * setLineInput.
     */
    virtual void pending(void);

    /**
     * Called by the service thread when an exception has occured
     * such as a hangup.
     */
    virtual void disconnect(void);

    /**
     * Transmit "send" data to the serial port.  This is not public
     * since it's meant to support internal protocols rather than
     * direct public access to the device.
     *
     * @return number of bytes send.
     * @param buf address of buffer to send.
     * @param len of bytes to send.
     */
    inline int output(void *buf, int len)
        {return aWrite((char *)buf, len);}

    /**
     * Perform when output is available for sending data.
     */
    virtual void output(void);

    /**
     * Receive "input" for pending data from the serial port.  This
     * is not a public member since it's meant to support internal
     * protocols rather than direct external access to the device.
     *
     * @return number of bytes received.
     * @param buf address of buffer to input.
     * @param len of input buffer used.
     */
    inline int input(void *buf, int len)
        {return aRead((char *)buf, len);}

public:
    /**
     * Derived setTimer to notify the service thread pool of changes
     * in expected timeout.  This allows SerialService to
     * reschedule all timers.
     *
     * @param timeout in milliseconds.
     */
    void setTimer(timeout_t timeout = 0);

    /**
     * Derived incTimer to notify the service thread pool of a
     * change in expected timeout.  This allows SerialService to
     * reschedule all timers.
     */
    void incTimer(timeout_t timeout);
};

/**
 * The SerialService is a thead service object that is meant to service
 * attached serial ports.  Multiple pool objects may be created and
 * multiple serial ports may be attached to the same thread of
 * of execution.  This allows one to balance threads and the serial ports
 * they service.
 *
 *  The TTYPort and TTYService classes are used to form thread-pool serviced
 *  serial I/O protocol sets.  These can be used when one has a large number
 *  of serial devices to manage, and a single (or limited number of) thread(s)
 *  can then be used to service the tty port objects present.  Each tty port
 *  supports a timer control and several virtual methods that the service
 *  thread can call when events occur.  This model provides for "callback"
 *  event management, whereby the service thread performs a "callback" into
 *  the port object when events occur.  Specific events supported include the
 *  expiration of a TTYPort timer, pending input data waiting to be read, and
 *  "sighup" connection breaks.
 *
 *
 * @author David Sugar <dyfet@ostel.com>
 * @short Thread pool service for serial ports.
 */
class __EXPORT SerialService : public Thread, private Mutex
{
private:
    fd_set connect;
    int iosync[2];
    int hiwater;
    int count;
    SerialPort *first, *last;

    /**
     * Attach a new serial port to this service thread.
     *
     * @param port of SerialPort derived object to attach.
     */
    void attach(SerialPort *port);

    /**
     * Detach a serial port from this service thread.
     *
     * @param port of SerialPort derived object to remove.
     */
    void detach(SerialPort *port);

    /**
     * The service thread itself.
     */
    void run(void);

    friend class SerialPort;

protected:
    /**
     * A virtual handler for processing user defined update
     * requests (1-254) which have been posted through Update.
     *
     * @param flag of update request.
     */
    virtual void onUpdate(unsigned char flag);

    /**
     * A virtual handler for event loop calls.  This can be
     * used to extend event loop processing.
     */
    virtual void onEvent(void);

    /**
     * A virtual handler for adding support for additional
     * callback events into SerialPort.
     *
     * @param port serial port currently being evaluated.
     */
    virtual void onCallback(SerialPort *port);

public:
    /**
     * Notify service thread that a port has been added or
     * removed, or a timer changed, so that a new schedule
     * can be computed for expiring attached ports.  This
     * can also be used to pass requests to the OnUpdate()
     * event handler.
     *
     * @param flag event for OnUpdate, termination, or reschedule.
     */
    void update(unsigned char flag = 0xff);

    /**
     * Create a service thread for attaching serial ports.  The
     * thread begins execution with the first attached port.
     *
     * @param pri of this thread to run under.
     * @param stack stack size.
     * @param id stack ID.
     */
    SerialService(int pri = 0, size_t stack = 0, const char *id = NULL);

    /**
     * Terminate the service thread and update attached objects.
     */
    virtual ~SerialService();

    /**
     * Get current reference count.  This can be used when selecting
     * the lead used service handler from a pool.
     *
     * @return count of active ports.
     */
    inline int getCount(void)
        {return count;}
};

#endif

#ifdef  CCXX_EXCEPTIONS
class __EXPORT SerException : public IOException
{
public:
    SerException(const String &str) : IOException(str) {}
};
#endif

} // namespace ost

#endif
