module variant;
// By Reiner Pope

version (Tango)
    import tango.core.Tuple : IndexOf, Unique;
else
    import std.typetuple : IndexOf, Unique = NoDuplicates;

/** Simple Discriminated Unions for D. (Lacking support for recursive types and _extensible_ pattern matching.)

    This module presents an implementation of type-safe discriminated unions for D, employing meta-programming
    to implement pattern-matching.

    The base data-structure is the Variant struct, which takes a list of types:
         alias Variant!(int, char[], Foo) MyVariant; // MyVariant is a type which can be assigned ints, char[]s, or Foo's.

    Pattern matching can be done with a Match!() or a MatchStrict!() statement, both of which have the same prototype: 
       they expect the variable as a first parameter, followed by a list of cases.

    A case is created using the Case!() template:
       first parameter expresses the pattern, which can be of two forms:
            "type name"                 eg    "int n"
            "{type1|type2|...} name"    eg    "{int|float|char[]} f"
       second parameter is the code to be executed with this newly-declared variable. It should be D code, which makes sense
          when injected (via mixin) to the calling scope.*/

public:
template Case(char[] pattern, char[] code)
{
    static if (pattern[0] == '{')
    {
        alias MakeCaseList!(findNewPatterns(pattern), code).List Case;
    }
    else
        alias CaseImpl!(pattern, code) Case;
}

template Match(alias Variant, Cases...)
{
    const char[] Match = MatchImpl!(Variant, false, Cases);
}

template MatchStrict(alias Variant, Cases...)
{
    const char[] MatchStrict = MatchImpl!(Variant, true, Cases);
}

struct Variant(T...)
{
    ulong _variant_type;
    alias T Types;
    union { T _variant_data; }
   
    public:
    mixin(makeOpAssigns!(0, T));
}


private:
template CaseImpl(char[] pattern, char[] code)
{
    const bool IsCase = true;
    const char[] Pattern = pattern;
    const char[] Code = code;

    const int SeparatorIndex = findLastWhitespace(pattern);

    static assert(SeparatorIndex != -2, "Pattern must have no newlines");
    static assert(SeparatorIndex > 0, "Pattern must be of the form 'type name'");

    const char[] TypeName = pattern[0..SeparatorIndex];
    const char[] VariableName = pattern[SeparatorIndex..$];

    mixin("alias " ~ TypeName ~ " Type;");

    char[] GenerateCode(ulong[] indices)
    {
        assert(indices.length > 0, "Unreachable case statement: " ~ pattern);
        char[] genCode = "// pattern: " ~ pattern ~ \n;

        // Generate the 'case 5:' statements
        foreach (i; indices)
            genCode ~= \t "case " ~ itoa(i) ~ ":\n";

	genCode ~= pattern ~ ";\n";

        // Instantiate the variable:
        if (indices.length == 1)
            genCode ~= VariableName ~ " = _variant_data[" ~ itoa(indices[0]) ~ "];\n";
   	else
	{
	    genCode ~= "switch (_variant_type) {\n";
            foreach (i; indices)
		genCode ~= "case " ~ itoa(i) ~ ": " ~ VariableName ~ " = _variant_data[" ~ itoa(i) ~ "]; break;\n";
	    genCode ~= "}\n";
	}

	genCode ~= code ~ \n "break;" \n \n;
        return genCode;
    }
}

template MakeCaseList(PatternsThenCode...)
{
    static if (PatternsThenCode[0].length == 0)
        alias Tuple!() List;
    else
        alias Tuple!(Case!(PatternsThenCode[0][0], PatternsThenCode[1]), 
              MakeCaseList!(PatternsThenCode[0].slice(1), PatternsThenCode[1]).List) List;
}

template Tuple(T...)
{
    alias T Tuple;
}

char[][] slice(char[][] list, size_t index1, size_t index2 = size_t.max)
{
    if (index2 == size_t.max) return list[index1..$];
    return list[index1..index2];
}

char[][] findNewPatterns(char[] pattern)
{
    char[][] types;
    char[] name;
    size_t beginIndex = 1;
    foreach (size_t i, char c; pattern)
    {
        if (c == '|' || c == '}')
        {
            assert(i > beginIndex, "Multi-pattern format is {type1|type2|type3|type4} name");
            types ~= pattern[beginIndex .. i];
            beginIndex = i + 1;
        }
        if (c == '}')
            name = pattern[i + 1..$];
    }
    
    char[][] retVal;
    foreach (char[] type; types)
    {
        retVal ~= type ~ name;
    }
    return retVal;
}

int findLastWhitespace(char[] pattern)
{
    int index = -1;
    for (int i = pattern.length - 1; i >= 0; i--)
    {
        if (pattern[i] == '\t' || pattern[i] == ' ' && index == -1)
            index = i;
    }
    return index;
}

const ulong[] EmptyIndexList = [];

template MatchImpl(alias Variant, bool Strict, Cases...)
{
    const char[] MatchImpl = "with (" ~ Variant.stringof ~ ") {" \n
                            "switch (_variant_type) {" \n ~
                                GenerateCases!(typeof(Variant), Strict, EmptyIndexList, Cases).Code ~ \n
                                "default: break;" \n
                            "} \n"
                         "}";
}

template TypeIndices(Type1, T...)
{
    static if (IndexOf!(Type1, T) >= 0)
        const ulong[] TypeIndices = [cast(ulong)IndexOf!(Type1, T)];
    else
        const ulong[] TypeIndices = DerivedIndices!(Type1, 0, T);
}

template GenerateCases(VType, bool Strict, IndicesThenCases...)
{
    // Lets grab our parameters again
    const ulong[] usedIndices = IndicesThenCases[0];
    alias IndicesThenCases[1..$] Cases;

    static if (Cases.length == 0)
    {
        static assert(!Strict || (usedIndices.length == VType.Types.length), "Not all cases expressed in match statement");
        const char[] Code = "";
    }
    else
    {
        static assert(Cases[0].IsCase, "Match statement must be composed of Case!() statements");
        alias Cases[0] TheFirstCase;   // A workaround for bug #1215
        alias TheFirstCase.Type TheType;
        const ulong[] indices = TypeIndices!(TheType, VType.Types);
        const ulong[] uniqueIndices = removeOnesUsedFrom(indices, usedIndices);

        const char[] Code = Cases[0].GenerateCode(uniqueIndices) ~ 
                 GenerateCases!(VType, Strict, getConcat(usedIndices, uniqueIndices), Cases[1..$]).Code;
    }
}


// A workaround for bug #1216
ulong[] getConcat(ulong[] a, ulong[] b)
{
    return a ~ b;
}

ulong[] removeOnesUsedFrom(ulong[] indices, ulong[] others)
{
    ulong[] retVal = [];
    foreach (k; indices)
    {
        bool found = false;
        foreach (j; others)
        {
            if (j == k) found = true;
            break;
        }
        if (!found) retVal ~= k;
    }
    return retVal;
}

template DerivedIndices(Type1, ulong index = 0, T...)
{
    static if (index == T.length)
    {
        const ulong[] DerivedIndices = [];
    }
    else
    {
        static if (is(T[index] : Type1))
        {
            const ulong[] DerivedIndices = index ~ DerivedIndices!(Type1, index+1, T);
        }
        else
        {
            const ulong[] DerivedIndices = DerivedIndices!(Type1, index+1, T);
        }
    }
}



template makeOpAssigns(ulong index, T...)
{
/*   This code here would be preferable, but because of overriding rules with index, it doesn't work at the moment.
    static if (index < T.length)
    {
        pragma(msg, itoa(index));
        typeof(*this) opAssign(T[index] value)
        {
            _variant_data[index] = value;
            _variant_type = index;
            return *this;
        }
        mixin makeOpAssigns!(index + 1, T);
    }
*/

    static if (index == T.length)
        const char[] makeOpAssigns = "";
    else
    {
        const char[] makeOpAssigns = "    typeof(*this) opAssign(" ~ T[index].stringof ~ 
              " value) { _variant_data[" ~ itoa(index) ~ "] = value; _variant_type = " 
              ~ itoa(index) ~ "; return *this; }\n"
              ~ makeOpAssigns!(index + 1, T);
    }
}

template GenerateList(ulong index, T...)
{
    static if (T.length == 0)
        const char[] GenerateList = "";
    else
        const char[] GenerateList = T[0].stringof ~ " _variant_data[" ~ itoa(index) ~ "]; " ~ GenerateList!(index + 1, T[1..$]);
}

char[] itoa(ulong x)
{
    char[] s="";
    do {
        s = cast(char)('0' + (x%10)) ~ s;
        x /= 10;
    } while (x>0);
    return s;
}






