/*
 * © Copyright 1996-2012 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include "mars.h"

int readudp(void* p, void* buf, int len) {
    udpinfo* info    = (udpinfo*)p;
    socklen_t sinlen = sizeof(struct sockaddr_in);
    int done         = 0;

    while (!done) {
        fd_set fds;
        struct timeval* t = &info->timeout;

        if (t->tv_sec == 0 && t->tv_usec == 0)
            t = NULL;

        FD_ZERO(&fds);
        FD_SET(info->soc, &fds);

        switch (select(FD_SETSIZE, &fds, NULL, NULL, t)) {
            case 0:
                /* marslog(LOG_EROR,"readudp timeout"); */
                /* time out */
                return -1;
                /* break; */

            case -1:
                if (errno != EINTR) {
                    marslog(LOG_EROR | LOG_PERR, "select");
                    return -1;
                }
                break;

            default:
                done = 1;
                break;
        }
    }

    len = recvfrom(info->soc, buf, len, 0, (struct sockaddr*)&info->sin, &sinlen);
    /* print_address("recvfrom",&info->sin); */
    return len;
}

int writeudp(void* p, void* buf, int len) {
    udpinfo* info = (udpinfo*)p;
    int n         = sendto(info->soc, buf, len, 0, (struct sockaddr*)&info->sin,
                           sizeof(struct sockaddr_in));
    /* print_address("sendto",&info->sin); */
    if (n != len)
        marslog(LOG_EROR | LOG_PERR, "sendto failed");
    return n;
}

int udp_socket(int port) {

    int flg;
    int s;

#ifdef SO_LINGER
    struct linger ling;
#endif
    struct sockaddr_in sin;

    s = socket(AF_INET, SOCK_DGRAM, 0);

    if (s < 0) {
        marslog(LOG_EROR | LOG_PERR, "socket");
        return -1;
    }

    flg = 1;
    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &flg, sizeof(flg)) < 0)
        marslog(LOG_WARN | LOG_PERR, "setsockopt SO_REUSEADDR");

        /*
            flg = 1 ;
            if(setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &flg, sizeof(flg))<0)
                marslog(LOG_WARN|LOG_PERR,"setsockopt SO_KEEPALIVE");
        */

#ifdef SO_REUSEPORT
    flg = 1;
    if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &flg, sizeof(flg)) < 0)
        marslog(LOG_WARN | LOG_PERR, "setsockopt SO_REUSEPORT");
#endif

#ifdef SO_LINGER
    ling.l_onoff  = 0;
    ling.l_linger = 0;
    if (setsockopt(s, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)) < 0)
        marslog(LOG_WARN | LOG_PERR, "setsockopt SO_LINGER");
#else
#ifdef SO_DONTLINGER
    if (setsockopt(s, SOL_SOCKET, SO_DONTLINGER, NULL, 0) < 0)
        marslog(LOG_WARN | LOG_PERR, "setsockopt SO_DONTLINGER");
#endif
#endif


    bzero(&sin, sizeof(struct sockaddr_in));
    sin.sin_port        = htons(port);
    sin.sin_family      = AF_INET;
    sin.sin_addr.s_addr = INADDR_ANY;

    while (bind(s, (struct sockaddr*)&sin, sizeof(struct sockaddr_in)) == -1) {
        marslog(LOG_WARN | LOG_PERR, "bind port = %d", port);
        sleep(5);
    }

    return s;
}

udpinfo* udp_server(int port) {
    udpinfo* info = NEW(udpinfo);
    info->soc     = udp_socket(port);
    if (info->soc < 0) {
        FREE(info);
        return NULL;
    }
    bzero(&info->sin, sizeof(struct sockaddr_in));
    info->timeout.tv_sec  = 0;
    info->timeout.tv_usec = 0;
    return info;
}

udpinfo* udp_call(const char* host, int port) {
#ifdef INADDR_NONE

    in_addr_t addr;
    in_addr_t none = INADDR_NONE;

#else

#ifdef __alpha
    unsigned int addr;
    unsigned int none = (unsigned int)~0;
#elif defined(fujitsu)
    u_int addr;
    u_int none = (u_int)~0;
#else
    unsigned long addr;
    unsigned long none = (unsigned long)-1;
#endif
#endif
    struct hostent* him;
    udpinfo* info = NEW(udpinfo);

    /* create a socket first */

    info->soc = udp_socket(0);
    if (info->soc < 0) {
        FREE(info);
        return NULL;
    }

    bzero(&info->sin, sizeof(struct sockaddr_in));

    info->sin.sin_port   = htons(port);
    info->sin.sin_family = AF_INET;

    marslog(LOG_DBUG, "Calling \"%s\" port %d", host, port);

    addr                      = inet_addr(host);
    info->sin.sin_addr.s_addr = addr;

    if (addr == none) {
        if ((him = gethostbyname(host)) == NULL) {
            marslog(LOG_EROR, "unknown host : %s", host);
            close(info->soc);
            FREE(info);
            return NULL;
        }
        info->sin.sin_family = him->h_addrtype;
        bcopy(him->h_addr_list[0], &info->sin.sin_addr, him->h_length);
    }

    info->timeout.tv_sec  = 2;
    info->timeout.tv_usec = 0; /* 1/2 sec */

    return info;
}

void udp_free(udpinfo* info) {
    close(info->soc);
    FREE(info);
}
