#include "exception.h"

// Use: gcc -fexceptions -Wall -Werror -g exception.c test-main.c

#include <stdio.h>
#include <stdbool.h>

// Helps with manual translation. It may or may not get folded into the
// header, that depends on how it interacts with concurancy.
void __try_resume_node_new(struct __try_resume_node * node,
        _Bool (*handler)(exception except)) {
    node->next = shared_stack.top_resume;
    shared_stack.top_resume = node;
    node->try_to_handle = handler;
}

// Local Print On Exit:
struct raii_base_type {
	const char * area;
};

void raii_dtor(struct raii_base_type * this) {
	printf("Exiting: %s\n", this->area);
}

#define raii_t __attribute__((cleanup(raii_dtor))) struct raii_base_type

// ===========================================================================
// Runtime code (post-translation).
void terminate(int except_value) {
	raii_t a = {"terminate function"};
	__throw_terminate(except_value);
	printf("terminate returned\n");
}

void resume(int except_value) {
	raii_t a = {"resume function"};
	__throw_resume(except_value);
	printf("resume returned\n");
}

// Termination Test: Two handlers: no catch, catch
void bar() {
	raii_t a = {"bar function"};
	{
		void bar_try1() {
			terminate(4);
		}
		void bar_catch1(int index, exception except) {
			switch(except) {
			case 1:
				printf("bar caught exception 3.\n");
				break;
			default:
				printf("INVALID INDEX in bar: %d (%d)\n", index, except);
			}
		}
		int bar_match1(exception except) {
			if (3 == except) {
				return 1;
			} else {
				return 0;
			}
		}
		__try_terminate(bar_try1, bar_catch1, bar_match1);
	}
}

void foo() {
	raii_t a = {"foo function"};
	{
		void foo_try1() {
			bar();
		}
		void foo_catch1(int index, exception except) {
			switch(index) {
			case 1:
				printf("foo caught exception 4.\n");
				break;
			case 2:
				printf("foo caught exception 2.\n");
				break;
			default:
				printf("INVALID INDEX in foo: %d (%d)\n", index, except);
			}
		}
		int foo_match1(exception except) {
			if (4 == except) {
				return 1;
			} else if (2 == except) {
				return 2;
			} else {
				return 0;
			}
		}
		__try_terminate(foo_try1, foo_catch1, foo_match1);
	}
}

// Resumption Two Handler Test: no catch, catch.
void beta() {
	raii_t a = {"beta function"};
	{
		bool beta_handle1(exception except) {
			if (3 == except) {
				printf("beta caught exception 3\n");
				return true;
			} else {
				return false;
			}
		}
		struct __try_resume_node node
			__attribute__((cleanup(__try_resume_cleanup)));
		__try_resume_node_new(&node, beta_handle1);
		{
			resume(4);
		}
	}
}
void alpha() {
	raii_t a = {"alpha function"};
	{
		bool alpha_handle1(exception except) {
			if (2 == except) {
				printf("alpha caught exception 2\n");
				return true;
			} else if (4 == except) {
				printf("alpha caught exception 4\n");
				return true;
			} else {
				return false;
			}
		}
		struct __try_resume_node node
			__attribute__((cleanup(__try_resume_cleanup)));
		__try_resume_node_new(&node, alpha_handle1);
		{
			beta();
		}
	}
}

// Finally Test:
void farewell(bool jump) {
	{
		void farewell_finally1() {
			printf("See you next time\n");
		}
		struct __cleanup_hook __hidden_hook
			__attribute__((cleanup(farewell_finally1)));
		{
			if (jump) {
				printf("jump out of farewell\n");
				goto endoffunction;
			} else {
				printf("walk out of farewell\n");
			}
		}
	}
	endoffunction:
	printf("leaving farewell\n");
}

// Resume-to-Terminate Test:
void fallback() {
	{
		void fallback_try1() {
			resume(1);
		}
		void fallback_catch1(int index, exception except) {
			switch (index) {
			case 1:
				printf("fallback caught termination 1\n");
				break;
			default:
				printf("INVALID INDEX in fallback: %d (%d)\n", index, except);
			}
		}
		int fallback_match1(exception except) {
			if (1 == except) {
				return 1;
			} else {
				return 0;
			}
		}
		__try_terminate(fallback_try1, fallback_catch1, fallback_match1);
	}
}

// Terminate Throw New Exception:
void terminate_swap() {
	raii_t a = {"terminate_swap"};
	{
		void fn_try1() {
			terminate(2);
		}
		void fn_catch1(int index, exception except) {
			switch (index) {
			case 1:
				terminate(1);
				break;
			default:
				printf("INVALID INDEX in terminate_swap: %d (%d)\n",
					index, except);
			}
		}
		int fn_match1(exception except) {
			if (2 == except) {
				return 1;
			} else {
				return 0;
			}
		}
		__try_terminate(fn_try1, fn_catch1, fn_match1);
	}
}

void terminate_swapped() {
	raii_t a = {"terminate_swapped"};
	{
		void fn_try1() {
			terminate_swap();
		}
		void fn_catch1(int index, exception except) {
			switch (index) {
			case 1:
				printf("terminate_swapped caught exception 1\n");
				break;
			default:
				printf("INVALID INDEX in terminate_swapped: %d (%d)\n",
					index, except);
			}
		}
		int fn_match1(exception except) {
			if (1 == except) {
				return 1;
			} else {
				return 0;
			}
		}
		__try_terminate(fn_try1, fn_catch1, fn_match1);
	}
}

// Resume Throw New Exception:
void resume_swap() {
	raii_t a = {"terminate_swap"};
	{
		bool fn_handle1(exception except) {
			if (2 == except) {
				resume(1);
				return true;
			} else {
				return false;
			}
		}
		struct __try_resume_node node
			__attribute__((cleanup(__try_resume_cleanup)));
		__try_resume_node_new(&node, fn_handle1);
		{
			resume(2);
		}
	}
}

void resume_swapped() {
	{
		bool fn_handle1(exception except) {
			if (1 == except) {
				printf("resume_swapped caught exception 1\n");
				return true;
			} else {
				return false;
			}
		}
		struct __try_resume_node node
			__attribute__((cleanup(__try_resume_cleanup)));
		__try_resume_node_new(&node, fn_handle1);
		{
			resume_swap();
		}
	}
}

// Terminate Rethrow:
// I don't have an implementation for this.
void reterminate() {
	{
		void fn_try1() {
			void fn_try2() {
				terminate(1);
			}
			void fn_catch2(int index, exception except) {
				switch (index) {
				case 1:
					printf("reterminate 2 caught and "
					       "will rethrow exception 1\n");
					__rethrow_terminate();
					break;
				default:
					printf("INVALID INDEX in reterminate 2: %d (%d)\n",
						index, except);
				}
			}
			int fn_match2(exception except) {
				if (1 == except) {
					return 1;
				} else {
					return 0;
				}
			}
			__try_terminate(fn_try2, fn_catch2, fn_match2);
		}
		void fn_catch1(int index, exception except) {
			switch (index) {
			case 1:
				printf("reterminate 1 caught exception 1\n");
				break;
			default:
				printf("INVALID INDEX in reterminate 1: %d (%d)\n",
					index, except);
			}
		}
		int fn_match1(exception except) {
			if (1 == except) {
				return 1;
			} else {
				return 0;
			}
		}
		__try_terminate(fn_try1, fn_catch1, fn_match1);
	}
}

// Resume Rethrow:
void reresume() {
	{
		bool reresume_handle1(exception except) {
			if (1 == except) {
				printf("reresume 1 caught exception 1\n");
				return true;
			} else {
				return false;
			}
		}
		struct __try_resume_node node
			__attribute__((cleanup(__try_resume_cleanup)));
		__try_resume_node_new(&node, reresume_handle1);
		{
			bool reresume_handle2(exception except) {
				if (1 == except) {
					printf("reresume 2 caught and rethrows exception 1\n");
					return false;
				} else {
					return false;
				}
			}
			struct __try_resume_node node
				__attribute__((cleanup(__try_resume_cleanup)));
			__try_resume_node_new(&node, reresume_handle2);
			{
				resume(1);
			}
		}
	}
}

// Terminate-Resume interaction:
void fum() {
	// terminate block, call resume
	{
		void fum_try1() {
			resume(3);
		}
		void fum_catch1(int index, exception except) {
			switch (index) {
			case 1:
				printf("fum caught exception 3\n");
				break;
			default:
				printf("INVALID INDEX in fum: %d (%d)\n", index, except);
			}
		}
		int fum_match1(exception except) {
			if (3 == except) {
				return 1;
			} else {
				return 0;
			}
		}
		__try_terminate(fum_try1, fum_catch1, fum_match1);
	}
}

void foe() {
	// resume block, call terminate
	{
		bool foe_handle1(exception except) {
			if (3 == except) {
				printf("foe caught exception 3\n");
				return true;
			} else {
				return false;
			}
		}
		struct __try_resume_node node
			__attribute__((cleanup(__try_resume_cleanup)));
		__try_resume_node_new(&node, foe_handle1);
		{
			terminate(3);
		}
	}
}

void fy() {
	// terminate block calls fum, call foe
	{
		void fy_try1() {
			foe();
		}
		void fy_catch1(int index, exception except) {
			switch (index) {
			case 1:
				printf("fy caught exception 3\n");
				fum();
				break;
			default:
				printf("INVALID INDEX in fy: %d (%d)\n", index, except);
			}
		}
		int fy_match1(exception except) {
			if (3 == except) {
				return 1;
			} else {
				return 0;
			}
		}
		__try_terminate(fy_try1, fy_catch1, fy_match1);
	}
}

void fee() {
	// resume block, call fy
	{
		bool fee_handle1(exception except) {
			if (3 == except) {
				printf("fee caught exception 3\n");
				return true;
			} else {
				return false;
			}
		}
		struct __try_resume_node node
			__attribute__((cleanup(__try_resume_cleanup)));
		__try_resume_node_new(&node, fee_handle1);
		{
			fy();
		}
	}
}


// main: choose which tests to run
int main(int argc, char * argv[]) {
	raii_t a = {"main function"};

	foo(); printf("\n");
	alpha(); printf("\n");
	farewell(false); printf("\n");
	farewell(true); printf("\n");
	fallback(); printf("\n");
	terminate_swapped(); printf("\n");
	resume_swapped(); printf("\n");
	reterminate(); printf("\n");
	reresume(); printf("\n");
	fee(); printf("\n");
	// Uncaught termination test.
	terminate(7);
}
