#include "html.hpp"

void Html::configure_tidy_doc(TidyDoc &doc) {
    bool conf_success =
        tidyOptSetBool(doc, TidyXmlOut, yes) &&
        tidyOptSetBool(doc, TidyForceOutput, yes) &&
        tidyOptSetBool(doc, TidyWarnPropAttrs, no) &&
        tidyOptSetBool(doc, TidyShowWarnings, no) &&
        tidyOptSetInt(doc, TidyShowErrors, 0);
    if (!conf_success) {
        throw std::runtime_error("Error with tidy configuration");
    }
}

TidyDoc Html::tidy_doc_from_file(std::string path) {
    TidyDoc doc = tidyCreate();
    configure_tidy_doc(doc);
    int res = tidyParseFile(doc, path.c_str());
    if (res < 0) throw std::runtime_error("Error parsing HTML");
    return doc;
}

std::string Html::convert_to_xml(TidyDoc doc) {
    TidyBuffer out_buf = {0};
    int res = -1;
    // res: 0=ok; 1=warnings; 2=errors
    res = tidyCleanAndRepair(doc);
    if (res >= 0) res = tidySaveBuffer(doc, &out_buf);
    if (res < 0) throw std::runtime_error("Error parsing HTML");

    std::string xml = (char*) out_buf.bp;
    tidyBufFree(&out_buf);
    tidyRelease(doc);
    return xml;
}

void Html::remove_useless_children(xml_node &root) {
    for (auto name: USELESS_CHILDREN) {
        xpath_node_set nodes = root.select_nodes(("//"+name).c_str());
        for (xpath_node node: nodes) {
            node.node().parent().remove_child(node.node());
        }
    }
}

Html::Html(TidyDoc &tdoc) {
    std::string xml = convert_to_xml(tdoc);
    xml_parse_result res = doc.load_string(xml.c_str());
    if (!res) {
        throw std::runtime_error("Error parsing XML file");
    }
    head = doc.document_element().child("head");
}

xml_node Html::get_body_node() {
    xml_node body_node = doc.document_element().child("body");
    remove_useless_children(body_node);
    return body_node;
}

Html::Html(std::string path) {
    std::string xml = convert_to_xml(tidy_doc_from_file(path));
    xml_parse_result res = doc.load_string(xml.c_str());
    if (!res) {
        throw std::runtime_error("Error parsing XML file: "+path);
    }
    head = doc.document_element().child("head");
}


Html Html::from_string(std::string s) {
    TidyDoc doc = tidyCreate();
    configure_tidy_doc(doc);
    int res = tidyParseString(doc, s.c_str());
    if (res < 0) throw std::runtime_error("Error parsing HTML");
    return Html(doc);
}

std::string Html::get_title() {
    if (title.empty()) {
        title = head.child("title").text().as_string();
    }
    return title;
}

std::string Html::get_icon_url() {
    if (icon_url.empty()) {
        std::string rel, size_s;
        int size = -1;
        for (
                xml_node link = head.child("link");
                link;
                link = link.next_sibling("link")
        ) {
            rel = link.attribute("rel").value();
            if (
                    rel == "apple-touch-icon" ||
                    rel == "apple-touch-icon-precomposed"
            ) {
                size_s = link.attribute("size").value();
                if (size_s.empty())
                    size_s = link.attribute("sizes").value();
                if (!size_s.empty()) {
                    int n_size = std::stoi(
                        SynDomUtils::split(size_s, 'x')[0]
                    );
                    if (n_size <= size) continue;
                    size = n_size;
                }
                icon_url = link.attribute("href").value();
                if (!icon_url.empty() && (size <= 0 || size >= 180)) break;
            }
        }
        if (!icon_url.empty()) return icon_url;
        for (
                xml_node link = head.child("link");
                link;
                link = link.next_sibling("link")
        ) {
            rel = link.attribute("rel").value();
            if (rel == "icon" || rel == "shortcut icon") {
                icon_url = link.attribute("href").value();
                if (!icon_url.empty()) break;
            }
        }
    }
    return icon_url;
}
std::string Html::get_img_url() {
    if (img_url.empty()) {
        std::string property, name;
        for (
                xml_node meta = head.child("meta");
                meta;
                meta = meta.next_sibling("meta")
        ) {
            property = meta.attribute("property").value();
            name = meta.attribute("name").value();
            if (
                    property == "og:image" || property == "image" ||
                    property == "twitter:image" ||
                    name == "og:image" || name == "image" ||
                    name == "twitter:image"
            ) {
                img_url = meta.attribute("content").value();
                if (!img_url.empty()) break;
            }
        }
    }
    return img_url;
}
std::string Html::get_rss_url() {
    if (rss_url.empty()) {
        std::string rel, type;
        for (
                xml_node link = head.child("link");
                link;
                link = link.next_sibling("link")
        ) {
            rel = link.attribute("rel").value();
            type = link.attribute("type").value();
            if (rel == "alternate" && (
                    type == "application/rss+xml" ||
                    type == "application/atom+xml"
            )) {
                rss_url = link.attribute("href").value();
                if (!rss_url.empty()) break;
            }
        }
    }
    return rss_url;
}
std::string Html::get_body() {
    if (body.empty()) {
        xml_node body_node = get_body_node();
        SynDomUtils::xml_string_writer writer;
        body_node.print(writer, "");
        body = writer.result;
    }
    return body;
}
std::string Html::get_article() {
    if (article.empty()) {
        xml_node body_node = doc.document_element().child("body");
        xml_node article_node = body_node.find_child([](xml_node n){
            return std::strcmp(n.name(), "article") == 0;
        });
        SynDomUtils::xml_string_writer writer;
        if (article_node) {
            remove_useless_children(article_node);
            article_node.print(writer, "");
            article = writer.result;
        }
        else {
            xml_node clean_body_node = get_body_node();
            clean_body_node.set_name("article");
            SynDomUtils::xml_string_writer writer;
            clean_body_node.print(writer, "");
            article = writer.result;
        }
    }
    return article;
}
std::string Html::get_description() {
    if (description.empty()) {
        std::string property;
        for (
                xml_node meta = head.child("meta");
                meta;
                meta = meta.next_sibling("meta")
        ) {
            property = meta.attribute("property").value();
            if (
                    property == "og:description" ||
                    property == "description"
            ) {
                description = meta.attribute("content").value();
                if (!description.empty()) break;
            }
        }
    }
    return description;
}
std::string Html::to_json(bool metadata_only) {
    std::string res = "{\n"
        "\t\"title\": \"" + get_title() + "\",\n"
        "\t\"icon_url\": \"" + get_icon_url() + "\",\n"
        "\t\"img_url\": \"" + get_img_url() + "\",\n"
        "\t\"rss_url\": \"" + get_rss_url() + "\",\n"
        "\t\"description\": \"" + get_description() + "\"";

    if (!metadata_only) {
        res += ",\n"
            "\t\"body\": \"" + get_body() + "\",\n"
            "\t\"article\": \"" + get_article() + "\"\n"
        "}\n";
    }
    else {
        res += "\n}\n";
    }

    return res;
}
