#include "core.h"
#include "alotalot.h"
#include <string.h>
#include <jsonincpp/string_representation.h>
#include <assert.h>

namespace nytl {
    struct Ditch {
        std::string result;
        size_t cur_line_width = 0;

        void append(const std::string& text) {
            size_t n = result.size();
            result.resize(n + text.size());
            memcpy((void*)&result.c_str()[n], text.c_str(), text.size());
            for (char ch: text) {
                if (ch == '\n')
                    cur_line_width = 0;
                else
                    cur_line_width++;
            }
        }
    };

#define RFrame_passed const global_elem_set_t& elem_ns, Ditch& result, const std::function<std::string(std::string)>& escape
    struct RFrame {
        virtual uptr<RFrame> toMe(bool returned, RFrame_passed) {assert(false);}

        virtual ~RFrame() = default;
    };

    struct RFrame_OverParts : public RFrame{
        std::string name;
        std::vector<LocalVarValue> passed_args;
        /* This parameter incapsulates `cur_line_width` at some point for multiline `put-parts` */
        size_t multiline_put_start = 0;
        /* main iterator of this frame. Persistent across control returns */
        size_t part_to_do = 0;

        RFrame_OverParts(const std::string &name, const std::vector<LocalVarValue> &passed_args,
            size_t multiline_put_start)
            : name(name),
              passed_args(passed_args),
              multiline_put_start(multiline_put_start) {
        }

        uptr<RFrame> toMe(bool returned, RFrame_passed) override {
            if (!returned)
                ASSERT(elem_ns.count(name) == 1, "No such element");
            const Element& el = elem_ns.at(name);
            if (!returned) {
                /* Continue to do checks */
                size_t n = el.arguments.size();
                ASSERT(n == passed_args.size(), "Argument count mismatch");
                for (size_t i = 0; i < n; i++) {
                    if (el.arguments[i].type == json::true_symbol) {
                        ASSERT(passed_args[i].is_json, "Expected json element argument, got element");
                    } else {
                        // If not json is expected, element must be expected
                        assert(el.arguments[i].isArray());
                        ASSERT(!passed_args[i].is_json, "Expected element element arguemnt, got json");
                        ASSERT(elem_ns.count(passed_args[i].EL_name), "No such element, can't compare signatures of argument value");
                        const Element& arg_element = elem_ns.at(passed_args[i].EL_name);
                        // ASSERT(passed_args);
                        if(el.arguments[i].asArray() != arg_element.arguments)
                            THROW("Signature of argument " + std::to_string(i) + " does not match");
                    }
                }
            }
            if (el.base) {
                assert(!returned);
                assert(passed_args.size() == 1);
                const json::JSON* X = passed_args[0].JSON_subval;
                assert(X);
                if (name == "jesc") {
                    result.append(escape(json::generate_str(*X, json::print_pretty)));
                } else if (name == "str2text") {
                    ASSERT(X->isString(), "str2text takes json string");
                    result.append(escape(X->asString()));
                } else if (name == "str2code") {
                    ASSERT(X->isString(), "str2code takes json string");
                    result.append(X->asString());
                }
                return NULL;
            }
            while (true) {
                if (part_to_do == el.parts.size())
                    return NULL;
                const ElementPart& cur_part = el.parts[part_to_do++];
                if (cur_part.type == element_part_types::code) {
                    const ElementPart::when_code_S& pt = cur_part.when_code;
                    result.append(pt.lines);
                } else if (cur_part.type == element_part_types::put) {
                    const ElementPart::when_put_S& pt = cur_part.when_put;
                    LocalVarValue called_element_expv = rendering_core_execute_expression(elem_ns, passed_args, pt.called_element);
                    ASSERT(!called_element_expv.is_json, "Can't PUT json variable");
                    size_t AN = pt.passed_arguments.size();
                    std::vector<LocalVarValue> passed_arguments_expv(AN);
                    for (size_t i = 0; i < AN; i++)
                        passed_arguments_expv[i] = rendering_core_execute_expression(elem_ns, passed_args, pt.passed_arguments[i]);
                    return uptr<RFrame>(new RFrame_OverParts(called_element_expv.EL_name, passed_arguments_expv, result.cur_line_width));
                } else if (cur_part.type == element_part_types::for_put) {
                    const ElementPart::when_for_put_S& pt = cur_part.when_for_put;
                    // todo
                } else if (cur_part.type == element_part_types::ref_put) {
                    const ElementPart::when_ref_put_S& pt = cur_part.when_ref_put;
                    // todo
                }
            }
        }
    };

    std::string rendering_core(const std::string& entry_func, const std::vector<json::JSON>& entry_arguments,
            const global_elem_set_t& elem_ns, const std::function<std::string(std::string)>& escape)
    {
        Ditch result;

        std::vector<uptr<RFrame>> stack;

        {
            size_t AN = entry_arguments.size();
            std::vector<LocalVarValue> entry_arguments_conv(AN);
            for (size_t i = 0; i < AN; i++)
                entry_arguments_conv[i] = {true, "", &entry_arguments[i]};
            // stack.push_back(std::make_unique<>());
            // make_frame(entry_func, entry_arguments_conv);
        }
        while (!stack.empty()) {
            // Frame& cur = *stack.back();

        }
        return result.result;
    }
}