#include "DoxytagResolver.hpp"

#include "CgStr.hpp"
#include "output.hpp"
#include "debug.hpp"
#include "xref.hpp"

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/utility/string_ref.hpp>

#include <iostream>

using namespace synth;

static boost::optional<std::string> getUrl(ptree::ptree const& elem)
{
    auto r = elem.get_optional<std::string>("anchorfile");
    if (!r)
        r = elem.get_optional<std::string>("filename");
    if (!r)
        return r;
    auto anchor = elem.get_child_optional("anchor");
    if (anchor && !anchor->data().empty()) {
        *r += '#';
        r->append(anchor->data());
    }
    return r;
}

DoxytagResolver::DoxytagResolver(ptree::ptree const& tagFileDom, boost::string_ref baseUrl)
    : m_baseUrl(baseUrl)
{
    for (auto const& kv : tagFileDom.get_child("tagfile")) {
        if (kv.first == "compound") {
            parseCompound(kv.second, std::string());
        } else {
            // TODO: Support more tags.
            throw std::runtime_error(
                "Unexpected XML tag in tagfile: " + kv.first);
        }
    }
}

DoxytagResolver DoxytagResolver::fromTagFilename(
    fs::path const& fname, boost::string_ref baseUrl)
{
    ptree::ptree dom;
    fs::ifstream file(fname);
    try {
        file.exceptions(std::ios::failbit | std::ios::badbit);
        ptree::read_xml(file, dom, ptree::xml_parser::no_comments);
    } catch (std::ios::failure const& e) {
        throw std::runtime_error(
            "Error reading tag file " + fname.string() + ": " + e.what());
    }
    return {dom, baseUrl};
}

void DoxytagResolver::link(Markup & m, CXCursor cur)
{
    CXCursor refd = clang_getCursorReferenced(cur);
    if (!isNamespaceLevelDeclaration(refd))
        return;
    auto doxyName = simpleQualifiedName(refd);
    auto it = m_dsts.find(doxyName);
    if (it == m_dsts.end())
        return;
    m.refd = [&dst = it->second, &baseUrl = m_baseUrl] (
        fs::path const&, MultiTuProcessor&)
    {
        return baseUrl + dst;
    };
}

void synth::DoxytagResolver::parseCompound(
    ptree::ptree const& compound, std::string const& prefix)
{
    std::string const* name = addTag(compound, prefix);
    std::string compoundName = name ? prefix + *name + "::" : prefix;
    for (auto const& kv : compound) {
        if (kv.first == "member") {
            addTag(kv.second, compoundName);
        } else if (kv.first == "compound") {
            parseCompound(kv.second, compoundName);
        }
    }
}

std::string const* synth::DoxytagResolver::addTag(
    ptree::ptree const& tag, std::string const& prefix)
{
    auto name = tag.get_optional<std::string>("name");
    if (!name)
        return nullptr;
    auto url = getUrl(tag);
    if (!url)
        return nullptr;
    std::string qname = name->find(':') != std::string::npos
        ? *name : prefix + *name;
    auto insRes = m_dsts.insert({ qname, *std::move(url) });
    if (!insRes.second)
        std::clog << "Duplicate doxytag ignored: " << name << '\n';
    return &insRes.first->first;
}