MODE: INLINE

#include <vector>

struct Point {
    double x;
    double y;
};

struct ShapeA {
    size_t point_count() const { return points.size(); }
    Point& get_point(size_t idx) { return points.at(idx); }
    void add_point(const Point& pt) { points.push_back(pt); }
private:
    std::vector<Point> points;
};

struct XSShapeA : public ShapeA {
    void add_label(SV* maybe_label = nullptr) {
        Sv label = maybe_label ? Sv{maybe_label} : Sv::undef;
        labels.push_back(Ref::create(maybe_label));
    }
    Ref& get_ref(size_t idx) { return labels.at(idx); }
private:
    std::vector<Ref> labels;
};


namespace xs {
    template <>
    struct Typemap<Point*> : TypemapObject<Point*, Point*, ObjectTypePtr, ObjectStorageMG> {
        static std::string package () { return "MyTest::Cookbook::Point"; }
    };

    template <typename D>
    struct Typemap<ShapeA*, D> : TypemapObject<ShapeA*, D, ObjectTypePtr, ObjectStorageMG> {
        static std::string package () { return "MyTest::Cookbook::ShapeA"; }
    };

    template <>
    struct Typemap<XSShapeA*> : Typemap<ShapeA*, XSShapeA*> {
        static std::string package () { return "MyTest::Cookbook::ShapeB"; }
    };

}

static xs::Sv::payload_marker_t payload_marker_09;

MODULE = MyTest::Cookbook                PACKAGE = MyTest::Cookbook::Point
PROTOTYPES: DISABLE

double Point::x(SV* new_val = nullptr) : ALIAS(y = 1) {
    double* val = nullptr;
    switch(ix) {
        case 1: val = &THIS->y; break;
        default: val = &THIS->x; break;
    }
    if (new_val) {
        *val = SvNV(new_val);
    }
    RETVAL = *val;
}

Point* Point::new(double x = 0, double y = 0) {
    RETVAL = new Point{x, y};
}

MODULE = MyTest::Cookbook                PACKAGE = MyTest::Cookbook::ShapeA
PROTOTYPES: DISABLE

size_t ShapeA::point_count()

void ShapeA::get_point(size_t idx) {
    Object self{ST(0)};
    Array payload(self.payload(&payload_marker_09).obj);

    auto& pt = THIS->get_point(idx);
    auto pt_copy = new Point{pt.x, pt.y};
    auto wrapped_pt = xs::out<>(pt_copy);
    mXPUSHs(wrapped_pt.detach());

    if (GIMME_V == G_ARRAY) {
        auto ref = payload.at(idx);
        Sv value = SvRV(ref);
        mXPUSHs(value.detach());
    }
}

void ShapeA::add_point(Point* pt, SV* maybe_label = nullptr) {
    THIS->add_point(*pt);
    Object self{ST(0)};
    Array payload(self.payload(&payload_marker_09).obj);
    Sv label = maybe_label ? Sv{maybe_label} : Sv::undef;
    Sv ref = Ref::create(maybe_label);
    payload.push(ref);
}

Sv ShapeA::new() {
    auto shape = new ShapeA();
    Object obj = xs::out<>(shape, CLASS);
    auto payload = Array::create();
    obj.payload_attach(payload, &payload_marker_09);
    RETVAL = obj.ref();
}

MODULE = MyTest::Cookbook                PACKAGE = MyTest::Cookbook::ShapeB
PROTOTYPES: DISABLE

XSShapeA* XSShapeA::new()

void XSShapeA::add_point(Point* pt, SV* maybe_label = nullptr) {
    THIS->add_point(*pt);
    THIS->add_label(maybe_label);
}

void XSShapeA::get_point(size_t idx) {
    auto& pt = THIS->get_point(idx);
    auto pt_copy = new Point{pt.x, pt.y};
    auto wrapped_pt = xs::out<>(pt_copy);
    mXPUSHs(wrapped_pt.detach());
    if (GIMME_V == G_ARRAY) {
        Sv value = SvRV(THIS->get_ref(idx));
        mXPUSHs(value.detach());
    }
}

BOOT {
    auto stash = Stash(__PACKAGE__, GV_ADD);
    stash.inherit("MyTest::Cookbook::ShapeA");
}