/**
 * Authors: k.inaba
 * License: NYSL 0.9982 http://www.kmonos.net/nysl/
 *
 * Common tricks and utilities for programming in D.
 */
module tricks.tricks;
import tricks.test;
import core.exception;
import std.array  : appender;
import std.format : formattedWrite;
import std.traits;
import std.typetuple;

/// Simple Wrapper for std.format.doFormat

string sprintf(string fmt, T...)(T params)
{
	auto writer = appender!string();
	formattedWrite(writer, fmt, params);
	return writer.data;
}

unittest
{
	assert_eq( sprintf!"%s == %04d"("1+2", 3), "1+2 == 0003" );
	assert_eq( sprintf!"%2$s == %1$s"("1+2", 5, 8), "5 == 1+2" );
	assert_throw!Error( sprintf!"%s%s"(1) );
}

/// Create an exception with automatically completed filename and lineno information

ExceptionType genex(ExceptionType, string fn=__FILE__, int ln=__LINE__, T...)(T params)
{
	static if( T.length > 0 && is(T[$-1] : Throwable) )
		return new ExceptionType(params[0..$-1], fn, ln, params[$-1]);
	else
		return new ExceptionType(params, fn, ln);
}

unittest
{
	assert_ne( genex!Exception("msg").file, "" );
	assert_ne( genex!Exception("msg").line, 0 );
	assert_ne( genex!Exception("msg",new Exception("bar")).next, Exception.init );
}

/// Mixing-in the bean constructor for a class

/*mixin*/
template SimpleConstructor()
{
	/// member-by-member constructor
	static if( is(typeof(super) == Object) || super.tupleof.length==0 )
		this( typeof(this.tupleof) params )
		{
			static if(this.tupleof.length>0)
				this.tupleof = params;
		}
	else
		this( typeof(super.tupleof) ps, typeof(this.tupleof) params )
		{
			// including (only) the direct super class members
			// may not always be a desirable choice, but should work for many cases
			super(ps);
			static if(this.tupleof.length>0)
				this.tupleof = params;
		}
}

unittest
{
	class Temp
	{
		int x;
		string y;
		mixin SimpleConstructor;
	}
	assert_eq( (new Temp(1,"foo")).x, 1 );
	assert_eq( (new Temp(1,"foo")).y, "foo" );
	assert( !__traits(compiles, new Temp) );
	assert( !__traits(compiles, new Temp(1)) );
	assert( !__traits(compiles, new Temp("foo",1)) );

	class Tomp : Temp
	{
		real z;
		mixin SimpleConstructor;
	}
	assert_eq( (new Tomp(1,"foo",2.5)).x, 1 );
	assert_eq( (new Tomp(1,"foo",2.5)).y, "foo" );
	assert_eq( (new Tomp(1,"foo",2.5)).z, 2.5 );
	assert( !__traits(compiles, new Tomp(3.14)) );

	// shiyo- desu. Don't use in this way.
	//   Tamp tries to call new Tomp(real) (because it only sees Tomp's members),
	//   but it fails because Tomp takes (int,string,real).
	assert( !__traits(compiles, {
		class Tamp : Tomp { mixin SimpleConstructor; }
	}) );
}

hash_t structuralHash(T)(T x)
{
	alias SC_Unqual!(T) UCT;

	static if(is(UCT == class))
		return (x is null ? 0 : (cast(UCT)x).toHash());
	else
	static if(SC_HasGoodHash!(UCT))
		{ return typeid(UCT).getHash(&x); }
	else
	static if(is(UCT T == T[]))
		{ hash_t h; foreach(e; x) h+=structuralHash(e); return h; }
	else
	static if(is(UCT == struct))
		static if(__traits(compiles, std.bigint.BigInt))
			static if(is(UCT == std.bigint.BigInt))
				return cast(hash_t) x.toInt();
			else
				static assert(false, "should not use struct.toHash");
		else
			static assert(false, "should not use struct.toHash");
	else
		static assert(false, "nonhashable datatype "~UCT.stringof);
}

alias std.traits.Unqual SC_Unqual;

template SC_HasGoodHash(T)
{
	enum SC_HasGoodHash =
		is(T : bool) || isNumeric!(T) || isSomeString!(T) || isSomeChar!(T) || isPointer!(T);
}

/// Mixing-in the MOST-DERIVED-member-wise comparator for a class
/// BE SURE THAT THIS IS CONSISTENT WITH opCmp and opEquals

template SimpleToHash()
{
	override hash_t toHash() const /// member-by-member hash
	{
		hash_t h = 0;
		foreach(mem; this.tupleof)
			h += structuralHash(mem);
		return h;
	}
}

/// Mixing-in the MOST-DERIVED-member-wise comparator for a class

template SimpleCompareWithoutToHash()
{
	override bool opEquals(Object rhs) const /// member-by-member equality
	{
		return opCmp(rhs) == 0;
	}

	override int opCmp(Object rhs_) const /// member-by-member compare
	{
		if( auto rhs = cast(typeof(this))rhs_ )
		{
			foreach(i,_; this.tupleof)
			{
				static if(is(typeof(_) == struct))
					auto c = (cast(SC_Unqual!(typeof(_)))this.tupleof[i]).opCmp(rhs.tupleof[i]);
				else
					auto c = typeid(_).compare(&this.tupleof[i],&rhs.tupleof[i]);
				if(c)
					return c;
			}
			return 0;
		}
		return typeid(this).opCmp(typeid(rhs_));
	}
}

/// Mixing-in the MOST-DERIVED-member-wise comparator for a class

/*mixin*/
template SimpleCompare()
{
	mixin SimpleToHash;
	mixin SimpleCompareWithoutToHash;
}

unittest
{
	class Temp
	{
		int x;
		string y;
		mixin SimpleConstructor;
		mixin SimpleCompare;
	}
	assert_eq( new Temp(1,"foo"), new Temp(1,"foo") );
	assert_eq( (new Temp(1,"foo")).toHash, (new Temp(1,"foo")).toHash );
	assert_ne( new Temp(1,"foo"), new Temp(2,"foo") );
	assert_ne( new Temp(1,"foo"), new Temp(1,"bar") );
	assert_gt( new Temp(1,"foo"), new Temp(1,"bar") );
	assert_lt( new Temp(1,"foo"), new Temp(2,"bar") );
	assert_ge( new Temp(1,"foo"), new Temp(1,"foo") );

	class TempDummy
	{
		int x;
		string y;
		mixin SimpleConstructor;
		mixin SimpleCompare;
	}
	assert_ne( new Temp(1,"foo"), new TempDummy(1,"foo") );
	assert_nothrow( new Temp(1,"foo") <= new TempDummy(1,"foo") );
}

/// Mixing-in a simple toString method

/*mixin*/
template SimpleToString()
{
	/// member-by-member toString
	override string toString()
	{
		string str = sprintf!"%s("(typeof(this).stringof);
		foreach(i,mem; this.tupleof)
		{
			if(i) str ~= ",";
			static if( is(typeof(mem) == std.bigint.BigInt) )
				str ~= std.bigint.toDecimalString(mem);
			else
				str ~= sprintf!"%s"(mem);
		}
		return str ~ ")";
	}
}

version(unittest) import std.bigint;
unittest
{
	class Temp
	{
		int x;
		string y;
		BigInt z;
		mixin SimpleConstructor;
		mixin SimpleToString;
	}
	assert_eq( (new Temp(1,"foo",BigInt(42))).toString(), "Temp(1,foo,42)" );
}

/// Everything is in

/*mixin*/
template SimpleClass()
{
	mixin SimpleConstructor;
	mixin SimpleCompare;
	mixin SimpleToString;
}

/// Utility

template firstParam(T)
{
	alias ParameterTypeTuple!(T)[0] firstParam;
}
