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

/// 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( sprintf!"%s == %d"("1+2", 3)   == "1+2 == 3" );
	assert( sprintf!"%s == %04d"("1+2", 3) == "1+2 == 0003" );
}

/// Unittest helper that asserts an expression must throw something

void assert_throw(ExceptionType, T, string fn=__FILE__, int ln=__LINE__)(lazy T t, string msg="")
{
	try {
		t();
	} catch(ExceptionType) {
		return;
	} catch(Throwable e) {
		onAssertErrorMsg(fn, ln, msg.length ? msg : sprintf!"exception [%s]"(e));
	}
	onAssertErrorMsg(fn, ln, msg.length ? msg : "no execption");
}

/// Unittest helper that asserts an expression must not throw anything

void assert_nothrow(T, string fn=__FILE__, int ln=__LINE__)(lazy T t, string msg="")
{
	try {
		t();
	} catch(Throwable e) {
		onAssertErrorMsg(fn, ln, msg.length ? msg : sprintf!"exception [%s]"(e));
	}
}

unittest
{
	auto error = {throw new Error("hello");};
	auto nothing = (){};
	auto assertError = {assert(0);};

	assert_nothrow          ( assert_nothrow(nothing()) );
	assert_throw!AssertError( assert_nothrow(error()) );
	assert_throw!AssertError( assert_nothrow(assertError()) );

	assert_nothrow          ( assert_throw!Error(error()) );
	assert_throw!AssertError( assert_throw!Error(nothing()) );
	assert_nothrow          ( assert_throw!Error(assertError()) );
	assert_throw!AssertError( assert_throw!AssertError(error()) );
}

/// Unittest helpers asserting two values are in some relation ==, !=, <, <=, >, >=

template assertOp(string op)
{
	void assertOp(A, B, string fn=__FILE__, int ln=__LINE__)(A a, B b, string msg="")
	{
		try {
			if( mixin("a"~op~"b") ) return;
		} catch(Throwable e) {
			onAssertErrorMsg(fn, ln, msg.length ? msg : sprintf!"exception [%s]"(e));
		}
		onAssertErrorMsg(fn, ln, msg.length ? msg : sprintf!"%s !%s %s"(a,op,b));
	}
}

alias assertOp!(`==`) assert_eq;
alias assertOp!(`!=`) assert_ne;
alias assertOp!(`<`)  assert_lt;
alias assertOp!(`<=`) assert_le;
alias assertOp!(`>`)  assert_gt;
alias assertOp!(`>=`) assert_ge;

unittest
{
	assert_nothrow( assert_eq(1, 1) );
	assert_nothrow( assert_ne(1, 0) );
	assert_nothrow( assert_lt(0, 1) );
	assert_nothrow( assert_le(0, 1) );
	assert_nothrow( assert_le(0, 0) );
	assert_nothrow( assert_gt(1, 0) );
	assert_nothrow( assert_ge(1, 0) );
	assert_nothrow( assert_ge(0, 0) );

	assert_throw!AssertError( assert_eq(1, 0) );
	assert_throw!AssertError( assert_ne(1, 1) );
	assert_throw!AssertError( assert_lt(1, 1) );
	assert_throw!AssertError( assert_lt(1, 0) );
	assert_throw!AssertError( assert_le(1, 0) );
	assert_throw!AssertError( assert_gt(0, 0) );
	assert_throw!AssertError( assert_gt(0, 1) );
	assert_throw!AssertError( assert_ge(0, 1) );

	class Temp { bool opEquals(int x){return x/x==x;} }
	assert_throw!AssertError( assert_eq(new Temp, 0) );
	assert_nothrow          ( assert_eq(new Temp, 1) );
	assert_throw!AssertError( assert_eq(new Temp, 2) );
}

/* [Todo] is there any way to clearnly implement "assert_compiles" and "assert_not_compile"? */

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

/*mixin*/ template SimpleConstructor()
{
	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;
		}
	}) );
}

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

/*mixin*/ template SimpleCompare()
{
	override bool opEquals(Object rhs_) const
	{
		if( auto rhs = cast(typeof(this))rhs_ )
		{
			foreach(i,_; this.tupleof)
				if( this.tupleof[i] != rhs.tupleof[i] )
					return false;
			return true;
		}
		assert(false, sprintf!"Cannot compare %s with %s"(typeid(this), typeid(rhs_)));
	}

	override hash_t toHash() const
	{
		hash_t h = 0;
		foreach(mem; this.tupleof)
			h += typeid(mem).getHash(&mem);
		return h;
	}

	override int opCmp(Object rhs_) const
	{
		if( auto rhs = cast(typeof(this))rhs_ )
		{
			foreach(i,_; this.tupleof)
				if(auto c = typeid(_).compare(&this.tupleof[i],&rhs.tupleof[i]))
					return c;
			return 0;
		}
		assert(false, sprintf!"Cannot compare %s with %s"(typeid(this), typeid(rhs_)));
	}
}

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_throw!AssertError( new Temp(1,"foo") == new TempDummy(1,"foo") );
	assert_throw!AssertError( new Temp(1,"foo") <= new TempDummy(1,"foo") );
}

/// Mixing-in a simple toString method

/*mixin*/ template SimpleToString()
{
	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)" );
}
