// 练习 1：在 epoll echo 的基础上，打印每个客户端的 IP 和端口
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>

#include <cerrno>
#include <cstdio>
#include <map>
#include <string>

static void set_nonblock(int fd) {
    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
}

// 把 sockaddr_in 转成 "127.0.0.1:41844" 这样的字符串
static std::string to_ip_port(const sockaddr_in& a) {
    char ip[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &a.sin_addr, ip, sizeof(ip));  // 二进制 IP -> 点分十进制字符串
    return std::string(ip) + ":" + std::to_string(ntohs(a.sin_port));  // 端口转回主机字节序
}

int main() {
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    int opt = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(8888);
    if (bind(listen_fd, (sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); return 1; }
    listen(listen_fd, 1024);
    set_nonblock(listen_fd);

    int epfd = epoll_create1(0);
    epoll_event ev{};
    ev.events = EPOLLIN;
    ev.data.fd = listen_fd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);
    printf("listening on 8888\n");

    std::map<int, std::string> peers;  // fd -> "ip:port"，断开时还要打印
    epoll_event events[1024];
    char buf[4096];
    while (true) {
        int n = epoll_wait(epfd, events, 1024, -1);
        for (int i = 0; i < n; ++i) {
            int fd = events[i].data.fd;
            if (fd == listen_fd) {
                while (true) {
                    sockaddr_in peer{};
                    socklen_t len = sizeof(peer);  // 必须先设成结构体的大小，accept 会改写它
                    int conn_fd = accept(listen_fd, (sockaddr*)&peer, &len);
                    if (conn_fd < 0) break;
                    set_nonblock(conn_fd);
                    epoll_event cev{};
                    cev.events = EPOLLIN;
                    cev.data.fd = conn_fd;
                    epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &cev);
                    peers[conn_fd] = to_ip_port(peer);
                    printf("new client %s fd=%d\n", peers[conn_fd].c_str(), conn_fd);
                }
            } else {
                ssize_t r = read(fd, buf, sizeof(buf));
                if (r > 0) {
                    write(fd, buf, r);
                } else if (r == 0 || errno != EAGAIN) {
                    printf("client %s fd=%d closed\n", peers[fd].c_str(), fd);
                    peers.erase(fd);
                    epoll_ctl(epfd, EPOLL_CTL_DEL, fd, nullptr);
                    close(fd);
                }
            }
        }
    }
}
