/* Object.hpp
 *
 * author: Roland Conybeare, Jul 2025
 */

#pragma once

#include "xo/allocutil/IObject.hpp"
#include "xo/reflect/TaggedPtr.hpp"
#include "xo/allocutil/ObjectVisitor.hpp"
#include "xo/allocutil/gc_ptr.hpp"
#include <concepts>
#include <cstdint>

namespace xo {
    namespace gc {
        class IAlloc;
        class GC;
        class ObjectStatistics;
    };

    /** Root class for all xo GC-collectable objects.
     *
     *  Design notes:
     *  1. xo::IObject -> xo-allocutil  header-only library
     *     xo::Object  -> xo-alloc      ordinary library
     *  2. relying on inheritance means we insist that GC traits
     *     for a type appear directly in that type's vtable, and at specific locations.
     *     This implies one level of indirection when GC traverses an instance.
     *  3. Could adapt a gc-aware XO library (such as xo-ordinaltree)
     *     to a non-xo garbage collector.
     *     Would still need to use xo::IObject and xo::gc::gc_allocator_traits,
     *     but not necessarily xo::Object
     *  4. Would be feasible to relax the must-inherit-from-Object constraint
     *     by having GC use its own wrapper, at cost of an extra layer of indirection
     **/
    class Object : public IObject {
    public:
        using TaggedPtr = xo::reflect::TaggedPtr;

    public:
        static gp<Object> from(gp<IObject> x) {
            return dynamic_cast<Object*>(x.ptr());
        }

        virtual ~Object() noexcept = default;

        /** memory allocator for objects.  Likely this will be a GC instance,
         *  but simple arena also supported.
         *
         *  Load-bearing for .assign_member()
         **/
        static gc::IAlloc * mm;

        /** assign value @p rhs to member @p *lhs of @p parent.
         *  if assignment creates a cross-generational or cross-checkpoint pointer,
         *  add mutation log entry.
         *
         *  DEPRECATED. prefer IObject::_gc_assign_member, for explicit alloc
         **/
        template <typename T>
        static void assign_member(gp<IObject> parent, gp<T> * lhs, gp<IObject> rhs);

        /** use from GC aux functions **/
        static gc::GC * _gc() { return reinterpret_cast<gc::GC*>(mm); }

        /** during GC
         *  1. copy destination object @p *addr to (new) to-space.
         *  2. overwrite existing object @p *addr with a forwarding pointer to
         *     copy made in step 1.
         *  3. return the location of the copy make in step 1.
         *
         *  @p src.        source object to be forwarded
         *  @p gc.         allocator (poassibly garbage collector)
         */
        static IObject * _forward(IObject * src, gc::IAlloc * gc);

        template <typename T>
        static void _forward_inplace(T ** src_addr, gc::IAlloc * gc) {
            IObject * fwd = _forward(*src_addr, gc);

            *src_addr = reinterpret_cast<T *>(fwd);
        }

        template <typename T>
        static void _forward_inplace(gp<T> & src, gc::IAlloc * gc) {
            _forward_inplace<T>(src.ptr_address(), gc);
        }

        /** primary workhorse for garbage collection.
         *
         *  we assign each object one of three colors: black|gray|white.
         *
         *  color | location   | children   | action                  |
         *  ------+------------+------------+-------------------------+
         *  black | from-space | any        | move to to-space        |
         *  gray  |   to-space | any        | move remaining children |
         *  white |   to-space | white/gray | done                    |
         *
         *  initially all reachable objects are black.
         *  GC is complete when all reachable objects are white.
         *  GC needs a variable amount of temporary storage to keep track of all gray objects
         *
         *  Evacuate reachable object graph rooted at @p src to to-space.
         *  On return all objects reachable from @p src are white
         *
         *  @param src     address of object to evacuate
         *  @param gc      garbage collector
         *  @param stats   per-object-type GC statistics
         **/
        static IObject * _deep_move(IObject * src, gc::GC * gc, gc::ObjectStatistics * stats);

        /** copy @p src to to-space. Overwrite original with forwarding pointer to new location.
         *  return the new location
         **/
        static IObject * _shallow_move(IObject * src, gc::IAlloc * gc);

        // Reflection support

        /** tagged pointer with runtime type information
         **/
        virtual TaggedPtr self_tp() const;

        /** print on stream @p os **/
        virtual void display(std::ostream & os) const;

        // Inherited from IObject..

        //virtual bool _is_forwarded() const override { return false; }
        //virtual IObject * _offset_destination(IObject * src) const override { return src; };
        virtual void _forward_to(IObject * dest) override;
        //virtual IObject * _destination() override { return nullptr; }

        virtual std::size_t _shallow_size() const override = 0;
        virtual IObject * _shallow_copy(gc::IAlloc * gc) const override = 0;
        virtual std::size_t _forward_children(gc::IAlloc * gc) override = 0;
    };

    static_assert(std::is_destructible_v<Object>, "Object must be destructible");
    static_assert(std::is_nothrow_destructible_v<Object>, "Object must be noexcept destructible");

    template <typename T>
    void
    Object::assign_member(gp<IObject> parent, gp<T> * lhs, gp<IObject> rhs)
    {
        Object::mm->assign_member(reinterpret_cast<IObject *>(parent.ptr()),
                                  reinterpret_cast<IObject **>(lhs->ptr_address()),
                                  reinterpret_cast<IObject *>(rhs.ptr()));
    }

    namespace gc {
        template <typename T>
        class ObjectVisitor<gp<T>> {
        public:
            static void forward_children(gp<T> & target,
                                         IAlloc * gc)
            {
                Object::_forward_inplace(target, gc);
            }
        };
    }

    std::ostream &
    operator<< (std::ostream & os, gp<Object> x);

} /*namespace xo*/

void * operator new (std::size_t z, const xo::Cpof & copy);

/* end Object.hpp */
