// 练习 2：群聊服务器。任何一个客户端发的消息，转发给其他所有客户端
#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 <set>
#include <string>

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

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("chat server listening on 8888\n");

    std::set<int> clients;  // 当前在线的所有连接
    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) {
                    int conn_fd = accept(listen_fd, nullptr, nullptr);
                    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);
                    clients.insert(conn_fd);
                    printf("user %d joined, %zu online\n", conn_fd, clients.size());
                }
            } else {
                ssize_t r = read(fd, buf, sizeof(buf));
                if (r > 0) {
                    // 在消息前面加上发送者的编号，再发给除了自己以外的所有人
                    std::string msg = "[user " + std::to_string(fd) + "] " + std::string(buf, r);
                    for (int other : clients) {
                        if (other != fd) write(other, msg.data(), msg.size());
                    }
                } else if (r == 0 || errno != EAGAIN) {
                    clients.erase(fd);
                    printf("user %d left, %zu online\n", fd, clients.size());
                    epoll_ctl(epfd, EPOLL_CTL_DEL, fd, nullptr);
                    close(fd);
                }
            }
        }
    }
}
