#include "vs_generate.h"

#include <cassert>
#include <fstream>
#include <regex>
#include <string>

VsParseSln::VsParseSln() = default;

// 解析关联
void VsParseSln::parse_relate() {
    for (auto& proj : projs_) {
        for (auto& relate : proj.guid_relate_) {
            std::string opth = hashmap_[relate];
            proj.relate_.push_back(opth);
        }
    }
}

bool VsParseSln::parse(const std::string& path) {
    std::ifstream sln_file(path, std::ios::in);
    if (!sln_file.is_open()) {
        std::cout << "没有找到 sln 文件或者打开文件失败,路径:" << path
                  << "\n";
        return false;
    }
    fs::path full_path(path);
    fs::path parent_path = full_path.parent_path();

    projs_.clear();
    std::string tmp{};
    std::regex  re(R"(\"(.*?)\")");
    std::regex  re2(R"((\{.*?\}))");
    std::smatch match{};
    ProjectInfo proj{};
    bool        related = false;
    bool        add = false;
    while (std::getline(sln_file, tmp)) {
        if (tmp.find("Project") != std::string::npos &&
            tmp.find(".vcxproj") != std::string::npos) {
            std::vector<std::string> vec{};
            while (std::regex_search(tmp, match, re)) {
                vec.push_back(match[1]);
                tmp = match.suffix().str();
            }
            // 这里应该有4个结果
            assert(vec.size() == 4);
            proj.guid_ = vec[3];
            proj.path_ = parent_path.string() + "\\" + vec[2];
            add = true;
        }
        if (tmp.find("ProjectDependencies") != std::string::npos) {
            related = true;
            continue;
        }
        if (tmp.find("EndProjectSection") != std::string::npos) {
            related = false;
            continue;
        }
        if (related) {
            // 这里要简单处理一下
            if (std::regex_search(tmp, match, re2)) {
                std::string restr = match[1];
                proj.guid_relate_.push_back(restr);
            }
        }
        if (tmp.find("EndProject") != std::string::npos && add) {
            projs_.push_back(proj);
            hashmap_[proj.guid_] = proj.path_;
            proj.guid_relate_.clear();
            add = false;
        }
    }

    parse_relate();
    sln_file.close();
    return true;
}

std::vector<ProjectInfo> VsParseSln::get_project() const { return projs_; }

void VsParsePro::handle_include(tinyxml2::XMLElement* node,
                                const std::string&    key) const {
    if (!node) {
        return;
    }
    const char* data = node->Attribute("Condition");
    if (!data) {
        return;
    }
    if (std::string(data).find(key) == std::string::npos) {
        return;
    }
    tinyxml2::XMLElement* purpose = node->FirstChildElement();
    while (purpose) {
        const char* name = purpose->Name();
        if (std::strcmp(name, "IncludePath") != 0) {
            purpose = purpose->NextSiblingElement();
            continue;
        }
        const char* include_path = purpose->GetText();
        current_proj_->addtion_.append(";");
        current_proj_->addtion_.append(include_path);
        break;
    }
}
void VsParsePro::additon_include_and_predefine(tinyxml2::XMLElement* node,
                                               const std::string&    key) const {
    if (!node) {
        return;
    }
    const char* data = node->Attribute("Condition");
    if (!data) {
        return;
    }
    if (std::string(data).find(key) == std::string::npos) {
        return;
    }
    tinyxml2::XMLElement* cl = get_child_element(node, "ClCompile");
    const tinyxml2::XMLElement* purpose =
        get_child_element(cl, "AdditionalIncludeDirectories");
    if (purpose) {
        const char* addtion_dir = purpose->GetText();
        current_proj_->addtion_.append(";");
        current_proj_->addtion_.append(addtion_dir);
    }

    purpose = get_child_element(cl, "PreprocessorDefinitions");
    if (purpose) {
        const char* predefinition = purpose->GetText();
        current_proj_->predefinition_.append(";");
        current_proj_->predefinition_.append(predefinition);
    }
}

tinyxml2::XMLElement* VsParsePro::get_child_element(tinyxml2::XMLElement* node,
                                                    const std::string&    key) {
    tinyxml2::XMLElement* result{};
    if (!node) {
        return result;
    }
    tinyxml2::XMLElement* n = node->FirstChildElement();
    while (n) {
        if (std::strcmp(n->Name(), key.c_str()) == 0) {
            result = n;
            break;
        }
        n = n->NextSiblingElement();
    }
    return result;
}

bool VsParsePro::parse(ProjectInfo* proj, const std::string& key) {
    current_proj_ = proj;
    supplement(current_proj_);

    int ret = m_doc_.LoadFile(current_proj_->path_.c_str());
    if (ret != 0) {
        std::cout << "解析失败:" << current_proj_->path_ << "\n";
        return false;
    }

    tinyxml2::XMLElement* element = m_doc_.FirstChildElement();
    if (std::strcmp(element->Name(), "Project") != 0) {
        std::cout << "不是合法的vcxproj文件。" << current_proj_->path_ << "\n";
        return false;
    }

    addition_dir_.clear();
    tinyxml2::XMLElement* node = element->FirstChildElement();
    while (node) {
        if (std::strcmp(node->Name(), "PropertyGroup") == 0) {
            handle_include(node, key);
        }
        if (std::strcmp(node->Name(), "ItemDefinitionGroup") == 0) {
            additon_include_and_predefine(node, key);
        }
        node = node->NextSiblingElement();
    }
    tidy(current_proj_);
    return true;
}

// 对初步解析出的工程进行整理
void VsParsePro::tidy(ProjectInfo* proj) {}

// 补充其他信息
void VsParsePro::supplement(ProjectInfo* proj) {
    if (proj->path_.empty() || !fs::exists(proj->path_)) {
        return;
    }
    fs::path path(proj->path_);
    if (proj->proj_root_.empty()) {
        proj->proj_root_ = path.parent_path().string();
    }
    if (proj->name_.empty()) {
        proj->name_ = path.filename().string();
    }
}