#include "wx/dlimpexp.h" /* for WXDLLIMPEXP_NET */
 
+class WXDLLIMPEXP_FWD_NET wxSocketBase;
+
 #include <stddef.h>
 
 /*
 class GSocketBase
 {
 public:
-    // static factory function
-    static GSocket *Create();
+    // static factory function: creates the low-level socket associated with
+    // the given wxSocket (and inherits its attributes such as timeout)
+    static GSocket *Create(wxSocketBase& wxsocket);
 
+    void SetTimeout(unsigned long millisec);
     virtual ~GSocketBase();
 
     GSocketEventFlags Select(GSocketEventFlags flags);
 
+    virtual GSocket *WaitConnection(wxSocketBase& wxsocket) = 0;
+
     virtual void Close() = 0;
     virtual void Shutdown();
 
 #endif
 
     GSocketEventFlags m_detected;
-    GSocketCallback m_cbacks[GSOCK_MAX_EVENT];
-    char *m_data[GSOCK_MAX_EVENT];
 
 protected:
-    GSocketBase();
+    GSocketBase(wxSocketBase& wxsocket);
+
+    // notify m_wxsocket
+    void NotifyOnStateChange(GSocketEvent event);
+
+private:
+    // set in ctor and never changed except that it's reset to NULL when the
+    // socket is shut down
+    wxSocketBase *m_wxsocket;
+
+    DECLARE_NO_COPY_CLASS(GSocketBase)
 };
 
 #if defined(__WINDOWS__)
 
 class GSocket : public GSocketBase
 {
 public:
-  GSocket() : GSocketBase() { m_msgnumber = 0; }
+    GSocket::GSocket(wxSocketBase& wxsocket)
+        : GSocketBase(wxsocket)
+    {
+        m_msgnumber = 0;
+    }
+
+    virtual void Close();
+
+    virtual GSocket *WaitConnection(wxSocketBase& wxsocket);
 
-  virtual void Close();
 
   GSocketError SetLocal(GAddress *address);
   GSocketError SetPeer(GAddress *address);
   GAddress *GetLocal();
   GAddress *GetPeer();
   GSocketError SetServer();
-  GSocket *WaitConnection();
+
   // not used under MSW
   void Notify(bool) { }
   bool SetReusable();
   int Read(char *buffer, int size);
   int Write(const char *buffer, int size);
   void SetNonBlocking(bool non_block);
-  void SetTimeout(unsigned long millis);
   GSocketError WXDLLIMPEXP_NET GetError();
-  void SetCallback(GSocketEventFlags flags,
-    GSocketCallback callback, char *cdata);
-  void UnsetCallback(GSocketEventFlags flags);
   GSocketError GetSockOpt(int level, int optname,
     void *optval, int *optlen);
   GSocketError SetSockOpt(int level, int optname,
 
 class GSocket : public GSocketBase
 {
 public:
-    GSocket();
-    ~GSocket();
+    GSocket(wxSocketBase& wxsocket);
+    virtual ~GSocket();
+
     virtual void Close();
     virtual void Shutdown();
+    virtual GSocket *WaitConnection(wxSocketBase& wxsocket);
+
     GSocketError SetLocal(GAddress *address);
     GSocketError SetPeer(GAddress *address);
     GAddress *GetLocal();
     GAddress *GetPeer();
     GSocketError SetServer();
-    GSocket *WaitConnection();
     bool SetReusable();
     bool SetBroadcast();
     bool DontDoBind();
     int Read(char *buffer, int size);
     int Write(const char *buffer, int size);
     void SetNonBlocking(bool non_block);
-    void SetTimeout(unsigned long millisec);
     GSocketError WXDLLIMPEXP_NET GetError();
-    void SetCallback(GSocketEventFlags flags,
-        GSocketCallback callback, char *cdata);
-    void UnsetCallback(GSocketEventFlags flags);
     GSocketError GetSockOpt(int level, int optname, void *optval, int *optlen);
     GSocketError SetSockOpt(int level, int optname,
         const void *optval, int optlen);
 
   // pointer for storing extra (usually GUI-specific) data
   void *m_gui_dependent;
+
+private:
+    // notify the associated wxSocket about a change in socket state and shut
+    // down the socket if the event is GSOCK_LOST
+    void OnStateChange(GSocketEvent event);
 };
 
 // A version of GSocketManager which uses FDs for socket IO
 
 // ==========================================================================
 
 /* static */
-GSocket *GSocketBase::Create()
+GSocket *GSocketBase::Create(wxSocketBase& wxsocket)
 {
-    GSocket * const newsocket = new GSocket();
+    GSocket * const newsocket = new GSocket(wxsocket);
     if ( !GSocketManager::Get()->Init_Socket(newsocket) )
     {
         delete newsocket;
     return newsocket;
 }
 
-GSocketBase::GSocketBase()
+GSocketBase::GSocketBase(wxSocketBase& wxsocket)
+    : m_wxsocket(&wxsocket)
 {
     m_fd              = INVALID_SOCKET;
     m_detected        = 0;
     m_server          = false;
     m_stream          = true;
     m_non_blocking    = false;
-#ifdef __WINDOWS__
-    m_timeout.tv_sec  = 10 * 60;  /* 10 minutes */
-    m_timeout.tv_usec = 0;
-#else
-    m_timeout         = 10*60*1000; /* 10 minutes * 60 sec * 1000 millisec */
-#endif
+
+    SetTimeout(wxsocket.GetTimeout() * 1000);
 
     m_establishing    = false;
     m_reusable        = false;
     m_dobind          = true;
     m_initialRecvBufferSize = -1;
     m_initialSendBufferSize = -1;
-
-    for ( int i = 0; i < GSOCK_MAX_EVENT; i++ )
-        m_cbacks[i] = NULL;
 }
 
 GSocketBase::~GSocketBase()
         Close();
     }
 
-    /* Disable GUI callbacks */
-    for ( int evt = 0; evt < GSOCK_MAX_EVENT; evt++ )
-        m_cbacks[evt] = NULL;
-
     m_detected = GSOCK_LOST_FLAG;
 }
 
+/* GSocket_SetTimeout:
+ *  Sets the timeout for blocking calls. Time is expressed in
+ *  milliseconds.
+ */
+void GSocketBase::SetTimeout(unsigned long millis)
+{
+#ifdef __WXMSW__
+    m_timeout.tv_sec  = (millis / 1000);
+    m_timeout.tv_usec = (millis % 1000) * 1000;
+#else
+    m_timeout = millis;
+#endif
+}
+
+void GSocketBase::NotifyOnStateChange(GSocketEvent event)
+{
+    // GSocketEvent and wxSocketNotify enums have the same elements with the
+    // same values
+    m_wxsocket->OnRequest(static_cast<wxSocketNotify>(event));
+}
+
 // ==========================================================================
 // wxSocketBase
 // ==========================================================================
     InterruptWait();
 
     if (m_socket)
-    {
-        // Disable callbacks
-        m_socket->UnsetCallback(
-            GSOCK_INPUT_FLAG |
-            GSOCK_OUTPUT_FLAG |
-            GSOCK_LOST_FLAG |
-            GSOCK_CONNECTION_FLAG
-        );
-
-        // Shutdown the connection
         m_socket->Shutdown();
-    }
 
     m_connected = false;
     m_establishing = false;
 {
     wxLogTrace( wxTRACE_Socket, _T("Opening wxSocketServer") );
 
-    m_socket = GSocket::Create();
+    m_socket = GSocket::Create(*this);
 
     if (!m_socket)
     {
         return;
     }
 
-    m_socket->SetTimeout(m_timeout * 1000);
-    m_socket->SetCallback(GSOCK_INPUT_FLAG | GSOCK_OUTPUT_FLAG |
-                                  GSOCK_LOST_FLAG | GSOCK_CONNECTION_FLAG,
-                                  wx_socket_callback, (char *)this);
-
     wxLogTrace( wxTRACE_Socket, _T("wxSocketServer on fd %d"), m_socket->m_fd );
 }
 
     // When we are finished, we put the socket to blocking mode
     // again.
     wxSocketUnblocker unblock(m_socket, !wait);
-    GSocket * const child_socket = m_socket->WaitConnection();
+    sock.m_socket = m_socket->WaitConnection(sock);
 
-    if (!child_socket)
+    if ( !sock.m_socket )
         return false;
 
     sock.m_type = wxSOCKET_BASE;
-    sock.m_socket = child_socket;
     sock.m_connected = true;
 
-    sock.m_socket->SetTimeout(sock.m_timeout * 1000);
-    sock.m_socket->SetCallback(GSOCK_INPUT_FLAG | GSOCK_OUTPUT_FLAG |
-            GSOCK_LOST_FLAG | GSOCK_CONNECTION_FLAG,
-            wx_socket_callback, (char *)&sock);
-
     return true;
 }
 
         delete m_socket;
     }
 
-    m_socket = GSocket::Create();
+    m_socket = GSocket::Create(*this);
     m_connected = false;
     m_establishing = false;
 
     if (!m_socket)
         return false;
 
-    m_socket->SetTimeout(m_timeout * 1000);
-    m_socket->SetCallback(
-        GSOCK_INPUT_FLAG |
-        GSOCK_OUTPUT_FLAG |
-        GSOCK_LOST_FLAG |
-        GSOCK_CONNECTION_FLAG,
-        wx_socket_callback,
-        (char *)this
-    );
-
     // If wait == false, then the call should be nonblocking. When we are
     // finished, we put the socket to blocking mode again.
     wxSocketUnblocker unblock(m_socket, !wait);
                 : wxSocketBase( flags, wxSOCKET_DATAGRAM )
 {
     // Create the socket
-    m_socket = GSocket::Create();
+    m_socket = GSocket::Create(*this);
 
     if (!m_socket)
-    {
-        wxFAIL_MSG( _T("datagram socket not new'd") );
         return;
-    }
+
     m_socket->Notify(m_notify);
     // Setup the socket as non connection oriented
     m_socket->SetLocal(addr.GetAddress());
     // Initialize all stuff
     m_connected = false;
     m_establishing = false;
-    m_socket->SetTimeout( m_timeout * 1000 );
-    m_socket->SetCallback( GSOCK_INPUT_FLAG | GSOCK_OUTPUT_FLAG |
-                           GSOCK_LOST_FLAG | GSOCK_CONNECTION_FLAG,
-                           wx_socket_callback, (char*)this );
 }
 
 wxDatagramSocket& wxDatagramSocket::RecvFrom( wxSockAddress& addr,
 
  *    GSOCK_MEMERR     - couldn't allocate memory.
  *    GSOCK_IOERR      - low-level error.
  */
-GSocket *GSocket::WaitConnection()
+GSocket *GSocket::WaitConnection(wxSocketBase& wxsocket)
 {
   GSocket *connection;
   wxSockAddr from;
   }
 
   /* Create a GSocket object for the new connection */
-  connection = GSocket::Create();
+  connection = GSocket::Create(wxsocket);
 
   if (!connection)
   {
   m_non_blocking = non_block;
 }
 
-/* GSocket_SetTimeout:
- *  Sets the timeout for blocking calls. Time is expressed in
- *  milliseconds.
- */
-void GSocket::SetTimeout(unsigned long millis)
-{
-  m_timeout.tv_sec  = (millis / 1000);
-  m_timeout.tv_usec = (millis % 1000) * 1000;
-}
-
 /* GSocket_GetError:
  *  Returns the last error occurred for this socket. Note that successful
  *  operations do not clear this back to GSOCK_NOERROR, so use it only
   return m_error;
 }
 
-/* Callbacks */
-
-/* GSOCK_INPUT:
- *   There is data to be read in the input buffer. If, after a read
- *   operation, there is still data available, the callback function will
- *   be called again.
- * GSOCK_OUTPUT:
- *   The socket is available for writing. That is, the next write call
- *   won't block. This event is generated only once, when the connection is
- *   first established, and then only if a call failed with GSOCK_WOULDBLOCK,
- *   when the output buffer empties again. This means that the app should
- *   assume that it can write since the first OUTPUT event, and no more
- *   OUTPUT events will be generated unless an error occurs.
- * GSOCK_CONNECTION:
- *   Connection successfully established, for client sockets, or incoming
- *   client connection, for server sockets. Wait for this event (also watch
- *   out for GSOCK_LOST) after you issue a nonblocking GSocket_Connect() call.
- * GSOCK_LOST:
- *   The connection is lost (or a connection request failed); this could
- *   be due to a failure, or due to the peer closing it gracefully.
- */
-
-/* GSocket_SetCallback:
- *  Enables the callbacks specified by 'flags'. Note that 'flags'
- *  may be a combination of flags OR'ed toghether, so the same
- *  callback function can be made to accept different events.
- *  The callback function must have the following prototype:
- *
- *  void function(GSocket *socket, GSocketEvent event, char *cdata)
- */
-void GSocket::SetCallback(GSocketEventFlags flags,
-                         GSocketCallback callback, char *cdata)
-{
-  int count;
-
-  for (count = 0; count < GSOCK_MAX_EVENT; count++)
-  {
-    if ((flags & (1 << count)) != 0)
-    {
-      m_cbacks[count] = callback;
-      m_data[count] = cdata;
-    }
-  }
-}
-
-/* GSocket_UnsetCallback:
- *  Disables all callbacks specified by 'flags', which may be a
- *  combination of flags OR'ed toghether.
- */
-void GSocket::UnsetCallback(GSocketEventFlags flags)
-{
-  int count;
-
-  for (count = 0; count < GSOCK_MAX_EVENT; count++)
-  {
-    if ((flags & (1 << count)) != 0)
-    {
-      m_cbacks[count] = NULL;
-      m_data[count] = NULL;
-    }
-  }
-}
-
 GSocketError GSocket::GetSockOpt(int level, int optname,
                                 void *optval, int *optlen)
 {
 
 
 /* Constructors / Destructors for GSocket */
 
-GSocket::GSocket()
+GSocket::GSocket(wxSocketBase& wxsocket)
+    : GSocketBase(wxsocket)
 {
   m_handler             = NULL;
 
  *    GSOCK_MEMERR     - couldn't allocate memory.
  *    GSOCK_IOERR      - low-level error.
  */
-GSocket *GSocket::WaitConnection()
+GSocket *GSocket::WaitConnection(wxSocketBase& wxsocket)
 {
   wxSockAddr from;
   WX_SOCKLEN_T fromlen = sizeof(from);
   }
 
   /* Create a GSocket object for the new connection */
-  connection = GSocket::Create();
+  connection = GSocket::Create(wxsocket);
 
   if (!connection)
   {
   m_non_blocking = non_block;
 }
 
-/* GSocket_SetTimeout:
- *  Sets the timeout for blocking calls. Time is expressed in
- *  milliseconds.
- */
-void GSocket::SetTimeout(unsigned long millisec)
-{
-  assert(this);
-
-  m_timeout = millisec;
-}
-
 /* GSocket_GetError:
  *  Returns the last error occurred for this socket. Note that successful
  *  operations do not clear this back to GSOCK_NOERROR, so use it only
   return m_error;
 }
 
-/* Callbacks */
-
-/* GSOCK_INPUT:
- *   There is data to be read in the input buffer. If, after a read
- *   operation, there is still data available, the callback function will
- *   be called again.
- * GSOCK_OUTPUT:
- *   The socket is available for writing. That is, the next write call
- *   won't block. This event is generated only once, when the connection is
- *   first established, and then only if a call failed with GSOCK_WOULDBLOCK,
- *   when the output buffer empties again. This means that the app should
- *   assume that it can write since the first OUTPUT event, and no more
- *   OUTPUT events will be generated unless an error occurs.
- * GSOCK_CONNECTION:
- *   Connection successfully established, for client sockets, or incoming
- *   client connection, for server sockets. Wait for this event (also watch
- *   out for GSOCK_LOST) after you issue a nonblocking GSocket_Connect() call.
- * GSOCK_LOST:
- *   The connection is lost (or a connection request failed); this could
- *   be due to a failure, or due to the peer closing it gracefully.
- */
-
-/* GSocket_SetCallback:
- *  Enables the callbacks specified by 'flags'. Note that 'flags'
- *  may be a combination of flags OR'ed toghether, so the same
- *  callback function can be made to accept different events.
- *  The callback function must have the following prototype:
- *
- *  void function(GSocket *socket, GSocketEvent event, char *cdata)
- */
-void GSocket::SetCallback(GSocketEventFlags flags,
-                         GSocketCallback callback, char *cdata)
-{
-  int count;
-
-  assert(this);
-
-  for (count = 0; count < GSOCK_MAX_EVENT; count++)
-  {
-    if ((flags & (1 << count)) != 0)
-    {
-      m_cbacks[count] = callback;
-      m_data[count] = cdata;
-    }
-  }
-}
-
-/* GSocket_UnsetCallback:
- *  Disables all callbacks specified by 'flags', which may be a
- *  combination of flags OR'ed toghether.
- */
-void GSocket::UnsetCallback(GSocketEventFlags flags)
-{
-  int count;
-
-  assert(this);
-
-  for (count = 0; count < GSOCK_MAX_EVENT; count++)
-  {
-    if ((flags & (1 << count)) != 0)
-    {
-      m_cbacks[count] = NULL;
-      m_data[count] = NULL;
-    }
-  }
-}
-
 GSocketError GSocket::GetSockOpt(int level, int optname,
                                 void *optval, int *optlen)
 {
     return GSOCK_OPTERR;
 }
 
-#define CALL_CALLBACK(socket, event) {                                  \
-  socket->Disable(event);                                               \
-  if (socket->m_cbacks[event])                                          \
-    socket->m_cbacks[event](socket, event, socket->m_data[event]);      \
-}
-
-
 void GSocket::Enable(GSocketEvent event)
 {
     if (m_use_events)
   return ret;
 }
 
+void GSocket::OnStateChange(GSocketEvent event)
+{
+    Disable(event);
+    NotifyOnStateChange(event);
+
+    if ( event == GSOCK_LOST )
+        Shutdown();
+}
+
 void GSocket::Detected_Read()
 {
   char c;
   {
     m_establishing = false;
 
-    CALL_CALLBACK(this, GSOCK_LOST);
-    Shutdown();
+    OnStateChange(GSOCK_LOST);
     return;
   }
 
 
   if (num > 0)
   {
-    CALL_CALLBACK(this, GSOCK_INPUT);
+    OnStateChange(GSOCK_INPUT);
   }
   else
   {
     if (m_server && m_stream)
     {
-      CALL_CALLBACK(this, GSOCK_CONNECTION);
+      OnStateChange(GSOCK_CONNECTION);
     }
     else if (num == 0)
     {
       if (m_stream)
       {
         /* graceful shutdown */
-        CALL_CALLBACK(this, GSOCK_LOST);
-        Shutdown();
+        OnStateChange(GSOCK_LOST);
       }
       else
       {
         /* Empty datagram received */
-        CALL_CALLBACK(this, GSOCK_INPUT);
+        OnStateChange(GSOCK_INPUT);
       }
     }
     else
       /* Do not throw a lost event in cases where the socket isn't really lost */
       if ((errno == EWOULDBLOCK) || (errno == EAGAIN) || (errno == EINTR))
       {
-        CALL_CALLBACK(this, GSOCK_INPUT);
+        OnStateChange(GSOCK_INPUT);
       }
       else
       {
-        CALL_CALLBACK(this, GSOCK_LOST);
-        Shutdown();
+        OnStateChange(GSOCK_LOST);
       }
     }
   }
   {
     m_establishing = false;
 
-    CALL_CALLBACK(this, GSOCK_LOST);
-    Shutdown();
+    OnStateChange(GSOCK_LOST);
     return;
   }
 
 
     if (error)
     {
-      CALL_CALLBACK(this, GSOCK_LOST);
-      Shutdown();
+      OnStateChange(GSOCK_LOST);
     }
     else
     {
-      CALL_CALLBACK(this, GSOCK_CONNECTION);
+      OnStateChange(GSOCK_CONNECTION);
       /* We have to fire this event by hand because CONNECTION (for clients)
        * and OUTPUT are internally the same and we just disabled CONNECTION
        * events with the above macro.
        */
-      CALL_CALLBACK(this, GSOCK_OUTPUT);
+      OnStateChange(GSOCK_OUTPUT);
     }
   }
   else
   {
-    CALL_CALLBACK(this, GSOCK_OUTPUT);
+    OnStateChange(GSOCK_OUTPUT);
   }
 }