package accrue;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import polyglot.ast.NodeFactory;
import polyglot.frontend.CyclicDependencyException;
import polyglot.frontend.ExtensionInfo;
import polyglot.frontend.Job;
import polyglot.frontend.Scheduler;
import polyglot.frontend.goals.Barrier;
import polyglot.frontend.goals.EmptyGoal;
import polyglot.frontend.goals.Goal;
import polyglot.types.TypeSystem;
import polyglot.util.InternalCompilerError;
import accrue.analysis.antidep.AntiDepGoal;
import accrue.analysis.callgraph.PreciseCallGraphGoal;
import accrue.analysis.defuse.DefUseAnalysisUtil;
import accrue.analysis.defuse.DefUseGoal;
import accrue.analysis.domination.DominatorGoal;
import accrue.analysis.domination.NonFatalPostDominatorGoal;
import accrue.analysis.domination.PostDominatorGoal;
import accrue.analysis.goals.AnalysesDone;
import accrue.analysis.goals.ExpressionFlattenerGoal;
import accrue.analysis.goals.FindFinallyBlocksGoal;
import accrue.analysis.goals.InlineFinalConstantsGoal;
import accrue.analysis.goals.MakeInstanceInitializersExplicitGoal;
import accrue.analysis.goals.MakeNarrowingAssignmentsExplicitGoal;
import accrue.analysis.goals.MakeTargetsExplicitGoal;
import accrue.analysis.goals.MoveInnerClassesGoal;
import accrue.analysis.goals.MoveLocalClassesGoal;
import accrue.analysis.goals.MoveStaticFieldInitializersGoal;
import accrue.analysis.goals.PointerAnalysisGoal;
import accrue.analysis.goals.RegisterDeclsGoal;
import accrue.analysis.goals.RegisterExpressionsGoal;
import accrue.analysis.goals.RegisterPointerStmtsGoal;
import accrue.analysis.goals.RegisterProceduresGoal;
import accrue.analysis.goals.SimplifyExpressionsGoal;
import accrue.analysis.notnulldataflow.NotNullGoal;
import accrue.analysis.preciseExceptions.PreciseExGoal;
import accrue.analysis.readwritesets.ReadWriteSetsGoal;

/**
 * This class defines a number of Accrue-relevant
 * scheduler goals. This class is a helper class intended
 * to be contained in a Scheduler.
 *
 */
public class AccrueSchedulerHelper {
    /**
     * The Scheduler that this class is helping.
     */
    protected final Scheduler sched;
    /**
     * The ExtensionInfo for the scheduler this class is helping.
     */
    protected final ExtensionInfo extInfo;

    public AccrueSchedulerHelper(ExtensionInfo extInfo, Scheduler sched) {
        this.extInfo = extInfo;
        this.sched = sched;
    }

    public Goal AnalysesDone(Job job) {
        return AnalysesDone.create(this.sched, job);
    }

    public Goal SimplifyExpressionsGoal(Job job) {
        TypeSystem ts = extInfo.typeSystem();
        NodeFactory nf = extInfo.nodeFactory();
        return SimplifyExpressionsGoal.create(this.sched, job, nf, ts);
    }

    public Goal MakeTargetsExplicitGoal(Job job) {
        TypeSystem ts = extInfo.typeSystem();
        NodeFactory nf = extInfo.nodeFactory();
        return MakeTargetsExplicitGoal.create(this.sched, job, nf, ts);
    }

    public Goal MoveStaticFieldInitializers(Job job) {
        TypeSystem ts = extInfo.typeSystem();
        NodeFactory nf = extInfo.nodeFactory();
        return MoveStaticFieldInitializersGoal.create(this.sched, job, ts, nf);
    }

    public Goal MakeNarrowingAssignmentsExplicit(Job job) {
        NodeFactory nf = extInfo.nodeFactory();
        return MakeNarrowingAssignmentsExplicitGoal.create(this.sched, job, nf);
    }

    public Goal InlineFinalConstants(Job job) {
        TypeSystem ts = extInfo.typeSystem();
        NodeFactory nf = extInfo.nodeFactory();
        return InlineFinalConstantsGoal.create(this.sched, job, ts, nf);
    }

    private Goal registerProceduresDone = null;

    public Goal RegisterProceduresDone() {
        if (registerProceduresDone == null) {
            registerProceduresDone =
                    new Barrier("RegisterProceduresDone", this.sched) {
                        @Override
                        public Goal goalForJob(Job job) {
                            return RegisterProcedures(job);
                        }

                    };

        }
        return registerProceduresDone;
    }

    public Goal RegisterProcedures(Job job) {
        TypeSystem ts = extInfo.typeSystem();
        NodeFactory nf = extInfo.nodeFactory();
        return RegisterProceduresGoal.create(this.sched, job, ts, nf);
    }

    public Goal PointerAnalysis() {
        return PointerAnalysisGoal.singleton(extInfo);
    }

    private Goal registerPointerStmts = null;

    public Goal NotNullAnalysis() {
        return NotNullGoal.singleton(extInfo);
    }

    public Goal PreciseExceptionAnalysis(int i) {
        return PreciseExGoal.singleton(extInfo, i);
    }

    private Goal computeFinallyStructure = null;

    public Goal ComputeFinallyStructureDone() {
        if (computeFinallyStructure == null) {
            computeFinallyStructure =
                    new Barrier("ComputeFinallyStructureDone", this.sched) {
                        @Override
                        public Goal goalForJob(Job job) {
                            return ComputeFinallyStructure(job);
                        }

                    };

        }
        return computeFinallyStructure;
    }

    public Goal ComputeFinallyStructure(Job job) {
        TypeSystem ts = extInfo.typeSystem();
        NodeFactory nf = extInfo.nodeFactory();
        return FindFinallyBlocksGoal.create(this.sched, job, nf, ts);
    }

    public Goal RegisterPointerStmtsDone() {
        if (registerPointerStmts == null) {
            registerPointerStmts =
                    new Barrier("RegisterPointerDone", this.sched) {
                        @Override
                        public Goal goalForJob(Job job) {
                            return RegisterPointerStmts(job);
                        }

                    };
        }
        return registerPointerStmts;
    }

    public Goal RegisterPointerStmts(Job job) {
        TypeSystem ts = extInfo.typeSystem();
        NodeFactory nf = extInfo.nodeFactory();
        return RegisterPointerStmtsGoal.create(this.sched, job, ts, nf);
    }

    public Goal MakeInstanceInitializersExplicitGoal(Job job) {
        TypeSystem ts = extInfo.typeSystem();
        NodeFactory nf = extInfo.nodeFactory();
        return MakeInstanceInitializersExplicitGoal.create(this.sched,
                                                           job,
                                                           ts,
                                                           nf);
    }

    public Goal MoveInnerClassesGoal(Job job) {
        TypeSystem ts = extInfo.typeSystem();
        NodeFactory nf = extInfo.nodeFactory();
        return MoveInnerClassesGoal.create(this.sched, job, ts, nf);
    }

    public Goal MoveLocalClassesGoal(Job job) {
        return MoveLocalClassesGoal.singleton(extInfo);
    }

    public Goal DefUseAnalysis() {
        return DefUseGoal.singleton(extInfo,
                                    DefUseAnalysisUtil.Mode.INTER_EDGES);
    }

    public Goal PreciseCallGraph() {
        return PreciseCallGraphGoal.singleton(extInfo, 0);
    }

    public Goal AntiDepAnalysis() {
        Goal g = AntiDepGoal.singleton(extInfo);
        return g;
    }

    public Goal PostDominator() {
        return PostDominatorGoal.singleton(extInfo);
    }

    public Goal Dominator() {
        return DominatorGoal.singleton(extInfo);
    }

    public Goal ReadWriteSet() {
        return ReadWriteSetsGoal.singleton(extInfo);
    }

    public Goal NonFatalPostDominator() {
        return NonFatalPostDominatorGoal.singleton(extInfo);
    }

    public RegisterExpressionsGoal RegisterExpressions() {
        return RegisterExpressionsGoal.singleton(extInfo);
    }

    public RegisterDeclsGoal RegisterDecls() {
        return RegisterDeclsGoal.singleton(extInfo);
    }

    public Goal ExpressionFlattenerGoal(Job job) {
        return ExpressionFlattenerGoal.create(this.sched, job);
    }

    private Goal preModificationBarrier = null;

    /**
     * Return a barrier goal, with the intent that any
     * goal that modifies types to simplify the analyses
     * happen only after the barrier.
     */
    public Goal PreModificationBarrier() {
        if (preModificationBarrier == null) {
            preModificationBarrier =
                    new Barrier("PreModificationBarrier", this.sched) {
                        @Override
                        public Goal goalForJob(Job job) {
                            return AccrueSchedulerHelper.this.sched.internGoal(new EmptyGoal(job,
                                                                                             "AccruePreModBarrier") {
                                @Override
                                public Collection<Goal> prerequisiteGoals(
                                        Scheduler scheduler) {
                                    List<Goal> l = new ArrayList<Goal>();
                                    l.add(scheduler.ReachabilityChecked(job));
                                    l.add(scheduler.ConstantsChecked(job));
                                    l.add(scheduler.ExceptionsChecked(job));
                                    l.add(scheduler.ExitPathsChecked(job));
                                    l.add(scheduler.InitializationsChecked(job));
                                    l.add(scheduler.ConstructorCallsChecked(job));
                                    l.add(scheduler.ForwardReferencesChecked(job));
                                    l.addAll(super.prerequisiteGoals(scheduler));
                                    return l;
                                }

                            });
                        }
                    };

        }
        return preModificationBarrier;

    }

    /**
     * Return a goal that indicates all passes that will modify code 
     * to simplify analysis has been performed.
     */
    public Goal PostModificationGoal(Job job) {
        Goal g = new EmptyGoal(job, "PostModification");

        try {
            g.addPrerequisiteGoal(PreModificationBarrier(), this.sched);
//            g.addPrerequisiteGoal(MakeInstanceInitializersExplicitGoal(job),
//                                  this.sched);
            g.addPrerequisiteGoal(MoveInnerClassesGoal(job), this.sched);
            g.addPrerequisiteGoal(MoveLocalClassesGoal(job), this.sched);
            g.addPrerequisiteGoal(SimplifyExpressionsGoal(job), this.sched);
//            g.addPrerequisiteGoal(MoveStaticFieldInitializers(job), this.sched);
        }
        catch (CyclicDependencyException e) {
            throw new InternalCompilerError(e);
        }

        return this.sched.internGoal(g);
    }
}
