/*
This is a simple server that users io_uring in blocking mode.
It demonstrates the bare minimum needed to use io_uring.
It uses liburing for simplicity.
*/


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

#include <unistd.h>

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

#include <liburing.h>

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 );

	async_read( newsock );

	while(1) {
		struct io_uring_cqe * cqe;
		struct __kernel_timespec ts = { 2, 0 };
		// int ret = io_uring_wait_cqes( &ring, &cqe, 1, &ts, NULL); // Requires Linux 5.4
		int ret = io_uring_wait_cqe( &ring, &cqe );

		if( ret < 0 ) {
                  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 - 1, 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;
}