/* @file StructTdx.hpp */

#pragma once

#include "xo/reflect/TypeDescrExtra.hpp"
#include "xo/reflect/TaggedPtr.hpp"
#include "StructMember.hpp"
//#include "xo/reflect/struct/StructMember.hpp"
#include <vector>
#include <functional>
#include <memory>

namespace xo {
    namespace reflect {
        /* Extra type-associated information for a struct/class.
         * We use this to preserve information about memory layout
         * at runtime
         */
        class StructTdx : public TypeDescrExtra {
        public:
            /* named ctor idiom.  create new instance for struct with given member list
             *
             * to_self_tp.  use this function to support .most_derived_self_tp()
             */
            static std::unique_ptr<StructTdx> make(std::vector<StructMember> member_v,
                                                   bool have_to_self_tp,
                                                   std::function<TaggedPtr (void *)> to_self_tp);

            /* specialization for std::pair<Lhs, Rhs>
             * coordinates with [reflect/Reflect.hpp]
             */
            template<typename Lhs, typename Rhs>
            static std::unique_ptr<StructTdx> pair() {
                using struct_t = std::pair<Lhs, Rhs>;

                std::vector<StructMember> mv;
                {
                    auto lhs_access
                        (GeneralStructMemberAccessor<struct_t, struct_t, Lhs>::make
                         (&struct_t::first));

                    mv.push_back(StructMember("first", std::move(lhs_access)));
                }
                {
                    auto rhs_access
                        (GeneralStructMemberAccessor<struct_t, struct_t, Rhs>::make
                         (&struct_t::second));

                    mv.push_back(StructMember("second", std::move(rhs_access)));
                }

                std::function<TaggedPtr (void *)> null_to_self_tp;

                return make(std::move(mv),
                            false /*!have_to_self_tp*/,
                            null_to_self_tp);
            } /*pair*/

            // ----- Inherited from TypeDescrExtra -----

            virtual Metatype metatype() const override { return Metatype::mt_struct; }
            virtual TaggedPtr most_derived_self_tp(TypeDescrBase const * object_td,
                                                   void * object) const override {
                if (this->have_to_self_tp_) {
                    return this->to_self_tp_(object);
                } else {
                    return TypeDescrExtra::most_derived_self_tp(object_td, object);
                }
            }
            /* object argument ignored for structs,  since size is fixed */
            virtual uint32_t n_child(void * /*object*/) const override { return this->member_v_.size(); }
            virtual uint32_t n_child_fixed() const override { return this->member_v_.size(); }
            virtual TaggedPtr child_tp(uint32_t i, void * object) const override;
            virtual TypeDescr fixed_child_td(uint32_t i) const override;
            virtual std::string const & struct_member_name(uint32_t i) const override;
            virtual StructMember const * struct_member(uint32_t i) const override;

        private:
            StructTdx(std::vector<StructMember> member_v,
                      bool have_to_self_tp,
                      std::function<TaggedPtr (void*)> to_self_tp)
                : member_v_{std::move(member_v)},
                  have_to_self_tp_{have_to_self_tp},
                  to_self_tp_{std::move(to_self_tp)} {}

        private:
            /* per-instance-variable reflection details */
            std::vector<StructMember> member_v_;
            /* true if .to_self_tp() is defined */
            bool have_to_self_tp_ = false;
            /* get TaggedPtr for most-derived subtype of supplied T-instance */
            std::function<TaggedPtr (void *)> to_self_tp_;
        }; /*StructTdx*/

    } /*namespace reflect*/
} /*namespace xo*/

/* end StructTdx.hpp */
