package accrue.analysis.interprocanalysis;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import polyglot.ast.Node;
import polyglot.ast.ProcedureDecl;
import polyglot.types.ClassType;
import polyglot.types.CodeInstance;
import polyglot.types.SemanticException;
import polyglot.util.InternalCompilerError;
import accrue.AccrueExtensionInfo;
import accrue.analysis.AnalysisTopics;
import accrue.analysis.ext.AccrueExt_c;
import accrue.analysis.ext.ExtProcedureCall;
import accrue.analysis.interprocanalysis.ExitMap.Key;
import accrue.analysis.pointer.CallSiteNode;
import accrue.analysis.pointer.ConstructorContext;
import accrue.analysis.pointer.HContext;
import accrue.analysis.pointer.analyses.HeapAbstractionFactory;

/**
 * Utility class used by an interprocedural dataflow analysis
 * 
 * @param <A>
 *            Analysis result. The type that is the result of the analysis
 *            of an AnalysisUnit.
 */
public abstract class AnalysisFactory<A extends Ordered<A>> {
    /**
     * Get the name of the analysis
     */
    public abstract String analysisName();

    /**
     * Get the name of the analysis report
     */
    public abstract String analysisReportName();

    /**
     * Get the object sensitive analysis utility class.
     * 
     * @param wq
     *            Analysis work queue of {@link CodeInstance}
     * @param aunit
     *            Analysis unit that we are constructing the AnalysisUtil for.
     */
    public abstract AnalysisUtil<A> analysisUtil(WorkQueue<A> wq,
            AnalysisUnit aunit);

    /**
     * Get the start item for the analysis
     * 
     * @param autil
     *            Object sensitive analysis utility
     * @param startProc
     *            Entry procedure for the analysis
     * @return The start analysis result item
     * @throws SemanticException
     *             thrown if the start item cannot be computed due to a semantic
     *             error
     */
    public abstract A startItem(AnalysisUtil<A> autil, ProcedureDecl startProc)
            throws SemanticException;

    protected final AccrueExtensionInfo extInfo;

    public AnalysisFactory(AccrueExtensionInfo extInfo) {
        this.extInfo = extInfo;
    }

    private Set<String> reportTopics = null;

    /**
     * Return the analysis name and {@link AnalysisTopics} as a set
     */
    public Set<String> reportTopics() {
        if (reportTopics == null) {
            reportTopics = new HashSet<String>();
            reportTopics.add(analysisReportName());
            reportTopics.add(AnalysisTopics.ACCRUE);
        }
        return reportTopics;
    }

    private ExitMap<A> EMPTY =
            new ExitMap<A>(Collections.<Key, A> emptyMap(),
                           Collections.<ClassType> emptySet());

    /**
     * Get the {@link ExitMap}
     * @return
     */
    public ExitMap<A> emptyExitMap() {
        return EMPTY;
    }

    /**
     * Get the initial analysis context for the start procedure (i.e. static void main ...)
     */
    public AnalysisContext startProcAnalysisContext() {
        HeapAbstractionFactory af = this.extInfo.heapAbstractionFactory();
        return new AnalysisContext(af.initialContext(),
                                   ConstructorContext.NORMAL,
                                   this.extInfo.nodeFactory().lang());
    }

    /**
     * Create an analysis context for a procedure
     * 
     * @param caller
     *            Analysis utility class for the caller
     * @param callee
     *            Type information for the callee
     * @param calleeObjectName
     *            Heap context for the callee
     * @param callSite
     *            AST node for the call site
     * @return A new analysis context for the called procedure
     */
    public AnalysisContext createAnalysisContext(AnalysisUtil<A> caller,
            CodeInstance callee, HContext calleeObjectName, Node callSite) {
        HeapAbstractionFactory af = this.extInfo.heapAbstractionFactory();
        ExtProcedureCall ext = (ExtProcedureCall) AccrueExt_c.ext(callSite);
        CallSiteNode callSiteNode = ext.getCallSiteNode();
        if (callSiteNode == null) {
            throw new InternalCompilerError("No call site node for " + callSite
                    + " " + callSite.getClass());
        }
        return new AnalysisContext(af.merge(callSiteNode,
                                            calleeObjectName,
                                            caller.currentContext()
                                                  .getCContext()),
                                   ConstructorContext.NORMAL,
                                   this.extInfo.nodeFactory().lang());
    }
}
