#define EFL_NET_SERVER_UDP_CLIENT_PROTECTED 1
#define EFL_IO_READER_PROTECTED 1
#define EFL_IO_WRITER_PROTECTED 1
#define EFL_IO_CLOSER_PROTECTED 1
#define EFL_NET_SOCKET_PROTECTED 1

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include "Ecore.h"
#include "Ecore_Con.h"
#include "ecore_con_private.h"

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

#define MY_CLASS EFL_NET_SERVER_UDP_CLIENT_CLASS

typedef struct _Efl_Net_Server_Udp_Client_Packet
{
   EINA_INLIST;
   Eina_Rw_Slice slice;
} Efl_Net_Server_Udp_Client_Packet;

static void
_efl_net_server_udp_client_packet_free(Efl_Net_Server_Udp_Client_Packet *pkt)
{
   free(pkt->slice.mem);
   free(pkt);
}

typedef struct _Efl_Net_Server_Udp_Client_Data
{
   Eina_Stringshare *address_local;
   Eina_Stringshare *address_remote;
   Eina_Inlist *packets;
   struct sockaddr *addr_remote;
   socklen_t addr_remote_len;
   SOCKET fd;
   Eina_Bool close_on_invalidate;
   Eina_Bool eos;
   Eina_Bool can_read;
   Eina_Bool can_write;
} Efl_Net_Server_Udp_Client_Data;

EOLIAN static Efl_Object *
_efl_net_server_udp_client_efl_object_finalize(Eo *o, Efl_Net_Server_Udp_Client_Data *pd)
{
   o = efl_finalize(efl_super(o, MY_CLASS));
   EINA_SAFETY_ON_NULL_RETURN_VAL(pd->addr_remote, NULL);

   return o;
}

static void
_efl_net_server_udp_client_cleanup(Efl_Net_Server_Udp_Client_Data *pd)
{
   Efl_Net_Server_Udp_Client_Packet *pkt;

   pd->fd = INVALID_SOCKET;
   EINA_INLIST_FREE(pd->packets, pkt)
     {
        pd->packets = eina_inlist_remove(pd->packets, EINA_INLIST_GET(pkt));
        _efl_net_server_udp_client_packet_free(pkt);
     }
}

EOLIAN static void
_efl_net_server_udp_client_efl_object_invalidate(Eo *o, Efl_Net_Server_Udp_Client_Data *pd EINA_UNUSED)
{
   if (efl_io_closer_close_on_invalidate_get(o) &&
       (!efl_io_closer_closed_get(o)))
     {
        efl_event_freeze(o);
        efl_io_closer_close(o);
        efl_event_thaw(o);
     }

   efl_invalidate(efl_super(o, MY_CLASS));
}

EOLIAN static void
_efl_net_server_udp_client_efl_object_destructor(Eo *o, Efl_Net_Server_Udp_Client_Data *pd)
{
   efl_destructor(efl_super(o, MY_CLASS));

   _efl_net_server_udp_client_cleanup(pd);

   eina_stringshare_replace(&pd->address_local, NULL);
   eina_stringshare_replace(&pd->address_remote, NULL);
   free(pd->addr_remote);
   pd->addr_remote = NULL;
   pd->addr_remote_len = 0;
}

void
_efl_net_server_udp_client_init(Eo *o, SOCKET fd, const struct sockaddr *addr, socklen_t addrlen, const char *str)
{
   Efl_Net_Server_Udp_Client_Data *pd = efl_data_scope_get(o, MY_CLASS);
   pd->fd = fd;
   pd->addr_remote = malloc(addrlen);
   EINA_SAFETY_ON_NULL_RETURN(pd->addr_remote);
   memcpy(pd->addr_remote, addr, addrlen);
   pd->addr_remote_len = addrlen;
   efl_net_socket_address_remote_set(o, str);
}

void
_efl_net_server_udp_client_feed(Eo *o, Eina_Rw_Slice slice)
{
   Efl_Net_Server_Udp_Client_Data *pd = efl_data_scope_get(o, MY_CLASS);
   Efl_Net_Server_Udp_Client_Packet *pkt;

   pkt = malloc(sizeof(Efl_Net_Server_Udp_Client_Packet));
   EINA_SAFETY_ON_NULL_GOTO(pkt, error);
   pkt->slice = slice;
   pd->packets = eina_inlist_append(pd->packets, EINA_INLIST_GET(pkt));

   efl_io_reader_can_read_set(o, EINA_TRUE);
   return;

 error:
   free(slice.mem);
}

EOLIAN static size_t
_efl_net_server_udp_client_next_datagram_size_query(Eo *o EINA_UNUSED, Efl_Net_Server_Udp_Client_Data *pd)
{
   Efl_Net_Server_Udp_Client_Packet *pkt;

   if (!pd->packets) return 0;
   pkt = EINA_INLIST_CONTAINER_GET(pd->packets, Efl_Net_Server_Udp_Client_Packet);
   return pkt->slice.len;
}

EOLIAN static Eina_Error
_efl_net_server_udp_client_efl_io_closer_close(Eo *o, Efl_Net_Server_Udp_Client_Data *pd)
{
   EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_io_closer_closed_get(o), EBADF);

   efl_io_writer_can_write_set(o, EINA_FALSE);
   efl_io_reader_can_read_set(o, EINA_FALSE);
   efl_io_reader_eos_set(o, EINA_TRUE);

   _efl_net_server_udp_client_cleanup(pd);

   efl_event_callback_call(o, EFL_IO_CLOSER_EVENT_CLOSED, NULL);

   return 0;
}

EOLIAN static Eina_Error
_efl_net_server_udp_client_efl_io_reader_read(Eo *o, Efl_Net_Server_Udp_Client_Data *pd, Eina_Rw_Slice *rw_slice)
{
   Efl_Net_Server_Udp_Client_Packet *pkt;

   EINA_SAFETY_ON_NULL_RETURN_VAL(rw_slice, EINVAL);

   if (!pd->packets)
     {
        rw_slice->len = 0;
        rw_slice->mem = NULL;
        efl_io_reader_can_read_set(o, EINA_FALSE);
        return EAGAIN;
     }

   pkt = EINA_INLIST_CONTAINER_GET(pd->packets, Efl_Net_Server_Udp_Client_Packet);
   pd->packets = eina_inlist_remove(pd->packets, pd->packets);

   *rw_slice = eina_rw_slice_copy(*rw_slice, eina_rw_slice_slice_get(pkt->slice));
   _efl_net_server_udp_client_packet_free(pkt);
   efl_io_reader_can_read_set(o, !!pd->packets);

   return 0;
}

EOLIAN static Eina_Error
_efl_net_server_udp_client_efl_io_writer_write(Eo *o, Efl_Net_Server_Udp_Client_Data *pd, Eina_Slice *ro_slice, Eina_Slice *remaining)
{
   ssize_t r;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ro_slice, EINVAL);
   if (pd->fd == INVALID_SOCKET) goto error;

   do
     {
        r = sendto(pd->fd, ro_slice->mem, ro_slice->len, 0, pd->addr_remote, pd->addr_remote_len);
        if (r < 0)
          {
             Eina_Error err = efl_net_socket_error_get();

             if (err == EINTR) continue;

             if (remaining) *remaining = *ro_slice;
             ro_slice->len = 0;
             ro_slice->mem = NULL;
             efl_io_writer_can_write_set(o, EINA_FALSE);
             return err;
          }
     }
   while (r < 0);

   if (remaining)
     {
        remaining->len = ro_slice->len - r;
        remaining->bytes = ro_slice->bytes + r;
     }
   ro_slice->len = r;

   return 0;

 error:
   if (remaining) *remaining = *ro_slice;
   ro_slice->len = 0;
   ro_slice->mem = NULL;
   efl_io_writer_can_write_set(o, EINA_FALSE);
   return EINVAL;
}

EOLIAN static Eina_Bool
_efl_net_server_udp_client_efl_io_reader_can_read_get(const Eo *o EINA_UNUSED, Efl_Net_Server_Udp_Client_Data *pd)
{
   return pd->can_read;
}

EOLIAN static void
_efl_net_server_udp_client_efl_io_reader_can_read_set(Eo *o, Efl_Net_Server_Udp_Client_Data *pd, Eina_Bool can_read)
{
   EINA_SAFETY_ON_TRUE_RETURN(pd->fd == INVALID_SOCKET);
   if (pd->can_read == can_read) return;
   pd->can_read = can_read;
   efl_event_callback_call(o, EFL_IO_READER_EVENT_CAN_READ_CHANGED, &can_read);
}

EOLIAN static Eina_Bool
_efl_net_server_udp_client_efl_io_reader_eos_get(const Eo *o EINA_UNUSED, Efl_Net_Server_Udp_Client_Data *pd)
{
   return pd->eos;
}

EOLIAN static void
_efl_net_server_udp_client_efl_io_reader_eos_set(Eo *o, Efl_Net_Server_Udp_Client_Data *pd, Eina_Bool is_eos)
{
   EINA_SAFETY_ON_TRUE_RETURN(pd->fd == INVALID_SOCKET);
   if (pd->eos == is_eos) return;
   pd->eos = is_eos;
   if (is_eos)
     efl_event_callback_call(o, EFL_IO_READER_EVENT_EOS, NULL);
}

EOLIAN static Eina_Bool
_efl_net_server_udp_client_efl_io_writer_can_write_get(const Eo *o EINA_UNUSED, Efl_Net_Server_Udp_Client_Data *pd)
{
   return pd->can_write;
}

EOLIAN static void
_efl_net_server_udp_client_efl_io_writer_can_write_set(Eo *o, Efl_Net_Server_Udp_Client_Data *pd, Eina_Bool can_write)
{
   EINA_SAFETY_ON_TRUE_RETURN(pd->fd == INVALID_SOCKET);
   if (pd->can_write == can_write) return;
   pd->can_write = can_write;
   efl_event_callback_call(o, EFL_IO_WRITER_EVENT_CAN_WRITE_CHANGED, &can_write);
}

EOLIAN static Eina_Bool
_efl_net_server_udp_client_efl_io_closer_closed_get(const Eo *o EINA_UNUSED, Efl_Net_Server_Udp_Client_Data *pd)
{
   return pd->fd == INVALID_SOCKET;
}

EOLIAN static void
_efl_net_server_udp_client_efl_io_closer_close_on_invalidate_set(Eo *o EINA_UNUSED, Efl_Net_Server_Udp_Client_Data *pd, Eina_Bool close_on_invalidate)
{
   pd->close_on_invalidate = close_on_invalidate;
}

EOLIAN static Eina_Bool
_efl_net_server_udp_client_efl_io_closer_close_on_invalidate_get(const Eo *o EINA_UNUSED, Efl_Net_Server_Udp_Client_Data *pd)
{
   return pd->close_on_invalidate;
}

EOLIAN static Eina_Bool
_efl_net_server_udp_client_efl_io_closer_close_on_exec_set(Eo *o, Efl_Net_Server_Udp_Client_Data *pd EINA_UNUSED, Eina_Bool close_on_exec)
{
   if (efl_net_server_fd_close_on_exec_get(efl_parent_get(o)) != close_on_exec)
     {
        ERR("Efl.Net.Server.Udp.Client close-on-exec must be the same as the server setting, no file descriptor is created for each client!");
        return EINA_FALSE;
     }

   return EINA_TRUE;
}

EOLIAN static Eina_Bool
_efl_net_server_udp_client_efl_io_closer_close_on_exec_get(const Eo *o, Efl_Net_Server_Udp_Client_Data *pd EINA_UNUSED)
{
   return efl_net_server_fd_close_on_exec_get(efl_parent_get(o));
}

EOLIAN static void
_efl_net_server_udp_client_efl_net_socket_address_local_set(Eo *o EINA_UNUSED, Efl_Net_Server_Udp_Client_Data *pd, const char *address)
{
   eina_stringshare_replace(&pd->address_local, address);
}

EOLIAN static const char *
_efl_net_server_udp_client_efl_net_socket_address_local_get(const Eo *o EINA_UNUSED, Efl_Net_Server_Udp_Client_Data *pd)
{
   return pd->address_local;
}

EOLIAN static void
_efl_net_server_udp_client_efl_net_socket_address_remote_set(Eo *o EINA_UNUSED, Efl_Net_Server_Udp_Client_Data *pd, const char *address)
{
   eina_stringshare_replace(&pd->address_remote, address);
}

EOLIAN static const char *
_efl_net_server_udp_client_efl_net_socket_address_remote_get(const Eo *o EINA_UNUSED, Efl_Net_Server_Udp_Client_Data *pd)
{
   return pd->address_remote;
}

#include "efl_net_server_udp_client.eo.c"
