#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/sendfile.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

/* ****************************************************************** */
int create_listener(char *host, char *port) {

    int fd;

    struct addrinfo hints;
    struct addrinfo *result, *rp;
    int s;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    hints.ai_protocol = 0;

    if((s = getaddrinfo(host, port, &hints, &result))) {
        printf("getaddrinfo: %s\n", gai_strerror(s));
        return(-1);
    }

    for(rp=result; rp; rp=rp->ai_next) {
        if(0>(fd=socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol))) {
            printf("socket\n");
            continue;
        }

        int yes=1;
        if(0>setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int))) {
            printf("setsockopt\n");
            continue;
        }

        if(0>bind(fd, rp->ai_addr, rp->ai_addrlen)) {
            printf("could not bind socket to address\n");
            close(fd);
            continue;
        }
        break;
    }

    if(!rp) {               /* No address succeeded */
        fprintf(stderr, "Could not connect\n");
        return(-1);
    }

    freeaddrinfo(result);           /* No longer needed */

    /* set socket to listen for incoming connections */
    /* allow a queue of 5 */
    if(0>listen(fd, 5)) {
	printf("listen error\n");
	return(-1);
    }

    return(fd);
}

/* ****************************************************************** */
int do_use_fd(int fd_in, off_t *offset, size_t *size_left, int fd_out) {

    ssize_t n;

    if(-1==(n=sendfile(fd_out, fd_in, offset, *size_left))) {
        printf("Sendfile: failed: %s (%d)\n", strerror(errno), errno);
        return(-1);
    }
    if(!n) {
        printf("Sendfile: short send: %s (%d)\n", strerror(errno), errno);
        return(-2);
    }
    *size_left-=n;

    return(!(*size_left));
}

/* ****************************************************************** */
int main(int argc, char **argv) {

    char *host;
    char *port;
    char *fname;
    int fd_in;
    int n;
    int r;

    off_t offset;
    size_t size_left;
    struct stat st;

    struct sockaddr_in local;
    socklen_t addrlen;

    if(argc!=4) {
        printf("Usage: %s bind_addr bind_port file_name\n", argv[0]);
        return(1);
    }

    host=argv[1];
    port=argv[2];
    fname=argv[3];

#define MAX_EVENTS 10
    struct epoll_event ev, events[MAX_EVENTS];
    int listen_sock, conn_sock, nfds, epollfd;

    /* Set up listening socket, 'listen_sock' (socket(),
     bind(), listen()) */

    if(-1==(listen_sock=create_listener(host, port))) {
        printf("create_listener(): failed\n");
        return(1);
    }


    epollfd = epoll_create(10);
    if (epollfd == -1) {
        printf("epoll_create\n");
        return(2);
    }

    ev.events = EPOLLIN;
    ev.data.fd = listen_sock;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
        printf("epoll_ctl: listen_sock\n");
        return(3);
    }

    for (;;) {
        nfds = epoll_wait(epollfd, events, MAX_EVENTS, 5000);
        printf("epoll event: nfds: %d\n", nfds);
        if (nfds == -1) {
            printf("epoll_pwait\n");
            return(4);
        }

        for (n = 0; n < nfds; ++n) {
            if (events[n].data.fd == listen_sock) {

                conn_sock = accept(listen_sock,
                                   (struct sockaddr *) &local, &addrlen);
                if (conn_sock == -1) {
                    printf("accept\n");
                    return(5);
                }

                fcntl(conn_sock, F_SETFL, O_NONBLOCK);

                int yes=1;
                if(0>setsockopt(conn_sock, SOL_TCP, TCP_NODELAY, &yes, sizeof(int))) {
                    printf("setsockopt\n");
                    continue;
                }
                if(0>setsockopt(conn_sock, SOL_TCP, TCP_CORK, &yes, sizeof(int))) {
                    printf("setsockopt\n");
                    return(6);
                }

                ev.events = EPOLLIN | EPOLLOUT | EPOLLET | EPOLLRDHUP;
                //ev.events = EPOLLIN | EPOLLOUT;
                ev.data.fd = conn_sock;
                printf("epoll_ctl(add): events: %08X\n", ev.events);
                if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
                              &ev) == -1) {
                    printf("epoll_ctl: conn_sock\n");
                    return(7);
                }

                if(-1==(fd_in=open(fname, O_RDONLY)) || fstat(fd_in, &st)) {
                    printf("%s: %s (%d)\n", fname, strerror(errno), errno);
                    return(8);
                }
                offset=0;
                size_left=st.st_size;

            } else {
                if(events[n].events & (EPOLLHUP|EPOLLRDHUP)) {
                    // Disconnect
                    printf("disconnect\n");
                    close(events[n].data.fd);
                    close(fd_in);
                } else if(events[n].events & (EPOLLIN)) {
                    // Input
                    printf("got input, not supported\n");
                } else if(events[n].events & (EPOLLOUT)) {
                    // Output
                    printf("can output: events: %08X\n", events[n].events);

                    if(0<(r=do_use_fd(fd_in, &offset, &size_left, events[n].data.fd))) {
                        // Finished
                        printf("Finished: sent: %d, left: %d\n", offset, size_left);
                        close(events[n].data.fd);
                        close(fd_in);
                    } else if(!n) {
                        // Need more send
                        printf("Need more send: sent: %d, left: %d\n", offset, size_left);
                    } else {
                        // Error
                        printf("Sending file failed\n");
                        return(9);
                    }
                } else {
                    // Other
                }
            }
        }
    }
}
