module group;

// How to use this:
import std.typecons;
import std.stdio;

// First, define some functions that return tuples
alias Tuple!(int, string) ErrorTy;
ErrorTy getError(int no = 1) {
    switch (no) {
        case 0:
            return ErrorTy(-1, "Invalid error number.");
        case 1:
            return ErrorTy(42, "Question not found.");
        case 2:
            return ErrorTy(1, "Look! It's a distraction!");
        case 3:
            return ErrorTy(3, "Ran out of witty replies.");
        case 4:
            return ErrorTy(7, "Seriously, I'm out.");
    }
}

// Some examples:
void main() {
    int a;
    string s;
    
    // Most efficient version:
    group1!(a, s) = getError(1);
    
    writefln("Error %d: %s", a, s);
    
    // Unfortunately, things like these don't work:
    //auto myGroup = group1!(b, s);
    
    
    // I've found a way to implement something that
    // allows such things with a less nice syntax.
    
    // Unfortunately, this also allocates a closure :(
    
    group2!(a, s)() = getError(2);
    
    writefln("Error %d: %n", a, s);
    
    auto myGroup = group2!(a, s);
    
    myGroup = getError(3);
    
    writefln("Error %d: %n", a, s);
    
    
    // Also allocates a closure, and re-assigns all parameters to their
    // current value on each instance of "group!(<variables>)".
    // Nice syntax though, and storable.
    group3!(a, s) = getError(4);
    
    writefln("Error %d: %n", a, s);
    
    auto myGroup2 = group3!(a, s);
    
    myGroup2 = getError(0);
    
    writefln("Error %d: %n", a, s);
    
    
    // There's also a group4 below. It uses varargs to elide the
    // re-assignment issue in group3. Otherwise has same capabilities,
    // except type errors on assignment aren't detected until runtime.
}




// How it's implemented:

import std.typetuple;
import std.metastrings;

template TypesOf(Ts...) {
    static if (Ts.length > 0) {
        alias TypeTuple!(typeof(Ts[0]), TypesOf!(Ts[1 .. $])) TypesOf;
    } else {
        alias TypeTuple!() TypesOf;
    }
}

// Allows assignment but not storage. No closure is allocated.
// All the others use this one in their implementation.
void group1(Vs...)(Tuple!(TypesOf!(Vs)) vals) {
    foreach (i, v; Vs) {
        Vs[i] = mixin("vals._" ~ ToString!(i));
    }
}

// Return type for group2+
struct MultiVar(AssignerT, Ts...) {
    // A delegate if all variables are local, a function otherwise.
    private AssignerT assign;
    
    MultiVar opAssign(Tuple!(Ts) values) {
        assign(values);
        return *this;
    }
}

// To make the rest of the code more readable...
template MvType(Vs...) {
    alias MultiVar!(typeof(&group1!(Vs)), TypesOf!(Vs)) MvType;
}
template ValuesType(Vs...) {
    alias Tuple!(TypesOf!(Vs)) ValuesType;
}


// Requires extra parentheses when assigned to, and allocates a closure.
// Storable.
MvType!(Vs) group2(Vs...)() {
    return MvType!(Vs)(&group1!(Vs));
}


// This version reassigns variables to their current values when created.
// Assignable without extra (), storable.
// Also currently allocates a closure.
MvType!(Vs) group3(Vs...)(ValuesType!(Vs) vals = ValuesType!(Vs)(Vs)) {
    return MvType!(Vs)(&group1!(Vs)) = vals;
}


import std.stdarg;

// Doesn't assign to current value. Assignable without extra (), storable.
// Allocates a closure.
MvType!(Vs) group4(Vs...)(...) {
    auto result = MvType!(Vs)(&group1!(Vs));
    if (_arguments.length > 0) {
        assert(_arguments.length == 1, "Can only accept one parameter");
        assert(_arguments[0] == typeid(ValuesType!(Vs)), "Invalid assignment");
        result = va_arg!(ValuesType!(Vs))(_argptr);
    }
    return result;
}

