// gcc -o xmlpath xmlpath.c $(xml2-config --cflags --libs) -lxslt -lxml2

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include <libxml/HTMLparser.h>
#include <libxml/uri.h>
#include <signal.h>
#include <unistd.h>


#define MAX_DOCS 256
#define MAX_NODES 65536

typedef struct {
    int doc_id;
    xmlDocPtr doc;
    int valid;
} DocEntry;

typedef struct {
    int node_id;
    xmlNodePtr node;
    int doc_id;      // 所属文档ID，用于快速清理
    int valid;
} NodeEntry;

DocEntry docs[MAX_DOCS];
NodeEntry nodes[MAX_NODES];

// 初始化
void init() {
    LIBXML_TEST_VERSION
    memset(docs, 0, sizeof(docs));
    memset(nodes, 0, sizeof(nodes));
}

// 清理所有资源
void cleanup() {
    for (int i = 1; i < MAX_DOCS; i++) {
        if (docs[i].valid) {
            xmlFreeDoc(docs[i].doc);
        }
    }
    xmlCleanupParser();
}

// 分配文档句柄
int alloc_doc(xmlDocPtr doc) {
    for (int i = 1; i < MAX_DOCS; i++) {
        if (!docs[i].valid) {
            docs[i].doc = doc;
            docs[i].doc_id = i;
            docs[i].valid = 1;
            return i;
        }
    }
    return -1;
}

// 释放文档句柄及相关节点句柄
void free_doc(int doc_id) {
    if (doc_id < 1 || doc_id >= MAX_DOCS || !docs[doc_id].valid) return;
    xmlFreeDoc(docs[doc_id].doc);
    docs[doc_id].valid = 0;
    // 释放所有属于该文档的节点句柄
    for (int i = 1; i < MAX_NODES; i++) {
        if (nodes[i].valid && nodes[i].doc_id == doc_id) {
            nodes[i].valid = 0;
        }
    }
}

// 分配节点句柄
/*
int alloc_node(xmlNodePtr node, int doc_id) {
    for (int i = 1; i < MAX_NODES; i++) {
        if (!nodes[i].valid) {
            nodes[i].node = node;
            nodes[i].doc_id = doc_id;
            nodes[i].valid = 1;
            return i;
        }
    }
    return -1;
}
*/

int alloc_node(xmlNodePtr node, int doc_id) {
    // 先查找是否已存在相同节点指针
    for (int i = 1; i < MAX_NODES; i++) {
        if (nodes[i].valid && nodes[i].node == node && nodes[i].doc_id == doc_id) {
            return i;  // 返回已有句柄
        }
    }
    // 不存在，找空闲位置分配
    for (int i = 1; i < MAX_NODES; i++) {
        if (!nodes[i].valid) {
            nodes[i].node = node;
            nodes[i].doc_id = doc_id;
            nodes[i].valid = 1;
            nodes[i].node_id = i;  // 如果需要存储自身ID（可选）
            return i;
        }
    }
    return -1;  // 无空闲句柄
}

// 目前我们还没有实现 del 命令，但将来若增加删除节点的功能，必须同时遍历节点表，将所有指向被删除节点的句柄置为无效（valid = 0）。否则这些句柄会变成悬空指针。可以添加一个辅助函数：
void invalidate_node_handles(xmlNodePtr node) {
    for (int i = 1; i < MAX_NODES; i++) {
        if (nodes[i].valid && nodes[i].node == node) {
            nodes[i].valid = 0;
        }
    }
}

// 释放节点句柄（仅标记无效，不释放节点内存）
void free_node(int node_id) {
    if (node_id < 1 || node_id >= MAX_NODES || !nodes[node_id].valid) return;
    nodes[node_id].valid = 0;
}

// 根据节点句柄获取节点指针
xmlNodePtr get_node(int node_id) {
    if (node_id < 1 || node_id >= MAX_NODES || !nodes[node_id].valid) return NULL;
    return nodes[node_id].node;
}

// 根据文档句柄获取文档指针
xmlDocPtr get_doc(int doc_id) {
    if (doc_id < 1 || doc_id >= MAX_DOCS || !docs[doc_id].valid) return NULL;
    return docs[doc_id].doc;
}

// 解析带前缀的句柄字符串，返回ID，并通过type输出类型（'d'或'n'）
int parse_handle(const char *str, char *type) {
    if (!str || strlen(str) < 2) return -1;
    if (str[0] == 'd' || str[0] == 'D') {
        *type = 'd';
    } else if (str[0] == 'n' || str[0] == 'N') {
        *type = 'n';
    } else {
        return -1;
    }
    char *endptr;
    long id = strtol(str + 1, &endptr, 16);
    if (*endptr != '\0' || id <= 0) return -1;
    if (*type == 'd' && id >= MAX_DOCS) return -1;
    if (*type == 'n' && id >= MAX_NODES) return -1;
    return (int)id;
}

// 输出节点句柄列表（空格分隔）
void print_node_handles(xmlNodeSetPtr nodeset, int doc_id) {
    if (!nodeset) return;
    for (int i = 0; i < nodeset->nodeNr; i++) {
        xmlNodePtr node = nodeset->nodeTab[i];
        int node_id = alloc_node(node, doc_id);
        if (node_id > 0) {
            printf("n%x ", node_id);
        }
    }
    printf("\n");
}

// 处理XPath查询结果
void handle_xpath_result(xmlXPathObjectPtr result, int doc_id) {
    if (!result) {
        fprintf(stderr, "XPath evaluation returned NULL\n");
        return;
    }
    switch (result->type) {
        case XPATH_NODESET:
            print_node_handles(result->nodesetval, doc_id);
            break;
        case XPATH_BOOLEAN:
            printf("%s\n", result->boolval ? "true" : "false");
            break;
        case XPATH_NUMBER:
            printf("%g\n", result->floatval);
            break;
        case XPATH_STRING:
            printf("%s\n", result->stringval);
            break;
        default:
            printf("Unsupported XPath result type: %d\n", result->type);
    }
}

// 命令：open <type> <uri>
void cmd_open(char *type_str, char *uri) {
    xmlDocPtr doc = NULL;
    if (strcmp(type_str, "xml") == 0) {
        doc = xmlReadFile(uri, NULL, XML_PARSE_RECOVER);
    } else if (strcmp(type_str, "html") == 0) {
        doc = htmlReadFile(uri, NULL, HTML_PARSE_RECOVER | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING);
    } else {
        fprintf(stderr, "Invalid type: %s (use xml or html)\n", type_str);
        return;
    }
    if (!doc) {
        fprintf(stderr, "Failed to open %s\n", uri);
        return;
    }
    int doc_id = alloc_doc(doc);
    if (doc_id < 0) {
        fprintf(stderr, "No free document slot\n");
        xmlFreeDoc(doc);
        return;
    }
    printf("d%x\n", doc_id);
}

// 命令：xpath <doc-handle> <expr>
/*
void cmd_xpath(char *handle_str, char *expr) {
    char type;
    int id = parse_handle(handle_str, &type);
    if (id < 0 || type != 'd') {
        fprintf(stderr, "Invalid document handle\n");
        return;
    }
    xmlDocPtr doc = get_doc(id);
    if (!doc) {
        fprintf(stderr, "Document not found\n");
        return;
    }
    xmlXPathContextPtr ctx = xmlXPathNewContext(doc);
    xmlXPathObjectPtr result = xmlXPathEvalExpression(BAD_CAST expr, ctx);
    handle_xpath_result(result, id);
    xmlXPathFreeObject(result);
    xmlXPathFreeContext(ctx);
}
*/

void cmd_xpath(char *handle_str, char *expr) {
    char type;
    int id = parse_handle(handle_str, &type);
    if (id < 0) {
        fprintf(stderr, "Invalid handle\n");
        return;
    }

    xmlDocPtr doc = NULL;
    xmlNodePtr node = NULL;
    int doc_id = -1;

    if (type == 'd') {
        doc = get_doc(id);
        if (!doc) {
            fprintf(stderr, "Document not found\n");
            return;
        }
        doc_id = id;
        // 上下文节点默认为文档根，稍后可不设置
    } else if (type == 'n') {
        node = get_node(id);
        if (!node) {
            fprintf(stderr, "Node not found\n");
            return;
        }
        doc = node->doc;
        doc_id = nodes[id].doc_id;  // 从节点表获取文档ID
        if (!doc) {
            fprintf(stderr, "Node's document not found\n");
            return;
        }
    } else {
        fprintf(stderr, "Unknown handle type\n");
        return;
    }

    xmlXPathContextPtr ctx = xmlXPathNewContext(doc);
    if (!ctx) {
        fprintf(stderr, "Failed to create XPath context\n");
        return;
    }

    // 如果提供了节点句柄，设置上下文节点
    if (type == 'n' && node) {
        if (xmlXPathSetContextNode(node, ctx) != 0) {
            fprintf(stderr, "Failed to set context node\n");
            xmlXPathFreeContext(ctx);
            return;
        }
    }

    xmlXPathObjectPtr result = xmlXPathEvalExpression(BAD_CAST expr, ctx);
    if (!result) {
        fprintf(stderr, "XPath evaluation failed\n");
        xmlXPathFreeContext(ctx);
        return;
    }

    handle_xpath_result(result, doc_id);  // 注意这里传入的是 doc_id，用于分配节点句柄

    xmlXPathFreeObject(result);
    xmlXPathFreeContext(ctx);
}

// 命令：get <node-handle>
void cmd_get(char *handle_str) {
    char type;
    int id = parse_handle(handle_str, &type);
    if (id < 0 || type != 'n') {
        fprintf(stderr, "Invalid node handle\n");
        return;
    }
    xmlNodePtr node = get_node(id);
    if (!node) {
        fprintf(stderr, "Node not found\n");
        return;
    }
    xmlBufferPtr buf = xmlBufferCreate();
    xmlNodeDump(buf, node->doc, node, 0, 1);
    printf("%s\n", (char*)buf->content);
    xmlBufferFree(buf);
}

// 命令：close <handle> (文档或节点)
void cmd_close(char *handle_str) {
    char type;
    int id = parse_handle(handle_str, &type);
    if (id < 0) {
        fprintf(stderr, "Invalid handle\n");
        return;
    }
    if (type == 'd') {
        free_doc(id);
        printf("OK\n");
    } else {
        free_node(id);
        printf("OK\n");
    }
}

void cmd_parent(char *handle_str) {
    char type;
    int id = parse_handle(handle_str, &type);
    if (id < 0 || type != 'n') {
        fprintf(stderr, "Invalid node handle\n");
        return;
    }
    xmlNodePtr node = get_node(id);
    if (!node) {
        fprintf(stderr, "Node not found\n");
        return;
    }
    xmlNodePtr parent = node->parent;
    if (!parent) {
        // 没有父节点，输出空或提示
        printf("\n");
        return;
    }
    int parent_id = alloc_node(parent, nodes[id].doc_id);
    if (parent_id > 0) {
        printf("n%x\n", parent_id);
    } else {
        fprintf(stderr, "Failed to allocate handle for parent\n");
    }
}

void cmd_text(char *handle_str) {
    char type;
    int id = parse_handle(handle_str, &type);
    if (id < 0 || type != 'n') {
        fprintf(stderr, "Invalid node handle\n");
        return;
    }
    xmlNodePtr node = get_node(id);
    if (!node) {
        fprintf(stderr, "Node not found\n");
        return;
    }
    xmlChar *content = xmlNodeGetContent(node);
    if (content) {
        printf("%s\n", (char*)content);
        xmlFree(content);
    } else {
        printf("\n"); // 空内容
    }
}

void output_node_handle(xmlNodePtr node, int doc_id) {
    if (!node) {
        printf("\n");   // 空行表示无节点
        return;
    }
    int node_id = alloc_node(node, doc_id);
    if (node_id > 0) {
        printf("n%x\n", node_id);
    } else {
        fprintf(stderr, "Failed to allocate node handle\n");
    }
}

void cmd_first_child(char *handle_str) {
    char type;
    int id = parse_handle(handle_str, &type);
    if (id < 0 || type != 'n') {
        fprintf(stderr, "Invalid node handle\n");
        return;
    }
    xmlNodePtr node = get_node(id);
    if (!node) {
        fprintf(stderr, "Node not found\n");
        return;
    }
    output_node_handle(node->children, nodes[id].doc_id);
}

void cmd_last_child(char *handle_str) {
    char type;
    int id = parse_handle(handle_str, &type);
    if (id < 0 || type != 'n') {
        fprintf(stderr, "Invalid node handle\n");
        return;
    }
    xmlNodePtr node = get_node(id);
    if (!node) {
        fprintf(stderr, "Node not found\n");
        return;
    }
    output_node_handle(node->last, nodes[id].doc_id);
}

void cmd_next_sibling(char *handle_str) {
    char type;
    int id = parse_handle(handle_str, &type);
    if (id < 0 || type != 'n') {
        fprintf(stderr, "Invalid node handle\n");
        return;
    }
    xmlNodePtr node = get_node(id);
    if (!node) {
        fprintf(stderr, "Node not found\n");
        return;
    }
    output_node_handle(node->next, nodes[id].doc_id);
}

void cmd_prev_sibling(char *handle_str) {
    char type;
    int id = parse_handle(handle_str, &type);
    if (id < 0 || type != 'n') {
        fprintf(stderr, "Invalid node handle\n");
        return;
    }
    xmlNodePtr node = get_node(id);
    if (!node) {
        fprintf(stderr, "Node not found\n");
        return;
    }
    output_node_handle(node->prev, nodes[id].doc_id);
}

void cmd_children(char *handle_str) {
    char type;
    int id = parse_handle(handle_str, &type);
    if (id < 0 || type != 'n') {
        fprintf(stderr, "Invalid node handle\n");
        return;
    }
    xmlNodePtr node = get_node(id);
    if (!node) {
        fprintf(stderr, "Node not found\n");
        return;
    }
    xmlNodePtr child = node->children;
    int first = 1;
    while (child) {
        int child_id = alloc_node(child, nodes[id].doc_id);
        if (child_id > 0) {
            if (!first) printf(" ");
            printf("n%x", child_id);
            first = 0;
        }
        child = child->next;
    }
    printf("\n");
}

// 主循环：从stdin读取命令
void repl() {
    char line[4096];
//    printf("xmlpath> ");            // 可选：命令提示符
//    fflush(stdout);
    while (fgets(line, sizeof(line), stdin)) {
        // 去除末尾换行
        line[strcspn(line, "\n")] = '\0';
        if (strlen(line) == 0) continue;

        // 解析第一个单词（命令）
        char *cmd = strtok(line, " \t");
        if (!cmd) continue;

        if (strcmp(cmd, "shutdown") == 0) {
            printf("Shutting down server...\n");
            fflush(stdout);
            //pid_t ppid = getppid();            // 获取父进程 PID
            //if (ppid > 1) {                    // 避免误杀 init（PID 1）
            //    kill(ppid, SIGTERM);           // 向 socat 主进程发送终止信号
            //}
            killpg(getpgrp(), SIGTERM);        // 杀死整个进程组（包括自己）
            cleanup();                         // 释放资源
            exit(0);                           // 退出当前客户端
	} else if (strcmp(cmd, "quit") == 0 || strcmp(cmd, "exit") == 0) {
            fflush(stdout);
            cleanup();                         // 释放资源
            break;
        } else if (strcmp(cmd, "open") == 0) {
            char *type = strtok(NULL, " \t");
            char *uri = strtok(NULL, "");  // 剩余部分作为uri
            if (!type || !uri) {
                fprintf(stderr, "Usage: open <xml|html> <uri>\n");
                continue;
            }
            // 跳过uri前导空格
            while (*uri == ' ' || *uri == '\t') uri++;
            cmd_open(type, uri);
        } else if (strcmp(cmd, "xpath") == 0) {
            char *handle = strtok(NULL, " \t");
            char *expr = strtok(NULL, "");
            if (!handle || !expr) {
                fprintf(stderr, "Usage: xpath <doc-handle> <xpath-expr>\n");
                continue;
            }
            while (*expr == ' ' || *expr == '\t') expr++;
            cmd_xpath(handle, expr);
        } else if (strcmp(cmd, "get") == 0) {
            char *handle = strtok(NULL, " \t");
            if (!handle) {
                fprintf(stderr, "Usage: get <node-handle>\n");
                continue;
            }
            cmd_get(handle);
       	} else if (strcmp(cmd, "text") == 0) {
            char *handle = strtok(NULL, " \t");
            if (!handle) {
                fprintf(stderr, "Usage: text <node-handle>\n");
                continue;
            }
            cmd_text(handle);
        } else if (strcmp(cmd, "parent") == 0) {
            char *handle = strtok(NULL, " \t");
            if (!handle) {
                fprintf(stderr, "Usage: parent <node-handle>\n");
                continue;
            }
            cmd_parent(handle);
        } else if (strcmp(cmd, "first-child") == 0) {
            char *handle = strtok(NULL, " \t");
            if (!handle) {
                fprintf(stderr, "Usage: first-child <node-handle>\n");
                continue;
            }
            cmd_first_child(handle);
        } else if (strcmp(cmd, "last-child") == 0) {
            char *handle = strtok(NULL, " \t");
            if (!handle) {
                fprintf(stderr, "Usage: last-child <node-handle>\n");
                continue;
             }
             cmd_last_child(handle);
        } else if (strcmp(cmd, "next-sibling") == 0) {
             char *handle = strtok(NULL, " \t");
             if (!handle) {
                 fprintf(stderr, "Usage: next-sibling <node-handle>\n");
                 continue;
             }
             cmd_next_sibling(handle);
        } else if (strcmp(cmd, "prev-sibling") == 0) {
             char *handle = strtok(NULL, " \t");
             if (!handle) {
                 fprintf(stderr, "Usage: prev-sibling <node-handle>\n");
                 continue;
             }
             cmd_prev_sibling(handle);
        } else if (strcmp(cmd, "children") == 0) {
             char *handle = strtok(NULL, " \t");
             if (!handle) {
                 fprintf(stderr, "Usage: children <node-handle>\n");
                 continue;
             }
             cmd_children(handle);
        } else if (strcmp(cmd, "close") == 0) {
            char *handle = strtok(NULL, " \t");
            if (!handle) {
                fprintf(stderr, "Usage: close <handle>\n");
                continue;
            }
            cmd_close(handle);
        } else {
            fprintf(stderr, "Unknown command: %s\n", cmd);
        }
	// 输出结果后确保刷新
	fflush(stdout);
	//printf("xmlpath> ");
	//fflush(stdout);
    }
    // 如果 stdin 关闭（比如网络断开），则退出
    if (feof(stdin)) {
        fprintf(stderr, "Connection closed.\n");
    }
}

void signalHandler(int sig) {
    fflush(stdout);
    cleanup();  // 释放资源
    _exit(sig);
}

int main(int argc, char **argv) {
//setvbuf(stdout, NULL, _IOLBF, 0);   // 设置 stdout 为行缓冲
//printf("xmlpath ready\n");            // 输出欢迎信息
//fflush(stdout);                     // 立即刷新
    init();
    signal(SIGINT, signalHandler); 
    signal(SIGTERM, signalHandler); 
    repl();
    cleanup();
    return 0;
}

//3. CSS选择器（暂未实现）
//可以在 cmd_css 中实现一个简单的转换器，例如：
//tag → //tag
//.class → //*[contains(concat(' ', @class, ' '), ' class ')]
//#id → //*[@id='id']
//tag.class → //tag[contains(concat(' ', @class, ' '), ' class ')]
//组合器如 >、+ 等需要更复杂的解析，建议初期只支持单步选择器，或直接提示用户改用XPath。
