//
// Cforall Version 1.0.0 Copyright (C) 2015 University of Waterloo
//
// The contents of this file are covered under the licence agreement in the
// file "LICENCE" distributed with Cforall.
//
// ExceptVisitor.cc --
//
// Author           : Andrew Beach
// Created On       : Wed Jun 14 16:49:00 2017
// Last Modified By : Andrew Beach
// Last Modified On : Thr Aug 17 17:19:00 2017
// Update Count     : 9
//

#include "ExceptTranslate.h"

#include <stddef.h>                   // for NULL
#include <cassert>                    // for assert, assertf
#include <iterator>                   // for back_inserter, inserter
#include <string>                     // for string, operator==

#include "Common/PassVisitor.h"       // for PassVisitor, WithGuards
#include "Common/SemanticError.h"     // for SemanticError
#include "Common/utility.h"           // for CodeLocation
#include "Parser/LinkageSpec.h"       // for Cforall
#include "SynTree/Attribute.h"        // for Attribute
#include "SynTree/Constant.h"         // for Constant
#include "SynTree/Declaration.h"      // for ObjectDecl, FunctionDecl, Struc...
#include "SynTree/Expression.h"       // for UntypedExpr, ConstantExpr, Name...
#include "SynTree/Initializer.h"      // for SingleInit, ListInit
#include "SynTree/Label.h"            // for Label
#include "SynTree/Mutator.h"          // for mutateAll
#include "SynTree/Statement.h"        // for CompoundStmt, CatchStmt, ThrowStmt
#include "SynTree/Type.h"             // for FunctionType, Type, noQualifiers
#include "SynTree/DeclReplacer.h"     // for DeclReplacer
#include "SynTree/Visitor.h"          // for acceptAll

namespace ControlStruct {

	// Buricratic Helpers (Not having to do with the paritular operation.)

	typedef std::list<CatchStmt*> CatchList;

	void split( CatchList& allHandlers, CatchList& terHandlers,
				CatchList& resHandlers ) {
		while ( !allHandlers.empty() ) {
			CatchStmt * stmt = allHandlers.front();
			allHandlers.pop_front();
			if (CatchStmt::Terminate == stmt->get_kind()) {
				terHandlers.push_back(stmt);
			} else {
				resHandlers.push_back(stmt);
			}
		}
	}

	void appendDeclStmt( CompoundStmt * block, Declaration * item ) {
		block->push_back(new DeclStmt(item));
	}

	Expression * nameOf( DeclarationWithType * decl ) {
		return new VariableExpr( decl );
	}

	class ExceptionMutatorCore : public WithGuards {
		enum Context { NoHandler, TerHandler, ResHandler };

		// Also need to handle goto, break & continue.
		// They need to be cut off in a ResHandler, until we enter another
		// loop, switch or the goto stays within the function.

		Context cur_context;

		// The current (innermost) termination handler exception declaration.
		ObjectDecl * handler_except_decl;

		// The built in types used in translation.
		StructDecl * except_decl;
		StructDecl * node_decl;
		StructDecl * hook_decl;

		// The many helper functions for code/syntree generation.
		Statement * create_given_throw(
			const char * throwFunc, ThrowStmt * throwStmt );
		Statement * create_terminate_throw( ThrowStmt * throwStmt );
		Statement * create_terminate_rethrow( ThrowStmt * throwStmt );
		Statement * create_resume_throw( ThrowStmt * throwStmt );
		Statement * create_resume_rethrow( ThrowStmt * throwStmt );
		CompoundStmt * take_try_block( TryStmt * tryStmt );
		FunctionDecl * create_try_wrapper( CompoundStmt * body );
		FunctionDecl * create_terminate_catch( CatchList &handlers );
		CompoundStmt * create_single_matcher(
			DeclarationWithType * except_obj, CatchStmt * modded_handler );
		FunctionDecl * create_terminate_match( CatchList &handlers );
		CompoundStmt * create_terminate_caller( FunctionDecl * try_wrapper,
			FunctionDecl * terminate_catch, FunctionDecl * terminate_match );
		FunctionDecl * create_resume_handler( CatchList &handlers );
		CompoundStmt * create_resume_wrapper(
			Statement * wraps, FunctionDecl * resume_handler );
		FunctionDecl * create_finally_wrapper( TryStmt * tryStmt );
		ObjectDecl * create_finally_hook( FunctionDecl * finally_wrapper );

		// Types used in translation, make sure to use clone.
		// void (*function)();
		FunctionType try_func_t;
		// void (*function)(int, exception);
		FunctionType catch_func_t;
		// int (*function)(exception);
		FunctionType match_func_t;
		// bool (*function)(exception);
		FunctionType handle_func_t;
		// void (*function)(__attribute__((unused)) void *);
		FunctionType finally_func_t;

		StructInstType * create_except_type() {
			assert( except_decl );
			return new StructInstType( noQualifiers, except_decl );
		}
		void init_func_types();

	public:
		ExceptionMutatorCore() :
			cur_context( NoHandler ),
			handler_except_decl( nullptr ),
			except_decl( nullptr ), node_decl( nullptr ), hook_decl( nullptr ),
			try_func_t( noQualifiers, false ),
			catch_func_t( noQualifiers, false ),
			match_func_t( noQualifiers, false ),
			handle_func_t( noQualifiers, false ),
			finally_func_t( noQualifiers, false )
		{}

		void premutate( CatchStmt *catchStmt );
		void premutate( StructDecl *structDecl );
		Statement * postmutate( ThrowStmt *throwStmt );
		Statement * postmutate( TryStmt *tryStmt );
	};

	void ExceptionMutatorCore::init_func_types() {
		assert( except_decl );

		ObjectDecl index_obj(
			"__handler_index",
			Type::StorageClasses(),
			LinkageSpec::Cforall,
			/*bitfieldWidth*/ NULL,
			new BasicType( noQualifiers, BasicType::SignedInt ),
			/*init*/ NULL
			);
		ObjectDecl exception_obj(
			"__exception_inst",
			Type::StorageClasses(),
			LinkageSpec::Cforall,
			/*bitfieldWidth*/ NULL,
			new PointerType(
				noQualifiers,
				new StructInstType( noQualifiers, except_decl )
				),
			/*init*/ NULL
			);
		ObjectDecl bool_obj(
			"__ret_bool",
			Type::StorageClasses(),
			LinkageSpec::Cforall,
			/*bitfieldWidth*/ NULL,
			new BasicType( noQualifiers, BasicType::Bool ),
			/*init*/ NULL,
			std::list<Attribute *>{ new Attribute( "unused" ) }
			);
		ObjectDecl voidptr_obj(
			"__hook",
			Type::StorageClasses(),
			LinkageSpec::Cforall,
			NULL,
			new PointerType(
				noQualifiers,
				new VoidType(
					noQualifiers
					),
				std::list<Attribute *>{ new Attribute( "unused" ) }
				),
			NULL
			);

		ObjectDecl * unused_index_obj = index_obj.clone();
		unused_index_obj->attributes.push_back( new Attribute( "unused" ) );

		catch_func_t.get_parameters().push_back( index_obj.clone() );
		catch_func_t.get_parameters().push_back( exception_obj.clone() );
		match_func_t.get_returnVals().push_back( unused_index_obj );
		match_func_t.get_parameters().push_back( exception_obj.clone() );
		handle_func_t.get_returnVals().push_back( bool_obj.clone() );
		handle_func_t.get_parameters().push_back( exception_obj.clone() );
		finally_func_t.get_parameters().push_back( voidptr_obj.clone() );
	}

	// ThrowStmt Mutation Helpers

	Statement * ExceptionMutatorCore::create_given_throw(
			const char * throwFunc, ThrowStmt * throwStmt ) {
		// `throwFunc`( `throwStmt->get_name` );
		UntypedExpr * call = new UntypedExpr( new NameExpr( throwFunc ) );
		call->get_args().push_back( throwStmt->get_expr() );
		throwStmt->set_expr( nullptr );
		delete throwStmt;
		return new ExprStmt( call );
	}

	Statement * ExceptionMutatorCore::create_terminate_throw(
			ThrowStmt *throwStmt ) {
		// __throw_terminate( `throwStmt->get_name()` ); }
		return create_given_throw( "__cfaabi_ehm__throw_terminate", throwStmt );
	}

	Statement * ExceptionMutatorCore::create_terminate_rethrow(
			ThrowStmt *throwStmt ) {
		// { `handler_except_decl` = NULL; __rethrow_terminate(); }
		assert( nullptr == throwStmt->get_expr() );
		assert( handler_except_decl );

		CompoundStmt * result = new CompoundStmt();
		result->labels =  throwStmt->labels;
		result->push_back( new ExprStmt( UntypedExpr::createAssign(
			nameOf( handler_except_decl ),
			new ConstantExpr( Constant::null(
				new PointerType(
					noQualifiers,
					handler_except_decl->get_type()->clone()
					)
				) )
			) ) );
		result->push_back( new ExprStmt(
			new UntypedExpr( new NameExpr( "__cfaabi_ehm__rethrow_terminate" ) )
			) );
		delete throwStmt;
		return result;
	}

	Statement * ExceptionMutatorCore::create_resume_throw(
			ThrowStmt *throwStmt ) {
		// __throw_resume( `throwStmt->get_name` );
		return create_given_throw( "__cfaabi_ehm__throw_resume", throwStmt );
	}

	Statement * ExceptionMutatorCore::create_resume_rethrow(
			ThrowStmt *throwStmt ) {
		// return false;
		Statement * result = new ReturnStmt(
			new ConstantExpr( Constant::from_bool( false ) )
			);
		result->labels = throwStmt->labels;
		delete throwStmt;
		return result;
	}

	// TryStmt Mutation Helpers

	CompoundStmt * ExceptionMutatorCore::take_try_block( TryStmt *tryStmt ) {
		CompoundStmt * block = tryStmt->get_block();
		tryStmt->set_block( nullptr );
		return block;
	}

	FunctionDecl * ExceptionMutatorCore::create_try_wrapper(
			CompoundStmt *body ) {

		return new FunctionDecl( "try", Type::StorageClasses(),
			LinkageSpec::Cforall, try_func_t.clone(), body );
	}

	FunctionDecl * ExceptionMutatorCore::create_terminate_catch(
			CatchList &handlers ) {
		std::list<CaseStmt *> handler_wrappers;

		FunctionType *func_type = catch_func_t.clone();
		DeclarationWithType * index_obj = func_type->get_parameters().front();
		DeclarationWithType * except_obj = func_type->get_parameters().back();

		// Index 1..{number of handlers}
		int index = 0;
		CatchList::iterator it = handlers.begin();
		for ( ; it != handlers.end() ; ++it ) {
			++index;
			CatchStmt * handler = *it;

			// case `index`:
			// {
			//     `handler.decl` = { (virtual `decl.type`)`except` };
			//     `handler.body`;
			// }
			// return;
			CompoundStmt * block = new CompoundStmt();

			// Just copy the exception value. (Post Validation)
			ObjectDecl * handler_decl =
				static_cast<ObjectDecl *>( handler->get_decl() );
			ObjectDecl * local_except = handler_decl->clone();
			local_except->set_init(
				new ListInit({ new SingleInit(
					new VirtualCastExpr( nameOf( except_obj ),
						local_except->get_type()
						)
					) })
				);
			block->push_back( new DeclStmt( local_except ) );

			// Add the cleanup attribute.
			local_except->get_attributes().push_back( new Attribute(
				"cleanup",
				{ new NameExpr( "__cfaabi_ehm__cleanup_terminate" ) }
				) );

			// Update variables in the body to point to this local copy.
			{
				DeclReplacer::DeclMap mapping;
				mapping[ handler_decl ] = local_except;
				DeclReplacer::replace( handler->body, mapping );
			}

			block->push_back( handler->get_body() );
			handler->set_body( nullptr );

			std::list<Statement *> caseBody
					{ block, new ReturnStmt( nullptr ) };
			handler_wrappers.push_back( new CaseStmt(
				new ConstantExpr( Constant::from_int( index ) ),
				caseBody
				) );
		}
		// TODO: Some sort of meaningful error on default perhaps?

		std::list<Statement*> stmt_handlers;
		while ( !handler_wrappers.empty() ) {
			stmt_handlers.push_back( handler_wrappers.front() );
			handler_wrappers.pop_front();
		}

		SwitchStmt * handler_lookup = new SwitchStmt(
			nameOf( index_obj ),
			stmt_handlers
			);
		CompoundStmt * body = new CompoundStmt();
		body->push_back( handler_lookup );

		return new FunctionDecl("catch", Type::StorageClasses(),
			LinkageSpec::Cforall, func_type, body);
	}

	// Create a single check from a moddified handler.
	// except_obj is referenced, modded_handler will be freed.
	CompoundStmt * ExceptionMutatorCore::create_single_matcher(
			DeclarationWithType * except_obj, CatchStmt * modded_handler ) {
		// {
		//     `modded_handler.decl`
		//     if ( `decl.name = (virtual `decl.type`)`except`
		//             [&& `modded_handler.cond`] ) {
		//         `modded_handler.body`
		//     }
		// }

		CompoundStmt * block = new CompoundStmt();

		// Local Declaration
		ObjectDecl * local_except =
			dynamic_cast<ObjectDecl *>( modded_handler->get_decl() );
		assert( local_except );
		block->push_back( new DeclStmt( local_except ) );

		// Check for type match.
		Expression * cond = UntypedExpr::createAssign( nameOf( local_except ),
			new VirtualCastExpr( nameOf( except_obj ),
				local_except->get_type()->clone() ) );

		// Add the check on the conditional if it is provided.
		if ( modded_handler->get_cond() ) {
			cond = new LogicalExpr( cond, modded_handler->get_cond() );
		}
		// Construct the match condition.
		block->push_back( new IfStmt(
			cond, modded_handler->get_body(), nullptr ) );

		modded_handler->set_decl( nullptr );
		modded_handler->set_cond( nullptr );
		modded_handler->set_body( nullptr );
		delete modded_handler;
		return block;
	}

	FunctionDecl * ExceptionMutatorCore::create_terminate_match(
			CatchList &handlers ) {
		// int match(exception * except) {
		//     HANDLER WRAPPERS { return `index`; }
		// }

		CompoundStmt * body = new CompoundStmt();

		FunctionType * func_type = match_func_t.clone();
		DeclarationWithType * except_obj = func_type->get_parameters().back();

		// Index 1..{number of handlers}
		int index = 0;
		CatchList::iterator it;
		for ( it = handlers.begin() ; it != handlers.end() ; ++it ) {
			++index;
			CatchStmt * handler = *it;

			// Body should have been taken by create_terminate_catch.
			assert( nullptr == handler->get_body() );

			// Create new body.
			handler->set_body( new ReturnStmt(
				new ConstantExpr( Constant::from_int( index ) ) ) );

			// Create the handler.
			body->push_back( create_single_matcher( except_obj, handler ) );
			*it = nullptr;
		}

		body->push_back( new ReturnStmt(
			new ConstantExpr( Constant::from_int( 0 ) ) ) );

		return new FunctionDecl("match", Type::StorageClasses(),
			LinkageSpec::Cforall, func_type, body);
	}

	CompoundStmt * ExceptionMutatorCore::create_terminate_caller(
			FunctionDecl * try_wrapper,
			FunctionDecl * terminate_catch,
			FunctionDecl * terminate_match ) {
		// { __cfaabi_ehm__try_terminate(`try`, `catch`, `match`); }

		UntypedExpr * caller = new UntypedExpr( new NameExpr(
			"__cfaabi_ehm__try_terminate" ) );
		std::list<Expression *>& args = caller->get_args();
		args.push_back( nameOf( try_wrapper ) );
		args.push_back( nameOf( terminate_catch ) );
		args.push_back( nameOf( terminate_match ) );

		CompoundStmt * callStmt = new CompoundStmt();
		callStmt->push_back( new ExprStmt( caller ) );
		return callStmt;
	}

	FunctionDecl * ExceptionMutatorCore::create_resume_handler(
			CatchList &handlers ) {
		// bool handle(exception * except) {
		//     HANDLER WRAPPERS { `hander->body`; return true; }
		// }
		CompoundStmt * body = new CompoundStmt();

		FunctionType * func_type = handle_func_t.clone();
		DeclarationWithType * except_obj = func_type->get_parameters().back();

		CatchList::iterator it;
		for ( it = handlers.begin() ; it != handlers.end() ; ++it ) {
			CatchStmt * handler = *it;

			// Modifiy body.
			CompoundStmt * handling_code =
				dynamic_cast<CompoundStmt*>( handler->get_body() );
			if ( ! handling_code ) {
				handling_code = new CompoundStmt();
				handling_code->push_back( handler->get_body() );
			}
			handling_code->push_back( new ReturnStmt(
				new ConstantExpr( Constant::from_bool( true ) ) ) );
			handler->set_body( handling_code );

			// Create the handler.
			body->push_back( create_single_matcher( except_obj, handler ) );
			*it = nullptr;
		}

		body->push_back( new ReturnStmt(
			new ConstantExpr( Constant::from_bool( false ) ) ) );

		return new FunctionDecl("handle", Type::StorageClasses(),
			LinkageSpec::Cforall, func_type, body);
	}

	CompoundStmt * ExceptionMutatorCore::create_resume_wrapper(
			Statement * wraps,
			FunctionDecl * resume_handler ) {
		CompoundStmt * body = new CompoundStmt();

		// struct __try_resume_node __resume_node
		//  	__attribute__((cleanup( __cfaabi_ehm__try_resume_cleanup )));
		// ** unwinding of the stack here could cause problems **
		// ** however I don't think that can happen currently **
		// __cfaabi_ehm__try_resume_setup( &__resume_node, resume_handler );

		std::list< Attribute * > attributes;
		{
			std::list< Expression * > attr_params;
			attr_params.push_back( new NameExpr(
				"__cfaabi_ehm__try_resume_cleanup" ) );
			attributes.push_back( new Attribute( "cleanup", attr_params ) );
		}

		ObjectDecl * obj = new ObjectDecl(
			"__resume_node",
			Type::StorageClasses(),
			LinkageSpec::Cforall,
			nullptr,
			new StructInstType(
				Type::Qualifiers(),
				node_decl
				),
			nullptr,
			attributes
			);
		appendDeclStmt( body, obj );

		UntypedExpr *setup = new UntypedExpr( new NameExpr(
			"__cfaabi_ehm__try_resume_setup" ) );
		setup->get_args().push_back( new AddressExpr( nameOf( obj ) ) );
		setup->get_args().push_back( nameOf( resume_handler ) );

		body->push_back( new ExprStmt( setup ) );

		body->push_back( wraps );
		return body;
	}

	FunctionDecl * ExceptionMutatorCore::create_finally_wrapper(
			TryStmt * tryStmt ) {
		// void finally() { <finally code> }
		FinallyStmt * finally = tryStmt->get_finally();
		CompoundStmt * body = finally->get_block();
		finally->set_block( nullptr );
		delete finally;
		tryStmt->set_finally( nullptr );

		return new FunctionDecl("finally", Type::StorageClasses(),
			LinkageSpec::Cforall, finally_func_t.clone(), body);
	}

	ObjectDecl * ExceptionMutatorCore::create_finally_hook(
			FunctionDecl * finally_wrapper ) {
		// struct __cfaabi_ehm__cleanup_hook __finally_hook
		//   	__attribute__((cleanup( finally_wrapper )));

		// Make Cleanup Attribute.
		std::list< Attribute * > attributes;
		{
			std::list< Expression * > attr_params;
			attr_params.push_back( nameOf( finally_wrapper ) );
			attributes.push_back( new Attribute( "cleanup", attr_params ) );
		}

		return new ObjectDecl(
			"__finally_hook",
			Type::StorageClasses(),
			LinkageSpec::Cforall,
			nullptr,
			new StructInstType(
				noQualifiers,
				hook_decl
				),
			nullptr,
			attributes
			);
	}

	// Visiting/Mutating Functions
	void ExceptionMutatorCore::premutate( CatchStmt *catchStmt ) {
		// Validate the Statement's form.
		ObjectDecl * decl =
			dynamic_cast<ObjectDecl *>( catchStmt->get_decl() );
		if ( decl && true /* check decl->get_type() */ ) {
			// Pass.
		} else if ( CatchStmt::Terminate == catchStmt->get_kind() ) {
			SemanticError(catchStmt->location, "catch must have exception type");
		} else {
			SemanticError(catchStmt->location, "catchResume must have exception type");
		}

		// Track the handler context.
		GuardValue( cur_context );
		if ( CatchStmt::Terminate == catchStmt->get_kind() ) {
			cur_context = TerHandler;

			GuardValue( handler_except_decl );
			handler_except_decl = decl;
		} else {
			cur_context = ResHandler;
		}
	}

	void ExceptionMutatorCore::premutate( StructDecl *structDecl ) {
		if ( !structDecl->has_body() ) {
			// Skip children?
			return;
		} else if ( structDecl->get_name() == "__cfaabi_ehm__base_exception_t" ) {
			assert( nullptr == except_decl );
			except_decl = structDecl;
			init_func_types();
		} else if ( structDecl->get_name() == "__cfaabi_ehm__try_resume_node" ) {
			assert( nullptr == node_decl );
			node_decl = structDecl;
		} else if ( structDecl->get_name() == "__cfaabi_ehm__cleanup_hook" ) {
			assert( nullptr == hook_decl );
			hook_decl = structDecl;
		}
		// Later we might get the exception type as well.
	}

	Statement * ExceptionMutatorCore::postmutate( ThrowStmt *throwStmt ) {
		assert( except_decl );

		// Ignoring throwStmt->get_target() for now.
		if ( ThrowStmt::Terminate == throwStmt->get_kind() ) {
			if ( throwStmt->get_expr() ) {
				return create_terminate_throw( throwStmt );
			} else if ( TerHandler == cur_context ) {
				return create_terminate_rethrow( throwStmt );
			} else {
				assertf(false, "Invalid throw in %s at %i\n",
					throwStmt->location.filename.c_str(),
					throwStmt->location.first_line);
				return nullptr;
			}
		} else {
			if ( throwStmt->get_expr() ) {
				return create_resume_throw( throwStmt );
			} else if ( ResHandler == cur_context ) {
				return create_resume_rethrow( throwStmt );
			} else {
				assertf(false, "Invalid throwResume in %s at %i\n",
					throwStmt->location.filename.c_str(),
					throwStmt->location.first_line);
				return nullptr;
			}
		}
	}

	Statement * ExceptionMutatorCore::postmutate( TryStmt *tryStmt ) {
		assert( except_decl );
		assert( node_decl );
		assert( hook_decl );

		// Generate a prefix for the function names?

		CompoundStmt * block = new CompoundStmt();
		CompoundStmt * inner = take_try_block( tryStmt );

		if ( tryStmt->get_finally() ) {
			// Define the helper function.
			FunctionDecl * finally_block =
				create_finally_wrapper( tryStmt );
			appendDeclStmt( block, finally_block );
			// Create and add the finally cleanup hook.
			appendDeclStmt( block, create_finally_hook( finally_block ) );
		}

		CatchList termination_handlers;
		CatchList resumption_handlers;
		split( tryStmt->get_catchers(),
			   termination_handlers, resumption_handlers );

		if ( resumption_handlers.size() ) {
			// Define the helper function.
			FunctionDecl * resume_handler =
				create_resume_handler( resumption_handlers );
			appendDeclStmt( block, resume_handler );
			// Prepare hooks
			inner = create_resume_wrapper( inner, resume_handler );
		}

		if ( termination_handlers.size() ) {
			// Define the three helper functions.
			FunctionDecl * try_wrapper = create_try_wrapper( inner );
			appendDeclStmt( block, try_wrapper );
			FunctionDecl * terminate_catch =
				create_terminate_catch( termination_handlers );
			appendDeclStmt( block, terminate_catch );
			FunctionDecl * terminate_match =
				create_terminate_match( termination_handlers );
			appendDeclStmt( block, terminate_match );
			// Build the call to the try wrapper.
			inner = create_terminate_caller(
				try_wrapper, terminate_catch, terminate_match );
		}

		// Embed the try block.
		block->push_back( inner );

		return block;
	}

	void translateEHM( std::list< Declaration *> & translationUnit ) {
		PassVisitor<ExceptionMutatorCore> translator;
		mutateAll( translationUnit, translator );
	}
}
