


//
// VariantRepr
//

record VariantRepr[..T] (
    tag : Int,
    data : Union[..T],
);



//
// VariantMember?, VariantMembers
//

[V,M | Variant?(V)]
VariantMember?(static V, static M) =
    VariantMemberIndex(V, M) < VariantMemberCount(V);

VariantMembers(V) {
    var unionType = Type(variantRepr(typeToLValue(V)).data);
    return ..UnionMembers(unionType);
}



//
// construct from value of member type
//

[V,T | Variant?(V) and VariantMember?(V, T)]
alias overload V(x:T) --> returned:V {
    ref repr = variantRepr(returned);
    alias index = Int(VariantMemberIndex(V, T));
    repr.tag <-- index;
    staticIndex(repr.data, static index) <-- x;
}

[V,T,..AA | Variant?(V) and VariantMember?(V, T) and CallDefined?(T, ..AA)]
alias overload V(static T, ..args:AA) = V(T(..args));


//
//
// copy constructor
//

[V | Variant?(V) and not BitwiseCopiedType?(V)]
overload V(src:V) --> returned:V {
    ref destRepr = variantRepr(returned);
    ref srcRepr = variantRepr(src);
    var tag = srcRepr.tag;
    destRepr.tag = tag;
    ref destData = destRepr.data;
    ref srcData = srcRepr.data;
    alias n = Int(VariantMemberCount(V));
    ..for (i in staticIntegers(static n)) {
        if (tag == unwrapStatic(i)) {
            ref dest = staticIndex(destData, i);
            ref src = staticIndex(srcData, i);
            dest <-- src;
            goto end;
        }
    }
    errorNoThrow("invalid variant, tag = ", tag);
    end :
}

[V | Variant?(V) and not (BitwiseMovedType?(V) and NotResetType?(V))]
inline overload V(rvalue src:V) = move(src);



//
// moveUnsafe, resetUnsafe
//

[V | Variant?(V) and not BitwiseMovedType?(V)]
overload moveUnsafe(src:V) --> returned:V {
    ref destRepr = variantRepr(returned);
    ref srcRepr = variantRepr(src);
    var tag = srcRepr.tag;
    destRepr.tag = tag;
    ref destData = destRepr.data;
    ref srcData = srcRepr.data;
    alias n = Int(VariantMemberCount(V));
    ..for (i in staticIntegers(static n)) {
        if (tag == unwrapStatic(i)) {
            ref dest = staticIndex(destData, i);
            ref src = staticIndex(srcData, i);
            dest <-- moveUnsafe(src);
            goto end;
        }
    }
    errorNoThrow("invalid variant, tag = ", tag);
    end :
}

[V | Variant?(V) and not NotResetType?(V)]
inline overload resetUnsafe(x:V) {
    ref repr = variantRepr(x);
    repr.tag = 0;
    resetUnsafe(staticIndex(repr.data, static 0));
}



//
// assign
//

[V | Variant?(V) and not BitwiseAssignedType?(V)]
overload assign(ref dest:V, ref src:V) {
    ref destRepr = variantRepr(dest);
    ref srcRepr = variantRepr(src);
    var tag = destRepr.tag;
    if (tag != srcRepr.tag) {
        if (&dest == &src)
            return;
        var temp = moveUnsafe(dest);
        try {
            dest <-- src;
        } catch (ex) {
            dest <-- move(temp);
            throw ex;
        }
        return;
    }
    ref destData = destRepr.data;
    ref srcData = srcRepr.data;
    alias n = Int(VariantMemberCount(V));
    ..for (i in staticIntegers(static n)) {
        if (tag == unwrapStatic(i)) {
            ref dest2 = staticIndex(destData, i);
            ref src2 = staticIndex(srcData, i);
            assign(dest2, src2);
            goto end;
        }
    }
    errorNoThrow("invalid variant, tag = ", tag);
    end :
}



//
// destroy
//

[V | Variant?(V) and not NotDestroyedType?(V)]
overload destroy(x:V) {
    ref repr = variantRepr(x);
    var tag = repr.tag;
    ref data = repr.data;
    alias n = Int(VariantMemberCount(V));
    ..for (i in staticIntegers(static n)) {
        if (tag == unwrapStatic(i)) {
            ref dest = staticIndex(data, i);
            destroy(dest);
            return;
        }
    }
    errorNoThrow("invalid variant, tag = ", tag);
}



//
// equals?
//

[V | Variant?(V)]
overload equals?(a:V, b:V) {
    ref aRepr, bRepr = variantRepr(a), variantRepr(b);
    var tag = aRepr.tag;
    if (tag != bRepr.tag)
        return false;
    ref aData, bData = aRepr.data, bRepr.data;
    alias n = Int(VariantMemberCount(V));
    ..for (i in staticIntegers(static n)) {
        if (tag == unwrapStatic(i))
            return staticIndex(aData, i) == staticIndex(bData, i);
    }
    errorNoThrow("invalid variant, tag = ", tag);
    return false;
}



//
// variantIs?
//

[V,T | Variant?(V) and VariantMember?(V, T)]
inline variantIs?(x:V, static T) {
    ref repr = variantRepr(x);
    alias index = Int(VariantMemberIndex(V, T));
    return repr.tag == index;
}



//
// variantAs
//

define variantAs;

[V,T | Variant?(V) and VariantMember?(V, T)]
inline overload variantAs(ref x:V, static T) {
    ref repr = variantRepr(x);
    alias index = Int(VariantMemberIndex(V, T));
    assert(repr.tag == index, "invalid cast");
    return ref staticIndex(repr.data, static index);
}

[V,T | Variant?(V) and VariantMember?(V, T)]
inline overload variantAs(rvalue x:V, static T) {
    return move(variantAs(x, T));
}



//
// variantAsPtr - test and unbox in one operation
//

[V,T | Variant?(V) and VariantMember?(V,T)]
inline variantAsPtr(x:V, static T) {
    ref repr = variantRepr(x);
    alias index = Int(VariantMemberIndex(V, T));
    if (repr.tag == index)
        return &staticIndex(repr.data, static index);
    else
        return null(T);
}



//
// variantTag, unsafeVariantIndex, invalidVariant
//

[V | Variant?(V)]
inline variantTag(x:V) = variantRepr(x).tag;

[V,i | Variant?(V)]
inline unsafeVariantIndex(x:V, static i) =
    ref staticIndex(variantRepr(x).data, static i);

[V | Variant?(V)]
inline invalidVariant(x:V) {
    errorNoThrow("invalid variant, tag = ", variantTag(x));
}


//
// variant matching
//

define MatchDefault; // dummy symbol used to implement default case

private define matchValueByType;
[T, U | T != U] inline overload matchValueByType(forward x:T, static U, fn, ..cases)
    = forward ..matchValueByType(x, ..cases);
[T] inline overload matchValueByType(forward x:T, static T, fn, ..cases) = forward ..fn(x);
inline overload matchValueByType(forward x, static MatchDefault, fn, ..cases) = forward ..fn(x);

inline matchByType(forward v, ..cases) = forward ..matchValueByType(*v, ..cases);

private define matchValue;
[T]
inline overload matchValue(forward x:T, forward f, forward ..fs)
    = forward ..matchValue(x, ..fs);
[T, F | CallDefined?(call, F, T)]
inline overload matchValue(forward x:T, forward f:F, forward ..fs)
    = forward ..f(x);

inline match(forward v, forward ..fs) = forward ..matchValue(*v, ..fs);

