module tailconst;

import std.traits;
import std.range;

/**
 * Return the tail-const type for a  given type
**/
template TailConst( T ) {
    static if ( is( T U : U[] ) ) {
        alias const(Unqual!U)[] TailConst;
    } else static if ( is( T U : U* ) ) {
        alias const(Unqual!U)* TailConst;
    } else static if ( is( T.tailconst_t ) ) {
        alias T.tailconst_t TailConst;
    } else static assert( false );
}

/**
 *  Ditto for tail-mutable
**/
template TailMutable( T ) {
    static if ( is( T U : U[] ) ) {
        alias Unqual!U[] TailMutable;
    } else static if ( is( T U : U* ) ) {
        alias Unqual!U* TailMutable;
    } else static if ( is( T.tailmutable_t ) ) {
        alias T.tailmutable_t TailMutable;
    } else static assert( false );
}

/**
 *  Ditto for tail-immutable
**/
template TailImmutable( T ) {
    static if ( is( T U : U[] ) ) {
        alias immutable(Unqual!U)[] TailImmutable;
    } else static if ( is( T U : U* ) ) {
        alias immutable(Unqual!U)* TailImmutable;
    } else static if ( is( T.tailimmutable_t ) ) {
        alias T.tailimmutable_t TailImmutable;
    } else static assert( false );
}

unittest {
    struct test {
        alias int tailmutable_t;
        alias double tailconst_t;
        alias string tailimmutable_t;
    }
    
    assert( is( TailMutable!( int[] ) == int[] ) );
    assert( is( TailMutable!( immutable( int[] ) ) == int[] ) );
    assert( is( TailMutable!( const(int)[] ) == int[] ) );
    assert( is( TailMutable!test  == int ) );
    
    assert( is( TailConst!( int[] ) == const(int)[] ) );
    assert( is( TailConst!( immutable( int[] ) ) == const(int)[] ) );
    assert( is( TailConst!( const(int)[] ) == const(int)[] ) );
    assert( is( TailConst!test == double ) );
    
    assert( is( TailImmutable!( int[] ) == immutable(int)[] ) );
    assert( is( TailImmutable!( immutable( int[] ) ) == immutable(int)[] ) );
    assert( is( TailImmutable!( const(int)[] ) == immutable(int)[] ) );
    assert( is( TailImmutable!test == string ) );
}

/**
 * Converts the given parameter to tail const
**/
TailConst!T tailconst( T )( T t ) {
    TailConst!T tmp = t;
    return tmp;
}

/**
 * Converts the given parameter to tail mutable
**/
TailMutable!T tailmutable( T )( T t ) {
	TailMutable!T tmp = t;
	return tmp;
}

/**
 * Converts the given parameter to tail immutable
**/
TailImmutable!T tailimmutable( T )( T t ) {
	TailImmutable!T tmp = t;
	return tmp;
}

unittest {
    struct test( T ) {
        alias test!(Unqual!T) tailmutable_t;
        alias test!(const Unqual!T) tailconst_t;
        alias test!(immutable T) tailimmutable_t;
        this( tailmutable_t t ) {}
        this( tailconst_t t ) {}
        this( tailimmutable_t t ) {}
    }
    
	test!(const int) t; tailmutable( t );
	
    assert( __traits( compiles, { tailconst( [1,2,3] ); } ) );
    assert( __traits( compiles, { test!int t; tailconst( t ); } ) );
    assert( __traits( compiles, { test!(const int) t; tailconst( t ); } ) );
    assert( __traits( compiles, { test!(immutable int) t; tailconst( t ); } ) );
    assert( __traits( compiles, { test!int t; tailconst( t ); } ) );
    assert( __traits( compiles, { tailmutable( [1,2,3] ); } ) );
    assert( __traits( compiles, { test!int t; tailmutable( t ); } ) );
    assert( __traits( compiles, { test!(const int) t; tailmutable( t ); } ) );
    assert( __traits( compiles, { test!(immutable int) t; tailmutable( t ); } ) );
    assert( !__traits( compiles, { tailimmutable( [1,2,3] ); } ) );
    assert( __traits( compiles, { test!int t; tailimmutable( t ); } ) );
    assert( __traits( compiles, { test!(const int) t; tailimmutable( t ); } ) );
    assert( __traits( compiles, { test!(immutable int) t; tailimmutable( t ); } ) );
}

/**
 *  Example range implementing what is needed for tail-const.
**/
struct SimpleRange( T ) {
	T innerRange;
	alias SimpleRange!(TailConst!T) tailconst_t;
	alias SimpleRange!(TailMutable!T) tailmutable_t;
	alias SimpleRange!(TailImmutable!T) tailimmutable_t;
	
	static if ( !is( T == TailMutable!T ) ) {
		this( TailImmutable!SimpleRange r ) {
			innerRange = r.innerRange;
		}
	}
	static if ( !is( T == TailImmutable!T ) ) {
		this( TailMutable!SimpleRange r ) {
			innerRange = r.innerRange;
		}
	}
	static if ( !is( T == TailConst!T ) ) {
		TailConst!SimpleRange get( ) {
			return tailconst( this );
		}
		alias get this;
	}
	
	this( T t ) {
		innerRange = t;
	}
	
	auto front( ) {
		return innerRange.front;
	}
	
	void popFront( ) {
		innerRange.popFront( );
	}
	
	bool empty( ) {
		return innerRange.empty;
	}
	
	SimpleRange save( ) {
		return this;
	}
}

SimpleRange!T simpleRange( T )( T r ) {
	return SimpleRange!T( r );
}

void main( ) {
	int[] a = [1,2,3];
	auto o = simpleRange( a );
	TailConst!( typeof( o ) ) c = o;
	immutable(int)[] b = [4,5,6];
	auto p = simpleRange( b );
	TailConst!( typeof( p ) ) d = p;
}