/*
Similar to the server in servier.c, this is a simple server
that instead uses epoll to block.
It opens the door to have several polling user-thread per cluster.
It uses liburing for simplicity.
*/


#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>

#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include <liburing.h>

#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];

struct io_uring ring;

char data[256];
struct iovec iov = { data, 256 };
struct msghdr msg = { (void *)"", 0, &iov, 1, NULL, 0, 0 };
static void async_read(int sock) {
	/* get an sqe and fill in a READ operation */
      struct io_uring_sqe * sqe = io_uring_get_sqe(&ring);
      io_uring_prep_recvmsg(sqe, sock, &msg, 0);
      sqe->user_data = 0;

      /* tell the kernel we have an sqe ready for consumption */
      int ret = io_uring_submit(&ring);
      assert(ret == 1);
}

int main(int argc, char *argv[]) {
	if(argc != 2) {
            printf("usage:    %s portnumber\n", argv[0]);
            exit( EXIT_FAILURE );
      }
      int port = atoi(argv[1]);
      if(port < 1) {
            printf("Invalid port : %d (from %s)\n", port, argv[1]);
            exit( EXIT_FAILURE );
      }

	int sock = socket(AF_INET, SOCK_STREAM, 0);
	if(sock < 0) {
		perror( "socket" );
		exit( EXIT_FAILURE );
	}

	struct sockaddr_in serv_addr;
      memset(&serv_addr, 0, sizeof(serv_addr));
      serv_addr.sin_family = AF_INET;
      serv_addr.sin_addr.s_addr = INADDR_ANY;
      serv_addr.sin_port = htons(port);

	int ret = bind(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr));
	if(ret < 0) {
		perror( "bind" );
		exit( EXIT_FAILURE );
	}


     	listen(sock,1);

	struct sockaddr_in cli_addr;
     	__socklen_t clilen = sizeof(cli_addr);
	int newsock = accept(sock, (struct sockaddr *) &cli_addr, &clilen);
     	if (newsock < 0) {
		perror( "accept" );
		exit( EXIT_FAILURE );
	}

	io_uring_queue_init( 16, &ring, 0 );

      int epollfd = epoll_create1(0);
      if (epollfd == -1) {
            perror("epoll_create1");
            exit(EXIT_FAILURE);
      }

      ev.events = EPOLLOUT | EPOLLIN | EPOLLONESHOT;
      ev.data.u64 = (uint64_t)&ring;
      if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ring.ring_fd, &ev) == -1) {
            perror("epoll_ctl: first");
            exit(EXIT_FAILURE);
      }


	async_read( newsock );

	while(1) {
            BLOCK:;
            int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
            if (nfds == -1) {
                  perror("epoll_wait");
                  exit(EXIT_FAILURE);
            }


		while(1) {
                  struct io_uring_cqe * cqe;
                  int ret = io_uring_peek_cqe( &ring, &cqe );

                  if( ret < 0 ) {
                        if(-ret == EAGAIN) {
                              if (epoll_ctl(epollfd, EPOLL_CTL_MOD, ring.ring_fd, &ev) == -1) {
                                    perror("epoll_ctl: loop");
                                    exit(EXIT_FAILURE);
                              }
                              goto BLOCK;
                        }
                        printf( "Main Loop Error : %s\n", strerror(-ret) );
                        close( sock );
                        exit( EXIT_FAILURE );
                  }

                  switch(cqe->user_data) {
                        // Read completed
                        case 0:
                              // If it is the end of file we are done
                              if( cqe->res == 0 ) {
                                    goto END;
                              }

                              if( cqe->res < 0 ) {
                                    perror( "Main Loop Error" );
                                    close( sock );
                                    exit( EXIT_FAILURE );
                              }

                              printf("'%.*s'\n", cqe->res, data);

                              async_read( newsock );

                              // otherwise prepare a new read
                              break;
                        // Wait timed out, time to print
                        // Requires Linux 5.4
                        case LIBURING_UDATA_TIMEOUT:
                              printf(".");
                              break;
                        // Problem
                        default:
                              printf("Unexpected user data : %llu", cqe->user_data);
                              exit( EXIT_FAILURE );
                  }

                  io_uring_cqe_seen( &ring, cqe );
            }
	}
END:

	io_uring_queue_exit( &ring );

	ret = close(newsock);
      if(ret < 0) {
            perror( "close new" );
            exit( EXIT_FAILURE );
      }

	ret = close(sock);
      if(ret < 0) {
            perror( "close old" );
            exit( EXIT_FAILURE );
      }

	return 0;
}