/** @file ObjectConverter.hpp
 *
 *  @author Roland Conybeare, Nov 2025
 **/

#pragma once

#include "Object.hpp"
#include "xo/reflect/Reflect.hpp"
//#include "xo/reflect/TaggedPtr.hpp"
#include "xo/reflect/TypeDrivenMap.hpp"

namespace xo {
    namespace obj {
        /* Convert between xo::reflect::TaggedPtr and xo::Object for
         * a particular wrapped c++ type
         */
        struct Converter {
            using TaggedPtr = xo::reflect::TaggedPtr;
            using ConvertToObjectFn = gp<Object> (*)(gc::IAlloc *, const TaggedPtr &);

        public:
            Converter() = default;
            explicit Converter(ConvertToObjectFn f) : cvt_to_object_{f} {}

            /** convert tagged pointer @p tp to new object,
             *  allocated via @p mm.
             *
             *  Conversion will typically be for some specific type;
             *  see @ref ObjectConverter
             **/
            ConvertToObjectFn cvt_to_object_ = nullptr;
        };

        /** @class ObjectConverter
         *  @brief Conversion to/from Object
         *
         *  For some instance of type T:
         *
         *  @code
         *  ObjectConverter & converters = ...;
         *  T x = ...;
         *  TaggedPtr tp = Reflect::make_tp(&x);
         *  TypeId tid = tp.td()->id();
         *
         *  const Converter * cvt = converters.cvt_.lookup(tid);
         *
         *  if (cvt) {
         *    // cvt is a converter for T instances
         *    gp<Object> obj = (*(cvt->cvt_to_object_))(mm,
         *  @endcode
         **/
        class ObjectConverter {
        public:
            using TaggedPtr = xo::reflect::TaggedPtr;
            using IAlloc = xo::gc::IAlloc;

            /** sets up standard conversions **/
            ObjectConverter();

            /** establish conversion: use @p fn to convert values of type @tparam T. **/
            template <typename T>
            void establish_conversion(Converter::ConvertToObjectFn fn);

            /** convert tagged poitner @p tp to object.  allocates memory only from @p mm.
             *  return nullptr if no converter available and @p throw_flag not set.
             *  throw exception if no converter available and @p throw_flag set.
             **/
            gp<Object> tp_to_object(IAlloc * mm, const TaggedPtr & tp, bool throw_flag);

            /** convert @p x to object.
             *  return converted object; if allocated, using only memory from @p mm.
             *  return nullptr if no converter available, and @p throw_flag not set.
             *  throw exception if no converter available, and @p throw_flag set.
             **/
            template <typename T>
            gp<Object> to_object(IAlloc * mm, const T & x, bool throw_flag);

        private:
            /** expandable type-driven conversion table.
             **/
            xo::reflect::TypeDrivenMap<Converter> cvt_;
        };

        template <typename T>
        void
        ObjectConverter::establish_conversion(Converter::ConvertToObjectFn fn)
        {
            using xo::reflect::TypeDescrW;
            using xo::reflect::Reflect;

            TypeDescrW td = Reflect::require<T>();
            Converter * cvt = cvt_.require(td);

            *cvt = Converter(fn);
        }

        template <typename T>
        gp<xo::Object>
        ObjectConverter::to_object(IAlloc * mm, const T & x, bool throw_flag)
        {
            using xo::reflect::Reflect;
            using xo::reflect::TaggedPtr;

            TaggedPtr x_tp = Reflect::make_tp(&x);

            return tp_to_object(mm, x_tp, throw_flag);
        }
    }
}

/* end ObjectConverter.hpp */
