// Copyright (C) 2006-2014 David Sugar, Tycho Softworks.
//
// This file is part of GNU uCommon C++.
//
// GNU uCommon C++ 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.
//
// GNU uCommon C++ 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 GNU uCommon C++.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Any ucommon streaming classes that are built from std::streamio facilities
 * and that support ANSI C++ stream operators.
 * @file ucommon/stream.h
 */

#if defined(OLD_STDCPP) || defined(NEW_STDCPP)
#ifndef _UCOMMON_STREAM_H_
#define _UCOMMON_STREAM_H_

#ifndef _UCOMMON_CONFIG_H
#include <ucommon/platform.h>
#endif

#ifndef _UCOMMON_PROTOCOLS_H_
#include <ucommon/protocols.h>
#endif

#ifndef _UCOMMON_THREAD_H_
#include <ucommon/thread.h>
#endif

#ifndef _UCOMMON_SOCKET_H_
#include <ucommon/socket.h>
#endif

#ifndef _UCOMMON_FSYS_H_
#include <ucommon/fsys.h>
#endif

#ifndef _UCOMMON_SHELL_H_
#include <ucommon/shell.h>
#endif

#include <iostream>

namespace ucommon {

/**
 * Common stream buffer for std C++ i/o classes.    This both binds the
 * character protocol to iostream and offers a common base class for all
 * other c++ stdlib based streaming classes.
 * @author David Sugar <dyfet@gnutelephony.org>
 */
class __EXPORT StreamBuffer : protected std::streambuf, public std::iostream
{
protected:
    size_t bufsize;
    char *gbuf, *pbuf;

    StreamBuffer();

    /**
     * This streambuf method is used for doing unbuffered reads
     * through the establish tcp socket connection when in interactive mode.
     * Also this method will handle proper use of buffers if not in
     * interactive mode.
     *
     * @return char from tcp socket connection, EOF if not connected.
     */
    int uflow();

    void release(void);

    void allocate(size_t size);

public:
    /**
     * Flush the stream input and output buffers, writes pending output.
     * @return 0 on success, or error code.
     */
    int sync(void);

    inline bool is_open(void)
        {return bufsize > 0;}

    inline operator bool()
        {return bufsize > 0;}

    inline bool operator!()
        {return bufsize == 0;}
};

/**
 * Streamable tcp connection between client and server.  The tcp stream
 * class can represent a client connection to a server or an instance of
 * a service generated by a tcp listener.  As a stream class, data can
 * be manipulated using the << and >> operators.
 *
 * @author David Sugar <dyfet@gnutelephony.org>
 */
class __EXPORT tcpstream : public StreamBuffer
{
private:
    __LOCAL void allocate(unsigned size);
    __LOCAL void reset(void);

protected:
    socket_t so;
    timeout_t timeout;

    virtual ssize_t _read(char *buffer, size_t size);

    virtual ssize_t _write(const char *buffer, size_t size);

    virtual bool _wait(void);

    /**
     * Release the tcp stream and destroy the underlying socket.
     */
    void release(void);

    /**
     * This streambuf method is used to load the input buffer
     * through the established tcp socket connection.
     *
     * @return char from get buffer, EOF if not connected.
     */
    int underflow(void);

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

    inline socket_t getsocket(void) const
        {return so;}

public:
    /**
     * Copy constructor...
     * @param copy for object.
     */
    tcpstream(const tcpstream& copy);

    /**
     * Create a stream from an existing tcp listener.
     * @param server to accept connection from.
     * @param segsize for tcp segments and buffering.
     * @param timeout for socket i/o operations.
     */
    tcpstream(const TCPServer *server, unsigned segsize = 536, timeout_t timeout = 0);

    /**
     * Create an unconnected tcp stream object that is idle until opened.
     * @param family of protocol to create.
     * @param timeout for socket i/o operations.
     */
    tcpstream(int family = PF_INET, timeout_t timeout = 0);

    /**
     * A convenience constructor that creates a connected tcp stream directly
     * from an address.  The socket is constructed to match the type of the
     * the address family in the socket address that is passed.
     * @param address of service to connect to.
     * @param segsize for tcp segments and buffering.
     * @param timeout for socket i/o operations.
     */
    tcpstream(Socket::address& address, unsigned segsize = 536, timeout_t timeout = 0);

    /**
     * Destroy a tcp stream.
     */
    virtual ~tcpstream();

    /**
     * See if stream connection is active.
     * @return true if stream is active.
     */
    inline operator bool() const
        {return so != INVALID_SOCKET && bufsize > 0;}

    /**
     * See if stream is disconnected.
     * @return true if stream disconnected.
     */
    inline bool operator!() const
        {return so == INVALID_SOCKET || bufsize == 0;}

    /**
     * Open a stream connection to a tcp service.
     * @param address of service to access.
     * @param segment buffering size to use.
     */
    void open(Socket::address& address, unsigned segment = 536);

    /**
     * Open a stream connectoion to a host and service.
     * @param host to connect to.
     * @param service to connect to by name or number as string.
     * @param segment buffering size to use.
     */
    void open(const char *host, const char *service, unsigned segment = 536);

    /**
     * Close an active stream connection.  This does not release the
     * socket but is a disconnect.
     */
    void close(void);
};

/**
 * Streamable tcp connection between client and server.  The tcp stream
 * class can represent a client connection to a server or an instance of
 * a service generated by a tcp listener.  As a stream class, data can
 * be manipulated using the << and >> operators.
 *
 * @author David Sugar <dyfet@gnutelephony.org>
 */
class __EXPORT pipestream : public StreamBuffer
{
public:
    typedef enum {
        RDONLY,
        WRONLY,
        RDWR
    } access_t;

private:
    __LOCAL void allocate(size_t size, access_t mode);

protected:
    fsys_t rd, wr;
    shell::pid_t pid;

    /**
     * Release the stream, detach/do not wait for the process.
     */
    void release(void);

    /**
     * This streambuf method is used to load the input buffer
     * through the established pipe connection.
     *
     * @return char from get buffer, EOF if not connected.
     */
    int underflow(void);

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

public:
    /**
     * Create an unopened pipe stream.
     */
    pipestream();

    /**
     * Create child process and start pipe.
     * @param path to execute.
     * @param access mode of pipe stream.
     * @param args to pass to command.
     * @param env to create in child.
     * @param size of buffer.
     */
    pipestream(const char *command, access_t access, char **args, char **env = NULL, size_t size = 512);

    /**
     * Destroy a pipe stream.
     */
    virtual ~pipestream();

    /**
     * See if stream connection is active.
     * @return true if stream is active.
     */
    inline operator bool() const
        {return (bufsize > 0);}

    /**
     * See if stream is disconnected.
     * @return true if stream disconnected.
     */
    inline bool operator!() const
        {return bufsize == 0;}

    /**
     * Open a stream connection to a pipe service.
     * @param path to execute.
     * @param access mode of stream.
     * @param args to pass to command.
     * @param env to create in child process.
     * @param buffering size to use.
     */
    void open(const char *path, access_t access, char **args, char **env = NULL, size_t buffering = 512);

    /**
     * Close an active stream connection.  This waits for the child to
     * terminate.
     */
    int close(void);

    /**
     * Force terminate child and close.
     */
    void terminate(void);

    inline void cancel(void)
        {terminate();}
};

/**
 * Streamable tcp connection between client and server.  The tcp stream
 * class can represent a client connection to a server or an instance of
 * a service generated by a tcp listener.  As a stream class, data can
 * be manipulated using the << and >> operators.
 *
 * @author David Sugar <dyfet@gnutelephony.org>
 */
class __EXPORT filestream : public StreamBuffer
{
public:
    typedef enum {
        RDONLY,
        WRONLY,
        RDWR
    } access_t;

private:
    __LOCAL void allocate(size_t size, fsys::access_t mode);

protected:
    fsys_t fd;
    fsys::access_t ac;

    /**
     * This streambuf method is used to load the input buffer
     * through the established pipe connection.
     *
     * @return char from get buffer, EOF if not connected.
     */
    int underflow(void);

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

public:
    /**
     * Create an unopened pipe stream.
     */
    filestream();

    /**
     * Create duplicate stream.
     */
    filestream(const filestream& copy);

    /**
     * Create and open a file stream.
     */
    filestream(const char *path, unsigned mode, fsys::access_t access, size_t bufsize = 512);

    /**
     * Open file stream.
     */
    filestream(const char *path, fsys::access_t access, size_t bufsize = 512);

    /**
     * Destroy a file stream.
     */
    virtual ~filestream();

    /**
     * See if stream connection is active.
     * @return true if stream is active.
     */
    inline operator bool() const
        {return (bufsize > 0);}

    /**
     * See if stream is disconnected.
     * @return true if stream disconnected.
     */
    inline bool operator!() const
        {return bufsize == 0;}

    /**
     * Open a stream connection to a tcp service.
     */
    void open(const char *filename, fsys::access_t access, size_t buffering = 512);

    /**
     * Create a stream connection to a tcp service.
     */
    void open(const char *filename, unsigned mode, fsys::access_t access, size_t buffering = 512);

    /**
     * Close an active stream connection.
     */
    void close(void);

    /**
     * Seek position.
     */
    void seek(fsys::offset_t offset);

    /**
     * Get error flag from last i/o operation.
     * @return last error.
     */
    inline int err(void) const
        {return fd.err();}
};

/**
 * At least with gcc, linking of stream operators was broken.  This provides
 * an auxillory class to solve the issue.
 */
class __EXPORT _stream_operators
{
private:
    inline _stream_operators() {}

public:
    static std::ostream& print(std::ostream& out, const PrintProtocol& format);

    static std::istream& input(std::istream& inp, InputProtocol& format);

    static std::ostream& print(std::ostream& out, const string_t& str);

    static std::istream& input(std::istream& inp, string_t& str);

    static std::ostream& print(std::ostream& out, const stringlist_t& list);

    static std::istream& input(std::istream& in, stringlist_t& list);

};

inline std::ostream& operator<< (std::ostream& out, const PrintProtocol& format)
        {return _stream_operators::print(out, format);}

inline std::istream& operator>> (std::istream& inp, InputProtocol& format)
        {return _stream_operators::input(inp, format);}

inline std::ostream& operator<< (std::ostream& out, const string_t& str)
        {return _stream_operators::print(out, str);}

inline std::istream& operator>> (std::istream& inp, string_t& str)
        {return _stream_operators::input(inp, str);}

inline std::ostream& operator<< (std::ostream& out, const stringlist_t& list)
        {return _stream_operators::print(out, list);}

inline std::istream& operator>> (std::istream& in, stringlist_t& list)
        {return _stream_operators::input(in, list);}

} // namespace ucommon

#endif
#endif
