/** @file VirtualSchematikaMachine.hpp **/

#pragma once

#include "VsmInstr.hpp"
#include "VsmStackFrame.hpp"
#include "SchematikaError.hpp"
#include "GlobalEnv.hpp"
#include "xo/expression/Expression.hpp"
#include "xo/object/ObjectConverter.hpp"
#include "xo/alloc/Object.hpp"

namespace xo {
    namespace scm {
        /** @brief state that may be shared across VirtualSchematikaMachine instances **/
        struct VirtualSchematikaMachineFlyweight {
            explicit VirtualSchematikaMachineFlyweight(gc::IAlloc * mm,
                                                       gp<GlobalEnv> env,
                                                       log_level log_level);

            /** memory allocator for interpreter operation. **/
            gc::IAlloc * object_mm_ = nullptr;
            /** global environment **/
            gp<GlobalEnv> toplevel_env_;
            /** convert TaggedPtr->Object **/
            xo::obj::ObjectConverter object_converter_;
            /** control logging level. higher values -> more logging **/
            log_level log_level_;
        };

        /** @class VirtualSchematikaMachine
         *  @brief Virtual machine implementing a Schematika interpreter
         *
         **/
        class VirtualSchematikaMachine {
        public:
            using IAlloc = xo::gc::IAlloc;

        public:
            VirtualSchematikaMachine(IAlloc * mm, gp<GlobalEnv> toplevel_env, log_level log_level);
            ~VirtualSchematikaMachine();

            gp<GlobalEnv> toplevel_env() const { return flyweight_.toplevel_env_; }

            /** evaluate expression @p expr.
             *  borrows calling thread until completion
             *  return [value, error]. error ignored unless value is nullptr.
             *  conversely when value is nullptr, error gives details of original
             *  error.
             *
             *  Evaluate schematika expression @p expr in environment @p env
             **/
            std::pair<gp<Object>, SchematikaError> eval(bp<Expression> expr, gp<GlobalEnv> env);

            /** evaluate expression @p expr in toplevel environment **/
            std::pair<gp<Object>, SchematikaError> toplevel_eval(bp<Expression> expr);

        private:
            /** Not moveable or copyable.
             *  One constraint is member variables added to flyweight_.object_mm_
             *  as GC roots, with no provision for unwinding later.
             **/
            VirtualSchematikaMachine(const VirtualSchematikaMachine &) = delete;
            VirtualSchematikaMachine(VirtualSchematikaMachine &&) = delete;

            /** borrow calling thread to run schematika machine
             *  indefinitely, or until null continuation
             **/
            void run();

            /** execute vsm instruction in program counter.
             *  Note: may possibly be able to replace with just opcode
             *
             *  Registers:
             *  - expr_  input, caller saves
             *  - env_   input, caller saves
             *  - cont_  input, caller saves
             *  - stack_ input, caller saves
             *  - value_ output
             *  - error_ output
             *
             **/
            void execute_one();

            /* design note:
             * - eval_xxx_op() methods are primary VSM transitions,
             *   in the sense that they begin a sequence of transitions to interpret a
             *   particular kind of expression
             * - do_xxx_op() methods represent secondary VSM transitions,
             *   that continue an instruction sequence that was initiated by a preceding
             *   eval_xxx_op() method
             */

            /** interpret literal constant expression **/
            void eval_constant_op();

            /** interpreter literal primitive expression
             *  (these appear implicitly as result of builtin operators like {+, ==, ..})
             **/
            void eval_primitive_op();

            /** execute define expression (finished in do_complete_assign_op()) **/
            void eval_define_op();
            /** execute assign expression (finishes in do_complete_assign_op()) **/
            void eval_assign_op();
            /** continue after establishing value fo rhs of define exprsssion **/
            void do_complete_assign_op();

            /** interpret variable expression **/
            void eval_variable_op();

            /** interpret if-expression **/
            void eval_ifexpr_op();
            /** continue after establish value of test expression **/
            void do_complete_ifexpr_op();

            /** interpret sequence **/
            void eval_sequence_op();
            /** continue after establishing value for a sequence element **/
            void do_complete_sequence_op();

            /** interpret apply-expression (i.e. function call) **/
            void eval_apply_op();
            /** continue assembling args for a function call;
             *  transition to (interpretation of) called function once all arguments
             *  are evaluated.
             **/
            void do_complete_evalargs_op();

            /** execute function application, given actuals in top stack frame **/
            void apply_op();

            /** goto error state with message @p err **/
            void report_error(const std::string & err);

            /** implementation class; contains instruction implementations **/
            friend struct VsmOps;

        private:
            /** program counter.
             *  (Perhaps replace with VsmInstr::Opcode ?)
             **/
            const VsmInstr * pc_ = nullptr;

            /** register to hold Schematika expression to drive @ref execute_one.
             *
             *  caller saves!
             **/
            rp<Expression> expr_;
            /** holds bindings for all schematika variables, to drive @ref execute_one.
             *  execute_one will not save this
             *
             *  caller saves!
             **/
            gp<GlobalEnv> env_;

            /** vsm stack.  callee saves!
             **/
            gp<VsmStackFrame> stack_;

            /** non-error result value from eval() / apply()
             *
             *  output register: caller must save
             **/
            gp<Object> value_;

            /** error result value from eval() / apply()
             *
             *  output regisetr: caller must save
             **/
            SchematikaError error_;

            /** continuation
             *  (Perhaps replace with VsmInstr::Opcode ?)
             *
             *  input register: callee saves!
             **/
            const VsmInstr * cont_ = nullptr;

            /** possibly-shared data **/
            VirtualSchematikaMachineFlyweight flyweight_;
        };

    } /*namespace scm*/
} /*namespace xo*/

/* end VirtualSchematikaMachine.hpp */
