/**
 * @file simp_httpd_m.c
 * @author maxwell@void
 * @version 0.2
 * @date 2024-07-12
 *
 * @copyright Copyright (c) 2024
 * 功能：
 * 1. 实现一个简单的HTTP服务器，可以处理GET请求，并返回指定文件的内容。
 * 2. 支持命令行参数，可以指定端口号和目录。
 * 3. 支持线程池，可以处理多个客户端连接。
 * 4. 支持SIGPIPE信号，可以忽略客户端断开连接时产生的错误。
 * 5. 这个版本支持连接池和持久连接
 * 编译：gcc -o sim_httpd_m simp_httpd_m.c -lpthread
 * 程序的流程是：
 * 1. 解析命令行参数，获取端口号和目录；如果没有指定，则使用默认目录和端口号
 * 2. 改变当前工作目录，使得服务器可以读取指定目录下的文件
 * 3. 忽略 SIGPIPE 信号，忽略客户端断开连接时产生的错误。否则会导致程序崩溃
 * 4. 初始化线程池，创建线程，并设置互斥锁、条件变量、任务队列、任务数量、关闭标志
 * 5. 创建服务器套接字，绑定到指定端口，监听连接
 * 6. 接受并处理客户端连接，向线程池添加任务
 * 7. 关闭服务器套接字，关闭线程池
 * 8. 线程池中的线程会从任务队列中获取任务，并处理任务，并通知其他线程
 * 9. 线程池中的线程在shutdown标志为1时退出循环
 * 10. 线程池中的线程在接收到SIGINT、SIGTERM信号时，会关闭线程池，并等待其他线程退出
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>
#include <fcntl.h>
#include <jansson.h>

#define BUFFER_SIZE 1024          // 缓冲区大小
#define THREAD_POOL_SIZE 10       // 线程池大小
#define CONFIG_FILE "config.json" // 配置文件名

// 定义处理客户端连接的函数
void handle_client(int client_socket);
// 定义向客户端发送文件内容的函数
void send_file(int client_socket, const char *filename);

// 定义线程池相关函数
void *thread_worker(void *arg);
int read_config(int *port, char **directory, char *config_file);
int create_default_config(char *config_file);
// 线程池结构
typedef struct
{
    pthread_t threads[THREAD_POOL_SIZE]; // 线程数组
    pthread_mutex_t lock;                // 互斥锁
    pthread_cond_t cond;                 // 条件变量
    int client_sockets[BUFFER_SIZE];     // 客户端套接字数组
    int head, tail;                      // 头尾指针
    int count;                           // 任务数量
    int shutdown;                        // 关闭标志
} thread_pool_t;

// 全局线程池变量
thread_pool_t pool;

/**
 * @brief
 * 初始化线程池
 * 初始化线程池，创建线程，并设置互斥锁、条件变量、任务队列、任务数量、关闭标志
 */
void init_thread_pool()
{
    pthread_mutex_init(&pool.lock, NULL);   // 初始化互斥锁
    pthread_cond_init(&pool.cond, NULL);    // 初始化条件变量
    pool.head = pool.tail = pool.count = 0; // 初始化头尾指针和任务数量
    pool.shutdown = 0;                      // 初始化关闭标志
    // 创建线程
    for (int i = 0; i < THREAD_POOL_SIZE; i++)
    {
        pthread_create(&pool.threads[i], NULL, thread_worker, NULL);
    }
}

/**
 * @brief
 * 向线程池添加任务
 * 向线程池中添加一个任务，任务是客户端套接字
 * 线程池中的线程会从任务队列中获取任务，并处理任务
 * @param client_socket
 * 客户端套接字
 * @return void
 */
void add_task(int client_socket)
{
    pthread_mutex_lock(&pool.lock);                 // 加锁
    pool.client_sockets[pool.tail] = client_socket; // 添加任务
    pool.tail = (pool.tail + 1) % BUFFER_SIZE;      // 更新尾指针
    pool.count++;                                   // 任务数量加1
    pthread_cond_signal(&pool.cond);                // 通知线程
    pthread_mutex_unlock(&pool.lock);               // 解锁
}

/**
 * @brief 线程工作函数
 * 线程工作函数, 循环处理任务
 * 线程池中的线程调用这个函数，等待任务，处理任务，并通知其他线程
 * 线程池中的线程在shutdown标志为1时退出循环
 * 线程池中的线程在接收到SIGINT、SIGTERM信号时，会关闭线程池，并等待其他线程退出
 * @param arg
 * @return void*
 */
void *thread_worker(void *arg)
{
    (void)arg; // 没有用到arg参数，但为了保持接口一致性，这里保留
    while (1)
    {
        pthread_mutex_lock(&pool.lock); // 加锁
        while (pool.count == 0 && !pool.shutdown)
        {                                              // 等待任务
            pthread_cond_wait(&pool.cond, &pool.lock); // 等待条件变量
        }
        // 关闭线程池
        if (pool.shutdown)
        {
            pthread_mutex_unlock(&pool.lock);
            break;
        }
        // 处理任务
        int client_socket = pool.client_sockets[pool.head]; // 获取任务
        pool.head = (pool.head + 1) % BUFFER_SIZE;          // 更新头指针
        pool.count--;                                       // 任务数量减1
        pthread_mutex_unlock(&pool.lock);                   //   解锁
        handle_client(client_socket);                       // 处理客户端连接
        close(client_socket);                               // 关闭客户端套接字
    }
    return NULL;
}

/**
 * @brief
 * 关闭线程池
 * 关闭线程池，设置关闭标志，通知所有线程，等待线程退出
 */
void shutdown_thread_pool()
{
    pthread_mutex_lock(&pool.lock);     // 加锁
    pool.shutdown = 1;                  // 设置关闭标志
    pthread_cond_broadcast(&pool.cond); // 通知所有线程
    pthread_mutex_unlock(&pool.lock);   // 解锁
    // 等待线程退出
    for (int i = 0; i < THREAD_POOL_SIZE; i++)
    {
        pthread_join(pool.threads[i], NULL);
    }
}

/**
 * @brief
 * 读取配置文件。
 * 读取默认配置文件时，如果不存在，则创建默认配置文件。
 * @param port
 * @param directory
 * @param config_file
 * @return int
 */
int read_config(int *port, char **directory, char *config_file)
{
    // 判断config_file是否为json格式
    if (strstr(config_file, ".json") == NULL)
    {
        fprintf(stderr, "Config file is not a json file\n");
        return -1;
    }
    // 读取配置文件
    json_error_t error;
    json_t *root = json_load_file(config_file, 0, &error);
    if (!root)
    {
        fprintf(stderr, "Config file  failed: %s\n", error.text);
        return -1;
    }
    json_t *port_json = json_object_get(root, "port");
    if (!json_is_integer(port_json))
    {
        fprintf(stderr, "port is not an integer\n");
        json_decref(root);
        return -1;
    }
    *port = json_integer_value(port_json);

    json_t *directory_json = json_object_get(root, "directory");
    if (!json_is_string(directory_json))
    {
        fprintf(stderr, "directory is not a string\n");
        json_decref(root);
        return -1;
    }
    *directory = strdup(json_string_value(directory_json));

    json_decref(root);
    return 0;
}

/**
 * @brief 
 * 
 */
int create_default_config(char *config_file)
{
    json_t *root = json_object();
    json_object_set_new(root, "port", json_integer(8080));
    json_object_set_new(root, "directory", json_string("."));

    // 写入配置文件
    if (json_dump_file(root, config_file, JSON_INDENT(4)) < 0)
    {
        perror(" Failed to creat json file. \n");
        json_decref(root);
        return -1;
    }; 
    json_decref(root); // 释放内存
    return 0;
}


/**
 * @brief
 * 主函数
 * 解析命令行参数，创建服务器套接字，绑定到指定端口，监听连接，接受并处理客户端连接
 * @param argc
 * 程序参数个数
 * @param argv
 * 程序参数数组
 * @return int
 * 返回值
 */

int main(int argc, char *argv[])
{
    int server_socket, client_socket;                // 服务器套接字、客户端套接字
    struct sockaddr_in server_addr, client_addr;     // 服务器地址、客户端地址
    socklen_t client_addr_len = sizeof(client_addr); // 客户端地址长度
    int port = 8080;                                    // 默认端口
    char *directory = ".";                          // 默认目录
    char *config_file = NULL;                        // 配置文件路径
    // 如果不存在config.json,则创建

    if (access(CONFIG_FILE, F_OK) == -1)
    {
        if (create_default_config(CONFIG_FILE) == -1)
        {
            perror("Create default config file failed. \n");
        }
        printf("Create a default config file. \n");
    }
    // 解析命令行参数
    // 如果有-c 参数，则忽略其他参数，读取配置文件
    // 如果没有-c 参数，则读取命令行参数
    // 如果没有命令行参数，则读取默认配置文件
    // 如果没有默认配置文件，则创建默认配置文件
    int opt;
    while ((opt = getopt(argc, argv, "p:d:c:")) != -1)
    {
        switch (opt)
        {
        case 'p':
            port = atoi(optarg);
            break;
        case 'd':
            directory = optarg;
            break;
        case 'c':
            config_file = optarg;
            break;
        default:
            fprintf(stderr, "Usage: %s [-p port] [-d directory]\n", argv[0]);
            exit(EXIT_FAILURE);
        }
    }
    // 如果指定了配置文件或什么参数都没有，则读取配置文件
    if (optind < argc || config_file != NULL)
    {
        if (read_config(&port, &directory, config_file) == -1){
            perror("Read config file failed");
            exit(EXIT_FAILURE);
        }
    }

    // 改变当前工作目录，目的在于使得服务器可以读取指定目录下的文件
    if (chdir(directory) != 0)
    {
        perror("chdir failed");
        exit(EXIT_FAILURE);
    }

    // 忽略 SIGPIPE 信号，目的在于忽略客户端断开连接时产生的错误
    signal(SIGPIPE, SIG_IGN);

    // 初始化线程池，目的在于处理多个客户端连接
    init_thread_pool();

    // 创建服务器套接字，目的在于监听客户端连接
    server_socket = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字, TCP, IPv4, 0表示默认协议
    if (server_socket == -1)
    {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }
    // For MacOS only,判断系统是否为MacOS

#ifdef __APPLE__
    // 创建SO_REUSEADDR套接字选项，使得服务器可以重用端口号
    int optval = 1;
    if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
    {
        perror("setsockopt failed");
        exit(EXIT_FAILURE);
    }
#endif

    // 设置服务器地址
    server_addr.sin_family = AF_INET;         // IPv4
    server_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有地址。如果监听本地地址，可以改为INADDR_LOOPBACK
    server_addr.sin_port = htons(port);       // 端口号

    // 绑定套接字到指定端口,目的在于监听客户端连接
    if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)
    { // 绑定地址, 地址长度
        perror("Bind failed");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    // 监听连接,10表示最大连接数
    if (listen(server_socket, 10) == -1)
    {
        perror("Listen failed");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    printf("Server is listening on port %d, serving directory %s...\n", port, directory);

    // 接受并处理客户端连接
    while (1)
    {
        client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_len); // 接受连接, 地址, 地址长度
        if (client_socket == -1)
        {
            perror("Accept failed");
            continue;
        }
        printf("Client connected: %s\n", inet_ntoa(client_addr.sin_addr));
        add_task(client_socket); // 向线程池添加任务
    }

    // 关闭服务器套接字
    close(server_socket);

    // 关闭线程池
    shutdown_thread_pool();

    return 0;
}

/**
 * @brief
 * 处理客户端连接
 * 处理客户端连接，接收请求，解析请求，发送响应
 * @param client_socket
 * 客户端套接字
 */
void handle_client(int client_socket)
{
    char buffer[BUFFER_SIZE]; // 缓冲区
    int received;             // 接收到的字节数
    while ((received = recv(client_socket, buffer, BUFFER_SIZE - 1, 0)) > 0)
    {                            // 接收请求, 缓冲区, 缓冲区大小, 0表示不等待
        buffer[received] = '\0'; // 结尾加上'\0'
        printf("Request: %s\n", buffer);

        // 简单解析HTTP GET请求
        char method[16], path[256], protocol[16];           // 方法、路径、协议
        sscanf(buffer, "%s %s %s", method, path, protocol); // 解析请求
        // 判断是否为GET请求
        if (strcmp(method, "GET") == 0)
        {
            // 去掉路径中的'/'
            if (path[0] == '/')
            {
                memmove(path, path + 1, strlen(path));
            }
            send_file(client_socket, path); // 发送文件内容
        }
        else
        {
            const char *response = "HTTP/1.1 405 Method Not Allowed\r\n\r\n";
            send(client_socket, response, strlen(response), 0); // 发送响应
        }

        // 检查是否为持久连接, 如果是持久连接，则继续接收请求，否则断开连接
        if (strstr(buffer, "Connection: keep-alive") == NULL)
        {
            break; // 非持久连接，断开
        }
    }
}

/**
 * @brief
 * 向客户端发送文件内容
 * 向客户端发送文件内容，读取文件内容，并发送给客户端
 * 发送响应头，包括Content-Length，并发送响应内容
 * 关闭客户端套接字
 * @param client_socket
 * @param filename
 */
void send_file(int client_socket, const char *filename)
{
    // 判断filename的长度,如果大于256,则返回414 Request-URI Too Long
    if (strlen(filename) > 256)
    {
        const char *response = "HTTP/1.1 414 Request-URI Too Long\r\n\r\n";
        send(client_socket, response, strlen(response), 0);
        close(client_socket);
        return;
    }
    // 判断filename是否为空,如果为空,则返回index.html
    if (strlen(filename) == 0)
    {
        filename = "index.html";
    }
    // 判断filename是否合法,如果不合法,则返回403 Forbidden
    if (filename[0] == '.' || strstr(filename, "..") != NULL)
    {
        const char *response = "HTTP/1.1 403 Forbidden\r\n\r\n";
        send(client_socket, response, strlen(response), 0);
        close(client_socket);
        return;
    }
    // 打开文件,如果文件不存在,则返回404 Not Found
    FILE *file = fopen(filename, "rb");
    if (file == NULL)
    {
        const char *response = "HTTP/1.1 404 Not Found\r\n\r\n";
        send(client_socket, response, strlen(response), 0);
        close(client_socket);
        return;
    }
    // 发送响应头,包括Content-Length
    fseek(file, 0, SEEK_END);     //     定位到文件末尾
    long file_size = ftell(file); // 获取文件大小
    fseek(file, 0, SEEK_SET);     //     定位到文件开头

    char header[BUFFER_SIZE]; // 响应头
    snprintf(header, BUFFER_SIZE, "HTTP/1.1 200 OK\r\nContent-Length: %ld\r\n\r\n", file_size);
    send(client_socket, header, strlen(header), 0);

    // 发送响应内容
    char file_buffer[BUFFER_SIZE]; // 文件缓冲区
    size_t bytes_read;             // 读取字节数
    // 循环读取文件内容，并发送给客户端
    while ((bytes_read = fread(file_buffer, 1, BUFFER_SIZE, file)) > 0)
    { // 读取文件内容, 缓冲区, 缓冲区大小, 文件
        if (send(client_socket, file_buffer, bytes_read, 0) == -1)
        { // 发送响应内容, 字节数, 0表示不等待
            perror("Send failed");
            break;
        }
    }

    fclose(file);
}
