using Gee;

public interface Clone {
	public abstract Object clone ();
}

public class Demo : Object, Clone {
	public Demo () {
		print ("Default Constructor called\n");
		Demo.with_string ("DefaultB");
	}
	public Demo.with_string (string _s) {
		print ("Constructor.with_string() called\n");
		s = _s;
	}

	public string s { get; set; default = "DefaultA"; }

	public virtual void draw () {
		print ("Demo::draw()\n");
	}

	public Object clone () {
		var d = new Demo ();
		d.s = this.s;
		return d;
	}
}

public interface Foo : Object {
	public abstract void foo (int i);
}

public class DemoChild : Demo, Foo {
	public override void draw () {
		base.draw();
		print ("DemoChild::draw()\n");
	}
	public void foo (int i) {
	}
}

enum Machine {
	CAR,
	BYCICLE,
	LEGS;

	public bool is_fast () {
		return this == CAR;
	}
}

public class MyHashMap : HashMap <string, int>, Clone {
	public Object clone () {
		var h = new MyHashMap ();

		foreach (var i in this.entries) {
			h[i.key] = i.value;
		}

		return h;
	}
}

public class Car : Object {
	public int color { get; set; default = 0; }

	[CCode (notify = false)]
	public int color_without_notification { get; set; default = 0; }
}

delegate void MyDelegateType (int a, double b);

void f (MyDelegateType deleg) {
	deleg (32, 0.25);
}

/* Signals */
public class MyButton : Object {
	public signal void clicked ();

	public void test () {
		print ("MyButton::test () called - calls clicked()\n");
		clicked ();
	}
}

void handler_c (MyButton source) {
	print ("Handler for MyButton called\n");
}

errordomain CarError {
	BROKEN_WHEEL,
	ENGINE_WEDGE
}

void method () throws CarError {
	throw new CarError.ENGINE_WEDGE ("Engine is broken!");
}

void init_a (ref int a) {
}

void my_method (int a, out int b, ref int c) {
	b = 3;
}

void my_null_method (Object? a, Object b) { }

int method_with_preconditions (double d, int i, Object obj)
	 requires (d >= 0.0 && d <= 1.0)
	 requires (i >= 0 && i <= 10)
	 ensures (result >= 0) {
	return i * i;
}

/* Plane vs Struct */
class Plane {
	public double speed;
	public int weight;

	public Plane (double speed, int weight) {
		this.speed = speed;
		this.weight = weight;
	}

	public void print () {
		stdout.printf ("(%g, %d)", this.speed, this.weight);
	}
}

struct Plane_s {
	public double speed;
	public int weight;

	public Plane_s (double speed, int weight) {
		this.speed = speed;
		this.weight = weight;
	}

	public void print () {
		stdout.printf ("(%g, %d)", this.speed, this.weight);
	}
}

/* Static Construct */
public class FooWithStaticConstruct {
	static construct {
		stdout.printf ("FooWithStaticConstruct: Static constructor invoked\n");
	}
}

public class FooWithStaticConstructChild {
	static construct {
		stdout.printf ("FooWithStaticConstructChild: Static constructor invoked\n");
	}
}

void main () {
	var d1 = new DemoChild ();
	print (d1.s + "\n");
	d1.draw();

	print ((d1 is DemoChild).to_string() + "\n");

	Demo d2 = d1.clone() as Demo;
	print ((d2 is DemoChild).to_string() + "\n");

	d1.s = "d1";
	d2.s = "d2";

	print (d2.s + "\n");

	d2.s = "d2 updated";

	print (d1.s + "\n");
	print (d2.s + "\n");

	print ("--- HashMap ---\n");
	var h1 = new MyHashMap ();
	var h2 = h1.clone () as MyHashMap;

	h1["aaa"] = 3;
	h2["bbb"] = 4;

	foreach (var h in h1.keys) {
		stdout.printf ("%s", h + "\n");
	}

	foreach (var h in h2.keys) {
		stdout.printf ("%s", h + "\n");
	}

	foreach (var h in h2.entries) {
		stdout.printf ("%s", "--- " + h.get_type ().name () + " ---\n");
	}

	stdout.printf ("%s", "--- " + h2.get_type ().name () + " ---\n");

	var car = new Car ();
	car.notify.connect ((s, p) => stdout.printf ("Property `%s` changed\n",
				p.name));
	car.notify["color"].connect ((s, p) => stdout.printf ("Property `%s` changed\n",
				p.name));
	car.color = 5;

	MyDelegateType deleg = (a, b) => {
		stdout.printf ("a = %d; b = %g\n", a, b);
	};

	deleg (42, 0.75);

	f (deleg);

	print ("--- Signals ---\n");
	var b = new MyButton ();
	b.clicked.connect ((s) => print ("Unnamed handler A\n"));
	b.clicked.connect ((s) => {
		print ("Unnamed handler B\n");
	});

	b.clicked.connect (handler_c);
	b.test ();
	b.clicked.disconnect (handler_c);

	print ("--- Exceptions ---\n");
	try {
		method ();
	} catch (CarError e) {
		stderr.printf ("Error: %s\n", e.message);
	}

	print ("--- Out and Ref Variables ---\n");
	int a1, b1, c1 = 5;
	my_method (a1 = 5, out b1, ref c1);

	print ("--- Nullability ---\n");
	my_null_method (null, new Object ());
	string? a = "hello";
	string a2 = (!) a;

	print ("--- {Pre,Post}conditions ---\n");
	method_with_preconditions (1, 0, new Object ());

	print ("--- Class vs Struct ---\n");
	var p1 = new Plane (23.23, 293);
	var p2 = p1;
	print ("Class: ");
	p1.print ();
	p2.print ();
	print (" -> ");
	p2.speed = 777;
	p1.print ();
	p2.print ();
	stdout.printf (" p1 == p2 ? %s", (p1 == p2).to_string ());
	print ("\n");

	var p1_s = Plane_s (23.23, 293);
	var p2_s = p1_s;
	print ("Struct: ");
	p1_s.print ();
	p2_s.print ();
	print (" -> ");
	p2_s.speed = 777;
	p1_s.print ();
	p2_s.print ();
	stdout.printf (" p1_s == p2_s ? %s", (p1_s == p2_s).to_string ());
	print ("\n");

	print ("--- Static Construct ---\n");
	var foo_construct = new FooWithStaticConstruct ();
	var foo_construct2 = new FooWithStaticConstruct ();
	var foo_construct_child = new FooWithStaticConstructChild ();
	var foo_construct_child2 = new FooWithStaticConstructChild ();
}
