/** @file GlobalEnv.cpp **/

#include "GlobalEnv.hpp"
#include "xo/reflect/Reflect.hpp"

namespace xo {
    using xo::reflect::Reflect;
    using xo::reflect::TaggedPtr;

    namespace scm {
        gp<GlobalEnv>
        GlobalEnv::make_empty(gc::IAlloc * mm, const rp<GlobalSymtab> & symtab)
        {
            /* by design: GlobalEnv and GlobalEnv.slot_map_ are heap-allocated */

             return new GlobalEnv(mm, symtab);
        }

        GlobalEnv::GlobalEnv(const GlobalEnv & x)
            : mm_{x.mm_},
              symtab_{x.symtab_},
              slot_map_{std::make_unique<map_type>(*x.slot_map_)}
        {
        }

        GlobalEnv::GlobalEnv(gc::IAlloc * mm,
                             const rp<GlobalSymtab> & symtab) : mm_{mm},
                                                                symtab_{symtab},
                                                                slot_map_{std::make_unique<map_type>()}
        {}

        bool
        GlobalEnv::local_contains_var(const std::string & vname) const
        {
            return symtab_->lookup_local(vname).get();
        }

        gp<Object> *
        GlobalEnv::lookup_slot(const std::string & vname)
        {
            scope log(XO_DEBUG(true), xtag("name", vname));

            assert(slot_map_.get());

            auto ix = slot_map_->find(vname);

            if (ix == slot_map_->end()) {
                return nullptr;
            } else {
                log && log("binding found", xtag("vname", vname));
                return &(ix->second);
            }
        }

        gp<Object> *
        GlobalEnv::establish_var(bp<Variable> var)
        {
            scope log(XO_DEBUG(true), xtag("name", var->name()), xtag("type", var->valuetype()));

            // Warning: altering declared type for an already-existing variable
            // invalidates any type checking that relied on that variable.
            //
            // Ignoring this problem for now.
            //
            // Actual solution might look like:
            // - keep track of which functions/defs depend on each global variable.
            // - invalidate any jit / types for such variables.
            // - maybe use seqno's to track
            // - re-check / re-complie
            // - need to admit invalid states.
            //   suppose have mutually recursive functions f(), g()
            //   want ability to modify type signatures separately
            //
            // Alternatives:
            // - forbid changing type of an already-established variable
            //   Actually: can't even change values if we intend supporting dependent types
            // - quietly number variables so new definitions shadow old ones but don't
            //   affect previously-encountered expressions

            this->symtab_->require_global(var->name(), var);

            gp<Object> &slot = (*this->slot_map_)[var->name()];

            /* discard any pre-existing value, we're redefining a variable */
            slot = gp<Object>();

            return &slot;
        }

        TaggedPtr
        GlobalEnv::self_tp() const
        {
            return Reflect::make_tp(const_cast<GlobalEnv *>(this));
        }

        void
        GlobalEnv::display(std::ostream & os) const
        {
            os << "<global-env" << xtag("n", slot_map_->size()) << ">";
        }

        std::size_t
        GlobalEnv::_shallow_size() const
        {
            return sizeof(GlobalEnv);
        }

        Object *
        GlobalEnv::_shallow_copy(gc::IAlloc * mm) const
        {
            Cpof cpof(mm, this);

            return new (cpof) GlobalEnv(*this);
        }

        std::size_t
        GlobalEnv::_forward_children(gc::IAlloc * gc)
        {
            for (auto & ix : *slot_map_) {
                Object::_forward_inplace(ix.second, gc);
            }
            return _shallow_size();
        }
    } /*namespace scm*/
} /*namespace xo*/

/* end GlobalEnv.cpp */
