/**
 * @file
 * $Id$
 * $Revision$
 * $Author$
 * $Date$
 *
 * This file is part of The iWear Framework.
 *
 * The iWear Framework 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 as in version 2 of the License.

 * 
 * The iWear Framework 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
 * The iWear Framework; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


#ifndef __IWEAR_SOCKETCONNECTION_H
#define __IWEAR_SOCKETCONNECTION_H

#ifndef __IWEAR_CONNECTION_H
#include <net/connection.h>
#endif

#include <iwear/exceptions.h>

extern "C"
{
#include <stdint.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
//#include <net/if.h>

#if defined(IW_MAC)
#include <sys/ioctls.h>
#endif
}

#include <string>


namespace iwear
{
    namespace net
    {

enum block_mode
{
    non_blocking = 0,
    blocking	 = 1
};

enum fd_mode
{
    fdread,
    fdwrite,
    fdexcept
};


/**
 * This is a base class for all operations on a socket. This may even include
 * external library classes which provide direct bluetooth/irda access through
 * a socket.
 * @note Unless stated otherwise, you should always keep in mind that
 * communication over sockets is unsafe and may be intercepted/redirected.
 * @warning This class and all of its descendants are neve thread safe. If you
 * want to be able to use a socket over multiple threads, ensure proper locking
 * yourself or copy buffers/buff pointers around.
 */
class SocketConnection : public virtual Connection
{
private:   
protected:
    /**
     * The socket this stuff all goes through
     */
    int fd;

    int domain;

    int type;

    int protocol;
    /**
     * If the Socket should be blocking
     */
    block_mode bmode;

    /**
     * @throws an exception in bad error cases.
     * @return
     * - -1 in case of error
     * - 0 in case of no event
     * - 1 in case of ready event for desired check    
     */
    int check_fd( int fd, fd_mode fdm, float timeout = DEFAULT_TIMEOUT );

    void get_socket( void );

    uint32_t get_socket_error(void);

    static void set_non_blocking( int& fd );
public:
    /**
     * As parameters we need the domain, type and protocol to pass it to the
     * socket() call.
     * @param d is the domain of the socket, e.g. AF_INET or AF_INET6
     * @param t is the type of the socket, e.g. SOCK_STREAM or SOCK_DGRAM
     */
    SocketConnection(block_mode bm, int d, int t, int p);

    int get_domain( void ) { return domain; }

    virtual ~SocketConnection();

    /**
     * Sending Data with this functions is mostly usefull in udp cases, or when
     * trying to send out of band data on tcp streams.
     */
    virtual ssize_t recv( void * buf, size_t len, float tout = DEFAULT_TIMEOUT, 
	    int flags=0, struct sockaddr* from=NULL, socklen_t* fromlen=NULL, bool checkfd = true);

    virtual ssize_t recvmsg( struct msghdr* msg, int flags=0);

    virtual ssize_t send ( const void * msg , size_t len, float tout = DEFAULT_TIMEOUT, 
	    int flags=0, struct sockaddr* to=NULL, socklen_t tolen=0, bool checkfd = true);

    virtual ssize_t sendmsg( const struct msghdr* msg, int flags=0);

    inline int get_fd() const { return fd; } 

    static string get_hwaddr( const string& iface );

    /**
     * @name iwear::net::Connection interface implementation part --------  
     * @{
     */

    /**
     * @param checkfd is true per default since a normal socket will usually
     * check the filedescriptor via select() before reading from it. If this
     * SocketConnection is within a socketset this set has previously checked
     * the status of the fd, so we cant select() on it any more.
     */
    virtual ssize_t read( void * buf, size_t count, float to = DEFAULT_TIMEOUT, bool check = true );

    /**
     * This Functions reads a std::string from the stream. It will have a
     * maximum length as specified by count, but might be shorter, in case of a
     * \\0 within the stream (The rest of the data will be discarded then !!)
     * @note This functions is slow by its nature, since it first needs to copy
     * a c-string buffer internally into a string, and when it returns the
     * string it will copy it again.
     */
    virtual string read( size_t count, float to = DEFAULT_TIMEOUT, bool check = true);
 
    /**
     * @note From select_tut(2): <br>
     * 5. The  functions  read(),  recv(), write(), and send() do not
     * necessarily read/write the full amount of data that you have requested.
     * If they do read/write the full amount, its because you have a  low
     * traffic  load and  a  fast  stream. This is not always going to be the
     * case. You should cope with the case of your functions only managing to
     * send or receive a single byte.<p>
     * This might be very important in case of slow/high latency serial/IR
     * links
     */
    virtual ssize_t write( const void * buf , size_t count, float to = DEFAULT_TIMEOUT, bool check = true );
    virtual ssize_t write( const string& dat, string::size_type pos = 0, float to = DEFAULT_TIMEOUT, bool check = true );   

    virtual void disconnect( void );
    virtual void reconnect( float to = DEFAULT_TIMEOUT ) = 0;

    virtual Connection* accept( float to = DEFAULT_TIMEOUT, bool checkfd = true ) = 0;

    /**
     * @}
     */
};

/**
 * Helper class that destroys the socket on destruction, and sets the given
 * socket variable to -1
 */
class FDCloser
{
    int fd;
    int* fdp;
public:
    FDCloser( int& f ): fd(f), fdp(&f)
    {
	if( fd <= 0 )
	    THROW( socket_error,("Not able to close non-socket", ENOTSOCK));
    }

    ~FDCloser( )
    {
	if( fd ) { ::close(fd); *fdp = -1; }
    }

    int release( void ) { int ret = fd; fdp = 0; fd = 0; return ret; }
};

} // namespace net
} // namespace iwear
#endif
