#include <iostream>
#include <sstream>
#include <string>
#include <unordered_map>
#include <memory>
#include <cassert>
#include <cmath>
#include <variant>
#include <cctype>

/* converted form of the rayquake sdf parsing code I wrote with Caz, which was in Zig */

struct Expr;

struct BinArgs { std::unique_ptr<Expr> lhs, rhs; };
struct SqrtArg { std::unique_ptr<Expr> arg; };
struct Constant { float val; };
struct Input    { unsigned idx; };

using ExprVariant = std::variant<
    Constant, Input,
    struct Add, struct Sub, struct Mul, struct Div,
    struct Min, struct Max, struct Sqrt
>;

struct Add  { std::unique_ptr<Expr> lhs, rhs; };
struct Sub  { std::unique_ptr<Expr> lhs, rhs; };
struct Mul  { std::unique_ptr<Expr> lhs, rhs; };
struct Div  { std::unique_ptr<Expr> lhs, rhs; };
struct Min  { std::unique_ptr<Expr> lhs, rhs; };
struct Max  { std::unique_ptr<Expr> lhs, rhs; };
struct Sqrt { std::unique_ptr<Expr> arg; };

struct Expr {
    std::variant<Constant, Input, Add, Sub, Mul, Div, Min, Max, Sqrt> v;

    template<typename T>
    Expr(T &&t) : v(std::forward<T>(t)) {}

    Expr(Expr &&) = default;
    Expr &operator=(Expr &&) = default;
    Expr(const Expr &) = delete;
    Expr &operator=(const Expr &) = delete;
};

static std::unique_ptr<Expr> box(Expr e) {
    return std::make_unique<Expr>(std::move(e));
}


Expr make_constant(float f) { return Expr{ Constant{ f } }; }
Expr make_input(unsigned i)  { return Expr{ Input{ i } }; }

Expr expr_add(Expr l, Expr r) { return Expr{ Add{ box(std::move(l)), box(std::move(r)) } }; }
Expr expr_sub(Expr l, Expr r) { return Expr{ Sub{ box(std::move(l)), box(std::move(r)) } }; }
Expr expr_mul(Expr l, Expr r) { return Expr{ Mul{ box(std::move(l)), box(std::move(r)) } }; }
Expr expr_div(Expr l, Expr r) { return Expr{ Div{ box(std::move(l)), box(std::move(r)) } }; }
Expr expr_min(Expr l, Expr r) { return Expr{ Min{ box(std::move(l)), box(std::move(r)) } }; }
Expr expr_max(Expr l, Expr r) { return Expr{ Max{ box(std::move(l)), box(std::move(r)) } }; }
Expr expr_sqrt(Expr a)        { return Expr{ Sqrt{ box(std::move(a)) } }; }


/* deep copy */
Expr clone(const Expr &e);

template<typename T>
std::unique_ptr<Expr> clone_box(const std::unique_ptr<T> &p) {
    return box(clone(*p));
}

Expr clone(const Expr &e) {
    return std::visit([](const auto &node) -> Expr {
        using T = std::decay_t<decltype(node)>;
        /* filter 0-ary */
        if constexpr (std::is_same_v<T, Constant> || std::is_same_v<T, Input>) {
            return Expr{ node };
        } else if constexpr (std::is_same_v<T, Sqrt>) {
            /* only unary */
            return expr_sqrt(clone(*node.arg));
        } else {
            /* all binary nodes have lhs and rhs */
            using R = T;
            return Expr{ R{ box(clone(*node.lhs)), box(clone(*node.rhs)) } };
        }
    }, e.v);
}

float expr_eval(const Expr &e, const float *vals) {
    return std::visit([&](const auto &node) -> float {
        using T = std::decay_t<decltype(node)>;
        if constexpr (std::is_same_v<T, Constant>) return node.val;
        if constexpr (std::is_same_v<T, Input>)    return vals[node.idx];
        if constexpr (std::is_same_v<T, Add>)  return expr_eval(*node.lhs, vals) + expr_eval(*node.rhs, vals);
        if constexpr (std::is_same_v<T, Sub>)  return expr_eval(*node.lhs, vals) - expr_eval(*node.rhs, vals);
        if constexpr (std::is_same_v<T, Mul>)  return expr_eval(*node.lhs, vals) * expr_eval(*node.rhs, vals);
        if constexpr (std::is_same_v<T, Div>)  return expr_eval(*node.lhs, vals) / expr_eval(*node.rhs, vals);
        if constexpr (std::is_same_v<T, Min>)  return std::min(expr_eval(*node.lhs, vals), expr_eval(*node.rhs, vals));
        if constexpr (std::is_same_v<T, Max>)  return std::max(expr_eval(*node.lhs, vals), expr_eval(*node.rhs, vals));
        if constexpr (std::is_same_v<T, Sqrt>) return std::sqrt(expr_eval(*node.arg, vals));
        return 0.f;
    }, e.v);
}

template<class... Ts>
struct overloads : Ts... { using Ts::operator()...; };

// symbolic differentiation
Expr expr_diff(const Expr &e, unsigned i) {
    return std::visit([&](const auto &node) -> Expr {
        using T = std::decay_t<decltype(node)>;

        /* Rules for diff:
         *  - Variable/Input: same? 1 : 0
         *  - Constant: 0
         *  - Addition/Subtraction: a' +/- b'
         *  - Mul: f'g + g'f
         *  - Div: (fg' - gf') / g^2
         *  - Sqrt: (1/2)f' / sqrt(f)
         *  - Min/Max: Identity
         */

        if constexpr (std::is_same_v<T, Input>)
            return make_constant(node.idx == i ? 1.f : 0.f);

        if constexpr (std::is_same_v<T, Constant>)
            return make_constant(0.f);

        if constexpr (std::is_same_v<T, Add>)
            return expr_add(expr_diff(*node.lhs, i), expr_diff(*node.rhs, i));

        if constexpr (std::is_same_v<T, Sub>)
            return expr_sub(expr_diff(*node.lhs, i), expr_diff(*node.rhs, i));

        if constexpr (std::is_same_v<T, Mul>)
            return expr_add(
                expr_mul(expr_diff(*node.lhs, i), clone(*node.rhs)),
                expr_mul(expr_diff(*node.rhs, i), clone(*node.lhs))
            );

        if constexpr (std::is_same_v<T, Div>)
            return expr_div(
                expr_add(
                    expr_mul(clone(*node.lhs), expr_diff(*node.rhs, i)),
                    expr_mul(expr_mul(clone(*node.rhs), expr_diff(*node.lhs, i)),
                             make_constant(-1.f))
                ),
                expr_mul(clone(*node.rhs), clone(*node.rhs))
            );

        if constexpr (std::is_same_v<T, Sqrt>)
            return expr_mul(
                expr_diff(*node.arg, i),
                expr_mul(make_constant(0.5f),
                         expr_div(make_constant(1.f), expr_sqrt(clone(*node.arg))))
            );

        if constexpr (std::is_same_v<T, Min> || std::is_same_v<T, Max>)
            return clone(e);

        return make_constant(0.f);
    }, e.v);
}


struct Dual { float val, d; };


/* Autodiff */

Dual expr_auto_diff(const Expr &e, const Dual *pos) {
    return std::visit([&](const auto &node) -> Dual {
        using T = std::decay_t<decltype(node)>;

        if constexpr (std::is_same_v<T, Input>)    return pos[node.idx];
        if constexpr (std::is_same_v<T, Constant>) return { node.val, 0.f };

        if constexpr (std::is_same_v<T, Add>) {
            auto l = expr_auto_diff(*node.lhs, pos), r = expr_auto_diff(*node.rhs, pos);
            return { l.val + r.val, l.d + r.d };
        }
        if constexpr (std::is_same_v<T, Sub>) {
            auto l = expr_auto_diff(*node.lhs, pos), r = expr_auto_diff(*node.rhs, pos);
            return { l.val - r.val, l.d - r.d };
        }
        if constexpr (std::is_same_v<T, Mul>) {
            auto l = expr_auto_diff(*node.lhs, pos), r = expr_auto_diff(*node.rhs, pos);
            return { l.val * r.val, l.val * r.d + l.d * r.val };
        }
        if constexpr (std::is_same_v<T, Div>) {
            auto l = expr_auto_diff(*node.lhs, pos), r = expr_auto_diff(*node.rhs, pos);
            return { l.val / r.val, (l.d * r.val - l.val * r.d) / (r.val * r.val) };
        }
        if constexpr (std::is_same_v<T, Sqrt>) {
            auto v = expr_auto_diff(*node.arg, pos);
            float sv = std::sqrt(v.val);
            return { sv, v.d / (2.f * sv) };
        }
        if constexpr (std::is_same_v<T, Min>) {
            auto l = expr_auto_diff(*node.lhs, pos), r = expr_auto_diff(*node.rhs, pos);
            return l.val < r.val ? l : r;
        }
        if constexpr (std::is_same_v<T, Max>) {
            auto l = expr_auto_diff(*node.lhs, pos), r = expr_auto_diff(*node.rhs, pos);
            return l.val > r.val ? l : r;
        }
        return { 0.f, 0.f };
    }, e.v);
}


void expr_ndiff(const Expr &e, const float *vals, float eps, float out[3]) {
    for (int k = 0; k < 3; k++) {
        float vp[3] = { vals[0], vals[1], vals[2] };
        float vm[3] = { vals[0], vals[1], vals[2] };
        vp[k] += eps; vm[k] -= eps;
        out[k] = (expr_eval(e, vp) - expr_eval(e, vm)) / (2.f * eps);
    }
}

void expr_print(const Expr &e, std::ostream &os = std::cout) {
    const auto visitor = overloads {
        [&](const Input& node) { os << "x" << node.idx; },
        [&](const Constant& node) { os << node.val; },
        [&](const Sqrt& node)  { os << "(sqrt "; expr_print(*node.arg, os); os << ")"; },
        [&](const auto& node) {
            using T = std::decay_t<decltype(node)>;
            if constexpr (std::is_same_v<T, Input>)    { os << "x" << node.idx; return; }
            if constexpr (std::is_same_v<T, Constant>) { os << node.val; return; }
            if constexpr (std::is_same_v<T, Sqrt>) {
                os << "(sqrt "; expr_print(*node.arg, os); os << ")"; return;
            }

            const char *op =
                std::is_same_v<T, Add> ? "+" :
                std::is_same_v<T, Sub> ? "-" :
                std::is_same_v<T, Mul> ? "*" :
                std::is_same_v<T, Div> ? "/" :
                std::is_same_v<T, Min> ? "min" : "max";

            os << "(" << op << " ";
            expr_print(*node.lhs, os);
            os << " ";
            expr_print(*node.rhs, os);
            os << ")";
        }
    };

    std::visit(visitor, e.v);
}

static size_t gen_glsl_step(
    const Expr &e,
    std::unordered_map<const Expr *, size_t> &visited,
    std::ostringstream &ss)
{
    auto it = visited.find(&e);
    if (it != visited.end()) return it->second;

    size_t idx = visited.size();
    visited[&e] = idx;

    std::ostringstream tmp;

    const auto visitor = overloads {
        [&](const Constant& node) -> std::string  {
            tmp << "vec2(" << node.val << ", 0.0)";
            return tmp.str();
        },
        [&](const Input& node)-> std::string  {
            switch (node.idx) {
                case 0: return "vec2(v.x, vp.x)";
                case 1: return "vec2(v.y, vp.y)";
                case 2: return "vec2(v.z, vp.z)";
                default: return "vec2(0.0, 0.0)";
            }
        },
        [&](const Sqrt& node) -> std::string  {
            size_t a = gen_glsl_step(*node.arg, visited, ss);
            tmp << "nsqrt(_" << a << ")";
            return tmp.str();
        },
        [&](const auto& node) -> std::string {
            /* binops */
            size_t l = gen_glsl_step(*node.lhs, visited, ss);
            size_t r = gen_glsl_step(*node.rhs, visited, ss);

            using T = std::decay_t<decltype(node)>;
        
            const char *fn =
                std::is_same_v<T, Add> ? nullptr :
                std::is_same_v<T, Sub> ? nullptr :
                std::is_same_v<T, Mul> ? "nmul" :
                std::is_same_v<T, Div> ? "ndiv" :
                std::is_same_v<T, Min> ? "nmin" : "nmax";

            if constexpr (std::is_same_v<T, Add>)
                tmp << "(_" << l << " + _" << r << ")";
            else if constexpr (std::is_same_v<T, Sub>)
                tmp << "(_" << l << " - _" << r << ")";
            else
                tmp << fn << "(_" << l << ", _" << r << ")";

            return tmp.str();
        }
    };
    
    std::string rhs = std::visit(visitor, e.v);

    ss << "vec2 _" << idx << " = " << rhs << ";\n";
    return idx;
}

std::string gen_glsl(const Expr &e) {
    std::unordered_map<const Expr *, size_t> visited;
    std::ostringstream ss;
    size_t last = gen_glsl_step(e, visited, ss);
    ss << "return _" << last << ";";
    return ss.str();
}


static void skip_ws(const std::string &s, size_t &i) {
    while (i < s.size() && std::isspace((unsigned char)s[i])) i++;
}

static bool is_op_char(char c) {
    return std::isalpha((unsigned char)c) || std::string("+-/*").find(c) != std::string::npos;
}

static std::string parse_op_str(const std::string &s, size_t &i) {
    size_t start = i;
    while (i < s.size() && is_op_char(s[i])) i++;
    return s.substr(start, i - start);
}

static Expr parse_arg(const std::string &s, size_t &i,
                      std::unordered_map<std::string, unsigned> &vm);

static Expr parse_iden(const std::string &s, size_t &i,
                       std::unordered_map<std::string, unsigned> &vm) {
    size_t start = i;
    while (i < s.size() && std::isalnum((unsigned char)s[i])) i++;
    std::string name = s.substr(start, i - start);
    auto [it, inserted] = vm.emplace(name, (unsigned)vm.size());
    return make_input(it->second);
}

static Expr parse_num(const std::string &s, size_t &i) {
    bool neg = false;
    if (i < s.size() && s[i] == '-') { neg = true; ++i; }
    float num = 0.f;
    int frac_digits = 0;
    size_t start = i;
    bool saw_digit = false;


    while (i < s.size() && std::isdigit((unsigned char)s[i])) {
        saw_digit = true;
        num = num * 10.f + (s[i] - '0');
        ++i;
    }

    if (i < s.size() && s[i] == '.') {
        ++i;
        while (i < s.size() && std::isdigit((unsigned char)s[i])) {
            saw_digit = true;
            num = num * 10.f + (s[i] - '0');
            ++i;
            ++frac_digits;
        }
    }

    if (!saw_digit) {
        return make_constant(0.0f);
    }

    if (frac_digits > 0) {
        num /= std::pow(10.f, (float)frac_digits);
    }

    if (neg) num = -num;
    return make_constant(num);
}



static Expr parse_expr(const std::string &s, size_t &i,
                       std::unordered_map<std::string, unsigned> &vm) {
    skip_ws(s, i);
    assert(s[i] == '('); i++;
    skip_ws(s, i);

    std::string op = parse_op_str(s, i);

    Expr e = [&]() -> Expr {
        if (op == "sqrt") return expr_sqrt(parse_arg(s, i, vm));
        Expr lhs = parse_arg(s, i, vm);
        Expr rhs = parse_arg(s, i, vm);
        if (op == "+")   return expr_add(std::move(lhs), std::move(rhs));
        if (op == "-")   return expr_sub(std::move(lhs), std::move(rhs));
        if (op == "*")   return expr_mul(std::move(lhs), std::move(rhs));
        if (op == "/")   return expr_div(std::move(lhs), std::move(rhs));
        if (op == "min") return expr_min(std::move(lhs), std::move(rhs));
        if (op == "max") return expr_max(std::move(lhs), std::move(rhs));
        std::cerr << "Unknown op: " << op << "\n"; std::exit(1);
    }();

    skip_ws(s, i);
    assert(s[i] == ')'); i++;
    return e;
}

static Expr parse_arg(const std::string &s, size_t &i,
                      std::unordered_map<std::string, unsigned> &vm) {
    skip_ws(s, i);
    char c = s[i];
    if (c == '(')                           return parse_expr(s, i, vm);
    if (std::isalpha((unsigned char)c))     return parse_iden(s, i, vm);
    if (std::isdigit((unsigned char)c) || c == '-' || c == '.')
                                            return parse_num(s, i);
    std::cerr << "Unexpected char '" << c << "'\n"; std::exit(1);
}

Expr expr_parse(const std::string &src) {
    std::unordered_map<std::string, unsigned> vm;
    size_t i = 0;
    skip_ws(src, i);
    return parse_expr(src, i, vm);
}


Expr sdf_length(Expr pos[3]) {
    return expr_sqrt(
        expr_add(
            expr_mul(clone(pos[0]), clone(pos[0])),
            expr_add(
                expr_mul(clone(pos[1]), clone(pos[1])),
                expr_mul(clone(pos[2]), clone(pos[2]))
            )
        )
    );
}

Expr sdf_sphere(Expr pos[3], float radius) {
    return expr_sub(sdf_length(pos), make_constant(radius));
}

Expr sdf_box(Expr p[3], float b[3]) {
    Expr q[3] = {
        expr_sub(expr_sqrt(expr_mul(clone(p[0]), clone(p[0]))), make_constant(b[0])),
        expr_sub(expr_sqrt(expr_mul(clone(p[1]), clone(p[1]))), make_constant(b[1])),
        expr_sub(expr_sqrt(expr_mul(clone(p[2]), clone(p[2]))), make_constant(b[2])),
    };
    Expr inner[3] = {
        expr_max(clone(q[0]), make_constant(0.f)),
        expr_max(clone(q[1]), make_constant(0.f)),
        expr_max(clone(q[2]), make_constant(0.f)),
    };
    return expr_add(
        sdf_length(inner),
        expr_min(
            expr_max(clone(q[0]), expr_max(clone(q[1]), clone(q[2]))),
            make_constant(0.f)
        )
    );
}

/*
int main() {
    Expr x = make_input(0), y = make_input(1), z = make_input(2);
    Expr x2 = expr_mul(clone(x), clone(x));
    Expr y2 = expr_mul(clone(y), clone(y));
    Expr z2 = expr_mul(clone(z), clone(z));
    Expr l  = expr_sqrt(expr_add(expr_add(std::move(x2), std::move(y2)), std::move(z2)));
    Expr dl = expr_diff(l, 0);

    expr_print(l);  std::cout << "\n";
    expr_print(dl); std::cout << "\n";

    float v[3] = { 0.5f, 0.3f, 0.2f };
    std::cout << expr_eval(l, v) << "\n";

    std::string src =
        "(min"
        "  (- y 1)"
        "  (max"
        "      (max x (+ -9 (* -1 x)))"
        "      (* -1 (- (sqrt (+ (+ (* x x) (* y y)) (* z z))) 15))"
        "   ))";

    Expr parsed = expr_parse(src);
    expr_print(parsed);
    std::cout << "\n";

    std::cout << "\nGLSL:\n" << gen_glsl(l) << "\n";

    return 0;
}
*/
