package org.benf.cfr.reader.bytecode.analysis.opgraph.op4rewriters;

import org.benf.cfr.reader.bytecode.analysis.opgraph.Op04StructuredStatement;
import org.benf.cfr.reader.bytecode.analysis.opgraph.op4rewriters.transformers.VariableNameTidier;
import org.benf.cfr.reader.bytecode.analysis.opgraph.op4rewriters.util.MiscStatementTools;
import org.benf.cfr.reader.bytecode.analysis.parse.LValue;
import org.benf.cfr.reader.bytecode.analysis.parse.lvalue.LocalVariable;
import org.benf.cfr.reader.bytecode.analysis.structured.StructuredStatement;
import org.benf.cfr.reader.bytecode.analysis.types.MethodPrototype;
import org.benf.cfr.reader.entities.ClassFileField;
import org.benf.cfr.reader.entities.Method;
import org.benf.cfr.reader.state.ClassCache;
import org.benf.cfr.reader.util.collections.ListFactory;
import org.benf.cfr.reader.util.collections.SetFactory;

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

/**
 * We may have deep inner classes, with references to each other.
 * <p/>
 * So
 * <p/>
 * this.Inner2.this.Inner1.this
 * <p/>
 * But this is illegal.  So remove the outer one, leaving
 * <p/>
 * this.Inner1.this (the LHS this is still illegal, but will be removed later).
 */
public class ScopeHidingVariableRewriter implements Op04Rewriter {

    private final Method method;
    private final ClassCache classCache;

    private final Set<String> outerNames = SetFactory.newSet();
    private final Set<String> usedNames = SetFactory.newSet();
    /*
     * Collect collisions in a first pass, so that we can avoid uneccesarily
     */
    private List<LocalVariable> collisions = ListFactory.newList();

    public ScopeHidingVariableRewriter(List<ClassFileField> fieldVariables, Method method, ClassCache classCache) {
        this.method = method;
        this.classCache = classCache;
        MethodPrototype prototype = method.getMethodPrototype();
        for (ClassFileField field : fieldVariables) {
            String fieldName = field.getFieldName();
            outerNames.add(fieldName);
            usedNames.add(fieldName);
        }
        if (prototype.parametersComputed()) {
            for (LocalVariable localVariable : prototype.getComputedParameters()) {
                checkCollision(localVariable);
            }
        }
    }

    private void checkCollision(LocalVariable localVariable) {
        String name = localVariable.getName().getStringName();
        if (outerNames.contains(name)) collisions.add(localVariable);
        usedNames.add(name);
    }

    @Override
    public void rewrite(Op04StructuredStatement root) {
        List<StructuredStatement> structuredStatements = MiscStatementTools.linearise(root);
        if (structuredStatements == null) return;

        for (StructuredStatement definition : structuredStatements) {
            List<LValue> createdHere = definition.findCreatedHere();
            if (createdHere == null) continue;

            for (LValue lValue : createdHere) {
                if (lValue instanceof LocalVariable) {
                    checkCollision((LocalVariable) lValue);
                }
            }
        }

        if (collisions.isEmpty()) return;

        VariableNameTidier variableNameTidier = new VariableNameTidier(method, classCache);
        variableNameTidier.renameToAvoidHiding(usedNames, collisions);
    }


}