#ifndef _UDP_SOCKET
#define _UDP_SOCKET

#include "networkAddress.h"
#include "sysdefs.h"
#include "debug.h"

#ifdef WIN32
typedef SOCKET SocketHandle;
#else
typedef int SocketHandle;
const int INVALID_SOCKET = -1;
#endif // if/else WIN32/UNIX

// The "UdpSocketOwner" class exists only for the derivation of 
// other classes. Therefore, no constructor is defined.
class UdpSocketOwner {};
typedef bool (UdpSocketOwner::*UdpSocketRecvHandler)(class UdpSocket* theSocket);

// For socket read notification installation
enum UdpSocketCmd{UDP_SOCKET_INSTALL, UDP_SOCKET_REMOVE};
typedef bool (UdpSocketInstallFunc)(UdpSocketCmd        cmd,
                                    class UdpSocket*    theSocket,
                                    const void*         installData);


// Possible errors returned by some UdpSocket calls
typedef enum UdpSocketError
{
    UDP_SOCKET_ERROR_NONE = 0, 
    UDP_SOCKET_ERROR,         // generic error
    UDP_SOCKET_ERROR_OPEN,    // socket() error
    UDP_SOCKET_ERROR_BIND,    // bind() error
    UDP_SOCKET_ERROR_CONNECT, // connect() error
    UDP_SOCKET_ERROR_NAME,    // getsockname() error
    UDP_SOCKET_ERROR_SEND,    // sendto() error
    UDP_SOCKET_ERROR_RECV,    // recvfrom() error        
    UDP_SOCKET_ERROR_MCAST,   // join group error
    UDP_SOCKET_ERROR_TTL,     // mcast ttl error
    UDP_SOCKET_ERROR_IFACE,   // mcast iface error
    UDP_SOCKET_ERROR_TOS,     // ip tos error
    UDP_SOCKET_ERROR_BCAST,   // broadcast option error
    UDP_SOCKET_ERROR_LOOP,    // loopback option error
    UDP_SOCKET_ERROR_REUSE,   // port/addr reuse error
    UDP_SOCKET_ERROR_SEC      // ipsec error
} UdpSocketError;


class UdpSocket
{
    public:
    // Construction
        UdpSocket();
        ~UdpSocket();
        
    // Initialization
        void Init(UdpSocketOwner*       theOwner, 
                  UdpSocketRecvHandler  recvHandler,
                  UdpSocketInstallFunc* installFunc,
                  const void*           installData)
        {
            owner = theOwner;
            recv_handler = recvHandler;
            install_func = installFunc;
            install_data = installData;
        }
        void SetInstaller(UdpSocketInstallFunc* installFunc,
                          const void*           installData)
        {
            install_func = installFunc;
            install_data = installData;
        }
        
        void SetListener(UdpSocketOwner*       theOwner, 
                         UdpSocketRecvHandler  recvHandler)
        {
            owner = theOwner;
            recv_handler = recvHandler;
        }
        
        UdpSocketOwner* Owner() {return owner;}
        UdpSocketRecvHandler RecvHandler() {return recv_handler;}
            
        void SetUserData(const void* userData) {user_data = userData;}
        const void* GetUserData() {return user_data;}

        // Control 
        UdpSocketError Open(unsigned short     thePort = 0, 
                            NetworkAddressType destType = IPv4,
                            bool               bindOnOpen = true);
        UdpSocketError Bind(unsigned short thePort);
        void Close();
        bool Connect(const NetworkAddress* theAddress);
        void Disconnect();
        
        UdpSocketError JoinGroup(const NetworkAddress* groupAddress, 
                                 const char* interfaceName = NULL);
        UdpSocketError LeaveGroup(const NetworkAddress* theAddress, 
                                  const char* interfaceName = NULL);
        
        // Helper functions for group join/leave
        static bool GetInterfaceAddress(const char*         interfaceName, 
                                        NetworkAddressType  addressType,
                                        NetworkAddress&     theAddress);        
#ifdef UNIX
        static unsigned int GetInterfaceIndex(const char* interfaceName);
        static bool GetInterfaceName(unsigned int index, char* buffer, unsigned int buflen);
#endif // UNIX    
            
        UdpSocketError SetTTL(unsigned char theTTL);
        UdpSocketError SetLoopback(bool loopback);
        UdpSocketError SetBroadcast(bool broadcast);
        UdpSocketError SetTOS(unsigned char tos);
#ifdef HAVE_IPV6
        bool SetFlowLabel(UINT32 label);
#endif //HAVE_IPV6
        UdpSocketError SetReuse(bool state);
        UdpSocketError SetMulticastInterface(const char* interfaceName);
        
        UdpSocketError SendTo(const NetworkAddress* dest, const char* buffer, unsigned int len);
        UdpSocketError RecvFrom(char* buffer, unsigned int* buflen, NetworkAddress* src);
        
        // Status
        bool IsOpen() const {return (bool)(handle != INVALID_SOCKET);}     
        bool IsBound() const 
        {
#ifdef SIMULATE
            return true;
#else
            return (IsOpen() ? (0 != port) : false);
#endif // SIMULATE
        }
        bool IsConnected() {return connected;}
        unsigned short Port() {return port;}
        SocketHandle Handle() {return handle;}
        NetworkAddressType AddressType() {return addr_type;}
        
        void OnReceive()
        {
            //TRACE("OnReceive handler:%p\n", recv_handler);
            if (recv_handler) (owner->*recv_handler)(this);
        }

        bool SetTxBufferSize(unsigned int bufferSize);
        unsigned int GetTxBufferSize();
        bool SetRxBufferSize(unsigned int bufferSize);
        unsigned int GetRxBufferSize();

#ifdef HAVE_IPV6
        static bool IsIPv6Capable();
        static bool SetIPv6Capable();
#endif  // HAVE_IPv6
                    
    private:
    // Members
        SocketHandle            handle;
        NetworkAddressType      addr_type;  // PF_INET or PF_INET6
        unsigned short          port;
        bool                    connected;
#ifdef HAVE_IPV6
        UINT32                  flow_label; //IPv6 flow label  
#endif // HAVE_IPV6
        UdpSocketOwner*         owner;
        UdpSocketRecvHandler    recv_handler;
        UdpSocketInstallFunc*   install_func;
        const void*             install_data;
        const void*             user_data;
              
};  // end class UdpSocket


#endif // _UDP_SOCKET

