package cd.transform.optimizers;

import cd.ir.Ast;
import cd.ir.Ast.ClassDecl;
import cd.ir.Ast.MethodDecl;
import cd.ir.Symbol.ClassSymbol;
import cd.ir.Symbol.MethodSymbol;
import cd.ir.Symbol.TypeSymbol;
import cd.transform.analysis.StaticMethodUsage;

import java.util.List;
import java.util.Set;

public class UnusedMethodRemoval extends GlobalOptimizerVisitor<Set<MethodSymbol>> {

	public boolean go(List<ClassDecl> classDecls, List<TypeSymbol> typeList) {
		Set<MethodSymbol> used = new StaticMethodUsage().go(classDecls);

		for (ClassDecl classDecl : classDecls)
			visit(classDecl, used);

		return hasChanged();
	}

	@Override
	public Ast methodDecl(MethodDecl ast, Set<MethodSymbol> arg) {
		// To remove a method, it mustn't be in the Set
		boolean keepMethod = arg.contains(ast.sym);

		if (keepMethod) return ast;
		// But also needs not to be overriding a method in the Set
		ClassSymbol classSym = ast.sym.owner;
		while (classSym != ClassSymbol.objectType) {
			keepMethod = arg.contains(classSym.getMethod(ast.name));
			classSym = classSym.superClass;
		}

		if (keepMethod) return ast;
		// Remove method from class (only Symbol, Visitor takes care of Ast)
		ast.sym.owner.methods.remove(ast.name);

		// Remove all methods overridden by the deleted method (both Ast and Symbol, as we are not currently
		// looping over it
		classSym = ast.sym.owner;
		while (classSym != ClassSymbol.objectType) {
			assert classSym.ast != null;
			classSym.ast.rwChildren.removeIf(child -> child instanceof MethodDecl &&
					((MethodDecl) child).name.equals(ast.name));
			classSym.methods.remove(ast.name);
			classSym = classSym.superClass;
		}

		// Return from this classes' Ast node
		return null;
	}
}
