/**
 * The traits module defines tools useful for obtaining detailed compile-time
 * information about a type.  Please note that the mixed naming scheme used in
 * this module is intentional.  Templates which evaluate to a type follow the
 * naming convention used for types, and templates which evaluate to a value
 * follow the naming convention used for functions.
 *
 * Copyright: Copyright (C) 2005-2006 Sean Kelly.  All rights reserved.
 * License:   BSD style: $(LICENSE)
 * Authors:   Sean Kelly
 */
module tango.core.Traits;


/**
 * Evaluates to true if T is char, wchar, or dchar.
 */
template isCharType( T )
{
    const bool isCharType = is( T == char )  ||
                            is( T == wchar ) ||
                            is( T == dchar );
}


/**
 * Evaluates to true if T is a signed integer type.
 */
template isSignedIntegerType( T )
{
    const bool isSignedIntegerType = is( T == byte )  ||
                                     is( T == short ) ||
                                     is( T == int )   ||
                                     is( T == long )/+||
                                     is( T == cent  )+/;
}


/**
 * Evaluates to true if T is an unsigned integer type.
 */
template isUnsignedIntegerType( T )
{
    const bool isUnsignedIntegerType = is( T == ubyte )  ||
                                       is( T == ushort ) ||
                                       is( T == uint )   ||
                                       is( T == ulong )/+||
                                       is( T == ucent  )+/;
}


/**
 * Evaluates to true if T is a signed or unsigned integer type.
 */
template isIntegerType( T )
{
    const bool isIntegerType = isSignedIntegerType!(T) ||
                               isUnsignedIntegerType!(T);
}


/**
 * Evaluates to true if T is a real floating-point type.
 */
template isRealType( T )
{
    const bool isRealType = is( T == float )  ||
                            is( T == double ) ||
                            is( T == real );
}


/**
 * Evaluates to true if T is a complex floating-point type.
 */
template isComplexType( T )
{
    const bool isComplexType = is( T == cfloat )  ||
                               is( T == cdouble ) ||
                               is( T == creal );
}


/**
 * Evaluates to true if T is an imaginary floating-point type.
 */
template isImaginaryType( T )
{
    const bool isImaginaryType = is( T == ifloat )  ||
                                 is( T == idouble ) ||
                                 is( T == ireal );
}


/**
 * Evaluates to true if T is any floating-point type: real, complex, or
 * imaginary.
 */
template isFloatingPointType( T )
{
    const bool isFloatingPointType = isRealType!(T)    ||
                                     isComplexType!(T) ||
                                     isImaginaryType!(T);
}

/**
 * complex type for the given type
 */
template ComplexTypeOf(T){
    static if(is(T==float)||is(T==ifloat)||is(T==cfloat)){
        alias cfloat ComplexTypeOf;
    } else static if(is(T==double)|| is(T==idouble)|| is(T==cdouble)){
        alias cdouble ComplexTypeOf;
    } else static if(is(T==real)|| is(T==ireal)|| is(T==creal)){
        alias creal ComplexTypeOf;
    } else static assert(0,"unsupported type in ComplexTypeOf "~T.stringof);
}

/**
 * real type for the given type
 */
template RealTypeOf(T){
    static if(is(T==float)|| is(T==ifloat)|| is(T==cfloat)){
        alias float RealTypeOf;
    } else static if(is(T==double)|| is(T==idouble)|| is(T==cdouble)){
        alias double RealTypeOf;
    } else static if(is(T==real)|| is(T==ireal)|| is(T==creal)){
        alias real RealTypeOf;
    } else static assert(0,"unsupported type in RealTypeOf "~T.stringof);
}

/**
 * Evaluates to true if T is a pointer type.
 */
template isPointerType(T)
{
        const isPointerType = false;
}

template isPointerType(T : T*)
{
        const isPointerType = true;
}

debug( UnitTest )
{
    unittest
    {
        assert( isPointerType!(void*) );
        assert( !isPointerType!(char[]) );
        assert( isPointerType!(char[]*) );
        assert( !isPointerType!(char*[]) );
        assert( isPointerType!(real*) );
        assert( !isPointerType!(uint) );

        class Ham
        {
            void* a;
        }

        assert( !isPointerType!(Ham) );

        union Eggs
        {
            void* a;
            uint  b;
        };

        assert( !isPointerType!(Eggs) );
        assert( isPointerType!(Eggs*) );

        struct Bacon {};

        assert( !isPointerType!(Bacon) );

    }
}

/**
 * Evaluates to true if T is a a pointer, class, interface, or delegate.
 */
template isReferenceType( T )
{

    const bool isReferenceType = isPointerType!(T)  ||
                               is( T == class )     ||
                               is( T == interface ) ||
                               is( T == delegate );
}


/**
 * Evaulates to true if T is a dynamic array type.
 */
template isDynamicArrayType( T )
{
    const bool isDynamicArrayType = is( typeof(T.init[0])[] == T );
}

/**
 * Evaluates to true if T is a static array type.
 */
version( GNU )
{
    // GDC should also be able to use the other version, but it probably
    // relies on a frontend fix in one of the latest DMD versions - will
    // remove this when GDC is ready. For now, this code pass the unittests.
    private template isStaticArrayTypeInst( T )
    {
        const T isStaticArrayTypeInst = void;
    }

    template isStaticArrayType( T )
    {
        static if( is( typeof(T.length) ) && !is( typeof(T) == typeof(T.init) ) )
        {
            const bool isStaticArrayType = is( T == typeof(T[0])[isStaticArrayTypeInst!(T).length] );
        }
        else
        {
            const bool isStaticArrayType = false;
        }
    }
}
else
{
    template isStaticArrayType( T : T[U], size_t U )
    {
        const bool isStaticArrayType = true;
    }

    template isStaticArrayType( T )
    {
        const bool isStaticArrayType = false;
    }
}

debug( UnitTest )
{
    unittest
    {
        assert( isStaticArrayType!(char[5][2]) );
        assert( !isDynamicArrayType!(char[5][2]) );

        assert( isStaticArrayType!(char[15]) );
        assert( !isStaticArrayType!(char[]) );

        assert( isDynamicArrayType!(char[]) );
        assert( !isDynamicArrayType!(char[15]) );
    }
}

/**
 * Evaluates to true if T is an associative array type.
 */
private template isAssocArrayType( T )
{
    const bool isAssocArrayType = is( typeof(T.init.values[0])[typeof(T.init.keys[0])] == T );
}


/**
 * Evaluates to true if T is a function, function pointer, delegate, or
 * callable object.
 */
template isCallableType( T )
{
    const bool isCallableType = is( T == function )             ||
                                is( typeof(*T) == function )    ||
                                is( T == delegate )             ||
                                is( typeof(T.opCall) == function );
}


/**
 * Evaluates to the return type of Fn.  Fn is required to be a callable type.
 */
template ReturnTypeOf( Fn )
{
    static if( is( Fn Ret == return ) )
        alias Ret ReturnTypeOf;
    else
        static assert( false, "Argument has no return type." );
}


/**
 * Evaluates to the return type of fn.  fn is required to be callable.
 */
template ReturnTypeOf( alias fn )
{
    static if( is( typeof(fn) Base == typedef ) )
        alias ReturnTypeOf!(Base) ReturnTypeOf;
    else
        alias ReturnTypeOf!(typeof(fn)) ReturnTypeOf;
}


/**
 * Evaluates to a tuple representing the parameters of Fn.  Fn is required to
 * be a callable type.
 */
template ParameterTupleOf( Fn )
{
    static if( is( Fn Params == function ) )
        alias Params ParameterTupleOf;
    else static if( is( Fn Params == delegate ) )
        alias ParameterTupleOf!(Params) ParameterTupleOf;
    else static if( is( Fn Params == Params* ) )
        alias ParameterTupleOf!(Params) ParameterTupleOf;
    else
        static assert( false, "Argument has no parameters." );
}


/**
 * Evaluates to a tuple representing the parameters of fn.  n is required to
 * be callable.
 */
template ParameterTupleOf( alias fn )
{
    static if( is( typeof(fn) Base == typedef ) )
        alias ParameterTupleOf!(Base) ParameterTupleOf;
    else
        alias ParameterTupleOf!(typeof(fn)) ParameterTupleOf;
}


/**
 * Evaluates to a tuple representing the ancestors of T.  T is required to be
 * a class or interface type.
 */
template BaseTypeTupleOf( T )
{
    static if( is( T Base == super ) )
        alias Base BaseTypeTupleOf;
    else
        static assert( false, "Argument is not a class or interface." );
}

/**
 * Strips the []'s off of a type.
 */
template BaseTypeOfArrays(T)
{
    static if( is( T S : S[]) ) {
        alias BaseTypeOfArrays!(S)  BaseTypeOfArrays;
    }
    else {
        alias T BaseTypeOfArrays;
    }
}

/**
 * strips one [] off a type
 */
template ElementTypeOfArray(T:T[])
{
    alias T ElementTypeOfArray;
}

/**
 * Count the []'s on an array type
 */
template rankOfArray(T) {
    static if(is(T S : S[])) {
        const uint rankOfArray = 1 + rankOfArray!(S);
    } else {
        const uint rankOfArray = 0;
    }
}

debug( UnitTest )
{
    static assert( is(BaseTypeOfArrays!(real[][])==real) );
    static assert( is(BaseTypeOfArrays!(real[2][3])==real) );
    static assert( is(ElementTypeOfArray!(real[])==real) );
    static assert( is(ElementTypeOfArray!(real[][])==real[]) );
    static assert( is(ElementTypeOfArray!(real[2][])==real[2]) );
    static assert( is(ElementTypeOfArray!(real[2][2])==real[2]) );
    static assert( rankOfArray!(real[][])==2 );
    static assert( rankOfArray!(real[2][])==2 );
}
