// gcc -shared -fPIC -I/pkg/include -I /pkg/include/bash -I /pkg/include/bash/builtins -I/pkg/include/bash/include -o xmlsh_builtins.so xmlsh_builtins.c $(xml2-config --cflags --libs) -lxslt -lxml2
// gcc -shared -fPIC -o xmlsh_builtins.so xmlsh_builtins.c
// enable -f ./xmlsh_builtins.so 

#include <bash/config.h>
#include <bash/builtins.h>
#include <bash/shell.h>
#include <bash/builtins/common.h>

#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();
}

static int initialized = 0;

/* 初始化函数声明 */
static void xmlsh_initialize(void) {
    if (!initialized) {
        init();
        atexit(cleanup);
        initialized = 1;
    }
}

// 分配文档句柄
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) {
            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_document(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>
int 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 1;
    }
    if (!doc) {
        fprintf(stderr, "Failed to open %s\n", uri);
        return 1;
    }
    int doc_id = alloc_doc(doc);
    if (doc_id < 0) {
        fprintf(stderr, "No free document slot\n");
        xmlFreeDoc(doc);
        return 1;
    }
    printf("d%x\n", doc_id);
    return 0;
}

// 命令：xpath <doc-handle> <expr>
int 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 1;
    }

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

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

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

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

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

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

    xmlXPathFreeObject(result);
    xmlXPathFreeContext(ctx);
    return 0;
}

// 命令：get <node-handle>
int 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 1;
    }
    xmlNodePtr node = get_node(id);
    if (!node) {
        fprintf(stderr, "Node not found\n");
        return 1;
    }
    xmlBufferPtr buf = xmlBufferCreate();
    xmlNodeDump(buf, node->doc, node, 0, 1);
    printf("%s\n", (char*)buf->content);
    xmlBufferFree(buf);
    return 0;
}

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

int 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 1;
    }
    xmlNodePtr node = get_node(id);
    if (!node) {
        fprintf(stderr, "Node not found\n");
        return 1;
    }
    xmlNodePtr parent = node->parent;
    if (!parent) {
        // 没有父节点，输出空或提示
        printf("\n");
        return 0;
    }
    int parent_id = alloc_node(parent, nodes[id].doc_id);
    if (parent_id > 0) {
        printf("n%x\n", parent_id);
	return 0;
    } else {
        fprintf(stderr, "Failed to allocate handle for parent\n");
	return 1;
    }
}

int 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 1;
    }
    xmlNodePtr node = get_node(id);
    if (!node) {
        fprintf(stderr, "Node not found\n");
        return 1;
    }
    xmlChar *content = xmlNodeGetContent(node);
    if (content) {
        printf("%s\n", (char*)content);
        xmlFree(content);
    } else {
        printf("\n"); // 空内容
    }
    return 0;
}

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

int 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 1;
    }
    xmlNodePtr node = get_node(id);
    if (!node) {
        fprintf(stderr, "Node not found\n");
        return 1;
    }
    return output_node_handle(node->children, nodes[id].doc_id);
}

int 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 1;
    }
    xmlNodePtr node = get_node(id);
    if (!node) {
        fprintf(stderr, "Node not found\n");
        return 1;
    }
    return output_node_handle(node->last, nodes[id].doc_id);
}

int 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 1;
    }
    xmlNodePtr node = get_node(id);
    if (!node) {
        fprintf(stderr, "Node not found\n");
        return 1;
    }
    return output_node_handle(node->next, nodes[id].doc_id);
}

int 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 1;
    }
    xmlNodePtr node = get_node(id);
    if (!node) {
        fprintf(stderr, "Node not found\n");
        return 1;
    }
    return output_node_handle(node->prev, nodes[id].doc_id);
}

int 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 1;
    }
    xmlNodePtr node = get_node(id);
    if (!node) {
        fprintf(stderr, "Node not found\n");
        return 1;
    }
    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");
    return 0;
}

int cmd_clear(void) {
    for (int i = 1; i < MAX_DOCS; i++) {
        if (docs[i].valid) {
            xmlFreeDoc(docs[i].doc);
            docs[i].valid = 0;
         }
    }
    for (int i = 1; i < MAX_NODES; i++) {
        nodes[i].valid = 0;
    }
    printf("OK\n");
    return 0;
}

int xdoc_builtin(WORD_LIST *list) {
    char *cmd;
    if (list == NULL) {
        builtin_help();
        return EX_USAGE;
    }
    cmd = list->word->word;
    if (strcmp(cmd, "help") == 0) {
        builtin_help();
        return EXECUTION_SUCCESS;
    } else if (strcmp(cmd, "clear") == 0) {
        int ret = cmd_clear();
        return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE;
    }
    if (list->next == NULL) {
        builtin_usage();
        return EX_USAGE;
    }
    if (strcmp(cmd, "get") == 0) {
        int ret = cmd_get(list->next->word->word);
        return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE;
    } else if (strcmp(cmd, "text") == 0) {
        int ret = cmd_text(list->next->word->word);
        return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE;
    } else if (strcmp(cmd, "first_child") == 0) {
        int ret = cmd_first_child(list->next->word->word);
        return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE;
    } else if (strcmp(cmd, "last_child") == 0) {
        int ret = cmd_last_child(list->next->word->word);
        return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE;
    } else if (strcmp(cmd, "next_sibling") == 0) {
        int ret = cmd_next_sibling(list->next->word->word);
        return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE;
    } else if (strcmp(cmd, "prev_sibling") == 0) {
        int ret = cmd_prev_sibling(list->next->word->word);
        return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE;
    } else if (strcmp(cmd, "children") == 0) {
        int ret = cmd_children(list->next->word->word);
        return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE;
    } else if (strcmp(cmd, "close") == 0) {
        int ret = cmd_close(list->next->word->word);
        return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE;
    }
    if (list->next->next == NULL) {
        builtin_usage();
        return EX_USAGE;
    }
    if (strcmp(cmd, "open") == 0) {
        char  *type, *full_uri;
        type = list->next->word->word;
        // 剩余部分组合为 uri（可能包含空格）
        full_uri = string_list(list->next->next);
        if (full_uri == NULL) {
            builtin_error("out of memory");
            return EXECUTION_FAILURE;
        }
        xmlsh_initialize();
        int ret = cmd_open(type, full_uri);
        free(full_uri);
        return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE;
    } else if (strcmp(cmd, "xpath") == 0) {
        char  *handle_str, *expr;
        handle_str = list->next->word->word;
        expr = string_list(list->next->next);
        if (expr == NULL) {
            builtin_error("out of memory");
            return EXECUTION_FAILURE;
        }
        int ret = cmd_xpath(handle_str, expr);
        free(expr);
        return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE;
    }
    builtin_usage();
    return EX_USAGE;
}

char *xdoc_doc[] = {
    "xdoc"
    " - XML/HTML query command with persistent states and XPath 1.0 support",
    "Usage: xdoc [help|clear|list] | [open xml|html uri] | [xpath expr] | [cmd handle]",
    "Examples:",
    "xdoc [help]",
    "xdoc clear",
    "xdoc open html ./test.html",
    "xdoc xpath d1 //h4",
    "xdoc xpath n3 //li",
    "xdoc close d1",
    "xdoc children n3",
    "xdoc get na",
    "xdoc text n2",
    "xdoc parent n3",
    "xdoc first_child n2",
    "xdoc last_child n2",
    "xdoc next_sibling n3",
    "xdoc prev_sibling n3",
    (char *)NULL
};

struct builtin xdoc_struct = {
    "xdoc",             // Command name
    xdoc_builtin,       // Function pointer
    BUILTIN_ENABLED,    // Initial flags
    xdoc_doc,           // Long documentation
    "xdoc [cmd [handle [uri|expr]]]", // Short documentation (synopsis)
    0                   // Reserved
};

