package accrue.analysis.interprocvarcontext;

import java.util.LinkedHashSet;
import java.util.Set;

import polyglot.ast.MethodDecl;
import polyglot.frontend.ExtensionInfo;
import polyglot.frontend.Pass;
import polyglot.types.MethodInstance;
import accrue.analysis.interprocanalysis.AbstractLocation;
import accrue.analysis.interprocanalysis.AnalysisFactory;
import accrue.analysis.interprocanalysis.AnalysisUnit;
import accrue.analysis.interprocanalysis.AnalysisUtil;
import accrue.analysis.interprocanalysis.ExitMap;
import accrue.analysis.interprocanalysis.Registrar;
import accrue.analysis.interprocanalysis.WorkQueue;

/**
 * Manages the work queue. Notices when analysis results change, and
 * adds dependent jobs back to the queue.
 */
public class WorkQueueVarContext<T extends AbstractValue<T>> extends
        WorkQueue<VarContext<T>> {
    public WorkQueueVarContext(Registrar registrar, ExtensionInfo extInfo,
            AnalysisFactory<VarContext<T>> factory, Pass pass) {
        super(registrar, extInfo, factory, pass);
    }

    @Override
    protected boolean existingResultSuitable(VarContext<T> input,
            AnalysisUnitRecord<VarContext<T>> rec) {
        AnalysisUnitRecordLocs<T> recloc = (AnalysisUnitRecordLocs<T>) rec;
        return recloc.input() != null
                && VarContext.leq(input, recloc.input(), recloc.readLocations);
    }

    @Override
    protected ExitMap<VarContext<T>> restoreCallingFrame(
            ExitMap<VarContext<T>> output, VarContext<T> input,
            AnalysisUnitRecord<VarContext<T>> rec) {
        AnalysisUnitRecordLocs<T> recloc = (AnalysisUnitRecordLocs<T>) rec;
        return factory().restoreCallingFrame((FinalVarContext<T>) output,
                                             input,
                                             recloc.writeLocations);
    }

    @Override
    protected boolean isOutputChanged(ExitMap<VarContext<T>> output,
            AnalysisUnitRecord<VarContext<T>> rec,
            AnalysisUtil<VarContext<T>> autil) {
        AnalysisUnitRecordLocs<T> recloc = (AnalysisUnitRecordLocs<T>) rec;
        AnalysisUtilVarContext<VarContext<T>, T> autilloc =
                (AnalysisUtilVarContext<VarContext<T>, T>) autil;

        return recloc.output() == null
                || !recloc.output().equals(output)
                || !recloc.readLocations.containsAll(autilloc.readLocations())
                || !recloc.writeLocations.containsAll(autilloc.writeLocations());
    }

    @Override
    protected void updateOutput(AnalysisUnitRecord<VarContext<T>> rec,
            ExitMap<VarContext<T>> output, AnalysisUtil<VarContext<T>> autil) {
        super.updateOutput(rec, output, autil);
        AnalysisUnitRecordLocs<T> recloc = (AnalysisUnitRecordLocs<T>) rec;
        AnalysisUtilVarContext<VarContext<T>, T> autilloc =
                (AnalysisUtilVarContext<VarContext<T>, T>) autil;

        recloc.readLocations.addAll(autilloc.readLocations());
        recloc.writeLocations.addAll(autilloc.writeLocations());

    }

    @Override
    public AnalysisFactoryVarContext<T> factory() {
        return (AnalysisFactoryVarContext<T>) super.factory();
    }

    @Override
    protected void updateCallerRecordAfterCalleeProcessing(
            AnalysisUnitRecord<VarContext<T>> callerRec,
            AnalysisUnitRecord<VarContext<T>> rec) {
        AnalysisUnitRecordLocs<T> callerRecLocs =
                (AnalysisUnitRecordLocs<T>) callerRec;
        AnalysisUnitRecordLocs<T> recLocs = (AnalysisUnitRecordLocs<T>) rec;
        callerRecLocs.readLocations.addAll(recLocs.readLocations);
        callerRecLocs.writeLocations.addAll(recLocs.writeLocations);
    }

    @Override
    public AnalysisUnitRecordLocs<T> getAnalysisUnitRecord(AnalysisUnit unit) {
        return (AnalysisUnitRecordLocs<T>) super.getAnalysisUnitRecord(unit);
    }

    @Override
    protected FinalVarContext<T> initialAnalysisUnitOutput(AnalysisUnit callee,
            VarContext<T> input) {
        FinalVarContext<T> f = (FinalVarContext<T>) createExitMap(input);
        if (callee.codeNode() instanceof MethodDecl) {
            MethodInstance mi =
                    (MethodInstance) callee.procedureDecl().procedureInstance();
            if (!mi.returnType().isVoid()) {
                f =
                        (FinalVarContext<T>) f.normalTermination(f.normalTermination()
                                                                  .returnResult(factory().bottomAbstractValue()));
            }
        }
        return f;
    }

    @Override
    protected AnalysisUnitRecord<VarContext<T>> createAnalysisUnitRecord() {
        return new AnalysisUnitRecordLocs<T>();
    }

    @Override
    protected AnalysisUnitRecord<VarContext<T>> createAnalysisUnitRecord(
            VarContext<T> initialInput, ExitMap<VarContext<T>> initialOutput) {
        return new AnalysisUnitRecordLocs<T>(initialInput,
                                             (FinalVarContext<T>) initialOutput);
    }

    public static class AnalysisUnitRecordLocs<T extends AbstractValue<T>>
            extends AnalysisUnitRecord<VarContext<T>> {
        public AnalysisUnitRecordLocs() {
            this(null, null);
        }

        public AnalysisUnitRecordLocs(VarContext<T> input,
                FinalVarContext<T> output) {
            super(input, output);
            this.readLocations = new LinkedHashSet<AbstractLocation>();
            this.writeLocations = new LinkedHashSet<AbstractLocation>();
        }

        private Set<AbstractLocation> readLocations;
        private Set<AbstractLocation> writeLocations;
    }

}
