package hms2019

import "core:fmt"
import "core:mem"
import "core:os"
import "core:reflect"
import "intrinsics"

/*
	Welcome to Handmade Seattle 2019!

	The Odin programming language is fast, concise, readable, pragmatic and open sourced. 
	It is designed with the intent of replacing C with the following goals:
	 * simplicity
	 * high performance
	 * built for modern systems
	 * joy of programming

	# Installing Odin
	Getting Started - https://odin-lang.org/docs/install/
		Instructions for downloading and install the Odin compiler and libraries.

	# Learning Odin
	Overview of Odin - https://odin-lang.org/docs/overview/
		An overview of the Odin programming language.
	Frequently Asked Questions (FAQ) - https://odin-lang.org/docs/faq/
		Answers to common questions about Odin.
*/

the_basics :: proc() {
	fmt.println("\n# the basics");

	{ // The Basics
		fmt.println("Hellope");

		// Lexical elements and literals
		// A comment

		my_integer_variable: int; // A comment for documentaton

		// Multi-line comments begin with /* and end with */. Multi-line comments can 
		// also be nested (unlike in C):
		/*
			You can have any text or code here and
			have it be commented.
			/*
				NOTE: comments can be nested!
			*/
		*/

		// String literals are enclosed in double quotes and character literals in single quotes.
		// Special characters are escaped with a backslash \

		some_string := "This is a string";
		_ = 'A'; // unicode codepoint literal
		_ = '\n';
		_ = "C:\\Windows\\notepad.exe";
		// Raw string literals are enclosed with single back ticks
		_ = `C:\Windows\notepad.exe`;

		// The length of a string in bytes can be found using the built-in `len` procedure:
		_ = len("Foo");
		_ = len(some_string);


		// Numbers

		// Numerical literals are written similar to most other programming languages. 
		// A useful feature in Odin is that underscores are allowed for better 
		// readability: 1_000_000_000 (one billion). A number that contains a dot is a 
		// floating point literal: 1.0e9 (one billion). If a number literal is suffixed 
		// with i, is an imaginary number literal: 2i (2 multiply the square root of -1).

		// Binary literals are prefixed with 0b, octal literals with 0o, and hexadecimal 
		// literals 0x. A leading zero does not produce an octal constant (unlike C).

		// In Odin, if a number constant is possible to be represented by a type without 
		// precision loss, it will automatically convert to that type.

		x: int = 1.0; // A float literal but it can be represented by an integer without precision loss
		// Constant literals are “untyped” which means that they can implicitly convert to a type.

		y: int; // `y` is typed of type `int`
		y = 1;  // `1` is an untyped integer literal which can implicitly convert to `int`

		z: f64; // `z` is typed of type `f64` (64-bit floating point number)
		z = 1;  // `1` is an untyped integer literals which can be implicity conver to `f64`
				// No need for any suffixes or decimal places like in other languages
				// CONSTANTS JUST WORK!!!


		// Assignment statements
		h: int = 123; // declares a new variable `h` with type `int` and assigns a value to it
		h = 637; // assigns a new value to `h`

		// `=` is the assignment operator

		// You can assign multiple variables with it:
		a, b := 1, "hello"; // declares `a` and `b` and infers the types from the assignments
		b, a = "byte", 0;

		// Note: `:=` is two tokens, `:` and `=`. The follow are equivalent
		/*
			i: int = 123;
			i:     = 123;
			i := 123
		*/

		// Constant declarations
		// Constants are entities (symbols) which have an assigned value. 
		// The constant’s value cannot be changed. 
		// The constant’s value must be able to be evaluated at compile time:
		X :: "what"; // constant `X` has the untyped string value "what"

		// Constants can be explicitly typed like a variable declaration:
		Y : int : 123;
		Z :: Y + 7; // constant computations are possible
	}
}

control_flow :: proc() {
	fmt.println("\n# control flow");
	{ // Control flow
		// For loop
		// Odin has only one loop statement, the `for` loop

		// Basic for loop
		for i := 0; i < 10; i += 1 {
			fmt.println(i);
		}

		// NOTE: Unlike other languages like C, there are no parentheses `( )` surrounding the three components.
		// Braces `{ }` or a `do` are always required>
		for i := 0; i < 10; i += 1 { }
		for i := 0; i < 10; i += 1 do fmt.print();

		// The initial and post statements are optional
		i := 0;
		for ; i < 10; {
			i += 1;
		}

		// These semicolons can be dropped. This `for` loop is equivalent to C's `while` loop
		i = 0;
		for i < 10 {
			i += 1;
		}

		// If the condition is omitted, this produces an infinite loop:
		for {
			break;
		}

		// Range-based for loop
		// The basic for loop
		for i := 0; i < 10; i += 1 {
			fmt.println(i);
		}
		// can also be written
		for i in 0..<10 {
			fmt.println(i);
		}
		for i in 0..9 {
			fmt.println(i);
		}

		// Certain built-in types can be iterated over
		some_string := "Hello, 世界";
		for character in some_string { // Strings are assumed to be UTF-8
			fmt.println(character);
		}

		some_array := [3]int{1, 4, 9};
		for value in some_array {
			fmt.println(value);
		}

		some_slice := []int{1, 4, 9};
		for value in some_slice {
			fmt.println(value);
		}

		some_dynamic_array := [dynamic]int{1, 4, 9};
		defer delete(some_dynamic_array);
		for value in some_dynamic_array {
			fmt.println(value);
		}


		some_map := map[string]int{"A" = 1, "C" = 9, "B" = 4};
		defer delete(some_map);
		for key in some_map {
			fmt.println(key);
		}

		// Alternatively a second index value can be added
		for character, index in some_string {
			fmt.println(index, character);
		}
		for value, index in some_array {
			fmt.println(index, value);
		}
		for value, index in some_slice {
			fmt.println(index, value);
		}
		for value, index in some_dynamic_array {
			fmt.println(index, value);
		}
		for key, value in some_map {
			fmt.println(key, value);
		}

		// The iterated values are copies and cannot be written to.
		// The following idiom is useful for iterating over a container in a by-reference manner:
		for _, i in some_slice {
			some_slice[i] = (i+1)*(i+1);
		}


		// If statements
		x := 123;
		if x >= 0 {
			fmt.println("x is positive");
		}

		if y := -34; y < 0 {
			fmt.println("y is negative");
		}

		if y := 123; y < 0 {
			fmt.println("y is negative");
		} else if y == 0 {
			fmt.println("y is zero");
		} else {
			fmt.println("y is positive");
		}

		// Switch statement
		// A switch statement is another way to write a sequence of if-else statements. 
		// In Odin, the default case is denoted as a case without any expression.

		switch arch := ODIN_ARCH; arch {
		case "386":
			fmt.println("32-bit");
		case "amd64":
			fmt.println("64-bit");
		case: // default
			fmt.println("Unsupported architecture");
		}

		// Odin’s `switch` is like one in C or C++, except that Odin only runs the selected case. 
		// This means that a `break` statement is not needed at the end of each case. 
		// Another important difference is that the case values need not be integers nor constants.

		// To achieve a C-like fall through into the next case block, the keyword `fallthrough` can be used.
		one_angry_dwarf :: proc() -> int { 
			fmt.println("one_angry_dwarf was called");
			return 1;
		}

		switch i := 0; i {
		case 0:
		case one_angry_dwarf():
		}

		// A switch statement without a condition is the same as `switch true`. 
		// This can be used to write a clean and long if-else chain and have the 
		// ability to break if needed

		switch {
		case x < 0:
			fmt.println("x is negative");
		case x == 0:
			fmt.println("x is zero");
		case:
			fmt.println("x is positive");
		}

		// A `switch` statement can also use ranges like a range-based loop:
		switch c := 'j'; c {
		case 'A'..'Z', 'a'..'z', '0'..'9':
			fmt.println("c is alphanumeric");
		}

		switch x {
		case 0..<10:
			fmt.println("units");
		case 10..<13:
			fmt.println("pre-teens");
		case 13..<20:
			fmt.println("teens");
		case 20..<30:
			fmt.println("twenties");
		}
	}

	{ // Defer statement
		// A defer statement defers the execution of a statement until the end of 
		// the scope it is in.

		// The following will print 4 then 234:
		{
			x := 123;
			defer fmt.println(x);
			{
				defer x = 4;
				x = 2;
			}
			fmt.println(x);

			x = 234;
		}

		// You can defer an entire block too:
		{
			bar :: proc() {}

			defer {
				fmt.println("1");
				fmt.println("2");
			}

			cond := false; 
			defer if cond {
				bar();
			}
		}	

		// Defer statements are executed in the reverse order that they were declared:
		{
			defer fmt.println("1");
			defer fmt.println("2");
			defer fmt.println("3");
		}
		// Will print 3, 2, and then 1.

		if false {
			f, err := os.open("my_file.txt");
			if err != 0 {
				// handle error
			}
			defer os.close(f);
			// rest of code
		}
	}

	{ // When statement
		/* 
			The when statement is almost identical to the if statement but with some differences:

			* Each condition must be a constant expression as a when 
			  statement is evaluated at compile time.
			* The statements within a branch do not create a new scope
			* The compiler checks the semantics and code only for statements 
			  that belong to the first condition that is true
			* An initial statement is not allowed in a when statement
			* when statements are allowed at file scope
		*/

		// Example
		when ODIN_ARCH == "386" {
			fmt.println("32 bit");
		} else when ODIN_ARCH == "amd64" {
			fmt.println("64 bit");
		} else {
			fmt.println("Unsupported architecture");
		}
		// The when statement is very useful for writing platform specific code. 
		// This is akin to the #if construct in C’s preprocessor however, in Odin, 
		// it is type checked.
	}

	{ // Branch statements
		cond, cond1, cond2 := false, false, false;
		one_step :: proc() { fmt.println("one_step"); }
		beyond :: proc() { fmt.println("beyond"); }

		// Break statement
		for cond {
			switch {
			case:
				if cond {
					break; // break out of the `switch` statement
				}
			}

			break; // break out of the `for` statement
		}

		loop: for cond1 {
			for cond2 {
				break loop; // leaves both loops
			}
		}

		// Continue statement
		for cond {
			if cond2 {
				continue;
			}
			fmt.println("Hellope");
		}

		// Fallthrough statement

		// Odin’s switch is like one in C or C++, except that Odin only runs the selected 
		// case. This means that a break statement is not needed at the end of each case. 
		// Another important difference is that the case values need not be integers nor 
		// constants.

		// fallthrough can be used to explicitly fall through into the next case block:

		switch i := 0; i {
		case 0:
			one_step();
			fallthrough;
		case 1:
			beyond();
		}
	}
}


named_proc_return_parameters :: proc() {
	fmt.println("\n# named proc return parameters");

	foo0 :: proc() -> int {
		return 123;
	}
	foo1 :: proc() -> (a: int) {
		a = 123;
		return;
	}
	foo2 :: proc() -> (a, b: int) {
		// Named return values act like variables within the scope
		a = 321;
		b = 567;
		return b, a;
	}
	fmt.println("foo0 =", foo0()); // 123
	fmt.println("foo1 =", foo1()); // 123
	fmt.println("foo2 =", foo2()); // 567 321
}


explicit_procedure_overloading :: proc() {
	fmt.println("\n# explicit procedure overloading");

	add_ints :: proc(a, b: int) -> int {
		x := a + b;
		fmt.println("add_ints", x);
		return x;
	}
	add_floats :: proc(a, b: f32) -> f32 {
		x := a + b;
		fmt.println("add_floats", x);
		return x;
	}
	add_numbers :: proc(a: int, b: f32, c: u8) -> int {
		x := int(a) + int(b) + int(c);
		fmt.println("add_numbers", x);
		return x;
	}

	add :: proc{add_ints, add_floats, add_numbers};

	add(int(1), int(2));
	add(f32(1), f32(2));
	add(int(1), f32(2), u8(3));

	add(1, 2);     // untyped ints coerce to int tighter than f32
	add(1.0, 2.0); // untyped floats coerce to f32 tighter than int
	add(1, 2, 3);  // three parameters

	// Ambiguous answers
	// add(1.0, 2);
	// add(1, 2.0);
}

struct_type :: proc() {
	fmt.println("\n# struct type");
	// A struct is a record type in Odin. It is a collection of fields. 
	// Struct fields are accessed by using a dot:	
	{
		Vector2 :: struct {
			x: f32,
			y: f32,
		};
		v := Vector2{1, 2};
		v.x = 4;
		fmt.println(v.x);

		// Struct fields can be accessed through a struct pointer:

		v = Vector2{1, 2};
		p := &v;
		p.x = 1335;
		fmt.println(v);

		// We could write p^.x, however, it is to nice abstract the ability 
		// to not explicitly dereference the pointer. This is very useful when 
		// refactoring code to use a pointer rather than a value, and vice versa.
	}
	{
		// A struct literal can be denoted by providing the struct’s type 
		// followed by {}. A struct literal must either provide all the 
		// arguments or none:
		Vector3 :: struct {
			x, y, z: f32,
		};
		v: Vector3;
		v = Vector3{}; // Zero value
		v = Vector3{1, 4, 9};

		// You can list just a subset of the fields if you specify the 
		// field by name (the order of the named fields does not matter):
		v = Vector3{z=1, y=2};
		assert(v.x == 0);
		assert(v.y == 2);
		assert(v.z == 1);		
	}
	{
		// Structs can tagged with different memory layout and alignment requirements:

		a :: struct #align 4   {}; // align to 4 bytes
		b :: struct #packed    {}; // remove padding between fields
		c :: struct #raw_union {}; // all fields share the same offset (0). This is the same as C's union
	}

}


union_type :: proc() {
	fmt.println("\n# union type");
	{
		val: union{int, bool};
		val = 137;
		if i, ok := val.(int); ok {
			fmt.println(i);
		}
		val = true;
		fmt.println(val);

		val = nil;

		switch v in val {
		case int:  fmt.println("int",  v);
		case bool: fmt.println("bool", v);
		case:      fmt.println("nil");
		}
	}
	{
		// There is a duality between `any` and `union`
		// An `any` has a pointer to the data and allows for any type (open)
		// A `union` has as binary blob to store the data and allows only certain types (closed)
		// The following code is with `any` but has the same syntax
		val: any;
		val = 137;
		if i, ok := val.(int); ok {
			fmt.println(i);
		}
		val = true;
		fmt.println(val);

		val = nil;

		switch v in val {
		case int:  fmt.println("int",  v);
		case bool: fmt.println("bool", v);
		case:      fmt.println("nil");
		}
	}

	Vector3 :: distinct [3]f32;
	Quaternion :: distinct quaternion128;

	// More realistic examples
	{
		// NOTE(bill): For the above basic examples, you may not have any
		// particular use for it. However, my main use for them is not for these
		// simple cases. My main use is for hierarchical types. Many prefer
		// subtyping, embedding the base data into the derived types. Below is
		// an example of this for a basic game Entity.

		Entity :: struct {
			id:          u64,
			name:        string,
			position:    Vector3,
			orientation: Quaternion,

			derived: any,
		};

		Frog :: struct {
			using entity: Entity,
			jump_height:  f32,
		};

		Monster :: struct {
			using entity: Entity,
			is_robot:     bool,
			is_zombie:    bool,
		};

		// See `parametric_polymorphism` procedure for details
		new_entity :: proc($T: typeid) -> ^Entity {
			t := new(T);
			t.derived = t^;
			return t;
		}

		entity := new_entity(Monster);

		switch e in entity.derived {
		case Frog:
			fmt.println("Ribbit");
		case Monster:
			if e.is_robot  do fmt.println("Robotic");
			if e.is_zombie do fmt.println("Grrrr!");
			fmt.println("I'm a monster");
		}
	}

	{
		// NOTE(bill): A union can be used to achieve something similar. Instead
		// of embedding the base data into the derived types, the derived data
		// in embedded into the base type. Below is the same example of the
		// basic game Entity but using an union.

		Entity :: struct {
			id:          u64,
			name:        string,
			position:    Vector3,
			orientation: Quaternion,

			derived: union {Frog, Monster},
		};

		Frog :: struct {
			using entity: ^Entity,
			jump_height:  f32,
		};

		Monster :: struct {
			using entity: ^Entity,
			is_robot:     bool,
			is_zombie:    bool,
		};

		// See `parametric_polymorphism` procedure for details
		new_entity :: proc($T: typeid) -> ^Entity {
			t := new(Entity);
			t.derived = T{entity = t};
			return t;
		}

		entity := new_entity(Monster);

		switch e in entity.derived {
		case Frog:
			fmt.println("Ribbit");
		case Monster:
			if e.is_robot  do fmt.println("Robotic");
			if e.is_zombie do fmt.println("Grrrr!");
		}

		// NOTE(bill): As you can see, the usage code has not changed, only its
		// memory layout. Both approaches have their own advantages but they can
		// be used together to achieve different results. The subtyping approach
		// can allow for a greater control of the memory layout and memory
		// allocation, e.g. storing the derivatives together. However, this is
		// also its disadvantage. You must either preallocate arrays for each
		// derivative separation (which can be easily missed) or preallocate a
		// bunch of "raw" memory; determining the maximum size of the derived
		// types would require the aid of metaprogramming. Unions solve this
		// particular problem as the data is stored with the base data.
		// Therefore, it is possible to preallocate, e.g. [100]Entity.

		// It should be noted that the union approach can have the same memory
		// layout as the any and with the same type restrictions by using a
		// pointer type for the derivatives.

		/*
			Entity :: struct {
				...
				derived: union{^Frog, ^Monster},
			}

			Frog :: struct {
				using entity: Entity,
				...
			}
			Monster :: struct {
				using entity: Entity,
				...

			}
			new_entity :: proc(T: type) -> ^Entity {
				t := new(T);
				t.derived = t;
				return t;
			}
		*/
	}
}

using_statement :: proc() {
	fmt.println("\n# using statement");
	// using can used to bring entities declared in a scope/namespace 
	// into the current scope. This can be applied to import declarations, 
	// import names, struct fields, procedure fields, and struct values.

	Vector3 :: struct{x, y, z: f32};
	{
		Entity :: struct {
			position: Vector3,
			orientation: quaternion128,
		};

		// It can used like this:
		foo0 :: proc(entity: ^Entity) {
			fmt.println(entity.position.x, entity.position.y, entity.position.z);
		}

		// The entity members can be brought into the procedure scope by using it:
		foo1 :: proc(entity: ^Entity) {
			using entity;
			fmt.println(position.x, position.y, position.z);
		}

		// The using can be applied to the parameter directly:
		foo2 :: proc(using entity: ^Entity) {
			fmt.println(position.x, position.y, position.z);
		}

		// It can also be applied to sub-fields:
		foo3 :: proc(entity: ^Entity) {
			using entity.position;
			fmt.println(x, y, z);
		}
	}
	{
		// We can also apply the using statement to the struct fields directly, 
		// making all the fields of position appear as if they on Entity itself:
		Entity :: struct {
			using position: Vector3,
			orientation: quaternion128,
		};
		foo :: proc(entity: ^Entity) {
			fmt.println(entity.x, entity.y, entity.z);
		}
		

		// Subtype polymorphism
		// It is possible to get subtype polymorphism, similar to inheritance-like 
		// functionality in C++, but without the requirement of vtables or unknown 
		// struct layout:

		Colour :: struct {r, g, b, a: u8};
		Frog :: struct {
			ribbit_volume: f32,
			using entity: Entity,
			colour: Colour,
		};

		frog: Frog;
		// Both work
		foo(&frog.entity);
		foo(&frog);
		frog.x = 123;

		// Note: using can be applied to arbitrarily many things, which allows 
		// the ability to have multiple subtype polymorphism (but also its issues).

		// Note: using’d fields can still be referred by name.
	}
	{ // using on an enum declaration

		using Foo :: enum {A, B, C};

		f0 := A;
		f1 := B;
		f2 := C;
		fmt.println(f0, f1, f2);
		fmt.println(len(Foo));
	}
}


implicit_context_system :: proc() {
	fmt.println("\n# implicit context system");
	// In each scope, there is an implicit value named context. This 
	// context variable is local to each scope and is implicitly passed 
	// by pointer to any procedure call in that scope (if the procedure 
	// has the Odin calling convention).

	// The main purpose of the implicit context system is for the ability 
	// to intercept third-party code and libraries and modify their 
	// functionality. One such case is modifying how a library allocates
	// something or logs something. In C, this was usually achieved with 
	// the library defining macros which could be overridden so that the 
	// user could define what he wanted. However, not many libraries 
	// supported this in many languages by default which meant intercepting 
	// third-party code to see what it does and to change how it does it is 
	// not possible.

	c := context; // copy the current scope's context

	context.user_index = 456;
	{
		context.allocator = my_custom_allocator();
		context.user_index = 123;
		what_a_fool_believes(); // the `context` for this scope is implicitly passed to `what_a_fool_believes`
	}

	// `context` value is local to the scope it is in
	assert(context.user_index == 456);

	what_a_fool_believes :: proc() {
		c := context; // this `context` is the same as the parent procedure that it was called from
		// From this example, context.user_index == 123
		// An context.allocator is assigned to the return value of `my_custom_allocator()`
		assert(context.user_index == 123);

		// The memory management procedure use the `context.allocator` by 
		// default unless explicitly specified otherwise
		china_grove := new(int);
		free(china_grove);
	}

	my_custom_allocator :: mem.nil_allocator;

	// By default, the context value has default values for its parameters which is 
	// decided in the package runtime. What the defaults are are compiler specific.

	// To see what the implicit context value contains, please see the following 
	// definition in package runtime.
}

parametric_polymorphism :: proc() {
	fmt.println("\n# parametric polymorphism");

	print_value :: proc(value: $T) {
		fmt.printf("print_value: %T %v\n", value, value);
	}

	v1: int    = 1;
	v2: f32    = 2.1;
	v3: f64    = 3.14;
	v4: string = "message";

	print_value(v1);
	print_value(v2);
	print_value(v3);
	print_value(v4);

	fmt.println();

	add :: proc(p, q: $T) -> T {
		x: T = p + q;
		return x;
	}

	a := add(3, 4);
	fmt.printf("a: %T = %v\n", a, a);

	b := add(3.2, 4.3);
	fmt.printf("b: %T = %v\n", b, b);

	// This is how `new` is implemented
	alloc_type :: proc($T: typeid) -> ^T {
		t := cast(^T)alloc(size_of(T), align_of(T));
		t^ = T{}; // Use default initialization value
		return t;
	}

	copy_slice :: proc(dst, src: []$T) -> int {
		n := min(len(dst), len(src));
		if n > 0 {
			mem.copy(&dst[0], &src[0], n*size_of(T));
		}
		return n;
	}

	double_params :: proc(a: $A, b: $B) -> A {
		return a + A(b);
	}

	fmt.println(double_params(12, 1.345));



	{ // Polymorphic Types and Type Specialization
		Table_Slot :: struct(Key, Value: typeid) {
			occupied: bool,
			hash:     u32,
			key:      Key,
			value:    Value,
		};
		TABLE_SIZE_MIN :: 32;
		Table :: struct(Key, Value: typeid) {
			count:     int,
			allocator: mem.Allocator,
			slots:     []Table_Slot(Key, Value),
		};

		// Only allow types that are specializations of a (polymorphic) slice
		make_slice :: proc($T: typeid/[]$E, len: int) -> T {
			return make(T, len);
		}

		// Only allow types that are specializations of `Table`
		allocate :: proc(table: ^$T/Table, capacity: int) {
			c := context;
			if table.allocator.procedure != nil do c.allocator = table.allocator;
			context = c;

			table.slots = make_slice(type_of(table.slots), max(capacity, TABLE_SIZE_MIN));
		}

		expand :: proc(table: ^$T/Table) {
			c := context;
			if table.allocator.procedure != nil do c.allocator = table.allocator;
			context = c;

			old_slots := table.slots;
			defer delete(old_slots);

			cap := max(2*len(table.slots), TABLE_SIZE_MIN);
			allocate(table, cap);

			for s in old_slots do if s.occupied {
				put(table, s.key, s.value);
			}
		}

		// Polymorphic determination of a polymorphic struct
		// put :: proc(table: ^$T/Table, key: T.Key, value: T.Value) {
		put :: proc(table: ^Table($Key, $Value), key: Key, value: Value) {
			hash := get_hash(key); // Ad-hoc method which would fail in a different scope
			index := find_index(table, key, hash);
			if index < 0 {
				if f64(table.count) >= 0.75*f64(len(table.slots)) {
					expand(table);
				}
				assert(table.count <= len(table.slots));

				index = int(hash % u32(len(table.slots)));

				for table.slots[index].occupied {
					if index += 1; index >= len(table.slots) {
						index = 0;
					}
				}

				table.count += 1;
			}

			slot := &table.slots[index];
			slot.occupied = true;
			slot.hash     = hash;
			slot.key      = key;
			slot.value    = value;
		}


		// find :: proc(table: ^$T/Table, key: T.Key) -> (T.Value, bool) {
		find :: proc(table: ^Table($Key, $Value), key: Key) -> (Value, bool) {
			hash := get_hash(key);
			index := find_index(table, key, hash);
			if index < 0 {
				return Value{}, false;
			}
			return table.slots[index].value, true;
		}

		find_index :: proc(table: ^Table($Key, $Value), key: Key, hash: u32) -> int {
			if len(table.slots) <= 0 do return -1;

			index := int(hash % u32(len(table.slots)));
			for table.slots[index].occupied {
				if table.slots[index].hash == hash {
					if table.slots[index].key == key {
						return index;
					}
				}

				if index += 1; index >= len(table.slots) {
					index = 0;
				}
			}

			return -1;
		}

		get_hash :: proc(s: string) -> u32 { // fnv32a
			h: u32 = 0x811c9dc5;
			for i in 0..<len(s) {
				h = (h ~ u32(s[i])) * 0x01000193;
			}
			return h;
		}


		table: Table(string, int);

		for i in 0..36 do put(&table, "Hellope", i);
		for i in 0..42 do put(&table, "World!",  i);

		found, _ := find(&table, "Hellope");
		fmt.printf("`found` is %v\n", found);

		found, _ = find(&table, "World!");
		fmt.printf("`found` is %v\n", found);

		// I would not personally design a hash table like this in production
		// but this is a nice basic example
		// A better approach would either use a `u64` or equivalent for the key
		// and let the user specify the hashing function or make the user store
		// the hashing procedure with the table
	}

	{ // Parametric polymorphic union
		Error :: enum {
			Foo0,
			Foo1,
			Foo2,
			Foo3,
		};
		Para_Union :: union(T: typeid) {T, Error};
		r: Para_Union(int);
		fmt.println(typeid_of(type_of(r)));

		fmt.println(r);
		r = 123;
		fmt.println(r);
		r = Error.Foo0; // r = .Foo0; is allow too, see implicit selector expressions below
		fmt.println(r);
	}

	{ // Polymorphic names
		foo :: proc($N: $I, $T: typeid) -> (res: [N]T) {
			// `N` is the constant value passed
			// `I` is the type of N
			// `T` is the type passed
			fmt.printf("Generating an array of type %v from the value %v of type %v\n",
					   typeid_of(type_of(res)), N, typeid_of(I));
			for i in 0..<N {
				res[i] = T(i*i);
			}
			return;
		}

		T :: int;
		array := foo(4, T);
		for v, i in array {
			assert(v == T(i*i));
		}

		// Matrix multiplication
		mul :: proc(a: [$M][$N]$T, b: [N][$P]T) -> (c: [M][P]T) {
			for i in 0..<M {
				for j in 0..<P {
					for k in 0..<N {
						c[i][j] += a[i][k] * b[k][j];
					}
				}
			}
			return;
		}

		x := [2][3]f32{
			{1, 2, 3},
			{3, 2, 1},
		};
		y := [3][2]f32{
			{0, 8},
			{6, 2},
			{8, 4},
		};
		z := mul(x, y);
		assert(z == {{36, 24}, {20, 32}});
	}
}


array_programming :: proc() {
	fmt.println("\n# array programming");
	{
		a := [3]f32{1, 2, 3};
		b := [3]f32{5, 6, 7};
		c := a * b;
		d := a + b;
		e := 1 +  (c - d) / 2;
		fmt.printf("%.1f\n", e); // [0.5, 3.0, 6.5]
	}

	{
		a := [3]f32{1, 2, 3};
		b := swizzle(a, 2, 1, 0);
		assert(b == [3]f32{3, 2, 1});

		c := swizzle(a, 0, 0);
		assert(c == [2]f32{1, 1});
		assert(c == 1);
	}

	{
		Vector3 :: distinct [3]f32;
		a := Vector3{1, 2, 3};
		b := Vector3{5, 6, 7};
		c := (a * b)/2 + 1;
		d := c.x + c.y + c.z;
		fmt.printf("%.1f\n", d); // 22.0

		cross :: proc(a, b: Vector3) -> Vector3 {
			i := swizzle(a, 1, 2, 0) * swizzle(b, 2, 0, 1);
			j := swizzle(a, 2, 0, 1) * swizzle(b, 1, 2, 0);
			return i - j;
		}

		blah :: proc(a: Vector3) -> f32 {
			return a.x + a.y + a.z;
		}

		x := cross(a, b);
		fmt.println(x);
		fmt.println(blah(x));
	}
}

map_type :: proc() {
	fmt.println("\n# map type");

	m := make(map[string]int);
	defer delete(m);

	m["Bob"] = 2; 
	m["Ted"] = 5;
	fmt.println(m["Bob"]);

	delete_key(&m, "Ted");

	// If an element of a key does not exist, the zero value of the 
	// element will be returned. To check to see if an element exists 
	// can be done in two ways:
	elem, ok := m["Bob"];
	exists := "Bob" in m;

}

implicit_selector_expression :: proc() {
	fmt.println("\n# implicit selector expression");

	Foo :: enum {A, B, C};

	f: Foo;
	f = Foo.A;
	f = .A;

	BAR :: bit_set[Foo]{.B, .C};

	switch f {
	case .A:
		fmt.println("HERE");
	case .B:
		fmt.println("NEVER");
	case .C:
		fmt.println("FOREVER");
	}

	my_map := make(map[Foo]int);
	defer delete(my_map);

	my_map[.A] = 123;
	my_map[Foo.B] = 345;

	fmt.println(my_map[.A] + my_map[Foo.B] + my_map[.C]);
}


complete_switch :: proc() {
	fmt.println("\n# complete_switch");
	{ // enum
		Foo :: enum {
			A,
			B,
			C,
			D,
		};

		f := Foo.A;
		#complete switch f {
		case .A: fmt.println("A");
		case .B: fmt.println("B");
		case .C: fmt.println("C");
		case .D: fmt.println("D");
		case:    fmt.println("?");
		}
	}
	{ // union
		Foo :: union {int, bool};
		f: Foo = 123;
		#complete switch in f {
		case int:  fmt.println("int");
		case bool: fmt.println("bool");
		case:
		}
	}
}

cstring_example :: proc() {
	fmt.println("\n# cstring_example");

	W :: "Hellope";
	X :: cstring(W);
	Y :: string(X);

	w := W;
	_ = w;
	x: cstring = X;
	y: string = Y;
	z := string(x);
	fmt.println(x, y, z);
	fmt.println(len(x), len(y), len(z));
	fmt.println(len(W), len(X), len(Y));
	// IMPORTANT NOTE for cstring variables
	// len(cstring) is O(N)
	// cast(string)cstring is O(N)
}

bit_set_type :: proc() {
	fmt.println("\n# bit_set type");

	{
		using Day :: enum {
			Sunday,
			Monday,
			Tuesday,
			Wednesday,
			Thursday,
			Friday,
			Saturday,
		};

		Days :: distinct bit_set[Day];
		WEEKEND :: Days{Sunday, Saturday};

		d: Days;
		d = {Sunday, Monday};
		e := d | WEEKEND;
		e |= {Monday};
		fmt.println(d, e);

		ok := Saturday in e; // `in` is only allowed for `map` and `bit_set` types
		fmt.println(ok);
		if Saturday in e {
			fmt.println("Saturday in", e);
		}
		X :: Saturday in WEEKEND; // Constant evaluation
		fmt.println(X);
		fmt.println("Cardinality:", card(e));
	}
	{
		x: bit_set['A'..'Z'];
		#assert(size_of(x) == size_of(u32));
		y: bit_set[0..8; u16];
		fmt.println(typeid_of(type_of(x))); // bit_set[A..Z]
		fmt.println(typeid_of(type_of(y))); // bit_set[0..8; u16]

		incl(&x, 'F');
		assert('F' in x);
		excl(&x, 'F');
		assert('F' not_in x);

		y |= {1, 4, 2};
		assert(2 in y);
	}
	{
		Letters :: bit_set['A'..'Z'];
		a := Letters{'A', 'B'};
		b := Letters{'A', 'B', 'C', 'D', 'F'};
		c := Letters{'A', 'B'};

		assert(a <= b); // 'a' is a subset of 'b'
		assert(b >= a); // 'b' is a superset of 'a'
		assert(a < b);  // 'a' is a strict subset of 'b'
		assert(b > a);  // 'b' is a strict superset of 'a'

		assert(!(a < c)); // 'a' is a not strict subset of 'c'
		assert(!(c > a)); // 'c' is a not strict superset of 'a'
	}
}

deferred_procedure_associations :: proc() {
	fmt.println("\n# deferred procedure associations");

	@(deferred_out=closure)
	open :: proc(s: string) -> bool {
		fmt.println(s);
		return true;
	}

	closure :: proc(ok: bool) {
		fmt.println("Goodbye?", ok);
	}

	if open("Welcome") {
		fmt.println("Something in the middle, mate.");
	}
}

reflection :: proc() {
	fmt.println("\n# reflection");

	Foo :: struct {
		x: int    `tag1`,
		y: string `json:"y_field"`,
		z: bool, // no tag
	};

	id := typeid_of(Foo);
	names := reflect.struct_field_names(id);
	types := reflect.struct_field_types(id);
	tags  := reflect.struct_field_tags(id);

	assert(len(names) == len(types) && len(names) == len(tags));

	fmt.println("Foo :: struct {");
	for tag, i in tags {
		name, type := names[i], types[i];
		if tag != "" {
			fmt.printf("\t%s: %T `%s`,\n", name, type, tag);
		} else {
			fmt.printf("\t%s: %T,\n", name, type);
		}
	}
	fmt.println("}");


	for tag, i in tags {
		if val, ok := reflect.struct_tag_lookup(tag, "json"); ok {
			fmt.printf("json: %s -> %s\n", names[i], val);
		}
	}
}

quaternions :: proc() {
	// Not just an April Fool's Joke any more, but a fully working thing!
	fmt.println("\n# quaternions");

	{ // Quaternion operations
		q := 1 + 2i + 3j + 4k;
		r := quaternion(5, 6, 7, 8);
		t := q * r;
		fmt.printf("(%v) * (%v) = %v\n", q, r, t);
		v := q / r;
		fmt.printf("(%v) / (%v) = %v\n", q, r, v);
		u := q + r;
		fmt.printf("(%v) + (%v) = %v\n", q, r, u);
		s := q - r;
		fmt.printf("(%v) - (%v) = %v\n", q, r, s);
	}
	{ // The quaternion types
		q128: quaternion128; // 4xf32
		q256: quaternion256; // 4xf64
		q128 = quaternion(1, 0, 0, 0);
		q256 = 1; // quaternion(1, 0, 0, 0);
	}
	{ // Built-in procedures
		q := 1 + 2i + 3j + 4k;
		fmt.println("q =", q);
		fmt.println("real(q) =", real(q));
		fmt.println("imag(q) =", imag(q));
		fmt.println("jmag(q) =", jmag(q));
		fmt.println("kmag(q) =", kmag(q));
		fmt.println("conj(q) =", conj(q));
		fmt.println("abs(q)  =", abs(q));
	}
	{ // Conversion of a complex type to a quaternion type
		c := 1 + 2i;
		q := quaternion256(c);
		fmt.println(c);
		fmt.println(q);
	}
	{ // Memory layout of Quaternions
		q := 1 + 2i + 3j + 4k;
		a := transmute([4]f64)q;
		fmt.println("Quaternion memory layout: xyzw/(ijkr)");
		fmt.println(q); // 1.000+2.000i+3.000j+4.000k
		fmt.println(a); // [2.000, 3.000, 4.000, 1.000]
	}
}

inline_for_statement :: proc() {
	fmt.println("\n#inline for statements");

	// 'inline for' works the same as if the 'inline' prefix did not
	// exist but these ranged loops are explicitly unrolled which can
	// be very very useful for certain optimizations

	fmt.println("Ranges");
	inline for x, i in 1..<4 {
		fmt.println(x, i);
	}

	fmt.println("Strings");
	inline for r, i in "Hello, 世界" {
		fmt.println(r, i);
	}

	fmt.println("Arrays");
	inline for elem, idx in ([4]int{1, 4, 9, 16}) {
		fmt.println(elem, idx);
	}


	Foo_Enum :: enum {
		A = 1,
		B,
		C = 6,
		D,
	};
	fmt.println("Enum types");
	inline for elem, idx in Foo_Enum {
		fmt.println(elem, idx);
	}
}

where_clauses :: proc() {
	fmt.println("\n#procedure 'where' clauses");

	{ // Sanity checks
		simple_sanity_check :: proc(x: [2]int)
			where len(x) > 1,
				  type_of(x) == [2]int {
			fmt.println(x);
		}
	}
	{ // Parametric polymorphism checks
		cross_2d :: proc(a, b: $T/[2]$E) -> E
			where intrinsics.type_is_numeric(E) {
			return a.x*b.y - a.y*b.x;
		}
		cross_3d :: proc(a, b: $T/[3]$E) -> T
			where intrinsics.type_is_numeric(E) {
			x := a.y*b.z - a.z*b.y;
			y := a.z*b.x - a.x*b.z;
			z := a.x*b.y - a.y*b.z;
			return T{x, y, z};
		}

		a := [2]int{1, 2};
		b := [2]int{5, -3};
		fmt.println(cross_2d(a, b));

		x := [3]f32{1, 4, 9};
		y := [3]f32{-5, 0, 3};
		fmt.println(cross_3d(x, y));

		// Failure case
		// i := [2]bool{true, false};
		// j := [2]bool{false, true};
		// fmt.println(cross_2d(i, j));

	}

	{ // Procedure groups usage
		foo :: proc(x: [$N]int) -> bool
			where N > 2 {
			fmt.println(#procedure, "was called with the parameter", x);
			return true;
		}

		bar :: proc(x: [$N]int) -> bool
			where 0 < N,
				  N <= 2 {
			fmt.println(#procedure, "was called with the parameter", x);
			return false;
		}

		baz :: proc{foo, bar};

		x := [3]int{1, 2, 3};
		y := [2]int{4, 9};
		ok_x := baz(x);
		ok_y := baz(y);
		assert(ok_x == true);
		assert(ok_y == false);
	}

	{ // Record types
		Foo :: struct(T: typeid, N: int)
			where intrinsics.type_is_integer(T),
				  N > 2 {
			x: [N]T,
			y: [N-2]T,
		};

		T :: i32;
		N :: 5;
		f: Foo(T, N);
		#assert(size_of(f) == (N+N-2)*size_of(T));
	}
}


when ODIN_OS == "windows" do foreign import kernel32 "system:kernel32.lib"

foreign_system :: proc() {
	fmt.println("\n#foreign system");
	when ODIN_OS == "windows" {
		// It is sometimes necessarily to interface with foreign code, 
		// such as a C library. In Odin, this is achieved through the 
		// foreign system. You can “import” a library into the code 
		// using the same semantics as a normal import declaration.

		// This foreign import declaration will create a 
		// “foreign import name” which can then be used to associate 
		// entities within a foreign block.

		foreign kernel32 {
			ExitProcess :: proc "stdcall" (exit_code: u32) ---
		}

		// Foreign procedure declarations have the cdecl/c calling 
		// convention by default unless specified otherwise. Due to 
		// foreign procedures do not have a body declared within this 
		// code, you need append the --- symbol to the end to distinguish 
		// it as a procedure literal without a body and not a procedure type.

		// The attributes system can be used to change specific properties 
		// of entities declared within a block:

		@(default_calling_convention = "std")
		foreign kernel32 {
			@(link_name="GetLastError") get_last_error :: proc() -> i32 ---
		}

		// Example using the link_prefix attribute
		@(default_calling_convention = "std")
		@(link_prefix = "Get")
		foreign kernel32 {
			LastError :: proc() -> i32 ---
		}
	}
}

ranged_fields_for_array_compound_literals :: proc() {
	fmt.println("\n#ranged fields for array compound literals");
	{ // Normal Array Literal
		foo := [?]int{1, 4, 9, 16};
		fmt.println(foo);
	}
	{ // Indexed
		foo := [?]int{
			3 = 16,
			1 = 4,
			2 = 9,
			0 = 1,
		};
		fmt.println(foo);
	}
	{ // Ranges
		i := 2;
		foo := [?]int {
			0 = 123,
			5..9 = 54,
			10..<16 = i*3 + (i-1)*2,
		};
		#assert(len(foo) == 16);
		fmt.println(foo); // [123, 0, 0, 0, 0, 54, 54, 54, 54, 54, 8, 8, 8, 8, 8]
	}
	{ // Slice and Dynamic Array support
		i := 2;
		foo_slice := []int {
			0 = 123,
			5..9 = 54,
			10..<16 = i*3 + (i-1)*2,
		};
		assert(len(foo_slice) == 16);
		fmt.println(foo_slice); // [123, 0, 0, 0, 0, 54, 54, 54, 54, 54, 8, 8, 8, 8, 8]

		foo_dynamic_array := [dynamic]int {
			0 = 123,
			5..9 = 54,
			10..<16 = i*3 + (i-1)*2,
		};
		assert(len(foo_dynamic_array) == 16);
		fmt.println(foo_dynamic_array); // [123, 0, 0, 0, 0, 54, 54, 54, 54, 54, 8, 8, 8, 8, 8]
	}
}

deprecated_attribute :: proc() {
	@(deprecated="Use foo_v2 instead")
	foo_v1 :: proc(x: int) {
		fmt.println("foo_v1");
	}
	foo_v2 :: proc(x: int) {
		fmt.println("foo_v2");
	}

	// NOTE: Uncomment to see the warning messages
	// foo_v1(1);
}

range_statements_with_multiple_return_values :: proc() {
	// IMPORTANT NOTE(bill, 2019-11-02): This feature is subject to be changed/removed
	fmt.println("\n#range statements with multiple return values");
	My_Iterator :: struct {
		index: int,
		data:  []i32,
	};
	make_my_iterator :: proc(data: []i32) -> My_Iterator {
		return My_Iterator{data = data};
	}
	my_iterator :: proc(it: ^My_Iterator) -> (val: i32, idx: int, cond: bool) {
		if cond = it.index < len(it.data); cond {
			val = it.data[it.index];
			idx = it.index;
			it.index += 1;
		}
		return;
	}

	data := make([]i32, 6);
	for _, i in data {
		data[i] = i32(i*i);
	}

	{
		it := make_my_iterator(data);
		for val in my_iterator(&it) {
			fmt.println(val);
		}
	}
	{
		it := make_my_iterator(data);
		for val, idx in my_iterator(&it) {
			fmt.println(val, idx);
		}
	}
	{
		it := make_my_iterator(data);
		for {
			val, _, cond := my_iterator(&it);
			if !cond do break;
			fmt.println(val);
		}
	}
}

soa_struct_layout :: proc() {
	// IMPORTANT NOTE(bill, 2019-11-03): This feature is subject to be changed/removed
	// NOTE(bill): Most likely #soa [N]T
	fmt.println("\n#SOA Struct Layout");

	{
		Vector3 :: struct {x, y, z: f32};

		N :: 2;
		v_aos: [N]Vector3;
		v_aos[0].x = 1;
		v_aos[0].y = 4;
		v_aos[0].z = 9;

		fmt.println(len(v_aos));
		fmt.println(v_aos[0]);
		fmt.println(v_aos[0].x);
		fmt.println(&v_aos[0].x);

		v_aos[1] = {0, 3, 4};
		v_aos[1].x = 2;
		fmt.println(v_aos[1]);
		fmt.println(v_aos);

		v_soa: #soa[N]Vector3;

		v_soa[0].x = 1;
		v_soa[0].y = 4;
		v_soa[0].z = 9;


		// Same syntax as AOS and treat as if it was an array
		fmt.println(len(v_soa));
		fmt.println(v_soa[0]);
		fmt.println(v_soa[0].x);
		v_soa[1] = {0, 3, 4};
		v_soa[1].x = 2;
		fmt.println(v_soa[1]);

		// Can use SOA syntax if necessary
		v_soa.x[0] = 1;
		v_soa.y[0] = 4;
		v_soa.z[0] = 9;
		fmt.println(v_soa.x[0]);

		// Same pointer addresses with both syntaxes
		assert(&v_soa[0].x == &v_soa.x[0]);


		// Same fmt printing
		fmt.println(v_aos);
		fmt.println(v_soa);
	}
	{
		// Works with arrays of length <= 4 which have the implicit fields xyzw/rgba
		Vector3 :: distinct [3]f32;

		N :: 2;
		v_aos: [N]Vector3;
		v_aos[0].x = 1;
		v_aos[0].y = 4;
		v_aos[0].z = 9;

		v_soa: #soa[N]Vector3;

		v_soa[0].x = 1;
		v_soa[0].y = 4;
		v_soa[0].z = 9;
	}
}


main :: proc() {
	when true {
		the_basics();
		control_flow();
		named_proc_return_parameters();
		explicit_procedure_overloading();
		struct_type();
		union_type();
		using_statement();
		implicit_context_system();
		parametric_polymorphism();
		array_programming();
		map_type();
		implicit_selector_expression();
		complete_switch();
		cstring_example();
		bit_set_type();
		deferred_procedure_associations();
		reflection();
		quaternions();
		inline_for_statement();
		where_clauses();
		foreign_system();
		ranged_fields_for_array_compound_literals();
		deprecated_attribute();
		range_statements_with_multiple_return_values();
		soa_struct_layout();
	}
}

