#ifndef _PROTO_SOCKET
#define _PROTO_SOCKET

#include "protoAddress.h"

#ifdef WIN32
#include <winsock2.h>  // for SOCKET type
#else
#include <errno.h> // for errno
#endif // if/else WIN32/UNIX

class ProtoSocket
{
    public:
        // Type definitions
        enum Protocol {UDP, TCP};
        enum State {CLOSED, IDLE, CONNECTING, LISTENING, CONNECTED}; 
#ifdef SIMULATE
        class Proxy {};
        typedef Proxy* Handle;
#else       
#ifdef WIN32
        typedef SOCKET Handle;
#else
        typedef int Handle;
#endif // if/else WIN32/UNIX
#endif // if/else SIMULATE
        static const Handle INVALID_HANDLE; 
        
        ProtoSocket(Protocol theProtocol);
        ~ProtoSocket();      
        
        // Control methods
        bool Open(UINT16                thePort = 0, 
                  ProtoAddress::Type    addrType = ProtoAddress::IPv4,
                  bool                  bindOnOpen = true);
        bool Bind(UINT16 thePort = 0, ProtoAddress* localAddr = NULL);
        bool Connect(const ProtoAddress& theAddress);
        void Disconnect();
        bool Listen(UINT16 thePort = 0);
        void Ignore();
        bool Accept(ProtoSocket* theSocket = NULL);
        bool Shutdown();
        void Close();
        bool JoinGroup(const ProtoAddress&  groupAddr, 
                       const char*          interfaceName = NULL);
        bool LeaveGroup(const ProtoAddress& groupAddr, 
                        const char*         interfaceName = NULL);
        
        // Status methods
        Protocol GetProtocol() const {return protocol;}
        State GetState() const {return state;}
        Handle GetHandle() const {return handle;}
        ProtoAddress::Type GetAddressType() const {return address_type;}
        UINT16 GetPort() const {return port < 0 ? 0 : port;}
        bool IsOpen() const {return (CLOSED != state);}
        bool IsBound() const {return (IsOpen() ? (port >= 0) : false);}
        bool IsConnected() const {return (CONNECTED == state);}
        const ProtoAddress& GetDestination() {return destination;}
#ifdef WIN32
        bool IsOutputReady() {return output_ready;}
#endif  // WIN32
        static const char* GetErrorString()
        {
#ifdef WIN32
            static char errorString[256];
            errorString[255] = '\0';
            FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | 
                          FORMAT_MESSAGE_IGNORE_INSERTS,
                          NULL,
                          WSAGetLastError(),
                          MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
                          (LPTSTR) errorString, 255, NULL);
            return errorString;
#else
            return strerror(errno);
#endif // if/else WIN32/UNIX
        }
        
        // Read/Write methods
        bool SendTo(const char* buffer, unsigned int buflen, const ProtoAddress& dstAddr);
        bool RecvFrom(char* buffer, unsigned int& numBytes, ProtoAddress& srcAddr);
        bool Send(const char* buffer, unsigned int& numBytes);
        bool Recv(char* buffer, unsigned int& numBytes);
        
        // Attributes
        bool SetTTL(unsigned char ttl);
        bool SetLoopback(bool loopback);
        bool SetBroadcast(bool broadcast);
        bool SetReuse(bool reuse);
        bool SetMulticastInterface(const char* interfaceName);
        bool SetTOS(unsigned char tos);      
        bool SetTxBufferSize(unsigned int bufferSize);
        unsigned int GetTxBufferSize();
        bool SetRxBufferSize(unsigned int bufferSize);
        unsigned int GetRxBufferSize();
        
        // Helper methods
#ifdef HAVE_IPV6
        static bool HostIsIPv6Capable();
        static bool SetHostIPv6Capable();
        bool SetFlowLabel(UINT32 label);
#endif //HAVE_IPV6
        bool GetInterfaceAddress(const char*        interfaceName, 
				                 ProtoAddress::Type addressType,
				                 ProtoAddress&      theAddress); 
        unsigned int GetInterfaceIndex(const char* interfaceName);
        bool GetInterfaceName(unsigned int index, char* buffer, unsigned int buflen);   
        bool GetInterfaceName(const ProtoAddress& ifAddr, char* buffer, unsigned int buflen);   

        // Asynchronous I/O notification stuff
        enum Flag
        {
            NOTIFY_NONE     = 0x00,
            NOTIFY_INPUT    = 0x01,
            NOTIFY_OUTPUT   = 0x02
        };
        class Notifier
        {
            public:
                virtual bool UpdateSocketNotification(ProtoSocket& theSocket, 
                                                      int          notifyFlags) {return true;} 
        };
        Notifier* GetNotifier() const {return notifier;}
        bool SetNotifier(ProtoSocket::Notifier* theNotifier);
        bool StartOutputNotification()
        {
            notify_output = true;
            notify_output = UpdateNotification();
            return notify_output;   
        }
        void StopOutputNotification()
        {
            notify_output = false;
            UpdateNotification();    
        }        
        bool OutputNotification() {return notify_output;}
        void OnNotify(ProtoSocket::Flag theFlag);
        
        enum Event {INVALID_EVENT, CONNECT, ACCEPT, SEND, RECV, DISCONNECT};
        
        // NOTE: For VC++ Debug builds "/ZI" or "/Z7" compile options must NOT be specified
        // (or else VC++ experiences an "internal compiler error")
        template <class listenerType>
        bool SetListener(listenerType* theListener, void(listenerType::*eventHandler)(ProtoSocket&, Event))
        {
            bool doUpdate = (NULL == listener);
            if (listener) delete listener;
            listener = theListener ? new LISTENER_TYPE<listenerType>(theListener, eventHandler) : NULL;
            bool result = theListener ? (NULL != theListener) : true;
            return result ? (doUpdate ? UpdateNotification() : true) : false;
        }
        
        bool HasListener() {return (NULL != listener);}
        void SetUserData(const void* userData) {user_data = userData;}
        const void* GetUserData() {return user_data;}
        
        // Here's a helper linked list class 
        class List
        {
            public:
                List();
                ~List();
                void Destroy();  // deletes list Items _and_ their socket
                
                bool AddSocket(ProtoSocket& theSocket);
                void RemoveSocket(ProtoSocket& theSocket);
            private:  
                class Item;
            public:
                class Iterator
                {
                    public:
                        Iterator(const List& theList);
                        const Item* GetNextItem()
                        {
                            const Item* current = next;
                            next = current ? current->GetNext() : current;
                            return current;   
                        }
                        ProtoSocket* GetNextSocket()
                        {
                            const Item* nextItem = GetNextItem();
                            return nextItem ? nextItem->GetSocket() : NULL;   
                        }
                        
                        
                    private:
                        const List&         list;
                        const class Item*   next;
                        
                };  // end class ProtoSocketList::Iterator
                friend class List::Iterator;
                
            private:
                class Item
                {
                    friend class List;
                    friend class Iterator;
                    public:
                        Item(ProtoSocket* theSocket);
                        ProtoSocket* GetSocket() const {return socket;}
                        
                    private:    
                        Item* GetPrev() const {return prev;}
                        void SetPrev(Item* item) {prev = item;}
                        Item* GetNext() const {return next;}
                        void SetNext(Item* item) {next = item;}
                        
                        ProtoSocket*    socket;
                        Item*           prev;
                        Item*           next;
                };   // end class ProtoSocketList::Item
                
                Item*   head;
        };  // end class ProtoSocketList
          
            
    private:
        class Listener
        {
            public:
                virtual void on_event(ProtoSocket& theSocket, Event theEvent) = 0;
        };
        template <class listenerType>
        class LISTENER_TYPE : public Listener
        {
            public:
                LISTENER_TYPE(listenerType* theListener, 
                              void(listenerType::*eventHandler)(ProtoSocket&, Event))
                    : listener(theListener), event_handler(eventHandler) {}
                void on_event(ProtoSocket& theSocket, Event theEvent)
                    {(listener->*event_handler)(theSocket, theEvent);}
            private:
                listenerType* listener;
                void(listenerType::*event_handler)(ProtoSocket&, Event);
        };
        
        bool UpdateNotification();
    
        Protocol                protocol;    
        State                   state;       
        Handle                  handle;      
        ProtoAddress::Type      address_type;
        int                     port;                              
#ifdef HAVE_IPV6
        UINT32                  flow_label; //IPv6 flow label      
#endif // HAVE_IPV6
        ProtoAddress            destination;                       
                
        Notifier*               notifier;                          
        bool                    notify_output;                     
#ifdef WIN32
        bool                    output_ready;
#endif // WIN32
        Listener*               listener;   
        
        const void*             user_data;
};  // end class ProtoSocket

#endif // _PROTO_SOCKET

