#include "WindowRule.hpp"
#include "../../view/Window.hpp"
#include "../../../helpers/Monitor.hpp"
#include "../../../Compositor.hpp"
#include "../../../managers/TokenManager.hpp"
#include "../../../desktop/state/FocusState.hpp"

#include <hyprutils/string/VarList2.hpp>

using namespace Desktop;
using namespace Desktop::Rule;
using namespace Hyprutils::String;

CWindowRule::CWindowRule(const std::string& name) : IRule(name) {
    ;
}

eRuleType CWindowRule::type() {
    return RULE_TYPE_WINDOW;
}

void CWindowRule::addEffect(CWindowRule::storageType e, const std::string& result) {
    m_effects.emplace_back(std::make_pair<>(e, result));
    m_effectSet.emplace(e);
}

const std::vector<std::pair<CWindowRule::storageType, std::string>>& CWindowRule::effects() {
    return m_effects;
}

bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) {
    if (m_matchEngines.empty() || !m_enabled)
        return false;

    for (const auto& [prop, engine] : m_matchEngines) {
        switch (prop) {
            default: {
                Log::logger->log(Log::TRACE, "CWindowRule::matches: skipping prop entry {}", sc<std::underlying_type_t<eRuleProperty>>(prop));
                break;
            }

            case RULE_PROP_TITLE:
                if (!engine->match(w->m_title))
                    return false;
                break;
            case RULE_PROP_INITIAL_TITLE:
                if (!engine->match(w->m_initialTitle))
                    return false;
                break;
            case RULE_PROP_CLASS:
                if (!engine->match(w->m_class))
                    return false;
                break;
            case RULE_PROP_INITIAL_CLASS:
                if (!engine->match(w->m_initialClass))
                    return false;
                break;
            case RULE_PROP_FLOATING:
                if (!engine->match(w->m_isFloating))
                    return false;
                break;
            case RULE_PROP_TAG:
                if (!engine->match(w->m_ruleApplicator->m_tagKeeper))
                    return false;
                break;
            case RULE_PROP_XWAYLAND:
                if (!engine->match(w->m_isX11))
                    return false;
                break;
            case RULE_PROP_FULLSCREEN:
                if (!engine->match(w->m_fullscreenState.internal != 0))
                    return false;
                break;
            case RULE_PROP_PINNED:
                if (!engine->match(w->m_pinned))
                    return false;
                break;
            case RULE_PROP_FOCUS:
                if (!engine->match(Desktop::focusState()->window() == w))
                    return false;
                break;
            case RULE_PROP_GROUP:
                if (!engine->match(!!w->m_group))
                    return false;
                break;
            case RULE_PROP_MODAL:
                if (!engine->match(w->isModal()))
                    return false;
                break;
            case RULE_PROP_FULLSCREENSTATE_INTERNAL:
                if (!engine->match(w->m_fullscreenState.internal))
                    return false;
                break;
            case RULE_PROP_FULLSCREENSTATE_CLIENT:
                if (!engine->match(w->m_fullscreenState.client))
                    return false;
                break;
            case RULE_PROP_ON_WORKSPACE:
                if (!engine->match(w->m_workspace))
                    return false;
                break;
            case RULE_PROP_CONTENT:
                if (!engine->match(std::format("{}", sc<size_t>(w->getContentType()))) && !engine->match(NContentType::toString(w->getContentType())))
                    return false;
                break;
            case RULE_PROP_XDG_TAG:
                if (!w->xdgTag().has_value() || !engine->match(*w->xdgTag()))
                    return false;
                break;

            case RULE_PROP_EXEC_TOKEN:
                if (!allowEnvLookup)
                    break;

                const auto ENV   = w->getEnv();
                bool       match = false;

                if (ENV.contains(EXEC_RULE_ENV_NAME)) {
                    if (engine->match(ENV.at(EXEC_RULE_ENV_NAME)))
                        match = true;
                } else if (m_matchEngines.contains(RULE_PROP_EXEC_PID)) {
                    if (m_matchEngines.at(RULE_PROP_EXEC_PID)->match(w->getPID()))
                        match = true;
                }
                if (!match)
                    return false;
                break;
        }
    }

    return true;
}

SP<CWindowRule> CWindowRule::buildFromExecString(std::string&& s) {
    CVarList2       varlist(std::move(s), 0, ';');
    SP<CWindowRule> wr = makeShared<CWindowRule>("__exec_rule");

    for (const auto& el : varlist) {
        // split element by space, can't do better
        size_t spacePos = el.find(' ');
        if (spacePos != std::string::npos) {
            // great, split and try to parse
            auto       LHS    = el.substr(0, spacePos);
            const auto EFFECT = windowEffects()->get(LHS);

            if (!EFFECT.has_value() || *EFFECT == WINDOW_RULE_EFFECT_NONE)
                continue; // invalid...

            wr->addEffect(*EFFECT, std::string{el.substr(spacePos + 1)});
            continue;
        }

        // assume 1 maybe...

        const auto EFFECT = windowEffects()->get(el);

        if (!EFFECT.has_value() || *EFFECT == WINDOW_RULE_EFFECT_NONE)
            continue; // invalid...

        wr->addEffect(*EFFECT, std::string{"1"});
    }

    return wr;
}

const std::unordered_set<CWindowRule::storageType>& CWindowRule::effectsSet() {
    return m_effectSet;
}

void CWindowRule::setEnabled(bool enable) {
    m_enabled = enable;
}

bool CWindowRule::isEnabled() const {
    return m_enabled;
}
