﻿(() => {
	let output: string = "";

	// Pass argument as generic type
	function PrintIt<T>(val: T): void
	{
		output += "Print " + val + "\n";
	}

	PrintIt<string>("Fred");
	PrintIt<number>(22);

	// Can allow Typescript to imply type from value
	// provided
	PrintIt(33);    // Implies number

	// Can return generic type
	function JustReturn<T>(val: T): T
	{
		return val;
	}
	function AppendItem<T>(items: T[], item: T): T[]
	{
		items.push(item);
		return items;
	}

	let x: number = JustReturn<number>(3);
	output += "Number is " + x + "\n";

	let words: string[] = ["One", "Two"];
	words = AppendItem(words, "Three");
	output += "Last word is " + words[2] + "\n";

	// Use multiple generic types
	function BuildObject<U, V>(x: U, y: V): { a: U, b: V }
	{
		return {
			a: x,
			b: y
		};
	}

	let obj: { a: string, b: number };
	obj = BuildObject<string, number>("Hi", 3);
	output += "Object1 set: a=" + obj.a + " b=" + obj.b + "\n";

	// Generic Classes
	class GenericClassA<A, B>
	{
		private _a: A;
		private _b: B;
		constructor(a: A, b: B)
		{
			this._a = a;
			this._b = b;
		}
		get ValueA(): A { return this._a; }
		get ValueB(): B { return this._b; }
	}

	let obj2: GenericClassA<string, number>;
	obj2 = new GenericClassA<string, number>("One", 1);
	output += "Object2 set: _a=" + obj2.ValueA + " _b=" + obj2.ValueB + "\n";

	// Generic Interfaces
	interface GenericInterface<T>
	{
		Display<T>(b: T): string;
	}

	class ClassB<T> implements GenericInterface<T>
	{
		private _a: T;

		constructor(a: T)
		{
			this._a = a;
		}

		Display<T>(b: T): string
		{
			return "Displaying '" + this._a + " " + b + "'\n";
		}
	}

	let obj3: ClassB<string> = new ClassB<string>("The message is ");
	output += obj3.Display("hello");

	// Generic constraints
	// May want to specify that the object being passed has
	// inherited from a specific parent class or interface
	class XOne
	{
		public ReadOne(): number
		{
			return 1;
		}
	}

	interface XReadResult
	{
		Result(): number;
	}

	class XOneTwo extends XOne implements XReadResult
	{
		public ReadTwo(): number
		{
			return 2;
		}

		public Result(): number
		{
			return this.ReadOne() + this.ReadTwo();
		}
	}

	// Must inherit from XOne
	class DisplayOne<T extends XOne>
	{
		public Display(obj: T): string
		{
			return "Display One " + obj.ReadOne() + "\n";
		}
	}

	// Must implement XReadResult
	class DisplayResult<T extends XReadResult>
	{
		public Display(obj: T): string
		{
			return "Display Result " + obj.Result() + "\n";
		}
	}

	let obj4: XOneTwo = new XOneTwo();
	let disp1: DisplayOne<XOneTwo> = new DisplayOne<XOneTwo>();
	let disp2: DisplayResult<XOneTwo> = new DisplayResult<XOneTwo>();

	output += disp1.Display(obj4);
	output += disp2.Display(obj4);

	// To create objects using a generic type, you have to refer
	// to the class types by their constructor functions.
	class Builder<T>
	{
		Build(c: { new (): T; }): T
		{
			return new c();
		}

		static BuildIt<U>(c: { new (): U; }): U
		{
			return new c();
		}
	}

	let obj5: XReadResult;
	let obj6: XOneTwo;
	let builder: Builder<XReadResult> = new Builder<XReadResult>();

	// Create object using Builder object
	obj5 = builder.Build(XOneTwo);

	// Create object using Buillder static method
	obj6 = Builder.BuildIt(XOneTwo); 

	output += "BUILDERS\n";
	output += new DisplayResult<XReadResult>().Display(obj5);
	output += new DisplayResult<XReadResult>().Display(obj6);

	console.log(output);  
})();