package accrue.jl5;

import polyglot.ast.NodeFactory;
import polyglot.ext.jl5.JL5Scheduler;
import polyglot.ext.jl5.types.JL5TypeSystem;
import polyglot.ext.jl5.visit.AutoBoxer;
import polyglot.ext.jl5.visit.RemoveExtendedFors;
import polyglot.frontend.CyclicDependencyException;
import polyglot.frontend.EmptyPass;
import polyglot.frontend.ExtensionInfo;
import polyglot.frontend.JLExtensionInfo;
import polyglot.frontend.Job;
import polyglot.frontend.Pass;
import polyglot.frontend.Scheduler;
import polyglot.frontend.goals.Goal;
import polyglot.frontend.goals.SourceFileGoal;
import polyglot.frontend.goals.VisitorGoal;
import polyglot.types.TypeSystem;
import polyglot.util.InternalCompilerError;
import accrue.AccrueExtensionInfo;
import accrue.AccrueScheduler;
import accrue.AccrueSchedulerHelper;
import accrue.jl5.goals.JL5RegisterPointerStmtsGoal;

public class AccrueJL5Scheduler extends JL5Scheduler implements AccrueScheduler {

    AccrueSchedulerHelper helper;

    public AccrueJL5Scheduler(JLExtensionInfo extInfo) {
        super(extInfo);
        this.helper = new AccrueJL5SchedulerHelper(extInfo, this);
    }

    @Override
    public Goal Parsed(Job job) {
        AccrueExtensionInfo extInfo = (AccrueExtensionInfo) this.extInfo;
        if (extInfo.isOutputExtensionInfo() && job.ast() != null) {
            // this ExtensionInfo is being used as an output extension info, i.e.,
            // it is taking the output of some other extension info.
            // So if the ast for the job is non-null, then return an empty pass, so that we don't look
            // for the source files.
            // If it is null, then return the previous behavior.
            return internGoal(new SourceFileGoal(job) {
                @Override
                public Pass createPass(ExtensionInfo extInfo) {
                    return new EmptyPass(this);
                }
            });

        }
        return super.Parsed(job);
    }

    @Override
    public AccrueSchedulerHelper helper() {
        return this.helper;
    }

    public static class AccrueJL5SchedulerHelper extends AccrueSchedulerHelper {

        public AccrueJL5SchedulerHelper(ExtensionInfo extInfo, Scheduler sched) {
            super(extInfo, sched);
        }

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

        @Override
        public Goal PostModificationGoal(Job job) {
            // Perform some useful code transformations.
            // Add autoboxing, remove extended fors.

            Goal g = super.PostModificationGoal(job);
            JL5TypeSystem ts = (JL5TypeSystem) extInfo.typeSystem();
            NodeFactory nf = extInfo.nodeFactory();

            try {
                {
                    Goal autoBoxer =
                            new VisitorGoal(job, new AutoBoxer(job, ts, nf));
                    autoBoxer.addPrerequisiteGoal(PreModificationBarrier(),
                                                  this.sched);
                    autoBoxer = this.sched.internGoal(autoBoxer);
                    g.addPrerequisiteGoal(autoBoxer, this.sched);
                }

                {
                    Goal removeExtendedFors =
                            new VisitorGoal(job, new RemoveExtendedFors(job,
                                                                        ts,
                                                                        nf));
                    removeExtendedFors.addPrerequisiteGoal(PreModificationBarrier(),
                                                           this.sched);

                    removeExtendedFors =
                            this.sched.internGoal(removeExtendedFors);
                    g.addPrerequisiteGoal(removeExtendedFors, this.sched);
                }
            }
            catch (CyclicDependencyException e) {
                throw new InternalCompilerError(e);
            }

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

}
