#include "expression.h"

#include <contrib/libs/yaml-cpp/include/yaml-cpp/yaml.h>

#include <util/string/strip.h>

#define EXPECT(node, expr, msg) do if (!(expr)) throw YAML::Exception((node).Mark(), msg); while (0)

NSv::NAppHost::TExpression::TExpression(const YAML::Node& arg, NSv::NAppHost::TItemResolver getItemId) {
    TStringBuf b = arg.Scalar();

    auto push = [&, depth = 0](size_t item, char op, int delta) mutable {
        // Rather this than allocate heap memory on every eval.
        EXPECT(arg, (depth += delta) < 128, "edge expression too complex: nesting level > 128");
        E_.emplace_back(item << 8 | op);
    };

    auto terminal = [&](TStringBuf t) {
        return (b = StripStringLeft(b)).SkipPrefix(t);
    };

    auto anyExcept = [&](TStringBuf chars) {
        b = StripStringLeft(b);
        auto stop = [&](char c) {
            return IsAsciiSpace(c) || chars.find(c) != TStringBuf::npos;
        };
        return b.NextTokAt(std::find_if(b.begin(), b.end(), stop) - b.begin());
    };

    auto leftAssoc = [&](TStringBuf op, auto&& f) {
        for (f(); terminal(op); push(0, op[0], -1)) {
            f();
        }
    };

    auto consumeFlag = [&](auto& consumeExpr) {
        bool neg = false;
        while (terminal("!")) {
            neg = !neg;
        }
        if (terminal("(")) {
            consumeExpr(consumeExpr);
            EXPECT(arg, terminal(")"), "unmatched (");
        } else {
            auto node = anyExcept("&!|()[]?^$@:");
            if (node == "true" || node == "false") {
                return push(0, (node == "true") ^ neg ? 't' : 'f', +1);
            }
            EXPECT(arg, node, "expected node name");
            EXPECT(arg, terminal("["), "no [ after node name");
            auto item = anyExcept("[]");
            EXPECT(arg, item, "expected item name");
            EXPECT(arg, terminal("]"), "no ] after item name");
            // TODO explicit coalescing directions in syntax?
            push(getItemId(node, item), item == "noans" ? '1' : '0', +1);
        }
        if (neg) {
            push(0, '!', 0);
        }
    };

    auto consumeExpr = [&](auto& consumeExpr) -> void {
        leftAssoc("||", [&] {
            leftAssoc("&&", [&] {
                consumeFlag(consumeExpr);
            });
        });
        if (terminal("?")) {
            consumeExpr(consumeExpr);
            EXPECT(arg, terminal(":"), "? without :");
            consumeExpr(consumeExpr);
            push(0, '?', -2);
        }
    };

    consumeExpr(consumeExpr);
    EXPECT(arg, arg.IsScalar(), "edge expression must be a string");
    EXPECT(arg, !StripStringLeft(b), "data after end of edge expression");
}

NSv::NAppHost::TTernary NSv::NAppHost::TExpression::operator()(const TVector<TTernary>& flags, bool coalesce) const {
    TTernary stack[128];
    size_t i = Y_ARRAY_SIZE(stack) - 1;
    for (const size_t op : E_) {
        switch (op & 0xFF) {
            case '0': stack[--i] = coalesce ? flags[op >> 8].IsTrue() : flags[op >> 8]; break;
            case '1': stack[--i] = coalesce ? !flags[op >> 8].IsFalse() : flags[op >> 8]; break;
            case 't': stack[--i] = true; break;
            case 'f': stack[--i] = false; break;
            case '!': stack[i] = !stack[i]; break;
            case '&': i++; stack[i] = stack[i] & stack[i - 1]; break;
            case '|': i++; stack[i] = stack[i] | stack[i - 1]; break;
            case '?': i += 2; stack[i] = (stack[i] & stack[i - 1]) | (!stack[i] & stack[i - 2]); break;
        }
    }
    return stack[i];
}
