#ifndef TCPCALLBACKCONNECTION_H
#define TCPCALLBACKCONNECTION_H

#include "tcpconnection.h"

namespace mbed {

#define NO_SENT_FNC    ((err_t (*)(TCPCallbackConnection *, u16_t))NULL)
#define NO_RECV_FNC    ((err_t (*)(TCPCallbackConnection *, struct pbuf *, err_t))NULL)
#define NO_POLL_FNC    ((err_t (*)(TCPCallbackConnection *))NULL)
#define NO_ACCEPT_FNC  ((err_t (*)(TCPCallbackConnection *, struct tcp_pcb *, err_t))NULL)
#define NO_CONNECT_FNC ((err_t (*)(TCPCallbackConnection *, err_t))NULL)
#define NO_ERR_FNC      ((void (*)(TCPCallbackConnection *, err_t))NULL)


class TCPCallbackConnection : public TCPConnection {
  public:
    TCPCallbackConnection(struct ip_addr ip_addr, u16_t port,
       err_t (*psent)(TCPCallbackConnection *, u16_t),
       err_t (*precv)(TCPCallbackConnection *, struct pbuf *, err_t),
       err_t (*ppoll)(TCPCallbackConnection *),
       err_t (*pconnected)(TCPCallbackConnection *, err_t),
       void  (*perr )(TCPCallbackConnection *, err_t))
      : TCPConnection(ip_addr, port) {
      _sent = psent;
      _recv = precv;
      _poll = ppoll;
      _connected = pconnected;
      _err  = perr;
      _membercaller_sent = NULL;
      _membercaller_recv = NULL;
      _membercaller_poll = NULL;
      _membercaller_connected = NULL;
      _membercaller_err  = NULL;
    }

    TCPCallbackConnection(TCPListener *parent, struct tcp_pcb *npcb,
       err_t (*psent)(TCPCallbackConnection *, u16_t),
       err_t (*precv)(TCPCallbackConnection *, struct pbuf *, err_t),
       err_t (*ppoll)(TCPCallbackConnection *),
       err_t (*pconnected)(TCPCallbackConnection *, err_t),
       void  (*perr )(TCPCallbackConnection *, err_t))
      : TCPConnection(parent, npcb) {
      _sent = psent;
      _recv = precv;
      _poll = ppoll;
      _connected = pconnected;
      _err  = perr;
      _membercaller_sent = NULL;
      _membercaller_recv = NULL;
      _membercaller_poll = NULL;
      _membercaller_connected = NULL;
      _membercaller_err  = NULL;
    }

/*
    template<typename T>
    TCPCallbackConnection(NetServer *nnet, const struct ip_addr &ip_addr, const u16_t &port,
       T *o,
       err_t (T::*psent)(TCPCallbackConnection *, u16_t),
       err_t (T::*precv)(TCPCallbackConnection *, struct pbuf *p, err_t err),
       err_t (T::*ppoll)(TCPCallbackConnection *),
       err_t (T::*pconnected)(TCPCallbackConnection *, err_t),
       void  (T::*perr )(TCPCallbackConnection *, err_t))
      : TCPConnection(nnet, ip_addr, port) {
      _sent =  NULL; _recv =  NULL; _poll =  NULL; _connected = NULL; _err = NULL;
      _object_sent = _object_recv = _object_poll = 
      _object_connected = _object_err = static_cast<void *>(o);
      
      memcpy(_member_sent, (char *)&psent, sizeof(psent));
      memcpy(_member_recv, (char *)&precv, sizeof(precv));
      memcpy(_member_poll, (char *)&ppoll, sizeof(ppoll));
      memcpy(_member_connected, (char *)&pconnected, sizeof(pconnected));
      memcpy(_member_err, (char *)&perr, sizeof(perr));
      
      _membercaller_sent = &TCPCallbackConnection::_fsent<T>;
      _membercaller_recv = &TCPCallbackConnection::_frecv<T>;
      _membercaller_poll = &TCPCallbackConnection::_fpoll<T>;
      _membercaller_connected = &TCPCallbackConnection::_fconnected<T>;
      _membercaller_err  = &TCPCallbackConnection::_ferr<T>;
    }

    template<typename T_sent, typename T_recv, typename T_poll, typename T_accept, typename T_connected, typename T_err>
    TCPCallbackConnection(NetServer *nnet, const struct ip_addr &ip_addr, const u16_t &port,
       T_sent *osent,
       err_t (T_sent::*psent)(TCPCallbackConnection *, u16_t),
       T_recv *orecv,
       err_t (T_recv::*precv)(TCPCallbackConnection *, struct pbuf *p, err_t err),
       T_poll *opoll,
       err_t (T_poll::*ppoll)(TCPCallbackConnection *),
       T_connected *oconnected,
       err_t (T_connected::*pconnected)(TCPCallbackConnection *, err_t),
       T_err  *oerr,
       void  (T_err::*perr )(TCPCallbackConnection *, err_t))
      : TCPConnection(nnet, ip_addr, port) {
      _sent =  NULL; _recv =  NULL; _poll =  NULL; _connected = NULL; _err = NULL;
      
      _object_sent      = static_cast<void *>(osent);
      _object_recv      = static_cast<void *>(orecv);
      _object_poll      = static_cast<void *>(opoll);
      _object_connected = static_cast<void *>(oconnected);
      _object_err       = static_cast<void *>(oerr);
      
      memcpy(_member_sent,      (char *)&psent,   sizeof(psent));
      memcpy(_member_recv,      (char *)&precv,   sizeof(precv));
      memcpy(_member_poll,      (char *)&ppoll,   sizeof(ppoll));
      memcpy(_member_connected, (char *)&paccept, sizeof(paccept));
      memcpy(_member_err,       (char *)&perr,    sizeof(perr));
      
      _membercaller_sent      = &TCPCallbackConnection::_fsent<T_sent>;
      _membercaller_recv      = &TCPCallbackConnection::_frecv<T_recv>;
      _membercaller_poll      = &TCPCallbackConnection::_fpoll<T_poll>;
      _membercaller_connected = &TCPCallbackConnection::_fconnected<T_connected>;
      _membercaller_err       = &TCPCallbackConnection::_ferr<T_err>;
    }
*/
    /*
     * Function to be called when more send buffer space is available.
     * @param space the amount of bytes available
     * @return ERR_OK: try to send some data by calling tcp_output
     */
    virtual err_t sent(u16_t space) { 
      if(_sent) {
        return (_sent)(this, space);
      } else if(_membercaller_sent) {
        return (_membercaller_sent)(this, space);
      } else {
        return ERR_OK;
      }
    }
  
    /*
     * Function to be called when (in-sequence) data has arrived.
     * @param p the packet buffer which arrived
     * @param err an error argument (TODO: that is current always ERR_OK?)
     * @return ERR_OK: try to send some data by calling tcp_output
     */
    virtual err_t recv(struct pbuf *p, err_t err) { 
      if(_recv) {
        return (_recv)(this, p, err);
      } else if(_membercaller_recv) {
        return (_membercaller_recv)(this, p, err);
      } else {
        return ERR_OK;
      }
    }

    /*
     * Function which is called periodically.
     * The period can be adjusted in multiples of the TCP slow timer interval
     * by changing tcp_pcb.polltmr.
     * @return ERR_OK: try to send some data by calling tcp_output
     */
    virtual err_t poll() {
      if(_poll) {
        return (_poll)(this);
      } else if(_membercaller_poll) {
        return (_membercaller_poll)(this);
      } else {
        return ERR_OK;
      }
    }

    virtual err_t connected(err_t err) {
      err = TCPConnection::connected(err);
      if(_connected) {
        return (_connected)(this, err);
      } else if(_membercaller_connected) {
        return (_membercaller_connected)(this, err);
      } else {
        return ERR_OK;
      }
    }

    /*
     * Function to be called whenever a fatal error occurs.
     * There is no pcb parameter since most of the times, the pcb is
     * already deallocated (or there is no pcb) when this function is called.
     * @param err an indication why the error callback is called:
     *            ERR_ABRT: aborted through tcp_abort or by a TCP timer
     *            ERR_RST: the connection was reset by the remote host
     */
    virtual void err(err_t err) {
      if(_err) {
        (_err)(this, err);
      } else if(_membercaller_err) {
        (_membercaller_err)(this, err);
      }
    }

  private:
    template<typename T> static err_t _fsent(TCPCallbackConnection *me, u16_t space) {
      T* o = static_cast<T *>(me->_object_sent);
      err_t (T::*m)(TCPCallbackConnection *, u16_t);
      memcpy((char *)&m, me->_member_sent, sizeof(m));
      return (o->*m)(me, space);
    }
    
    template<typename T> static err_t _frecv(TCPCallbackConnection *me, struct pbuf *p, err_t err) {
      T* o = static_cast<T *>(me->_object_recv);
      err_t (T::*m)(TCPCallbackConnection *, struct pbuf *p, err_t err);
      memcpy((char *)&m, me->_member_recv, sizeof(m));
      return (o->*m)(me, p, err);
    }
    
    template<typename T> static err_t _fpoll(TCPCallbackConnection *me) {
      T* o = static_cast<T *>(me->_object_poll);
      err_t (T::*m)(TCPCallbackConnection *);
      memcpy((char *)&m, me->_member_poll, sizeof(m));
      return (o->*m)(me);
    }
    
    template<typename T> static err_t _faccept(TCPCallbackConnection *me, struct tcp_pcb *newpcb, err_t err) {
      T* o = static_cast<T *>(me->_object_accept);
      err_t (T::*m)(TCPCallbackConnection *, struct tcp_pcb *, err_t);
      memcpy((char *)&m, me->_member_accept, sizeof(m));
      return (o->*m)(me, newpcb, err);
    }

    template<typename T> static err_t _fconnected(TCPCallbackConnection *me, err_t err) {
      T* o = static_cast<T *>(me->_object_connected);
      err_t (T::*m)(TCPCallbackConnection *, err_t);
      memcpy((char *)&m, me->_member_connected, sizeof(m));
      return (o->*m)(me, err);
    }

    template<typename T> static void _ferr(TCPCallbackConnection *me, err_t) {
      T* o = static_cast<T *>(me->_object_err);
      err_t (T::*m)(TCPCallbackConnection *, err_t);
      memcpy((char *)&m, me->_member_err, sizeof(m));
      (o->*m)(me, err);
    }

    err_t (*_sent)(TCPCallbackConnection *, u16_t);
    err_t (*_recv)(TCPCallbackConnection *, struct pbuf *p, err_t err);
    err_t (*_poll)(TCPCallbackConnection *);
    err_t (*_accept)(TCPCallbackConnection *, struct tcp_pcb *newpcb, err_t err);
    err_t (*_connected)(TCPCallbackConnection *, err_t err);
    void  (*_err )(TCPCallbackConnection *, err_t);
    void *_object_sent;
    void *_object_recv;
    void *_object_poll;
    void *_object_accept;
    void *_object_connected;
    void *_object_err;
    char _member_sent[16];
    char _member_recv[16];
    char _member_poll[16];
    char _member_accept[16];
    char _member_connected[16];
    char _member_err[16];
    err_t (*_membercaller_sent)(TCPCallbackConnection *, u16_t);
    err_t (*_membercaller_recv)(TCPCallbackConnection *, struct pbuf *p, err_t err);
    err_t (*_membercaller_poll)(TCPCallbackConnection *);
    err_t (*_membercaller_accept)(TCPCallbackConnection *, struct tcp_pcb *newpcb, err_t err);
    err_t (*_membercaller_connected)(TCPCallbackConnection *, err_t err);
    void  (*_membercaller_err) (TCPCallbackConnection *, err_t err);
};
  
};

#endif /* TCPCALLBACKCONNECTION_H */