#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libxml/parser.h>
#include <libxml/xmlschemas.h>
#include <libxml/xmlIO.h>
#include <emscripten.h>

char* read_file(const char* filename);
int validate_xml(const char* xml_content, const char* xsd_content);
const char* map_url_to_local_file(const char* url);
int custom_match_callback(const char* filename);
void* custom_open_callback(const char* filename);
int custom_read_callback(void* context, char* buffer, int len);
int custom_close_callback(void* context);

int main(int argc, char* argv[]) {
    if (argc != 2) {
        printf("Usage: %s <xml_file>\n", argv[0]);
        return 1;
    }

    char* xml = read_file(argv[1]);
    if (!xml) {
        printf("Error reading XML file\n");
        return 1;
    }

    xmlInitParser();

    // Register custom I/O callbacks to intercept HTTP and info: scheme requests
    xmlRegisterInputCallbacks(xmlIOHTTPMatch, custom_open_callback, custom_read_callback, custom_close_callback);
    xmlRegisterInputCallbacks(custom_match_callback, custom_open_callback, custom_read_callback, custom_close_callback);

    int out = validate_xml(xml, NULL);
    if (out == 0) {
        printf("Valid XML\n");
    } else {
        printf("Invalid XML\n");
    }

    free(xml);
    xmlCleanupParser();
    return out;
}

int validate_xml(const char* xml_content, const char* unused) {
    if (!xml_content) return 1;

    xmlDocPtr xml_doc = NULL;
    xmlSchemaParserCtxtPtr parser = NULL;
    xmlSchemaPtr schema = NULL;
    xmlSchemaValidCtxtPtr valid = NULL;
    int result = 1;

    xml_doc = xmlParseMemory(xml_content, strlen(xml_content));
    if (!xml_doc) {
        printf("Error parsing XML document\n");
        goto cleanup;
    }

    // Validate against the schema declared by the XML document
    xmlNodePtr root = xmlDocGetRootElement(xml_doc);
    if (root && root->ns && root->ns->href) {
        printf("DEBUG: Found namespace: %s\n", root->ns->href);
        parser = xmlSchemaNewParserCtxt((char*)root->ns->href);
        if (parser) {
            printf("DEBUG: Created schema parser for namespace\n");
            schema = xmlSchemaParse(parser);
            if (schema) {
                printf("DEBUG: Schema parsed successfully\n");
                valid = xmlSchemaNewValidCtxt(schema);
                if (valid) {
                    printf("DEBUG: Starting validation\n");
                    result = xmlSchemaValidateDoc(valid, xml_doc);
                }
            } else {
                printf("DEBUG: Failed to parse schema\n");
            }
        } else {
            printf("DEBUG: Failed to create schema parser\n");
        }
    } else {
        printf("DEBUG: No namespace found on root element\n");
    }

cleanup:
    if (valid) xmlSchemaFreeValidCtxt(valid);
    if (schema) xmlSchemaFree(schema);
    if (parser) xmlSchemaFreeParserCtxt(parser);
    if (xml_doc) xmlFreeDoc(xml_doc);
    return result;
}

char* read_file(const char* filename) {
    FILE* file = fopen(filename, "r");
    if (!file) return NULL;
    fseek(file, 0, SEEK_END);
    long size = ftell(file);
    fseek(file, 0, SEEK_SET);

    char* content = malloc(size + 1);
    fread(content, 1, size, file);
    content[size] = '\0';
    fclose(file);
    return content;
}

const char* map_url_to_local_file(const char* url) {
    static char local_path[512];
    const char* start = url;
    
    // Handle different URI schemes
    if (strncmp(url, "http://", 7) == 0) start = url + 7;
    else if (strncmp(url, "https://", 8) == 0) start = url + 8;
    else if (strncmp(url, "info:", 5) == 0) start = url + 5;
    else if (strncmp(url, "urn:", 4) == 0) start = url + 4;
    
    // Build transformed filename: /xsd/[transformed_url].xsd
    strcpy(local_path, "/xsd/");
    char* dest = local_path + 5; // after "/xsd/"
    for (const char* src = start; *src; src++) {
        if (*src == '.') *dest++ = '-';
        else if (*src == '/' || *src == ':') *dest++ = '_';
        else *dest++ = *src;
    }
    strcpy(dest, ".xsd");
    return local_path;
}

int custom_match_callback(const char* filename) {
    // Match info: and urn: scheme URIs and any other non-HTTP schemes we want to handle locally
    if (strncmp(filename, "info:", 5) == 0) return 1;
    if (strncmp(filename, "urn:", 4) == 0) return 1;
    return 0;
}

void* custom_open_callback(const char* filename) {
    const char* local_file = map_url_to_local_file(filename);
    if (local_file) {
        FILE* file = fopen(local_file, "r");
        if (!file) {
            printf("DEBUG: Failed to open '%s'\n", local_file);
        }
        return file;
    }
    printf("DEBUG: No mapping found for '%s'\n", filename);
    return NULL;
}

int custom_read_callback(void* context, char* buffer, int len) {
    if (!context) return -1;
    return fread(buffer, 1, len, (FILE*)context);
}

int custom_close_callback(void* context) {
    if (!context) return -1;
    return fclose((FILE*)context);
}
