module epic.bindableobject;

/**
 *	The Bindable Property System.
 *  A part of the Epic library.
 *	Author: Justin Whear
 *	Created: February 2009
 *
 *	This module provides BindableProperty, BindableObject, and AttachedProperty.
 */

public import std.boxer;
debug import std.stdio;

static public T DirectCast(T)(Box b) { return unbox!(T)(b); }
static public Box Package(T)(T val) { return box(val); }

/*
public typedef void* nulltype;
public nulltype Null;
*/

private template tSetValue(T) {
	public void SetValue(BindableProperty bp, T value) {
		Box v = box(value);
		_SetValue(bp, v);
	}
}

private template tRegister(T) {
	/**
	 * Registers a property with the Bindable Property system. Takes the property name, the TypeInfo of the property
	 *   type (use typeid(x)), a reference to the object which the property will reside on (usually "this"), and a 
	 * 	 default value.
	 */
	static public BindableProperty Register(string propName, TypeInfo ti, BindableObject owner, T defaultValue) {
		return _Register(propName, ti, owner, box(defaultValue));
	}
}

/**
 *	BindableProperty, together with the BindableObject form the basis of the Bindable Property System.
 *	Use BindableProperty.Register to add properties to a BindableObject.
 */
public class BindableProperty {
	/// The name of the property. This must be unique or it will replace existing properties with the same name.
	public string PropertyName;
	/// The type of the underlying property.
	public TypeInfo Type;
	/// A default value. The property will be initialized to this.
	public Box DefaultValue;
	/// If set, the property will attempt to coerce the value using this path
	public string DerivedValuePath;
	// public TypeConverter Converter;
	// public Event Changed;
	
	/**
	 * Default constructor
	 */
	public this() { }
	/**
	 * Constructor overload.
	 * @param string propName	The name of the property
	 * @param TypeInfo ti		The type of the underlying value
	 * @param Box defaultValue	A default value.
	 */
	public this(string propName, TypeInfo ti, Box defaultValue) {
		PropertyName = propName;
		Type = ti;
		DefaultValue = defaultValue;
	}
	
	/*
	mixin tRegister!(bool) tm_bool;		alias tm_bool.Register Register;
	mixin tRegister!(byte) tm_byte;		alias tm_byte.Register Register;
	mixin tRegister!(ubyte) tm_ubyte;	alias tm_ubyte.Register Register;
	mixin tRegister!(int) tm_int;		alias tm_int.Register Register;
	mixin tRegister!(uint) tm_uint;		alias tm_uint.Register Register;
	mixin tRegister!(long) tm_long;		alias tm_long.Register Register;
	mixin tRegister!(ulong) tm_ulong;	alias tm_ulong.Register Register;
	mixin tRegister!(float) tm_float;	alias tm_float.Register Register;
	mixin tRegister!(double) tm_double;	alias tm_double.Register Register;
	mixin tRegister!(real) tm_real;		alias tm_real.Register Register;
	mixin tRegister!(char) tm_char;		alias tm_char.Register Register;
	mixin tRegister!(char[]) tm_string;	alias tm_string.Register Register;
	mixin tRegister!(Object) tm_Object;	alias tm_Object.Register Register;
	mixin tRegister!(nulltype) tm_void; alias tm_void.Register Register;
	*/

	/*
	*	This is actually better than using the mixins above, as it forces the using class to box the defaulValue correctly.
	*/
	static public BindableProperty Register(string propName, TypeInfo ti, BindableObject owner, Box defaultValue) {
		return _Register(propName, ti, owner, defaultValue);
	}
	
	/**
	 * Does the actual work of registering this property with the BindableObject.
	 */
	static protected BindableProperty _Register(string propName, TypeInfo ti, BindableObject owner, Box defaultValue) {
		BindableProperty bp = new BindableProperty();
		bp.PropertyName = propName;
		bp.Type = ti;
		bp.DefaultValue = defaultValue;
		owner.RegisterBP(bp);
		return bp;
	}
}

public class AttachedProperty : BindableProperty {
	public Object AttachmentOwner;
	public this() { super(); }
	public this(string propName, TypeInfo ti, Object attacher, Box defaultValue) {
		super(propName, ti, defaultValue);
		AttachmentOwner = attacher;
	}
}

public class BindableObject {
	
	protected Box[string] _propHash;
	
	this() {
		InitBindableProperties();
	}
	
	public bool HasProperty(string name) {
		Box* loc = (name in _propHash);
		return (loc !is null);
	}
	
	public Box GetValue(BindableProperty bp) {
		return _propHash[bp.PropertyName];
	}
	
	public Box GetValue(string name) {
		return _propHash[name];
	}
	
	
	mixin tSetValue!(bool) tm_bool;			alias tm_bool.SetValue SetValue;
	mixin tSetValue!(byte) tm_byte;			alias tm_byte.SetValue SetValue;
	mixin tSetValue!(ubyte) tm_ubyte;		alias tm_ubyte.SetValue SetValue;
	mixin tSetValue!(int) tm_int;			alias tm_int.SetValue SetValue;
	mixin tSetValue!(uint) tm_uint;			alias tm_uint.SetValue SetValue;
	mixin tSetValue!(long) tm_long;			alias tm_long.SetValue SetValue;
	mixin tSetValue!(ulong) tm_ulong;		alias tm_ulong.SetValue SetValue;
	mixin tSetValue!(float) tm_float;		alias tm_float.SetValue SetValue;
	mixin tSetValue!(double) tm_double;		alias tm_double.SetValue SetValue;
	mixin tSetValue!(real) tm_real;			alias tm_real.SetValue SetValue;
	mixin tSetValue!(char) tm_char;			alias tm_char.SetValue SetValue;
	mixin tSetValue!(string) tm_string;		alias tm_string.SetValue SetValue;
	mixin tSetValue!(Object) tm_Object;		alias tm_Object.SetValue SetValue;
	
	// String setters
	public void SetValue(string propName, float value) {
		Box v = box(value);
		_SetValue(propName, v);
	}
	
	public void SetValue(string propName, string value) {
		Box v = box(value);
		_SetValue(propName, v);
	}
	
	public void SetValue(string propName, Object value) {
		Box v = box(value);
		_SetValue(propName, v);
	}
	
	public void SetValue(string propName, Box value) {
		_SetValue(propName, value);
	}


	// Bottom level set functions
	protected void _SetValue(BindableProperty bp, Box value) {
		_SetValue(bp.PropertyName, value);
	}
	
	protected void _SetValue(string name, Box value) {
		if (name in _propHash)
		{
			if (_propHash[name] !is value)
			{
				_propHash[name] = value;
			//	bp.Changed.Raise(null);
			}
		} else throw new Exception("SetValue: Property "~name~" does not exist on "~this.toString);
	}

	
	private void RegisterBP(BindableProperty bp) {
		_propHash[bp.PropertyName] = bp.DefaultValue;
	}
	
	/**
	 * Attach a property to this object.
	 */
	public void AttachProperty(AttachedProperty ap) {
		_propHash[ap.PropertyName] = ap.DefaultValue;
	}
	
	/**
	 * Detach a property from this object.
	 */
	public void DetachProperty(AttachedProperty ap) {
		_propHash.remove(ap.PropertyName);
	}
	
	protected void InitBindableProperties() {
	
	}
}

/**
 * Test class
 */
class Duck : BindableObject {
	public string Name() { return DirectCast!(string)(GetValue(NameProperty)); }
	public void Name(string value) { SetValue(NameProperty, value); }
	public BindableProperty NameProperty;
	
	public uint Anger() { return DirectCast!(uint)(GetValue(AngerProperty)); }
	public void Anger(uint value) { SetValue(AngerProperty, value); }
	public BindableProperty AngerProperty;
	
	public bool CanFly() { return DirectCast!(bool)(GetValue(CanFlyProperty)); }
	public void CanFly(bool value) { SetValue(CanFlyProperty, value); }
	public BindableProperty CanFlyProperty;
	
	protected override void InitBindableProperties() {
		super.InitBindableProperties();
		NameProperty = BindableProperty.Register("Name", typeid(string), this, Package!(string)("Donald"));
		AngerProperty = BindableProperty.Register("Anger", typeid(uint), this, Package!(uint)(15));
		CanFlyProperty = BindableProperty.Register("CanFly", typeid(bool), this, Package!(bool)(true));
	}
}
unittest {
	Duck a = new Duck();
	Duck b = new Duck();
	b.Name = "Daisy";	b.Anger = 3;
	assert(a.HasProperty("Name"));
	assert(a.HasProperty("Anger"));
	assert(!a.HasProperty("A_Fake_Property"));
	assert(a.Anger == 15);
	assert(b.Name == "Daisy");
	assert(b.Anger == 3);
}

// Test inheritance
unittest {
	class Mallard : Duck {
		public float FlightRating() { return DirectCast!(float)(GetValue(FlightRatingProperty)); }
		public void FlightRating(float value) { SetValue(FlightRatingProperty, value); }
		public BindableProperty FlightRatingProperty;
		
		protected override void InitBindableProperties() {
			super.InitBindableProperties();
			FlightRatingProperty = BindableProperty.Register("FlightRating", typeid(float), this, Package!(float)(0));
		}
	}
	
	auto m = new Mallard();
	assert(m.HasProperty("Name"));
	assert(m.HasProperty("Anger"));
	assert(m.HasProperty("FlightRating"));
	float testFloat = 0.6;
	m.FlightRating = testFloat;
	assert(m.FlightRating == testFloat);
}

// Test getting and setting by property name
unittest {
	Duck a = new Duck();
	a.SetValue("Name", "George");
	assert(a.Name == "George");
	assert(DirectCast!(string)(a.GetValue("Name")) == "George");
}

// Test attached properties
unittest {
	class DuckList {
		protected Duck[] _list;
		public Duck[] Flock() { return _list; }
		public void Add(Duck d) {
			_list.length = _list.length + 1;
			_list[$-1] = d;
		}
	}
	class DuckHerder : BindableObject {
		public DuckList Ducks() { return DirectCast!(DuckList)(GetValue(DucksProperty)); }
		public void Ducks(DuckList value) { SetValue(DucksProperty, value); }
		public BindableProperty DucksProperty;
		
		public this() {
			Ducks = new DuckList;
		}
		
		protected override void InitBindableProperties() {
			super.InitBindableProperties();
			DucksProperty = BindableProperty.Register("Ducks", typeid(DuckList), this, Package!(DuckList)(null));
		}
	}
	
	DuckHerder herder = new DuckHerder();
	herder.Ducks.Add(new Duck());
	herder.Ducks.Add(new Duck());
	foreach (int i, Duck d; herder.Ducks.Flock)
	{
		d.AttachProperty(new AttachedProperty("PlaceInFlock", typeid(int), herder, Package!(int)(99)));
		assert(d.HasProperty("PlaceInFlock"));
		d.SetValue("PlaceInFlock", Package!(int)(i));
	}
	assert(DirectCast!(int)(herder.Ducks.Flock[0].GetValue("PlaceInFlock")) == 0);
	assert(DirectCast!(int)(herder.Ducks.Flock[1].GetValue("PlaceInFlock")) == 1);
}