//
// 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.
//
// ExpandCasts.cc --
//
// Author           : Andrew Beach
// Created On       : Mon Jul 24 13:59:00 2017
// Last Modified By : Andrew Beach
// Last Modified On : Tus Aug  2 14:59:00 2017
// Update Count     : 1
//

#include "ExpandCasts.h"

#include <cassert>                 // for assert, assertf
#include <iterator>                // for back_inserter, inserter
#include <map>                     // for map, _Rb_tree_iterator, map<>::ite...
#include <string>                  // for string, allocator, operator==, ope...
#include <utility>                 // for pair

#include "Common/PassVisitor.h"    // for PassVisitor
#include "Common/SemanticError.h"  // for SemanticError
#include "SynTree/Declaration.h"   // for ObjectDecl, StructDecl, FunctionDecl
#include "SynTree/Expression.h"    // for VirtualCastExpr, CastExpr, Address...
#include "SynTree/Mutator.h"       // for mutateAll
#include "SynTree/Type.h"          // for Type, PointerType, StructInstType
#include "SynTree/Visitor.h"       // for acceptAll

namespace Virtual {

	/* Currently virtual depends on the rather brittle name matching between
	 * a (strict/explicate) virtual type, its vtable type and the vtable
	 * instance.
	 * A stronger implementation, would probably keep track of those triads
	 * and use that information to create better error messages.
	 */

	std::string get_vtable_name( std::string const & name ) {
		return name + "_vtable";
	}

	std::string get_vtable_inst_name( std::string const & name ) {
		return std::string("_") + get_vtable_name( name ) + "_instance";
	}

	std::string get_vtable_name_root( std::string const & name ) {
		return name.substr(0, name.size() - 7 );
	}

	std::string get_vtable_inst_name_root( std::string const & name ) {
		return get_vtable_name_root( name.substr(1, name.size() - 10 ) );
	}

	bool is_vtable_name( std::string const & name ) {
		return (name.substr( name.size() - 7 ) == "_vtable" );
	}

	bool is_vtable_inst_name( std::string const & name ) {
		return 17 < name.size() &&
			name == get_vtable_inst_name( get_vtable_inst_name_root( name ) );
	}

	class VirtualCastCore {
        std::map<std::string, ObjectDecl *> vtable_instances;
        FunctionDecl *vcast_decl;
        StructDecl *pvt_decl;

		Type * pointer_to_pvt(int level_of_indirection) {
			Type * type = new StructInstType(
				Type::Qualifiers( Type::Const ), pvt_decl );
			for (int i = 0 ; i < level_of_indirection ; ++i) {
				type = new PointerType( noQualifiers, type );
			}
			return type;
		}

	public:
		VirtualCastCore() :
			vtable_instances(), vcast_decl( nullptr ), pvt_decl( nullptr )
		{}

		void premutate( FunctionDecl * functionDecl );
		void premutate( StructDecl * structDecl );
		void premutate( ObjectDecl * objectDecl );

		Expression * postmutate( VirtualCastExpr * castExpr );
	};

	void VirtualCastCore::premutate( FunctionDecl * functionDecl ) {
		if ( (! vcast_decl) &&
		     functionDecl->get_name() == "__cfa__virtual_cast" ) {
			vcast_decl = functionDecl;
		}
	}

	void VirtualCastCore::premutate( StructDecl * structDecl ) {
		if ( pvt_decl || ! structDecl->has_body() ) {
			return;
		} else if ( structDecl->get_name() == "__cfa__parent_vtable" ) {
			pvt_decl = structDecl;
		}
	}

	void VirtualCastCore::premutate( ObjectDecl * objectDecl ) {
		if ( is_vtable_inst_name( objectDecl->get_name() ) ) {
			vtable_instances[objectDecl->get_name()] = objectDecl;
		}
	}

	Expression * VirtualCastCore::postmutate( VirtualCastExpr * castExpr ) {
		assertf( castExpr->get_result(), "Virtual Cast target not found before expansion." );

		assert( vcast_decl );
		assert( pvt_decl );

		// May only cast to a pointer or reference type.
		// A earlier validation should give a syntax error, this is
		// just to make sure errors don't creep during translation.
		// Move to helper with more detailed error messages.
		PointerType * target_type =
			dynamic_cast<PointerType *>( castExpr->get_result() );
		assert( target_type );

		StructInstType * target_struct =
			dynamic_cast<StructInstType *>( target_type->get_base() );
		assert( target_struct );

		StructDecl * target_decl = target_struct->get_baseStruct();

		std::map<std::string, ObjectDecl *>::iterator found =
			vtable_instances.find(
				get_vtable_inst_name( target_decl->get_name() ) );
		if ( vtable_instances.end() == found ) {
			assertf( false, "virtual table instance not found." );
		}
		ObjectDecl * table = found->second;

		Expression * result = new CastExpr(
			//new ApplicationExpr(
				//new AddressExpr( new VariableExpr( vcast_decl ) ),
				//new CastExpr( new VariableExpr( vcast_decl ),
				//	new PointerType( noQualifiers,
				//		vcast_decl->get_type()->clone()
				//		)
				//	),
			new UntypedExpr( new NameExpr( "__cfa__virtual_cast" ), {
					new CastExpr(
						new AddressExpr( new VariableExpr( table ) ),
						pointer_to_pvt(1)
						),
					new CastExpr(
						castExpr->get_arg(),
						pointer_to_pvt(2)
						)
				} ),
			castExpr->get_result()->clone()
			);

		castExpr->set_arg( nullptr );
		castExpr->set_result( nullptr );
		delete castExpr;
		return result;
	}

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