module cipher;

private import std.range : isInputRange, isForwardRange, ElementType, InputRange, ForwardRange, inputRangeObject;
private import std.string : format;

private import salsa20;
private import chacha20;

public:

enum Cipher
{
    salsa20,
    chacha20
}

mixin(cipherFunctionString.format(q{InputRange},
                                  q{isInputRange!R && !(isForwardRange!R)}));

mixin(cipherFunctionString.format(q{ForwardRange},
                                  q{isForwardRange!R}));

unittest
{
    import std.array;
    
    ubyte[32] key;
    ubyte[8] nonce;
    ubyte[] a = [1, 2, 3];
    auto b = a.cipherFunction(key, nonce, Cipher.salsa20);
    InputRange!ubyte c = a.inputRangeObject;
    auto d = c.cipherFunction(key, nonce, Cipher.salsa20);

    static assert(isForwardRange!(typeof(b)));
    static assert(isInputRange!(typeof(d)) && !(isForwardRange!(typeof(d))));
}

private:

enum cipherFunctionString = q{
    %s!(ElementType!R) cipherFunction(R)(R range, ubyte[32] key, ubyte[8] nonce, Cipher cipher)
        if (is(ElementType!R : ubyte) && %s)
    {
        final switch (cipher)
        {
            case Cipher.salsa20:
                return range.salsa20Cipher(key, nonce).inputRangeObject;
            case Cipher.chacha20:
                return range.chacha20Cipher(key, nonce).inputRangeObject;
        }
    }
};