/* 
 * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
 *
 * 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 General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */
#ifndef __PJPP_SOCK_HPP__
#define __PJPP_SOCK_HPP__

#include <pj/sock.h>
#include <pj/string.h>

class Pj_Event_Handler;

//
// Base class for address.
//
class Pj_Addr
{
};

//
// Internet address.
//
class Pj_Inet_Addr : public pj_sockaddr_in, public Pj_Addr
{
public:
    //
    // Get port number.
    //
    pj_uint16_t get_port_number() const
    {
        return pj_sockaddr_in_get_port(this);
    }

    //
    // Set port number.
    //
    void set_port_number(pj_uint16_t port)
    {
        sin_family = PJ_AF_INET;
        pj_sockaddr_in_set_port(this, port);
    }

    //
    // Get IP address.
    //
    pj_uint32_t get_ip_address() const
    {
        return pj_sockaddr_in_get_addr(this).s_addr;
    }

    //
    // Get address string.
    //
    const char *get_address() const
    {
        return pj_inet_ntoa(sin_addr);
    }

    //
    // Set IP address.
    //
    void set_ip_address(pj_uint32_t addr)
    {
        sin_family = PJ_AF_INET;
        pj_sockaddr_in_set_addr(this, addr);
    }

    //
    // Set address.
    //
    pj_status_t set_address(const pj_str_t *addr)
    {
        return pj_sockaddr_in_set_str_addr(this, addr);
    }

    //
    // Set address.
    //
    pj_status_t set_address(const char *addr)
    {
        pj_str_t s;
        return pj_sockaddr_in_set_str_addr(this, pj_cstr(&s, addr));
    }

    //
    // Compare for equality.
    //
    bool operator==(const Pj_Inet_Addr &rhs) const
    {
        return sin_family == rhs.sin_family &&
               sin_addr.s_addr == rhs.sin_addr.s_addr &&
               sin_port == rhs.sin_port;
    }

private:
    //
    // Dummy length used in pj_ioqueue_recvfrom() etc
    //
    friend class Pj_Event_Handler;
    friend class Pj_Socket;
    friend class Pj_Sock_Stream;
    friend class Pj_Sock_Dgram;

    int addrlen_;
};


//
// Socket base class.
//
// Note:
//  socket will not automatically be closed on destructor.
//
class Pj_Socket
{
public:
    //
    // Default constructor.
    //
    Pj_Socket()
        : sock_(PJ_INVALID_SOCKET) 
    {
    }

    //
    // Initialize from a socket handle.
    //
    explicit Pj_Socket(pj_sock_t sock)
        : sock_(sock)
    {
    }

    //
    // Copy constructor.
    //
    Pj_Socket(const Pj_Socket &rhs) 
        : sock_(rhs.sock_) 
    {
    }

    //
    // Destructor will not close the socket.
    // You must call close() explicitly.
    //
    ~Pj_Socket()
    {
    }

    //
    // Set socket handle.
    //
    void set_handle(pj_sock_t sock)
    {
        sock_ = sock;
    }

    //
    // Get socket handle.
    //
    pj_sock_t get_handle() const
    {
        return sock_;
    }

    //
    // Get socket handle.
    //
    pj_sock_t& get_handle()
    {
        return sock_;
    }

    //
    // See if the socket is valid.
    //
    bool is_valid() const
    {
        return sock_ != PJ_INVALID_SOCKET;
    }

    //
    // Create the socket.
    //
    pj_status_t create(int af, int type, int proto)
    {
        return pj_sock_socket(af, type, proto, &sock_);
    }

    //
    // Bind socket.
    //
    pj_status_t bind(const Pj_Inet_Addr &addr)
    {
        return pj_sock_bind(sock_, &addr, sizeof(Pj_Inet_Addr));
    }

    //
    // Close socket.
    //
    pj_status_t close()
    {
        pj_sock_close(sock_);
    }

    //
    // Get peer socket name.
    //
    pj_status_t getpeername(Pj_Inet_Addr *addr)
    {
        return pj_sock_getpeername(sock_, addr, &addr->addrlen_);
    }

    //
    // getsockname
    //
    pj_status_t getsockname(Pj_Inet_Addr *addr)
    {
        return pj_sock_getsockname(sock_, addr, &addr->addrlen_);
    }

    //
    // getsockopt.
    //
    pj_status_t getsockopt(pj_uint16_t level, pj_uint16_t optname, 
                           void *optval, int *optlen)
    {
        return pj_sock_getsockopt(sock_, level, optname, optval, optlen);
    }

    //
    // setsockopt
    // 
    pj_status_t setsockopt(pj_uint16_t level, pj_uint16_t optname, 
                           const void *optval, int optlen)
    {
        return pj_sock_setsockopt(sock_, level, optname, optval, optlen);
    }

    //
    // receive data.
    //
    pj_ssize_t recv(void *buf, pj_size_t len, int flag = 0)
    {
        pj_ssize_t bytes = len;
        if (pj_sock_recv(sock_, buf, &bytes, flag) != PJ_SUCCESS)
            return -1;
        return bytes;
    }

    //
    // send data.
    //
    pj_ssize_t send(const void *buf, pj_ssize_t len, int flag = 0)
    {
        pj_ssize_t bytes = len;
        if (pj_sock_send(sock_, buf, &bytes, flag) != PJ_SUCCESS)
            return -1;
        return bytes;
    }

    //
    // connect.
    //
    pj_status_t connect(const Pj_Inet_Addr &addr)
    {
        return pj_sock_connect(sock_, &addr, sizeof(Pj_Inet_Addr));
    }

    //
    // assignment.
    //
    Pj_Socket &operator=(const Pj_Socket &rhs)
    {
        sock_ = rhs.sock_;
        return *this;
    }

protected:
    friend class Pj_Event_Handler;
    pj_sock_t sock_;
};


#if PJ_HAS_TCP
//
// Stream socket.
//
class Pj_Sock_Stream : public Pj_Socket
{
public:
    //
    // Default constructor.
    //
    Pj_Sock_Stream() 
    {
    }

    //
    // Initialize from a socket handle.
    //
    explicit Pj_Sock_Stream(pj_sock_t sock)
        : Pj_Socket(sock)
    {
    }

    //
    // Copy constructor.
    //
    Pj_Sock_Stream(const Pj_Sock_Stream &rhs) : Pj_Socket(rhs) 
    {
    }

    //
    // Assignment.
    //
    Pj_Sock_Stream &operator=(const Pj_Sock_Stream &rhs) 
    { 
        sock_ = rhs.sock_; 
        return *this; 
    }

    //
    // listen()
    //
    pj_status_t listen(int backlog = 5)
    {
        return pj_sock_listen(sock_, backlog);
    }

    //
    // blocking accept()
    //
    Pj_Sock_Stream accept(Pj_Inet_Addr *remote_addr = NULL)
    {
        pj_sock_t newsock;
        int *addrlen = remote_addr ? &remote_addr->addrlen_ : NULL;
        pj_status_t status;
        
        status = pj_sock_accept(sock_, &newsock, remote_addr, addrlen);
        if (status != PJ_SUCCESS)
            return Pj_Sock_Stream(-1);

        return Pj_Sock_Stream(newsock);
    }

    //
    // shutdown()
    //
    pj_status_t shutdown(int how = PJ_SHUT_RDWR)
    {
        return pj_sock_shutdown(sock_, how);
    }

};
#endif

//
// Datagram socket.
//
class Pj_Sock_Dgram : public Pj_Socket
{
public:
    //
    // Default constructor.
    //
    Pj_Sock_Dgram() 
    {
    }

    //
    // Initialize from a socket handle.
    //
    explicit Pj_Sock_Dgram(pj_sock_t sock)
        : Pj_Socket(sock)
    {
    }

    //
    // Copy constructor.
    //
    Pj_Sock_Dgram(const Pj_Sock_Dgram &rhs) 
        : Pj_Socket(rhs) 
    {
    }

    //
    // Assignment.
    //
    Pj_Sock_Dgram &operator=(const Pj_Sock_Dgram &rhs) 
    { 
        Pj_Socket::operator =(rhs);
        return *this; 
    }

    //
    // recvfrom()
    //
    pj_ssize_t recvfrom( void *buf, pj_size_t len, int flag = 0, 
                         Pj_Inet_Addr *fromaddr = NULL)
    {
        pj_ssize_t bytes = len;
        int *addrlen = fromaddr ? &fromaddr->addrlen_ : NULL;
        if (pj_sock_recvfrom( sock_, buf, &bytes, flag, 
                              fromaddr, addrlen) != PJ_SUCCESS)
        {
            return -1;
        }
        return bytes;
    }

    //
    // sendto()
    //
    pj_ssize_t sendto( const void *buf, pj_size_t len, int flag, 
                       const Pj_Inet_Addr &addr)
    {
        pj_ssize_t bytes = len;
        if (pj_sock_sendto( sock_, buf, &bytes, flag, 
                            &addr, sizeof(pj_sockaddr_in)) != PJ_SUCCESS)
        {
            return -1;
        }
        return bytes;
    }
};


#endif  /* __PJPP_SOCK_HPP__ */