#include <arpa/inet.h>   // 提供网络地址转换函数
#include <errno.h>       // 定义错误码
#include <fcntl.h>       // 文件操作相关函数
#include <netinet/in.h>  // 定义网络协议的结构体和函数
#include <signal.h>      // 提供信号处理函数
#include <stdio.h>       // 标准输入输出
#include <stdlib.h>      // 标准库函数，如exit()
#include <string.h>      // 字符串操作函数
#include <sys/socket.h>  // 套接字相关函数
#include <sys/time.h>    // 提供 gettimeofday 函数
#include <sys/types.h>   // 定义数据类型
#include <time.h>        // 时间相关函数
#include <unistd.h>      // 提供POSIX API，如read()、write()

#define VERSION 23     // 服务器版本号
#define BUFSIZE 8096   // 缓冲区大小
#define ERROR 42       // 错误日志类型
#define LOG 44         // 普通日志类型
#define FORBIDDEN 403  // HTTP状态码：禁止访问
#define NOTFOUND 404   // HTTP状态码：未找到资源

#ifndef SIGCLD
#define SIGCLD SIGCHLD  // 为兼容不同系统定义信号别名
#endif

// 支持的文件扩展名及其对应的MIME类型
struct {
    char *ext;       // 文件扩展名
    char *filetype;  // MIME类型
} extensions[] = {
    {"gif", "image/gif"},
    {"jpg", "image/jpg"},
    {"jpeg", "image/jpeg"},
    {"png", "image/png"},
    {"ico", "image/ico"},
    {"zip", "image/zip"},
    {"gz", "image/gz"},
    {"tar", "image/tar"},
    {"htm", "text/html"},
    {"html", "text/html"},
    {0, 0}  // 结束标志
};

/* Ctrl+C信号处理函数 */
void ctrlc_handler(int sig)
{
    printf("您按下了Ctrl+C终止了Web服务器\n");
    exit(0);
}

// 计算两个时间点之间的时间差（以毫秒为单位）
long calculate_time_diff(struct timespec start, struct timespec end) {
    long diff_sec = end.tv_sec - start.tv_sec;       // 秒部分差值
    long diff_nsec = end.tv_nsec - start.tv_nsec;    // 纳秒部分差值
    return diff_sec * 1000 + diff_nsec / 1000000.0;  // 转换为毫秒
}

void logger(int type, const char *s1, const char *s2, int socket_fd) {
    int fd;
    char logbuffer[BUFSIZE * 2];
    char timebuffer[64];  // 用于存储格式化时间字符串

    // 获取当前时间（秒和微秒）
    struct timeval tv;
    gettimeofday(&tv, NULL);

    // 将秒部分格式化为字符串（年月日 时分秒）
    struct tm *tm_info = localtime(&tv.tv_sec);
    strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", tm_info);

    // 添加毫秒部分到时间字符串
    char full_timebuffer[256];
    snprintf(full_timebuffer, sizeof(full_timebuffer), "[%s.%03ld]", timebuffer, tv.tv_usec / 1000);

    // 根据日志类型生成日志内容
    switch (type) {
        case ERROR:  // 错误日志
            snprintf(logbuffer, sizeof(logbuffer), "%s [ERROR] %s:%s Errno=%d exiting pid=%d",
                     full_timebuffer, s1, s2, errno, getpid());
            break;
        case FORBIDDEN:  // 403 禁止访问
            if (write(socket_fd,
                      "HTTP/1.1 403 Forbidden\nContent-Length: 185\nConnection: "
                      "close\nContent-Type: text/html\n\n"
                      "<html><head>\n<title>403 "
                      "Forbidden</title>\n</head><body>\n<h1>Forbidden</h1>\n"
                      "The requested URL, file type or operation is not allowed on this simple "
                      "static file webserver.\n"
                      "</body></html>\n",
                      271) < 0) {
                perror("Failed to send 403 Forbidden response");
            }
            snprintf(logbuffer, sizeof(logbuffer), "%s [FORBIDDEN] %s:%s", full_timebuffer, s1, s2);
            break;
        case NOTFOUND:  // 404 未找到
            if (write(socket_fd,
                      "HTTP/1.1 404 Not Found\nContent-Length: 136\nConnection: "
                      "close\nContent-Type: text/html\n\n"
                      "<html><head>\n<title>404 Not Found</title>\n</head><body>\n<h1>Not "
                      "Found</h1>\n"
                      "The requested URL was not found on this server.\n</body></html>\n",
                      224) < 0) {
                perror("Failed to send 404 Not Found response");
            }
            snprintf(logbuffer, sizeof(logbuffer), "%s [NOT FOUND] %s:%s", full_timebuffer, s1, s2);
            break;
        case LOG:  // 普通日志
            snprintf(logbuffer, sizeof(logbuffer), "%s [INFO] %s:%s:%d", full_timebuffer, s1, s2,
                     socket_fd);
            break;
    }

    // 打开日志文件并写入日志内容
    #ifdef DEBUG
    if ((fd = open("webserver.log", O_CREAT | O_WRONLY | O_APPEND, 0644)) >= 0) {
        if (write(fd, logbuffer, strlen(logbuffer)) < 0) {
            perror("Failed to write log to file");
        }
        if (write(fd, "\n", 1) < 0) {
            perror("Failed to write newline to log file");
        }
        close(fd);
    } else {
        perror("Failed to open log file");
    }
    #endif
}

// 处理HTTP请求的函数
void web(int fd, int hit) {
    int j, file_fd, buflen;
    long i, ret, len;
    char *fstr;
    static char buffer[BUFSIZE + 1];  // 静态缓冲区，自动初始化为0

    // 定义计时变量
    struct timespec start, end;
    long duration;
    logger(LOG, "Process new web request", "", fd);

    // 记录开始时间（读取请求）
    clock_gettime(CLOCK_MONOTONIC, &start);
    ret = read(fd, buffer, BUFSIZE);  // 读取客户端请求
    if (ret <= 0) {                   // 读取失败
        logger(FORBIDDEN, "Failed to read browser request", "", fd);
        return;
    }
    buffer[ret]='\0';
    
    #ifdef DEBUG
    printf("第%d个客户请求:%s\n",hit,buffer);
    #endif 
    
    // 记录结束时间并计算耗时
    clock_gettime(CLOCK_MONOTONIC, &end);
    duration = calculate_time_diff(start, end);
    logger(LOG, "Time taken for request reading", "", duration);

    // 清除换行符和回车符
    for (i = 0; i < ret; i++) {
        if (buffer[i] == '\r' || buffer[i] == '\n') buffer[i] = '*';
    }

    // 记录开始时间（解析请求）
    clock_gettime(CLOCK_MONOTONIC, &start);
    if (strncmp(buffer, "GET ", 4)) {  // 检查是否为GET请求
        logger(FORBIDDEN, "Only simple GET operation supported", buffer, fd);
        return;
    }
    logger(LOG, "request", buffer, hit);

    for (i = 4; i < BUFSIZE; i++) {  // 截取文件路径
        if (buffer[i] == ' ') {
            buffer[i] = 0;
            break;
        }
    }
    clock_gettime(CLOCK_MONOTONIC, &end);
    duration = calculate_time_diff(start, end);
    logger(LOG, "Time taken for request parsing", "", duration);

    // 检查路径是否合法
    for (j = 0; j < i - 1; j++) {
        if (buffer[j] == '.' && buffer[j + 1] == '.') {
            logger(FORBIDDEN, "Parent directory (..) path names not supported", buffer, fd);
            return;
        }
    }

    // 默认返回 index.html
    if (!strncmp(&buffer[0], "GET /\0", 6)) strcpy(buffer, "GET /index.html");

    // 文件类型解析
    buflen = strlen(buffer);
    fstr = 0;
    clock_gettime(CLOCK_MONOTONIC, &start);
    for (i = 0; extensions[i].ext != 0; i++) {
        len = strlen(extensions[i].ext);
        if (!strncmp(&buffer[buflen - len], extensions[i].ext, len)) {
            fstr = extensions[i].filetype;
            break;
        }
    }
    clock_gettime(CLOCK_MONOTONIC, &end);
    duration = calculate_time_diff(start, end);
    logger(LOG, "Time taken for file type detection", "", duration);

    if (fstr == 0) {
        logger(FORBIDDEN, "File extension type not supported", buffer, fd);
        return;
    }

    // 文件打开
    clock_gettime(CLOCK_MONOTONIC, &start);
    if ((file_fd = open(&buffer[5], O_RDONLY)) == -1) {
        logger(NOTFOUND, "Failed to open file", &buffer[5], fd);
        return;
    }
    clock_gettime(CLOCK_MONOTONIC, &end);
    duration = calculate_time_diff(start, end);
    logger(LOG, "Time taken for file opening", "", duration);

    // 文件读取与传输
    logger(LOG, "SEND", &buffer[5], hit);
    clock_gettime(CLOCK_MONOTONIC, &start);
    len = (long)lseek(file_fd, 0, SEEK_END);
    lseek(file_fd, 0, SEEK_SET);
    sprintf(buffer,
            "HTTP/1.1 200 OK\nServer: nweb/%d.0\nContent-Length: %ld\nConnection: "
            "close\nContent-Type: %s\n\n",
            VERSION, len, fstr);

    logger(LOG, "Header", buffer, hit);
    if (write(fd, buffer, strlen(buffer)) < 0) {
        logger(ERROR, "Failed to write HTTP header", "", fd);
        close(file_fd);
        return;
    }

    // 分块读取文件内容并发送
    while ((ret = read(file_fd, buffer, BUFSIZE)) > 0) {
        if (write(fd, buffer, ret) < 0) {
            logger(ERROR, "Failed to write file content", "", fd);
            break;
        }
    }
    clock_gettime(CLOCK_MONOTONIC, &end);
    duration = calculate_time_diff(start, end);
    logger(LOG, "Time taken for file transfer", "", duration);

    #ifdef DEBUG
    printf("第%d个客户服务器完成\n",hit);
    #endif 

    // 关闭文件和套接字
    close(file_fd);
    close(fd);
}

// 主函数，设置服务器并监听客户端连接
int main(int argc, char **argv) {
    int i, port, listenfd, socketfd, hit;
    socklen_t length;
    static struct sockaddr_in cli_addr;   // 客户端地址
    static struct sockaddr_in serv_addr;  // 服务器地址

    // 检查命令行参数是否正确
    if (argc < 3 || argc > 3 || !strcmp(argv[1], "-?")) {
        (void)printf(
            "hint: webserver Port-Number Top-Directory\t\tversion %d\n\n"
            "\twebserver is a small and very safe mini web server\n"
            "\twebserver only servers out file/web pages with extensions named below\n"
            "\t and only from the named directory or its sub-directories.\n"
            "\tThere is no fancy features = safe and secure.\n\n"
            "\tExample: webserver 8181 /home/oslab &\n\n"
            "\tOnly Supports:",
            VERSION);
        for (i = 0; extensions[i].ext != 0; i++) (void)printf(" %s", extensions[i].ext);

        (void)printf(
            "\n\tNot Supported: URLs including \"..\", Java, Javascript, CGI\n"
            "\tNot Supported: directories / /etc /bin /lib /tmp /usr /dev /sbin \n"
            "\tNo warranty given or implied\n\tNigel Griffiths nag@uk.ibm.com\n");
        exit(0);
    }
    
    /* 设置Ctrl+C信号处理 */
    if(signal(SIGINT,ctrlc_handler)==SIG_ERR) 
        (void)printf("错误: 无法设置SIGINT信号处理\n");

    if (!strncmp(argv[2], "/", 2) || !strncmp(argv[2], "/etc", 5) || !strncmp(argv[2], "/bin", 5) ||
        !strncmp(argv[2], "/lib", 5) || !strncmp(argv[2], "/tmp", 5) ||
        !strncmp(argv[2], "/usr", 5) || !strncmp(argv[2], "/dev", 5) ||
        !strncmp(argv[2], "/sbin", 6)) {
        (void)printf("ERROR: Bad top directory %s, see nweb -?\n", argv[2]);
        exit(3);
    }

    // 验证并切换到指定目录
    if (chdir(argv[2]) == -1) {
        perror("Failed to change directory");
        exit(4);
    }

    port = atoi(argv[1]);
    if (port < 1 || port > 60000) {
        fprintf(stderr, "Invalid port number: %d\n", port);
        exit(1);
    }
     // 设置服务器监听套接字
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket creation failed");
        exit(1);
    }   serv_addr.sin_family = AF_INET;
    
       /* Eliminates "Address already in use" error from bind. */
    int   optval=1;
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, 
		   (const void *)&optval , sizeof(int)) < 0)
	return -1;

    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(port);
    if (bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("Bind failed");
        exit(1);
    }
    if (listen(listenfd, 64) < 0) {
        perror("Listen failed");
        exit(1);
    }

    // 接受客户端连接
    for (hit = 1;; hit++) {
        length = sizeof(cli_addr);
        if ((socketfd = accept(listenfd, (struct sockaddr *)&cli_addr, &length)) < 0) {
            logger(ERROR, "system call", "accept", 0);
        }
        web(socketfd, hit);
    }
    logger(LOG, "webserver exit noramlly", "", 0);
    return 0;
}
