import std.stdio;
import std.algorithm.mutation;

struct DeepCopyContext {   // memory to save links
    DomElement[DomElement] org_copy_map;

    void register_element(DomElement key, DomElement copy) {
        org_copy_map [key] = copy;
    }

    DomElement resolve(DomElement original) {
        auto res = original in org_copy_map;

        return res is null ? original : *res;
   }
}

class DomElement {
    DomElement parent;

    this() {}
    this(DomElement c) { this.parent = c.parent; }
    ~this() { parent = null; }

    DomElement deep_copy(ref DeepCopyContext ctx) {
        auto rc = clone();
        ctx.register_element(this, rc);
        return rc;
    }

    void restore_link(ref DeepCopyContext){}

    DomElement clone() { return new DomElement(this); }
}

class DomGroup(ElementType: DomElement): DomElement {
    ElementType[] items;

    DomGroup add_item(ElementType item) {
        for (DomElement i = this; i; i = i.parent) {
            if (i == item)
                throw new Exception("Cycle in parent chain");
        }
        if (item.parent)
            throw new Exception("Multiparenting");

        item.parent = this;
        items ~= item;

        return this;
    }

    void remove_item(ElementType item) {
        assert(item.parent == this);

        items = remove!(x => x == item)(items);
        item.parent = null;

        // invalidate all Connectors?
        // or easier way - check connector.from.parent == null
    }

    override DomGroup deep_copy(ref DeepCopyContext ctx) {
        auto result = new DomGroup;
		result.parent = this;

        ctx.register_element(this, result);

        result.items.length = items.length; // pre-alloc
        for(int i = 0; i < items.length; i++)
            result.items[i] = items[i].deep_copy(ctx);
        return result;
    }

    override void restore_link(ref DeepCopyContext ctx){
        foreach(i; items) i.restore_link(ctx);
    }
}

class Style {
    immutable string  font;
    immutable float   size;
    immutable int     weight;

    this(string f, float s, int w) { font = f; size = s; weight = w; }
    this(const ref Style st) { font = st.font; size = st.size; weight = st.weight; }
}

struct Bitmap {
    immutable string src;
    this(string s) { src = s; }
}

alias CardItem = DomElement;
alias Card = DomGroup!(DomElement);
alias GroupItem = DomGroup!(DomElement);
alias Document = DomGroup!(Card);

class TextItem : CardItem {
    string  text;
    Style   style;

    this(string t, Style s) { super(); text = t; style = s; }
    this(TextItem c) { super(c); text = c.text; style = c.style; }

    override DomElement clone() { return new TextItem(this); }
}

class ImageItem : CardItem {
    Bitmap  bitmap;

    this(Bitmap b) { bitmap = b; }
    this(ImageItem c) { super(c); bitmap = c.bitmap; }

    override DomElement clone() { return new ImageItem(this); }
}

class ButtonItem : CardItem {
    string  caption;
    Card    target_card;

    this(string cap, Card target) { caption = cap; target_card = target; }
    this(ButtonItem c) { super(c); caption = c.caption; target_card = c.target_card; }

    override DomElement clone() { return new ButtonItem(this); }

    ~this() { target_card = null; }

    override void restore_link(ref DeepCopyContext ctx) {
        target_card = cast(Card)ctx.resolve(target_card);
    }
}

class ConnectorItem : CardItem {
    CardItem from, to;

    this(CardItem f, CardItem t) { from = f; to = t; }
    this(ConnectorItem c) { super(c); from = c.from; to = c.to; }

    override DomElement clone() { return new ConnectorItem(this); }

    override void restore_link(ref DeepCopyContext ctx) {
        from = ctx.resolve(from);
        to = ctx.resolve(to);
    }
}

ElementType deep_copy(ElementType: DomElement) (ref ElementType src) {
    DeepCopyContext ctx;
    auto result = cast(ElementType)src.deep_copy(ctx);
    result.restore_link(ctx);
    return result;
}

void main() {
    auto doc = new Document;
    {
        auto style = new Style("Times", 16.5f, 600);
        auto card = new Card;
        auto hello = new TextItem("Hello", style);
        auto button = new ButtonItem("Click me", card);
        auto conn = new ConnectorItem(hello, button);
        doc.add_item(card.add_item(hello)
                     .add_item(button)
                     .add_item(conn)
                    );
    }
    {
        /// "Unshare on modification" enforcement at compile-time.
        auto hello_text = cast(TextItem)(doc.items[0].items[0]);
        /// This won't compile:
        //hello_text.style.size++;

        /// This compiles and unshares on modification
        auto new_style = new Style(hello_text.style.font, hello_text.style.size +1, hello_text.style.weight);
        hello_text.style = new_style;

        /// Object protection by stack pointers:
        doc.items[0].remove_item(hello_text);
        /// Check that hello text is alive and pointed to by weak pointers
        /// as long as it's retained by hello_text local
        assert(doc.items[0].items[1] !is null);  // ConnectorItem
        /// here it's finally deleted
    }
    /// Weaks handling:
    /// Let's check that connector is no more pointing to the deleted text:
    //assert(doc.items[0].items[1] is null);  // ConnectorItem FAIL, GC
    //коннектор будет держать удаленные связи


    // Topologically correct copy operation:
    // Let's copy the whole doc...
    auto new_doc = deep_copy(doc);

    /// ...and check that connector copy points to the button copy
     assert(new_doc.items[0].items[0] ==
        (cast(ConnectorItem)new_doc.items[0].items[1]).to);

    /// Let's check that button copy points to the card copy
    assert(new_doc.items[0] ==
        (cast(ButtonItem)new_doc.items[0].items[0]).target_card);

    /// Protection against multiparenting (runtime-only)
    try {
        doc.add_item(new_doc.items[0]);
    } catch (Exception e) {
        writeln(e.msg);
    }

    /// Protection against cycles (runtime-only)
    try {
        auto group = new GroupItem;
        auto subgroup = new GroupItem;
        group.add_item(subgroup);
        subgroup.add_item(group);
    } catch (Exception e) {
        writeln(e.msg);
    }
}

Embed on website

To embed this program on your website, copy the following code and paste it into your website's HTML: