#include <vcpkg/base/checks.h>
#include <vcpkg/base/lineinfo.h>
#include <vcpkg/base/parse.h>
#include <vcpkg/base/strings.h>
#include <vcpkg/base/util.h>

#include <vcpkg/platform-expression.h>

#include <numeric>
#include <string>
#include <vector>

namespace vcpkg::PlatformExpression
{
    enum class Identifier
    {
        invalid = -1, // not a recognized identifier
        x86,
        x64,
        arm,
        arm32,
        arm64,
        arm64ec,
        wasm32,
        mips64,

        windows,
        mingw,
        linux,
        freebsd,
        openbsd,
        netbsd,
        bsd,
        solaris,
        osx,
        uwp,
        xbox,
        android,
        emscripten,
        ios,
        qnx,
        vxworks,
        tvos,
        watchos,
        visionos,
        ohos,

        static_link,
        static_crt,

        native, // HOST_TRIPLET == TARGET_TRIPLET
    };

    static Identifier string2identifier(StringView name)
    {
        static const std::map<StringView, Identifier> id_map = {
            {"x86", Identifier::x86},
            {"x64", Identifier::x64},
            {"arm", Identifier::arm},
            {"arm32", Identifier::arm32},
            {"arm64", Identifier::arm64},
            {"arm64ec", Identifier::arm64ec},
            {"wasm32", Identifier::wasm32},
            {"mips64", Identifier::mips64},
            {"windows", Identifier::windows},
            {"mingw", Identifier::mingw},
            {"linux", Identifier::linux},
            {"freebsd", Identifier::freebsd},
            {"openbsd", Identifier::openbsd},
            {"netbsd", Identifier::netbsd},
            {"bsd", Identifier::bsd},
            {"solaris", Identifier::solaris},
            {"osx", Identifier::osx},
            {"uwp", Identifier::uwp},
            {"xbox", Identifier::xbox},
            {"android", Identifier::android},
            {"emscripten", Identifier::emscripten},
            {"ios", Identifier::ios},
            {"qnx", Identifier::qnx},
            {"vxworks", Identifier::vxworks},
            {"tvos", Identifier::tvos},
            {"watchos", Identifier::watchos},
            {"visionos", Identifier::visionos},
            {"ohos", Identifier::ohos},
            {"static", Identifier::static_link},
            {"staticcrt", Identifier::static_crt},
            {"native", Identifier::native},
        };

        auto id_pair = id_map.find(name);

        if (id_pair == id_map.end())
        {
            return Identifier::invalid;
        }

        return id_pair->second;
    }

    namespace detail
    {
        enum class ExprKind
        {
            identifier,
            op_not,
            op_and,
            op_or,
            op_list,
            op_empty,
            op_invalid
        };

        struct ExprImpl
        {
            ExprImpl(ExprKind k, std::string i, std::vector<std::unique_ptr<ExprImpl>> es)
                : kind(k), identifier(std::move(i)), exprs(std::move(es))
            {
            }

            ExprImpl(ExprKind k, std::string i) : kind(k), identifier(std::move(i)) { }
            ExprImpl(ExprKind k, std::unique_ptr<ExprImpl> a) : kind(k) { exprs.push_back(std::move(a)); }
            ExprImpl(ExprKind k, std::vector<std::unique_ptr<ExprImpl>> es) : kind(k), exprs(std::move(es)) { }

            ExprKind kind;
            std::string identifier;
            std::vector<std::unique_ptr<ExprImpl>> exprs;

            std::unique_ptr<ExprImpl> clone() const
            {
                return std::make_unique<ExprImpl>(
                    ExprImpl{kind, identifier, Util::fmap(exprs, [](auto&& p) { return p->clone(); })});
            }

            bool operator==(const ExprImpl& other) const noexcept
            {
                if (kind != other.kind) return false;
                if (kind == ExprKind::identifier)
                {
                    return identifier == other.identifier;
                }
                return std::equal(exprs.begin(),
                                  exprs.end(),
                                  other.exprs.begin(),
                                  other.exprs.end(),
                                  [](const std::unique_ptr<detail::ExprImpl>& lhs,
                                     const std::unique_ptr<detail::ExprImpl>& rhs) noexcept { return *lhs == *rhs; });
            }

            bool operator!=(const ExprImpl& other) const { return !(*this == other); }

            void negate()
            {
                switch (kind)
                {
                    case ExprKind::identifier:
                    {
                        exprs.push_back(std::make_unique<ExprImpl>(std::move(*this)));
                        kind = ExprKind::op_not;
                        return;
                    }
                    case ExprKind::op_not:
                    {
                        auto sub_expr = std::move(*exprs.at(0));
                        *this = std::move(sub_expr);
                        return;
                    }
                    case ExprKind::op_and:
                    case ExprKind::op_or:
                    case ExprKind::op_list:
                    {
                        kind = (kind == ExprKind::op_and ? ExprKind::op_or : ExprKind::op_and);
                        for (auto& expr : exprs)
                        {
                            expr->negate();
                        }
                        return;
                    }
                    case ExprKind::op_empty:
                    case ExprKind::op_invalid: return;
                }
                Checks::unreachable(VCPKG_LINE_INFO);
            }

            static void add_if_not_contains(std::vector<std::unique_ptr<ExprImpl>>& exprs,
                                            std::unique_ptr<ExprImpl>&& new_expr)
            {
                if (Util::all_of(exprs, [&](const auto& expr) { return *expr != *new_expr; }))
                {
                    exprs.push_back(std::move(new_expr));
                }
            }

            static bool is_orish(ExprKind kind) { return kind == ExprKind::op_or || kind == ExprKind::op_list; }

            void simplify()
            {
                switch (kind)
                {
                    case ExprKind::op_not:
                    {
                        exprs.at(0)->simplify();
                        if (exprs.at(0)->kind == ExprKind::op_not)
                        {
                            auto sub_sub_expr = std::move(*exprs.at(0)->exprs.at(0));
                            *this = std::move(sub_sub_expr);
                            simplify();
                        }
                        return;
                    }
                    case ExprKind::op_and:
                    case ExprKind::op_or:
                    case ExprKind::op_list:
                    {
                        auto old_exprs = std::move(exprs);
                        for (auto&& expr : old_exprs)
                        {
                            expr->simplify();
                            if ((kind == ExprKind::op_and && expr->kind == ExprKind::op_and) ||
                                (is_orish(kind) && is_orish(expr->kind)))
                            {
                                for (auto&& sub_expr : expr->exprs)
                                {
                                    add_if_not_contains(exprs, std::move(sub_expr));
                                }
                            }
                            else
                            {
                                add_if_not_contains(exprs, std::move(expr));
                            }
                        }
                        if (exprs.size() == 1)
                        {
                            auto sub_expr = std::move(*exprs[0]);
                            *this = std::move(sub_expr);
                        }
                        return;
                    }
                    case ExprKind::identifier:
                    case ExprKind::op_empty:
                    case ExprKind::op_invalid: return;
                }
                Checks::unreachable(VCPKG_LINE_INFO);
            }
        };

        struct ExpressionParser : ParserBase
        {
            ExpressionParser(StringView str, MultipleBinaryOperators multiple_binary_operators)
                : ParserBase(str, "CONTROL", {0, 0}), multiple_binary_operators(multiple_binary_operators)
            {
            }

            MultipleBinaryOperators multiple_binary_operators;

            bool allow_multiple_binary_operators() const
            {
                return multiple_binary_operators == MultipleBinaryOperators::Allow;
            }

            // top-level-platform-expression = optional-whitespace, platform-expression
            PlatformExpression::Expr parse()
            {
                skip_whitespace();
                auto res = expr();

                if (!at_eof())
                {
                    add_error(msg::format(msgInvalidLogicExpressionUnexpectedCharacter));
                }

                return Expr(std::move(res));
            }

        private:
            // identifier-character =
            // | lowercase-alpha
            // | digit ;
            static bool is_identifier_char(char32_t ch) { return is_lower_alpha(ch) || is_ascii_digit(ch); }

            // platform-expression =
            // | platform-expression-not
            // | platform-expression-and
            // | platform-expression-or
            std::unique_ptr<ExprImpl> expr()
            {
                // this is the common prefix of all the variants
                // platform-expression-not,
                auto result = expr_not();

                // the first expression must be followed by a logical operator (or nothing)
                auto oper = expr_operator();
                switch (oper)
                {
                    case ExprKind::op_and:
                        // { "&", optional-whitespace, platform-expression-not }
                        // { "and", platform-expression-binary-keyword-second-operand }
                        return expr_binary<ExprKind::op_and, ExprKind::op_or>(
                            std::make_unique<ExprImpl>(oper, std::move(result)));

                    case ExprKind::op_or:
                        // { "|", optional-whitespace, platform-expression-not }
                        return expr_binary<ExprKind::op_or, ExprKind::op_and>(
                            std::make_unique<ExprImpl>(oper, std::move(result)));

                    case ExprKind::op_list:
                        // { ",", optional-whitespace, platform-expression }
                        return expr_binary<ExprKind::op_list, ExprKind::op_invalid>(
                            std::make_unique<ExprImpl>(oper, std::move(result)));

                    case ExprKind::op_empty: return result;

                    default:
                        // op_identifier and op_invalid both indicate a syntax error, which should have
                        // already been flagged by expr_operator.
                        return result;
                }
            }

            ExprKind expr_operator()
            {
                auto oper = cur();

                // Support chains of the vcpkg operators (`&`, `|`)  to avoid breaking backwards compatibility
                switch (oper)
                {
                    case '|':
                    case '&':
                        do
                        {
                            next();
                        } while (allow_multiple_binary_operators() && cur() == oper);
                        break;
                }

                switch (oper)
                {
                    case '|':
                    {
                        // { "|", optional-whitespace, platform-expression-not }
                        return ExprKind::op_or;
                    }
                    case '&':
                    {
                        // { "&", optional-whitespace, platform-expression-not }
                        return ExprKind::op_and;
                    }
                    case ',':
                    {
                        // { ",", optional-whitespace, platform-expression-not }
                        // "," is a near-synonym of "|", with the differences that it can be combined with "&"/"and",
                        // but has lower precedence
                        next();
                        return ExprKind::op_list;
                    }
                    case 'a':
                    case 'o':
                    {
                        // { "and", optional-whitespace, platform-expression-not }
                        // { "or", platform-expression-binary-keyword-second-operand } }
                        // "and" is a synonym of "&", "or" is reserved (but not yet supported) as a synonym of "|"
                        std::string name = match_while(is_identifier_char).to_string();
                        Checks::check_exit(VCPKG_LINE_INFO, !name.empty());

                        if (name == "and")
                        {
                            return ExprKind::op_and;
                        }
                        else if (name == "or")
                        {
                            add_error(msg::format(msgInvalidLogicExpressionUsePipe));
                            return ExprKind::op_invalid;
                        }

                        // Invalid alphanumeric strings or strings other than "and" are errors.
                        add_error(msg::format(msgInvalidLogicExpressionUnexpectedCharacter));
                        return ExprKind::op_invalid;
                    }
                    default:
                        // Perhaps this should be an error, but in the previous implementation, this
                        // was a do-nothing case, so let's maintain that behavior.
                        return ExprKind::op_empty;
                }
            }

            // platform-expression-simple =
            // | platform-expression-identifier
            // | platform-expression-grouped ;
            std::unique_ptr<ExprImpl> expr_simple()
            {
                // platform-expression-grouped =
                // | "(", optional-whitespace, platform-expression, ")", optional-whitespace ;
                if (cur() == '(')
                {
                    // "(",
                    next();
                    // optional-whitespace,
                    skip_whitespace();
                    // platform-expression,
                    auto result = expr();
                    if (cur() != ')')
                    {
                        add_error(msg::format(msgMissingClosingParen));
                        return result;
                    }
                    // ")",
                    next();
                    // optional-whitespace
                    skip_whitespace();
                    return result;
                }

                // platform-expression-identifier
                return expr_identifier();
            }

            // platform-expression-identifier =
            // | identifier-character, { identifier-character }, optional-whitespace ;
            std::unique_ptr<ExprImpl> expr_identifier()
            {
                // identifier-character, { identifier-character },
                std::string name = match_while(is_identifier_char).to_string();

                if (name.empty())
                {
                    add_error(msg::format(msgMissingOrInvalidIdentifer));
                }

                // optional-whitespace
                skip_whitespace();

                return std::make_unique<ExprImpl>(ExprKind::identifier, std::move(name));
            }

            // platform-expression-not =
            // | platform-expression-simple
            // | "!", optional-whitespace, platform-expression-simple
            // | "not", platform-expression-unary-keyword-operand ;
            std::unique_ptr<ExprImpl> expr_not()
            {
                if (cur() == '!')
                {
                    // "!",
                    next();
                    // optional-whitespace,
                    skip_whitespace();
                    // platform-expression-simple
                    return std::make_unique<ExprImpl>(ExprKind::op_not, expr_simple());
                }
                else if (cur() == 'n')
                {
                    std::string name = match_while(is_identifier_char).to_string();

                    // "not"
                    if (name == "not")
                    {
                        // required-whitespace, platform-expression-simple
                        // optional-whitespace, platform-expression-grouped
                        skip_whitespace();
                        return std::make_unique<ExprImpl>(ExprKind::op_not, expr_simple());
                    }

                    // optional-whitespace
                    skip_whitespace();

                    return std::make_unique<ExprImpl>(ExprKind::identifier, std::move(name));
                }

                // platform-expression-simple
                return expr_simple();
            }

            // platform-expression-list =
            // | platform-expression {",", optional-whitespace, platform-expression};
            //
            // platform-expression-binary-keyword-first-operand =
            // | platform-expression-not, required-whitespace
            // | platform-expression-grouped ;
            //
            // platform-expression-binary-keyword-second-operand =
            // | required-whitespace, platform-expression-not
            // | platform-expression-grouped ;
            //
            // platform-expression-and =
            // | platform-expression-not, { "&", optional-whitespace, platform-expression-not }
            // | platform-expression-binary-keyword-first-operand, { "and",
            // platform-expression-binary-keyword-second-operand } ;
            //
            // platform-expression-or =
            // | platform-expression-not, { "|", optional-whitespace, platform-expression-not }
            // | platform-expression-binary-keyword-first-operand, { "or",
            // platform-expression-binary-keyword-second-operand } (* to allow for future extension *) ;
            //
            // Processing of the operator was already taken care of by the caller: continue
            // with the next platform-expression-not or platform-expression-binary-keyword-second-operand.
            template<ExprKind oper, ExprKind unmixable_oper>
            std::unique_ptr<ExprImpl> expr_binary(std::unique_ptr<ExprImpl>&& seed)
            {
                // gather consecutive instances of the same operation into a single expr node
                // e.g., parsing 'A & B & C' yields {&, vector<A,B,C>}
                ExprKind next_oper = ExprKind::op_invalid;
                do
                {
                    // optional-whitespace,
                    skip_whitespace();

                    if constexpr (oper == ExprKind::op_list)
                    {
                        // platform-expression { ",", optional-whitespace, platform-expression } ;
                        seed->exprs.push_back(expr());
                    }
                    else
                    {
                        // platform-expression-not, (go back to start of repetition)
                        seed->exprs.push_back(expr_not());
                    }
                    next_oper = expr_operator();
                } while (next_oper == oper);

                if constexpr (unmixable_oper != ExprKind::op_invalid)
                {
                    if (next_oper == unmixable_oper)
                    {
                        add_error(msg::format(msgMixingBooleanOperationsNotAllowed));
                    }
                }

                if (next_oper == ExprKind::op_list)
                {
                    // platform-expression { ",", optional-whitespace, platform-expression } ;
                    //
                    // To handle a lower-precedence, treat the remainder of the string as a platform expression.
                    // E.g., "A & B , C | D" will be treated as "(A & B) , (C | D)", which preserves intended precedence
                    // In this case, see is the LHS at the point in which we see the ",".

                    return expr_binary<ExprKind::op_list, ExprKind::op_invalid>(
                        std::make_unique<ExprImpl>(next_oper, std::move(seed)));
                }
                else
                {
                    return std::move(seed);
                }
            }
        };
    }

    using namespace detail;

    Expr::Expr() noexcept = default;
    Expr::Expr(Expr&& other) noexcept = default;
    Expr& Expr::operator=(Expr&& other) noexcept = default;

    Expr::Expr(const Expr& other)
    {
        if (other.underlying_)
        {
            this->underlying_ = other.underlying_->clone();
        }
    }
    Expr& Expr::operator=(const Expr& other)
    {
        if (other.underlying_)
        {
            this->underlying_ = other.underlying_->clone();
        }
        else
        {
            this->underlying_.reset();
        }

        return *this;
    }

    Expr::Expr(std::unique_ptr<ExprImpl>&& e) : underlying_(std::move(e)) { }
    Expr::~Expr() = default;

    Expr Expr::Identifier(StringView id)
    {
        return Expr(std::make_unique<ExprImpl>(ExprKind::identifier, id.to_string()));
    }
    Expr Expr::Not(Expr&& e) { return Expr(std::make_unique<ExprImpl>(ExprKind::op_not, std::move(e.underlying_))); }
    Expr Expr::And(std::vector<Expr>&& exprs)
    {
        return Expr(std::make_unique<ExprImpl>(
            ExprKind::op_and, Util::fmap(exprs, [](Expr& expr) { return std::move(expr.underlying_); })));
    }
    Expr Expr::Or(std::vector<Expr>&& exprs)
    {
        return Expr(std::make_unique<ExprImpl>(
            ExprKind::op_or, Util::fmap(exprs, [](Expr& expr) { return std::move(expr.underlying_); })));
    }

    bool Expr::evaluate(const Context& context) const
    {
        if (!this->underlying_)
        {
            return true; // empty expression is always true
        }

        std::map<std::string, bool> override_ctxt;
        {
            auto override_vars = context.find("VCPKG_DEP_INFO_OVERRIDE_VARS");
            if (override_vars != context.end())
            {
                auto cmake_list = Strings::split(override_vars->second, ';');
                for (auto& override_id : cmake_list)
                {
                    if (!override_id.empty())
                    {
                        if (override_id[0] == '!')
                        {
                            override_ctxt.emplace(override_id.substr(1), false);
                        }
                        else
                        {
                            override_ctxt.emplace(override_id, true);
                        }
                    }
                }
            }
        }

        struct Visitor
        {
            const Context& context;
            const std::map<std::string, bool>& override_ctxt;

            bool true_if_exists_and_nonempty(const std::string& variable_name) const
            {
                auto iter = context.find(variable_name);
                if (iter == context.end())
                {
                    return false;
                }

                return !iter->second.empty();
            }

            bool true_if_exists_and_equal(const std::string& variable_name, const std::string& value) const
            {
                auto iter = context.find(variable_name);
                if (iter == context.end())
                {
                    return false;
                }
                return iter->second == value;
            }

            bool visit(const ExprImpl& expr) const
            {
                if (expr.kind == ExprKind::identifier)
                {
                    if (!override_ctxt.empty())
                    {
                        auto override_id = override_ctxt.find(expr.identifier);
                        if (override_id != override_ctxt.end())
                        {
                            return override_id->second;
                        }
                        // Fall through to use the cmake logic if the id does not have an override
                    }

                    auto id = string2identifier(expr.identifier);
                    switch (id)
                    {
                        case Identifier::invalid:
                            // Point out in the diagnostic that they should add to the override list because that is
                            // what most users should do, however it is also valid to update the built in identifiers to
                            // recognize the name.
                            msg::println_warning(msgUnrecognizedIdentifier, msg::value = expr.identifier);
                            return false;
                        case Identifier::x64: return true_if_exists_and_equal("VCPKG_TARGET_ARCHITECTURE", "x64");
                        case Identifier::x86: return true_if_exists_and_equal("VCPKG_TARGET_ARCHITECTURE", "x86");
                        case Identifier::arm:
                            // For backwards compatability arm is also true for arm64.
                            // This is because it previously was only checking for a substring.
                            return true_if_exists_and_equal("VCPKG_TARGET_ARCHITECTURE", "arm") ||
                                   true_if_exists_and_equal("VCPKG_TARGET_ARCHITECTURE", "arm64");
                        case Identifier::arm32: return true_if_exists_and_equal("VCPKG_TARGET_ARCHITECTURE", "arm");
                        case Identifier::arm64: return true_if_exists_and_equal("VCPKG_TARGET_ARCHITECTURE", "arm64");
                        case Identifier::arm64ec:
                            return true_if_exists_and_equal("VCPKG_TARGET_ARCHITECTURE", "arm64ec");
                        case Identifier::windows:
                            return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "") ||
                                   true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore") ||
                                   true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "MinGW");
                        case Identifier::mingw: return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "MinGW");
                        case Identifier::linux: return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "Linux");
                        case Identifier::freebsd: return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "FreeBSD");
                        case Identifier::openbsd: return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "OpenBSD");
                        case Identifier::netbsd: return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "NetBSD");
                        case Identifier::bsd:
                            return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "FreeBSD") ||
                                   true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "OpenBSD") ||
                                   true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "NetBSD");
                        case Identifier::solaris: return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "SunOS");
                        case Identifier::osx: return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "Darwin");
                        case Identifier::uwp:
                            return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore");
                        case Identifier::xbox: return true_if_exists_and_nonempty("VCPKG_XBOX_CONSOLE_TARGET");
                        case Identifier::android: return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "Android");
                        case Identifier::emscripten:
                            return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "Emscripten");
                        case Identifier::ios: return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "iOS");
                        case Identifier::qnx: return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "QNX");
                        case Identifier::vxworks: return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "VxWorks");
                        case Identifier::wasm32: return true_if_exists_and_equal("VCPKG_TARGET_ARCHITECTURE", "wasm32");
                        case Identifier::mips64: return true_if_exists_and_equal("VCPKG_TARGET_ARCHITECTURE", "mips64");
                        case Identifier::tvos: return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "tvOS");
                        case Identifier::watchos: return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "watchOS");
                        case Identifier::visionos:
                            return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "visionOS");
                        case Identifier::ohos: return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "OHOS");
                        case Identifier::static_link:
                            return true_if_exists_and_equal("VCPKG_LIBRARY_LINKAGE", "static");
                        case Identifier::static_crt: return true_if_exists_and_equal("VCPKG_CRT_LINKAGE", "static");
                        case Identifier::native:
                        {
                            auto is_native = context.find("Z_VCPKG_IS_NATIVE");
                            if (is_native == context.end())
                            {
                                Checks::unreachable(VCPKG_LINE_INFO);
                            }

                            return is_native->second == "1";
                        }
                    }
                    Checks::unreachable(VCPKG_LINE_INFO);
                }
                else if (expr.kind == ExprKind::op_not)
                {
                    return !visit(*expr.exprs.at(0));
                }
                else if (expr.kind == ExprKind::op_and)
                {
                    bool valid = true;

                    // we want to print errors in all expressions, so we check all of the expressions all the time
                    for (const auto& e : expr.exprs)
                    {
                        valid &= visit(*e);
                    }

                    return valid;
                }
                else if ((expr.kind == ExprKind::op_or) || (expr.kind == ExprKind::op_list))
                {
                    bool valid = false;

                    // we want to print errors in all expressions, so we check all of the expressions all the time
                    for (const auto& e : expr.exprs)
                    {
                        valid |= visit(*e);
                    }

                    return valid;
                }
                else
                {
                    Checks::unreachable(VCPKG_LINE_INFO);
                }
            }
        };

        return Visitor{context, override_ctxt}.visit(*this->underlying_);
    }

    int Expr::complexity() const
    {
        if (is_empty()) return 0;

        struct Impl
        {
            int operator()(const std::unique_ptr<detail::ExprImpl>& expr) const { return (*this)(*expr); }
            int operator()(const detail::ExprImpl& expr) const
            {
                if (expr.kind == ExprKind::identifier) return 1;

                if (expr.kind == ExprKind::op_not) return 1 + (*this)(expr.exprs.at(0));

                return 1 + std::accumulate(expr.exprs.begin(), expr.exprs.end(), 0, [](int acc, const auto& el) {
                           return acc + Impl{}(el);
                       });
            }
        };

        return Impl{}(underlying_);
    }

    Expr& Expr::negate()
    {
        underlying_->negate();
        return *this;
    }

    Expr& Expr::simplify()
    {
        underlying_->simplify();
        return *this;
    }

    ExpectedL<Expr> parse_platform_expression(StringView expression, MultipleBinaryOperators multiple_binary_operators)
    {
        ExpressionParser parser(expression, multiple_binary_operators);
        auto res = parser.parse();

        if (parser.messages().any_errors())
        {
            return parser.messages().join();
        }

        return res;
    }

    bool structurally_equal(const Expr& lhs, const Expr& rhs)
    {
        if (lhs.underlying_ && rhs.underlying_)
        {
            return *lhs.underlying_ == *rhs.underlying_;
        }
        return !lhs.underlying_ && !rhs.underlying_;
    }

    int compare(const Expr& lhs, const Expr& rhs)
    {
        auto lhs_platform_complexity = lhs.complexity();
        auto rhs_platform_complexity = rhs.complexity();

        if (lhs_platform_complexity < rhs_platform_complexity) return -1;
        if (rhs_platform_complexity < lhs_platform_complexity) return 1;

        auto lhs_platform = to_string(lhs);
        auto rhs_platform = to_string(rhs);

        if (lhs_platform.size() < rhs_platform.size()) return -1;
        if (rhs_platform.size() < lhs_platform.size()) return 1;

        auto platform_cmp = lhs_platform.compare(rhs_platform);
        if (platform_cmp < 0) return -1;
        if (platform_cmp > 0) return 1;

        return 0;
    }

    std::string to_string(const Expr& expr)
    {
        struct Impl
        {
            std::string operator()(const std::unique_ptr<detail::ExprImpl>& expr) const
            {
                return (*this)(*expr, false);
            }
            std::string operator()(const detail::ExprImpl& expr, bool outer) const
            {
                StringLiteral join = "bug";
                switch (expr.kind)
                {
                    case ExprKind::identifier: return expr.identifier;
                    case ExprKind::op_and: join = " & "; break;
                    case ExprKind::op_or: join = " | "; break;
                    case ExprKind::op_list: join = ", "; break;
                    case ExprKind::op_not: return Strings::concat('!', (*this)(expr.exprs.at(0)));
                    case ExprKind::op_empty: join = ""; break;
                    case ExprKind::op_invalid: join = " invalid "; break;
                    default: Checks::unreachable(VCPKG_LINE_INFO);
                }

                if (outer)
                {
                    return Strings::join(join, expr.exprs, *this);
                }
                else
                {
                    return Strings::concat('(', Strings::join(join, expr.exprs, *this), ')');
                }
            }
        };

        if (expr.is_empty())
        {
            return std::string{};
        }
        return Impl{}(*expr.underlying_, true);
    }

    const Expr Expr::always_true{};
}
