/**
 * The variant module contains a variant, or polymorphic type.
 *
 * Copyright: Copyright (C) 2005-2007 The Tango Team.  All rights reserved.
 * License:   BSD style: $(LICENSE)
 * Authors:   Daniel Keep, Sean Kelly
 */
module tango.core.Variant;

private import tango.core.Vararg : va_list;
private import tango.core.Traits;

private
{
    template maxT(uint a, uint b)
    {
        const maxT = (a > b) ? a : b;
    }

    struct AtomicTypes
    {
        union
        {
            bool _bool;
            char _char;
            wchar _wchar;
            dchar _dchar;
            byte _byte;
            short _short;
            int _int;
            long _long;
            ubyte _ubyte;
            ushort _ushort;
            uint _uint;
            ulong _ulong;
            float _float;
            double _double;
            real _real;
            ifloat _ifloat;
            idouble _idouble;
            ireal _ireal;
            void* ptr;
            void[] arr;
            Object obj;
            ubyte[maxT!(_real.sizeof,arr.sizeof)] data;
        }
    }

    template isAtomicType(T)
    {
        static if( is( T == bool )
                || is( T == char )
                || is( T == wchar )
                || is( T == dchar )
                || is( T == byte )
                || is( T == short )
                || is( T == int )
                || is( T == long )
                || is( T == ubyte )
                || is( T == ushort )
                || is( T == uint )
                || is( T == ulong )
                || is( T == float )
                || is( T == double )
                || is( T == real )
                || is( T == ifloat )
                || is( T == idouble )
                || is( T == ireal ) )
            const isAtomicType = true;
        else
            const isAtomicType = false;
    }

    template isObject(T)
    {
        static if( is( T : Object ) )
            const isObject = true;
        else
            const isObject = false;
    }

    template isInterface(T)
    {
        static if( is( T == interface ) )
            const isInterface = true;
        else
            const isInterface = false;
    }

    bool isAny(T,argsT...)(T v, argsT args)
    {
        foreach( arg ; args )
            if( v is arg ) return true;
        return false;
    }

    const tibool = typeid(bool);
    const tichar = typeid(char);
    const tiwchar = typeid(wchar);
    const tidchar = typeid(dchar);
    const tibyte = typeid(byte);
    const tishort = typeid(short);
    const tiint = typeid(int);
    const tilong = typeid(long);
    const tiubyte = typeid(ubyte);
    const tiushort = typeid(ushort);
    const tiuint = typeid(uint);
    const tiulong = typeid(ulong);
    const tifloat = typeid(float);
    const tidouble = typeid(double);
    const tireal = typeid(real);
    const tiifloat = typeid(ifloat);
    const tiidouble = typeid(idouble);
    const tiireal = typeid(ireal);

    bool canImplicitCastTo(dsttypeT)(TypeInfo srctype)
    {
        static if( is( dsttypeT == char ) )
            return isAny(srctype, tibool, tiubyte);

        else static if( is( dsttypeT == wchar ) )
            return isAny(srctype, tibool, tiubyte, tiushort, tichar);

        else static if( is( dsttypeT == dchar ) )
            return isAny(srctype, tibool, tiubyte, tiushort, tiuint, tichar,
                    tiwchar);

        else static if( is( dsttypeT == byte ) )
            return isAny(srctype, tibool);

        else static if( is( dsttypeT == ubyte ) )
            return isAny(srctype, tibool, tichar);

        else static if( is( dsttypeT == short ) )
            return isAny(srctype, tibool, tibyte, tiubyte, tichar);

        else static if( is( dsttypeT == ushort ) )
            return isAny(srctype, tibool, tibyte, tiubyte, tichar, tiwchar);

        else static if( is( dsttypeT == int ) )
            return isAny(srctype, tibool, tibyte, tiubyte, tishort, tiushort,
                    tichar, tiwchar);

        else static if( is( dsttypeT == uint ) )
            return isAny(srctype, tibool, tibyte, tiubyte, tishort, tiushort,
                    tichar, tiwchar, tidchar);

        else static if( is( dsttypeT == long ) || is( dsttypeT == ulong ) )
            return isAny(srctype, tibool, tibyte, tiubyte, tishort, tiushort,
                        tiint, tiuint, tichar, tiwchar, tidchar);

        else static if( is( dsttypeT == float ) )
            return isAny(srctype, tibool, tibyte, tiubyte);

        else static if( is( dsttypeT == double ) )
            return isAny(srctype, tibool, tibyte, tiubyte, tifloat);

        else static if( is( dsttypeT == real ) )
            return isAny(srctype, tibool, tibyte, tiubyte, tifloat, tidouble);

        else static if( is( dsttypeT == idouble ) )
            return isAny(srctype, tiifloat);

        else static if( is( dsttypeT == ireal ) )
            return isAny(srctype, tiifloat, tiidouble);

        else
            return false;
    }

    template storageT(T)
    {
        static if( isStaticArrayType!(T) )
            alias typeof(T.dup) storageT;
        else
            alias T storageT;
    }
}

/**
 * This exception is thrown whenever you attempt to get the value of a Variant
 * without using a compatible type.
 */
class VariantTypeMismatchException : Exception
{
    this(TypeInfo expected, TypeInfo got)
    {
        super("cannot convert "~expected.toString
                    ~" value to a "~got.toString);
    }
}

/**
 * The Variant type is used to dynamically store values of different types at
 * runtime.
 *
 * You can create a Variant using either the pseudo-constructor or direct
 * assignment.
 *
 * -----
 *  Variant v = Variant(42);
 *  v = "abc";
 * -----
 */
struct Variant
{
    /**
     * This pseudo-constructor is used to place a value into a new Variant.
     *
     * Params:
     *  value = The value you wish to put in the Variant.
     *
     * Returns:
     *  The new Variant.
     */
    static Variant opCall(T)(T value)
    {
        Variant _this;

        static if( isStaticArrayType!(T) )
            _this = value.dup;

        else
            _this = value;

        return _this;
    }

    /**
     * This operator allows you to assign arbitrary values directly into an
     * existing Variant.
     *
     * Params:
     *  value = The value you wish to put in the Variant.
     *
     * Returns:
     *  The new value of the assigned-to variant.
     */
    Variant opAssign(T)(T value)
    {
        static if( isStaticArrayType!(T) )
        {
            return (*this = value.dup);
        }
        else
        {
            type = typeid(T);

            static if( isAtomicType!(T) )
            {
                mixin("this.value._"~T.stringof~"=value;");
            }
            else static if( isDynamicArrayType!(T) )
            {
                this.value.arr = (cast(void*)value.ptr)
                    [0 .. value.length];
            }
            else static if( isPointerType!(T) )
            {
                this.value.ptr = cast(void*)value;
            }
            else static if( isObject!(T) )
            {
                this.value.obj = value;
            }
            else static if( isInterface!(T) )
            {
                this.value.obj = cast(Object) value;
            }
            else
            {
                if( T.sizeof <= this.value.data.length )
                {
                    this.value.data[0..T.sizeof] =
                        (cast(ubyte*)&value)[0..T.sizeof];
                }
                else
                {
                    auto buffer = (cast(ubyte*)&value)[0..T.sizeof].dup;
                    this.value.arr = cast(void[])buffer;
                }
            }
            return *this;
        }
    }

    /**
     * This member can be used to determine if the value stored in the Variant
     * is of the specified type.  Note that this comparison is exact: it does
     * not take implicit casting rules into account.
     *
     * Returns:
     *  true if the Variant contains a value of type T, false otherwise.
     */
    bool isA(T)()
    {
        return cast(bool)(typeid(T) is type);
    }

    /**
     * This member can be used to determine if the value stored in the Variant
     * is of the specified type.  This comparison attempts to take implicit
     * conversion rules into account.
     *
     * Returns:
     *  true if the Variant contains a value of type T, or if the Variant
     *  contains a value that can be implicitly cast to type T; false
     *  otherwise.
     */
    bool isImplicitly(T)()
    {
        return ( cast(bool)(typeid(T) is type)
                || canImplicitCastTo!(T)(type) );
    }

    /**
     * This determines whether the Variant has an assigned value or not.  It
     * is simply short-hand for calling the isA member with a type of void.
     *
     * Returns:
     *  true if the Variant does not contain a value, false otherwise.
     */
    bool isEmpty()
    {
        return isA!(void);
    }

    /**
     * This member will clear the Variant, returning it to an empty state.
     */
    void clear()
    {
        _type = typeid(void);
        value = value.init;
    }

    /**
     * This is the primary mechanism for extracting a value from a Variant.
     * Given a destination type S, it will attempt to extract the value of the
     * Variant into that type.  If the value contained within the Variant
     * cannot be implicitly cast to the given type S, it will throw an
     * exception.
     *
     * You can check to see if this operation will fail by calling the
     * isImplicitly member with the type S.
     *
     * Returns:
     *  The value stored within the Variant.
     */
    storageT!(S) get(S)()
    {
        alias storageT!(S) T;

        if( type !is typeid(T)
                // Let D do runtime check itself
                && !isObject!(T)
                && !isInterface!(T)
                // Allow implicit upcasts
                && !canImplicitCastTo!(T)(type)
          )
            throw new VariantTypeMismatchException(type,typeid(T));

        static if( isAtomicType!(T) )
        {
            if( type is typeid(T) )
            {
                return mixin("this.value._"~T.stringof);
            }
            else
            {
                if( type is tibool ) return cast(T)this.value._bool;
                else if( type is tichar ) return cast(T)this.value._char;
                else if( type is tiwchar ) return cast(T)this.value._wchar;
                else if( type is tidchar ) return cast(T)this.value._dchar;
                else if( type is tibyte ) return cast(T)this.value._byte;
                else if( type is tishort ) return cast(T)this.value._short;
                else if( type is tiint ) return cast(T)this.value._int;
                else if( type is tilong ) return cast(T)this.value._long;
                else if( type is tiubyte ) return cast(T)this.value._ubyte;
                else if( type is tiushort ) return cast(T)this.value._ushort;
                else if( type is tiuint ) return cast(T)this.value._uint;
                else if( type is tiulong ) return cast(T)this.value._ulong;
                else if( type is tifloat ) return cast(T)this.value._float;
                else if( type is tidouble ) return cast(T)this.value._double;
                else if( type is tireal ) return cast(T)this.value._real;
                else if( type is tiifloat ) return cast(T)this.value._ifloat;
                else if( type is tiidouble ) return cast(T)this.value._idouble;
                else if( type is tiireal ) return cast(T)this.value._ireal;
                else
                    throw new VariantTypeMismatchException(type,typeid(T));
            }
        }
        else static if( isDynamicArrayType!(T) )
        {
            return (cast(typeof(T[0])*)this.value.arr.ptr)
                [0 .. this.value.arr.length];
        }
        else static if( isPointerType!(T) )
        {
            return cast(T)this.value.ptr;
        }
        else static if( isObject!(T) )
        {
            return cast(T)this.value.obj;
        }
        else static if( isInterface!(T) )
        {
            return cast(T)this.value.obj;
        }
        else
        {
            if( T.sizeof <= this.value.data.length )
            {
                T result;
                (cast(ubyte*)&result)[0..T.sizeof] =
                    this.value.data[0..T.sizeof];
                return result;
            }
            else
            {
                T result;
                (cast(ubyte*)&result)[0..T.sizeof] =
                    (cast(ubyte[])this.value.arr)[0..T.sizeof];
                return result;
            }
        }
    }

    /**
     * The following operator overloads are defined for the sake of
     * convenience.  It is important to understand that they do not allow you
     * to use a Variant as both the left-hand and right-hand sides of an
     * expression.  One side of the operator must be a concrete type in order
     * for the Variant to know what code to generate.
     */
    typeof(T+T) opAdd(T)(T rhs)     { return get!(T) + rhs; }
    typeof(T+T) opAdd_r(T)(T lhs)   { return lhs + get!(T); } /// ditto
    typeof(T-T) opSub(T)(T rhs)     { return get!(T) - rhs; } /// ditto
    typeof(T-T) opSub_r(T)(T lhs)   { return lhs - get!(T); } /// ditto
    typeof(T*T) opMul(T)(T rhs)     { return get!(T) * rhs; } /// ditto
    typeof(T*T) opMul_r(T)(T lhs)   { return lhs * get!(T); } /// ditto
    typeof(T/T) opDiv(T)(T rhs)     { return get!(T) / rhs; } /// ditto
    typeof(T/T) opDiv_r(T)(T lhs)   { return lhs / get!(T); } /// ditto
    typeof(T%T) opMod(T)(T rhs)     { return get!(T) % rhs; } /// ditto
    typeof(T%T) opMod_r(T)(T lhs)   { return lhs % get!(T); } /// ditto
    typeof(T&T) opAnd(T)(T rhs)     { return get!(T) & rhs; } /// ditto
    typeof(T&T) opAnd_r(T)(T lhs)   { return lhs & get!(T); } /// ditto
    typeof(T|T) opOr(T)(T rhs)      { return get!(T) | rhs; } /// ditto
    typeof(T|T) opOr_r(T)(T lhs)    { return lhs | get!(T); } /// ditto
    typeof(T^T) opXor(T)(T rhs)     { return get!(T) ^ rhs; } /// ditto
    typeof(T^T) opXor_r(T)(T lhs)   { return lhs ^ get!(T); } /// ditto
    typeof(T<<T) opShl(T)(T rhs)    { return get!(T) << rhs; } /// ditto
    typeof(T<<T) opShl_r(T)(T lhs)  { return lhs << get!(T); } /// ditto
    typeof(T>>T) opShr(T)(T rhs)    { return get!(T) >> rhs; } /// ditto
    typeof(T>>T) opShr_r(T)(T lhs)  { return lhs >> get!(T); } /// ditto
    typeof(T>>>T) opUShr(T)(T rhs)  { return get!(T) >>> rhs; } /// ditto
    typeof(T>>>T) opUShr_r(T)(T lhs){ return lhs >>> get!(T); } /// ditto
    typeof(T~T) opCat(T)(T rhs)     { return get!(T) ~ rhs; } /// ditto
    typeof(T~T) opCat_r(T)(T lhs)   { return lhs ~ get!(T); } /// ditto

    Variant opAddAssign(T)(T value) { return (*this = get!(T) + value); } /// ditto
    Variant opSubAssign(T)(T value) { return (*this = get!(T) - value); } /// ditto
    Variant opMulAssign(T)(T value) { return (*this = get!(T) * value); } /// ditto
    Variant opDivAssign(T)(T value) { return (*this = get!(T) / value); } /// ditto
    Variant opModAssign(T)(T value) { return (*this = get!(T) % value); } /// ditto
    Variant opAndAssign(T)(T value) { return (*this = get!(T) & value); } /// ditto
    Variant opOrAssign(T)(T value)  { return (*this = get!(T) | value); } /// ditto
    Variant opXorAssign(T)(T value) { return (*this = get!(T) ^ value); } /// ditto
    Variant opShlAssign(T)(T value) { return (*this = get!(T) << value); } /// ditto
    Variant opShrAssign(T)(T value) { return (*this = get!(T) >> value); } /// ditto
    Variant opUShrAssign(T)(T value){ return (*this = get!(T) >>> value); } /// ditto
    Variant opCatAssign(T)(T value) { return (*this = get!(T) ~ value); } /// ditto

    /**
     * The following operators can be used with Variants on both sides.  Note
     * that these operators do not follow the standard rules of
     * implicit conversions.
     */
    int opEquals(T)(T rhs)
    {
        static if( is( T == Variant ) )
            return opEqualsVariant(rhs);
        else
            return get!(T) == rhs;
    }

    /// ditto
    int opCmp(T)(T rhs)
    {
        static if( is( T == Variant ) )
            return opCmpVariant(rhs);
        else
        {
            auto lhs = get!(T);
            return (lhs < rhs) ? -1 : (lhs == rhs) ? 0 : 1;
        }
    }

    /// ditto
    hash_t toHash()
    {
        return type.getHash(data.ptr);
    }

    /**
     * Performs "stringification" of the value stored within the Variant.  In
     * the case of the Variant having no assigned value, it will return the
     * string "Variant.init".
     *
     * Returns:
     *  The string representation of the value contained within the Variant.
     */
    char[] toString()
    {
        return type.toString;
    }

    /**
     * This can be used to retrieve the TypeInfo for the currently stored
     * value.
     */
    TypeInfo type()
    {
        return _type;
    }

private:
    TypeInfo _type = typeid(void);
    AtomicTypes value;

    TypeInfo type(TypeInfo v)
    {
        return (_type = v);
    }

    int opEqualsVariant(Variant rhs)
    {
        if( type != rhs.type ) return false;
        return cast(bool) type.equals(data.ptr, rhs.data.ptr);
    }

    int opCmpVariant(Variant rhs)
    {
        if( type != rhs.type )
            throw new VariantTypeMismatchException(type, rhs.type);
        return type.compare(data.ptr, rhs.data.ptr);
    }

    void[] data()
    {
        if( type.tsize <= value.data.length )
            return cast(void[])(value.data);
        else
            return value.arr;
    }
}

debug( UnitTest )
{
    unittest
    {
        Variant v;
        assert( v.isA!(void), v.type.toString );
        assert( v.isEmpty, v.type.toString );

        // Test basic integer storage and implicit casting support
        v = 42;
        assert( v.isA!(int), v.type.toString );
        assert( v.isImplicitly!(long), v.type.toString );
        assert( v.isImplicitly!(ulong), v.type.toString );
        assert( !v.isImplicitly!(uint), v.type.toString );
        assert( v.get!(int) == 42 );
        assert( v.get!(long) == 42L );
        assert( v.get!(ulong) == 42uL );

        // Test clearing
        v.clear;
        assert( v.isA!(void), v.type.toString );
        assert( v.isEmpty, v.type.toString );

        // Test strings
        v = "Hello, World!"c;
        assert( v.isA!(char[]), v.type.toString );
        assert( !v.isImplicitly!(wchar[]), v.type.toString );
        assert( v.get!(char[]) == "Hello, World!" );

        // Test array storage
        v = [1,2,3,4,5];
        assert( v.isA!(int[]), v.type.toString );
        assert( v.get!(int[]) == [1,2,3,4,5] );

        // Test pointer storage
        v = &v;
        assert( v.isA!(Variant*), v.type.toString );
        assert( !v.isImplicitly!(int*), v.type.toString );
        // NB: we *should* be able to implicitly cast any pointer to a void*;
        // I'm just not sure how to do it right now.  This test will catch
        // once it works, and remind us to switch the assert around.
        assert( !v.isImplicitly!(void*), "see above comment in source" );
        assert( v.get!(Variant*) == &v );

        // Test object storage
        {
            scope o = new Object;
            v = o;
            assert( v.isA!(Object), v.type.toString );
            assert( v.get!(Object) is o );
        }

        // Test interface support
        {
            interface A {}
            interface B : A {}
            class C : B {}
            class D : C {}

            A a = new D;
            Variant v2 = a;
            B b = v2.get!(B);
            C c = v2.get!(C);
            D d = v2.get!(D);
        }

        // Test doubles and implicit casting
        v = 3.1413;
        assert( v.isA!(double), v.type.toString );
        assert( v.isImplicitly!(real), v.type.toString );
        assert( !v.isImplicitly!(float), v.type.toString );
        assert( v.get!(double) == 3.1413 );

        // Test storage transitivity
        auto u = Variant(v);
        assert( u.isA!(double), u.type.toString );
        assert( u.get!(double) == 3.1413 );

        // Test operators
        v = 38;
        assert( v + 4 == 42 );
        assert( 4 + v == 42 );
        assert( v - 4 == 34 );
        assert( 4 - v == -34 );
        assert( v * 2 == 76 );
        assert( 2 * v == 76 );
        assert( v / 2 == 19 );
        assert( 2 / v == 0 );
        assert( v % 2 == 0 );
        assert( 2 % v == 2 );
        assert( (v & 6) == 6 );
        assert( (6 & v) == 6 );
        assert( (v | 9) == 47 );
        assert( (9 | v) == 47 );
        assert( (v ^ 5) == 35 );
        assert( (5 ^ v) == 35 );
        assert( v << 1 == 76 );
        assert( 1 << Variant(2) == 4 );
        assert( v >> 1 == 19 );
        assert( 4 >> Variant(2) == 1 );

        assert( Variant("abc") ~ "def" == "abcdef" );
        assert( "abc" ~ Variant("def") == "abcdef" );

        // Test op= operators
        v = 38; v += 4; assert( v == 42 );
        v = 38; v -= 4; assert( v == 34 );
        v = 38; v *= 2; assert( v == 76 );
        v = 38; v /= 2; assert( v == 19 );
        v = 38; v %= 2; assert( v == 0 );
        v = 38; v &= 6; assert( v == 6 );
        v = 38; v |= 9; assert( v == 47 );
        v = 38; v ^= 5; assert( v == 35 );
        v = 38; v <<= 1; assert( v == 76 );
        v = 38; v >>= 1; assert( v == 19 );

        v = "abc"; v ~= "def"; assert( v == "abcdef" );

        // Test comparison
        assert( Variant(0) < Variant(42) );
        assert( Variant(42) > Variant(0) );
        assert( Variant(21) == Variant(21) );
        assert( Variant(0) != Variant(42) );
        assert( Variant("bar") == Variant("bar") );
        assert( Variant("foo") != Variant("bar") );

        // Test variants as AA keys
        {
            auto v1 = Variant(42);
            auto v2 = Variant("foo");
            auto v3 = Variant(1+2.0i);

            int[Variant] hash;
            hash[v1] = 0;
            hash[v2] = 1;
            hash[v3] = 2;

            assert( hash[v1] == 0 );
            assert( hash[v2] == 1 );
            assert( hash[v3] == 2 );
        }

        // Test AA storage
        {
            int[char[]] hash;
            hash["a"] = 1;
            hash["b"] = 2;
            hash["c"] = 3;
            Variant vhash = hash;

            assert( vhash.get!(int[char[]])["a"] == 1 );
            assert( vhash.get!(int[char[]])["b"] == 2 );
            assert( vhash.get!(int[char[]])["c"] == 3 );
        }
    }
}
