// Copyright (C) 2006-2014 David Sugar, Tycho Softworks.
// Copyright (C) 2015-2020 Cherokees of Idaho.
//
// 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
 */

#ifndef UCOMMON_SYSRUNTIME
#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>
#include <fstream>

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
{
private:
	__DELETE_COPY(StreamBuffer);

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() __OVERRIDE;

    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) __OVERRIDE;

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

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

    inline bool operator!() const
        {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) __OVERRIDE;

    /**
     * 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) __OVERRIDE;

    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 pipe socket connection.  This creates a stream between
 * a parent and child process.  As a stream class, data can be
 * exchanged 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);

	__DELETE_COPY(pipestream);

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) __OVERRIDE;

    /**
     * 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) __OVERRIDE;

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 file class based on low level fsys io.  This differs from
 * the normal fstream classes as it may apply to other kinds of
 * file streams.
 *
 * @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) __OVERRIDE;

    /**
     * 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) __OVERRIDE;

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);

	void rewind(void);

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

/**
 * Stream class to read from a memory buffer.  May optionally be 0 byte
 * terminated if no length is specified.
 */
class __EXPORT imemstream : protected std::streambuf, public std::istream
{
private:
	__DELETE_DEFAULTS(imemstream);

	size_t count;
	const uint8_t *pos, *bp;

public:
	imemstream(const uint8_t *data, size_t size);
	imemstream(const char *data);

	int underflow() __OVERRIDE;

	int uflow() __OVERRIDE;

	inline size_t remains() const {
		return count;
	}

	inline const uint8_t *mem() const {
		return bp;
	}

	inline const char *chr() const {
		return (const char *)bp;
	}

	inline size_t len() const {
		return (size_t)(pos - bp) + count;
	}
};

/**
 * Stream class to write to memory buffer.
 */
class __EXPORT omemstream : protected std::streambuf, public std::ostream
{
private:
	__DELETE_DEFAULTS(omemstream);

	size_t count;
	uint8_t *pos, *bp;
	bool zb;

public:
	explicit omemstream(uint8_t *data, size_t size);
	omemstream(char *data, size_t size);

	int overflow(int ch) __OVERRIDE;

	inline size_t remains() const {
		return count;
	}

	inline uint8_t *mem() const {
		return bp;
	}

	inline char *chr() const {
		return (char *)bp;
	}

	inline size_t len() const {
		return (size_t)(pos - bp);
	}
};

bool __EXPORT getline(std::istream& in, char *buffer, size_t size);

bool __EXPORT putline(std::ostream& out, const char *buffer);

/**
 * At least with gcc, linking of stream operators was broken.  This provides
 * an auxillory class to solve the issue.
 */
class __EXPORT _stream_operators
{
private:
	__DELETE_DEFAULTS(_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);

    static std::string& append(std::string& target, String& source);
};

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);
}

inline std::string& operator+(std::string& target, String& source) {
    return _stream_operators::append(target, source);
}

inline std::string& operator+=(std::string& target, String& source) {
    return _stream_operators::append(target, source);
}

inline std::ostream& operator<<(std::ostream& os, Socket::address& addr) {
#ifdef  AF_INET6
    char buf[INET6_ADDRSTRLEN];
#else
    char buf[INET_ADDRSTRLEN];
#endif
    addr.print(buf, sizeof(buf), false, true);
    os << buf;
    return os;
}

} // namespace ucommon

namespace std {
	extern __EXPORT iostream& null;
}

#endif
#endif
