import libc.(malloc, free);

InterfaceType?(X) = false;
define InterfaceMembers;

private ImplicitInterfaceMembers() = [destroy, [], []];

private VtblSlotFunction(memFun) = memFun.0;
private VtblSlotInputTypes(memFun) = ..unpack(memFun.1);
private VtblSlotOutputTypes(memFun) = ..unpack(memFun.2);

InterfaceMember?(I, fn) = false;

[I, fn | InterfaceType?(I)]
overload InterfaceMember?(static I, static fn)
    = inValues?(fn, ..mapValues(VtblSlotFunction, ..InterfaceMembers(I)));

private VtblSlotType(memFun) = CodePointer[
    [Opaque, ..VtblSlotInputTypes(memFun)],
    [..VtblSlotOutputTypes(memFun)]
];

private record InterfaceVtbl[..memberFunctions]
    = [#methods, Tuple[..mapValues(VtblSlotType, ..memberFunctions)]];

interfaceType(..memberFunctions) = recordWithProperties(
    [
        [InterfaceType?, static true],
        [InterfaceMembers, 
            ..ImplicitInterfaceMembers(),
            ..memberFunctions
        ],
    ],
    [#vtbl, Pointer[InterfaceVtbl[..ImplicitInterfaceMembers(), ..memberFunctions]]],
    [#this, Opaque],
);

[I | InterfaceType?(I)]
overload RegularRecord?(static I) = false;

[I | InterfaceType?(I)]
overload destroy(i: I) { invoke(destroy, &i); }

private vtblSlotMember(T, memFun) = VtblSlotType(memFun)(
    makeCodePointer(VtblSlotFunction(memFun), T, ..VtblSlotInputTypes(memFun))
);

[I, T | InterfaceType?(I)]
private makeInterfaceVtbl(static I, static T) = InterfaceVtbl[..InterfaceMembers(I)](
    Tuple(
        ..mapValues(slot => vtblSlotMember(T, slot), ..InterfaceMembers(I))
    )
);

private var interfaceVtbl[I, T] = makeInterfaceVtbl(I, T);

BoxSize(T) = TypeSize(RawPointer) + TypeSize(T);

[I, T | InterfaceType?(I)]
alias allocateBox(static I, this: T) : Pointer[I] {
    var ptr = Pointer[I](malloc(BoxSize(T)));
    ptr^.vtbl = &interfaceVtbl[I, T];

    try {
        Pointer[T](&ptr^.this)^ <-- this;
    }
    catch (ex) {
        free(RawPointer(ptr));
        throw ex;
    }
    return ptr;
}

[..memberFunctions, fn]
private vtblSlot(vtbl: InterfaceVtbl[..memberFunctions], static fn)
    = staticIndex(
        vtbl.methods,
        static valueIndex(fn, ..mapValues(VtblSlotFunction, ..memberFunctions))
    );

[I, fn | InterfaceMember?(I, fn)]
invoke(static fn, i: Pointer[I], forward ..args)
    = ..vtblSlot(i^.vtbl^, fn)(i^.this, ..args);
