#ifndef _VIP_SOCKETMULTIPLEXER_HH_
#define _VIP_SOCKETMULTIPLEXER_HH_

#include <vos/vip/vipdefs.hh>

#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif

#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif

#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <deque>

#include <boost/thread/thread.hpp>
#include <boost/thread/condition.hpp>

#include <vos/vutil/structpack.hh>
#include <vos/vutil/refcount.hh>

#include <vos/vip/connection.hh>

namespace VIP {
    struct SockaddrWrapper
    {
        sockaddr_in sa;
        SockaddrWrapper(struct sockaddr_in* si)
            {
                memcpy(&sa, si, sizeof(sockaddr_in));
            }
        SockaddrWrapper(struct sockaddr_in& si)
            {
                memcpy(&sa, &si, sizeof(sockaddr_in));
            }
        SockaddrWrapper(const SockaddrWrapper& sw)
            {
                memcpy(&sa, &sw.sa, sizeof(sockaddr_in));
            }
    };

    class SockaddrCmp
    {
    public:
        inline bool operator()(const SockaddrWrapper& a,
                               const SockaddrWrapper& b) const
            {
                if(ntohl(a.sa.sin_addr.s_addr) > ntohl(b.sa.sin_addr.s_addr)) {
                    return true;
                } else {
                    if(ntohl(a.sa.sin_addr.s_addr) < ntohl(b.sa.sin_addr.s_addr)) {
                        return false;
                    } else {
                        return (ntohs(a.sa.sin_port) > ntohs(b.sa.sin_port));
                    }
                }
            }
    };

    struct SocketProcessorThread;

    class VIP_API NewConnectionCallback
    {
    public:
        virtual ~NewConnectionCallback() { }

        virtual void notifyNewConnection(VIP::Connection* m, bool inbound) = 0;
    };

    /** @class PortBindingError socketmultiplexer.hh vos/vip/socketmultiplexer.hh
        @ingroup libvip
    */
    class VIP_API PortBindingError : public std::runtime_error
    {
    public:
        PortBindingError(const std::string& s) : std::runtime_error(s) { };
    };

    /** @class HostnameError socketmultiplexer.hh vos/vip/socketmultiplexer.hh
        @ingroup libvip
    */
    class VIP_API HostnameError : public std::runtime_error
    {
    public:
        HostnameError(const std::string& s) : std::runtime_error(s) { };
    };

    class VIP_API SocketMultiplexer : public VUtil::RefCounted
    {
    private:
        int skt;
        boost::mutex connections_mutex;
        std::map<SockaddrWrapper, VUtil::vRef<Connection>, SockaddrCmp> connections;
        NewConnectionCallback* callback;

        void setupNewConnection(uint8_t* buf, int sz,
                                struct sockaddr_in* from);

    public:
        SocketMultiplexer(short int port);
        VUtil::vRef<Connection> makeConnection(const std::string& hostname,
                                               uint16_t port);

        void setCallback(NewConnectionCallback* cb) { callback = cb; }

        void handlePacket(uint8_t* buf, int ret, sockaddr_in* from);

        int getSocket() { return skt; }

        void removeConnection(struct sockaddr_in* c);
    };
}

#endif
