package jmv;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.ast.*;
import org.eclipse.jdt.internal.compiler.lookup.*;

/** Source-level local reservations, separate from executable JVM local values. */
public final class LocalPlans {
    public static final class Entry {
        final String name, type; final int line, start, end;
        Entry(String name,String type,int line,int start,int end) {this.name=name;this.type=type;this.line=line;this.start=start;this.end=end;}
    }
    static void collect(CompilationUnitDeclaration unit,String source,Map<String,List<Entry>> plans) {
        final int[] lines=new int[source.length()+1];int n=1;
        for(int i=0;i<source.length();i++){lines[i]=n;if(source.charAt(i)=='\n')n++;}lines[source.length()]=n;
        unit.traverse(new ASTVisitor() {
            final ArrayDeque<int[]> scopes=new ArrayDeque<>();
            int line(int offset){return lines[Math.max(0,Math.min(offset,source.length()))];}
            public boolean visit(MethodDeclaration node,ClassScope scope){scopes.push(new int[]{node.bodyStart,node.bodyEnd});return true;}
            public void endVisit(MethodDeclaration node,ClassScope scope){scopes.pop();}
            public boolean visit(ConstructorDeclaration node,ClassScope scope){scopes.push(new int[]{node.bodyStart,node.bodyEnd});return true;}
            public void endVisit(ConstructorDeclaration node,ClassScope scope){scopes.pop();}
            public boolean visit(Block node,BlockScope scope){scopes.push(new int[]{node.sourceStart,node.sourceEnd});return true;}
            public void endVisit(Block node,BlockScope scope){scopes.pop();}
            public boolean visit(LocalDeclaration node,BlockScope scope) {
                if(scope==null||scopes.isEmpty()||scope.methodScope().referenceContext instanceof LambdaExpression)return true;
                MethodBinding method=scope.methodScope().referenceMethodBinding();
                if(method==null||method.declaringClass==null)return true;
                String owner=new String(method.declaringClass.constantPoolName());
                String name=method.isConstructor()?"<init>":new String(method.selector);
                String key=owner+"#"+name+new String(method.signature());int[] bounds=scopes.peek();
                plans.computeIfAbsent(key,k->new ArrayList<>()).add(new Entry(new String(node.name),node.type.toString(),line(node.sourceStart),line(bounds[0]),line(bounds[1])));
                return true;
            }
        },unit.scope);
    }
}
