/** @file VirtualSchematikaMachine.hpp
 *
 *  @author Roland Conybare, Jan 2026
 **/

#pragma once

#include "VsmConfig.hpp"
#include "VsmInstr.hpp"
#include "VsmFrame.hpp"
#include "DLocalEnv.hpp"
#include "DGlobalEnv.hpp"
#include <xo/reader2/SchematikaReader.hpp>
#include <xo/expression2/Expression.hpp>
#include <xo/gc/GCObject.hpp>
#include <xo/facet/box.hpp>

namespace xo {
    namespace scm {
        struct EvaluationError {
            /** source location (in vsm implementation) at which error identified **/
            std::string_view src_function_;
            /** error description (allocated from ErrorArena) **/
            std::string_view error_description_;
            // TODO: info about location in schematika source
        };

        /** similar to @ref xo::scm::ReaderResult **/
        struct VsmResult {
            using AGCObject = xo::mm::AGCObject;
            using span_type = xo::mm::span<const char>;

            VsmResult() = default;
            VsmResult(obj<AGCObject> value) : result_{value} {}
            VsmResult(TokenizerError err) : result_{err} {}

            bool is_value() const { return std::holds_alternative<obj<AGCObject>>(result_); }
            bool is_tk_error() const { return std::holds_alternative<TokenizerError>(result_); }
            bool is_eval_error() const { return std::holds_alternative<EvaluationError>(result_); }

            const obj<AGCObject> * value() const { return std::get_if<obj<AGCObject>>(&result_); }

            /** result of evaluating first expression encountered in input **/
            std::variant<obj<AGCObject>, TokenizerError, EvaluationError> result_;
        };

        /** vsm result + reamining span **/
        struct VsmResultExt : public VsmResult {
            using span_type = VsmResult::span_type;

            VsmResultExt() = default;
            VsmResultExt(const VsmResult & result, span_type rem) : VsmResult{result}, remaining_{rem} {}

            /** unconsumed portion of input **/
            VsmResult::span_type remaining_;
        };

        /** @class VirtualSchematikaMachine
         *  @brief virtual machine for schematika
         **/
        class VirtualSchematikaMachine {
        public:
            // will be DArenaVector<obj<StackFrame>> probably
            using Stack = void *;
            using AAllocator = xo::mm::AAllocator;
            using AGCObject = xo::mm::AGCObject;
            using MemorySizeVisitor = xo::mm::MemorySizeVisitor;
            using span_type = xo::mm::span<const char>;

        public:
            VirtualSchematikaMachine(const VsmConfig & config);

            /** visit vsm-owned memory pools; call visitor(info) for each **/
            void visit_pools(const MemorySizeVisitor & visitor) const;

            /** begin interactive session. **/
            void begin_interactive_session();
            /** begin batch session **/
            void begin_batch_session();

            /** consume input @p input_cstr.
             *  Require: must first start interactive/batch session
             **/
            VsmResultExt read_eval_print(span_type input_span, bool eof);

            /** evaluate expression @p expr
             *  Require: must first start interactive/batch session
             **/
            VsmResult start_eval(obj<AExpression> expr);

            /** borrow calling thread to run indefinitely,
             *  until halt instruction
             **/
            void run();

            /** execute vsm instruction in @ref pc_.
             *  @retval instruction count. 1 unless pc_ is halt.
             **/
            bool execute_one();

        private:
            /** Require:
             *  - expression in @ref expr_
             **/
            void _do_eval_op();

            /** evaluate a constant expression
             *  Require:
             *  - expression in @ref expr_
             **/
            void _do_eval_constant_op();

            /** evaluate a define-expression
             *  Require:
             *  - expression in @ref expr_
             **/
            void _do_eval_define_op();

            /** evaluate a lambda expression
             *  Require:
             *  - expression in @ref expr_
             **/
            void _do_eval_lambda_op();

            /** evaluate variable expression (definition)
             *  Require:
             *  - expression in @ref expr_
             **/
            void _do_eval_variable_op();

            /** evaluate a variable reference (use after definition)
             *  Require:
             *  - expression in @ref expr_
             **/
            void _do_eval_varref_op();

            /** evaluate an apply expression
             *  Require:
             * - expression in @ref expr_
             **/
            void _do_eval_apply_op();

            /** evaluate an if-else expression
             *  Require:
             * - expression in @ref expr_
             **/
            void _do_eval_if_else_op();

            /** evaluate a sequence expression
             *  Require:
             * - expression in @ref expr_
             **/
            void _do_eval_sequence_op();

            /** apply a function to evaluated arguments **/
            void _do_apply_op();

            /** evaluate arguments on behalf of a function call
             *  Require:
             * - expression value in @ref value_
             * - stack:
             *    [0] VsmEvalArgsFrame
             *    [1] VsmApplyFrame
             *    ...
             **/
            void _do_evalargs_op();

        private:
            /*
             * Some registers are preserved by evaluation:
             *   stack_
             *   cont_
             *
             * Other registers are not preserved
             *   pc_
             *   expr_
             *   fn_
             *   args_
             *   value_
             */

            /** configuration **/
            VsmConfig config_;

            /** allocator (likely collector) for
             *  expressions and values
             **/
            box<AAllocator> mm_;

            /** runtime context for this vsm.
             *  For example, provides allocator to primitives
             **/
            box<ARuntimeContext> rcx_;

            // consider separate allocator (which _may_ turn out to be the same)
            // for VM stack. Only works for code that doesn't rely on fancy
            // lexical scoping

            // consider separate allocator for reader (i.e. program code)
            // and data (program execution)

            /** reader: text -> expression **/
            SchematikaReader reader_;

            /** program counter **/
            VsmInstr pc_ = VsmInstr::c_halt;

            /** stack pointer **/
            obj<AGCObject> stack_;

            /** expression register **/
            obj<AExpression> expr_;

            /** environment pointer.  Provides bindings
             *  for surrounding lexical scope at this point
             *  in execution
             **/
            DLocalEnv * local_env_ = nullptr;

        protected:  // temporarily, to appease compiler
            /** environment pointer.  Maintains bindings
             *  for global variables.
             **/
            DGlobalEnv * global_env_ = nullptr;

        private:
            /** function to call **/
            obj<AProcedure> fn_;
            /** evaluated argument list **/
            DArray * args_;

            /** result register **/
            VsmResult value_;

            /** continuation register **/
            VsmInstr cont_ = VsmInstr::c_halt;
        };
    } /*namespace scm*/
} /*namespace xo*/

/* end VirtualSchematikaMachine.hpp */
