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

// Proof of concept for resumption exception handling.
// Names, checks promises and so on all would have to be improved.

struct resume_group;

// Stackwise information (global for single stack)
struct code_stack_data {
	struct resume_group * top_resume;
	struct resume_group * current_resume;
} stack = {NULL, NULL};

// private exception header begin ============================================

struct resume_group {
	struct resume_group * next;
	bool (*try_to_handle)(int);
};

void __resume_group_dtor(struct resume_group * this) {
	stack.top_resume = this->next;
}

void __cfa_eh__throw_resume(int except) {
	struct resume_group * original_head = stack.current_resume;
	struct resume_group * current =
		(original_head) ? original_head->next : stack.top_resume;

	for ( ; current ; current = current->next) {
		stack.current_resume = current;
		if (current->try_to_handle(except)) {
			stack.current_resume = original_head;
			return;
		}
	}

	printf("Unhandled exception %d\n", except);
}

// private exception header end ==============================================

// Set up of unwind checker type.
struct type_raii_t {
	char * msg;
};

void dtor( struct type_raii_t * this ) {
	printf("%s\n", this->msg);
}

#define raii_t __attribute__((cleanup(dtor))) struct type_raii_t

void bar() {
	raii_t a = { "Bar dtor" };

	__cfa_eh__throw_resume( 3 );
}

void foo() {
	raii_t a = { "Foo dtor" };

	{
		bool foo_catch_resume(int exception_id) {
			if (exception_id == 3) {
				printf("Exception caught\n");
				return true;
			}
			return false;
		}
		struct resume_group __attribute__((cleanup(__resume_group_dtor)))
			foo_try_resume = {stack.top_resume, foo_catch_resume};
		stack.top_resume = &foo_try_resume;
		{
			raii_t b = { "Foo try dtor" };

			bar();

			printf("Called bar successfully\n");
		}
	}
	printf( "Foo exited normally\n" );
}

// Not in main.cfa
void foe() {
	raii_t a = { "Foe dtor" };

	printf("Foe throws\n");
	__cfa_eh__throw_resume( 4 );

	printf("Foe exits normally\n");
}

void fy() {
	raii_t a = { "Fy dtor" };

	{
		bool fy_catch_resume(int exception_id) {
			if (4 == exception_id) {
				printf("Rethrow in fy\n");
				__cfa_eh__throw_resume(exception_id);
				return true;
			}
			return false;
		}
		struct resume_group __attribute__((cleanup(__resume_group_dtor)))
			fy_try_resume = {stack.top_resume, fy_catch_resume};
		stack.top_resume = &fy_try_resume;
		{
			raii_t b = { "Fy try dtor" };
			foe();
		}
	}

	printf("Fy exits normally\n");
}

void fee() {
	raii_t a = { "Fee dtor" };

	{
		bool fee_catch_resume(int exception_id) {
			if (4 == exception_id) {
				printf("fee caught exception\n");
				return true;
			}
			return false;
		}
		struct resume_group __attribute__((cleanup(__resume_group_dtor)))
			fee_try_resume = {stack.top_resume, fee_catch_resume};
		stack.top_resume = &fee_try_resume;
		{
			raii_t b = { "Fee try dtor" };
			fy();
		}
	}

	printf("Fee exits normally\n");
}
// End not in main.cfa

int main() {
	raii_t a = { "Main dtor" };

	foo();

	fee();

	printf("End of program reached\n");
}
