package accrue.analysis.pointer.analyses;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import polyglot.types.MethodInstance;
import polyglot.types.Type;
import polyglot.util.InternalCompilerError;
import accrue.analysis.pointer.AllocSiteNode;
import accrue.analysis.pointer.CContext;
import accrue.analysis.pointer.CallSiteNode;
import accrue.analysis.pointer.HContext;

/**
 * Similar to CallSiteContextSensitiveAnalysis, this is a call-site
 * sensitive analysis. However, it only records call sites to static methods.
 * This is intended to be used in conjunction with object sensitive
 * analyses that analyze all static methods in the same analysis context.
 */
public class StaticCallSiteContextSensitiveAnalysis extends
        HeapAbstractionFactory {
    /**
     * Default depth of call sites to keep track of
     */
    private static final int DEFAULT_SENSITIVITY = 2;
    /**
     * Depth of the call sites to keep track of
     */
    private final int sensitivity;

    /**
     * Create an analysis with the default sensitivity
     */
    public StaticCallSiteContextSensitiveAnalysis() {
        this(DEFAULT_SENSITIVITY);
    }

    /**
     * Create an analysis with the given context-call-stack depth
     * 
     * @param sensitivity
     *            depth of the call stack to keep track of
     */
    public StaticCallSiteContextSensitiveAnalysis(int sensitivity) {
        this.sensitivity = sensitivity;
    }

    @Override
    public String toString() {
        return "StaticContext(" + this.sensitivity + ")";
    }

    @Override
    public HContext recordImpl(CContext context, AllocSiteNode alloc) {
        return (((ContextStack) context).pushAllocSite(alloc, sensitivity));
    }

    @Override
    public CContext mergeImpl(CallSiteNode callSite, HContext receiver,
            CContext callerContext) {
        return (((ContextStack) callerContext).pushCallSite(callSite,
                                                            sensitivity));
    }

    @Override
    public void init() {
        clearMemoization();
    }

    @Override
    public CContext initialContextImpl() {
        return EMPTY_CONTEXT;
    }

    /**
     * Empty context represents methods with no invoking objects (including
     * static methods).
     */
    public CContext EMPTY_CONTEXT =
            memoize(new ContextStack(Collections.<CallSiteNode> emptyList()));

    /**
     * A sequence of call sites
     */
    private static class ContextStack implements CContext {

        /**
         * List of call sites in the stack
         */
        private final List<CallSiteNode> sites;

        /**
         * Create a new stack from the given list of call sites
         * 
         * @param sites
         *            call sites in the new stack
         */
        private ContextStack(List<CallSiteNode> sites) {
            this.sites = sites;
        }

        @Override
        public int hashCode() {
            return sites.hashCode();
        }

        /**
         * Add the given call site to the stack
         * 
         * @param csn
         *            call site to add
         * @param callSiteASTNode 
         * @param sensitivity
         *            max depth of the call stack
         * @return new context stack with the given call site added
         */
        public ContextStack pushCallSite(CallSiteNode csn, int sensitivity) {
            ArrayList<CallSiteNode> s =
                    new ArrayList<CallSiteNode>(sites.size() + 1);
            if (csn == null) {
                throw new InternalCompilerError("Really?");
            }
            if (csn.callee() instanceof MethodInstance
                    && csn.callee().flags().isStatic()) {
                s.add(csn);
            }
            else {
                // not a static method callee
                // Just use null instead of the call site node.
                s.add(null);
            }
            s.addAll(sites);
            while (s.size() > sensitivity) {
                s.remove(s.size() - 1);
            }
            return new ContextStack(s);
        }

        public SCSHContext pushAllocSite(AllocSiteNode asn, int sensitivity) {
            return new SCSHContext(asn, this);
        }

        @Override
        public boolean equalsSemantic(CContext that) {
            if (this == that) return true;
            if (!(that instanceof ContextStack)) {
                return false;
            }
            return this.sites.equals(((ContextStack) that).sites);
        }

        @Override
        public boolean equals(Object o) {
            return (this == o);
        }

        @Override
        public String toString() {
            return "Context: " + sites.toString();
        }
    }

    private static class SCSHContext implements HContext {
        private final AllocSiteNode asn;
        private final ContextStack cstack;

        public SCSHContext(AllocSiteNode asn, ContextStack cstack) {
            this.asn = asn;
            this.cstack = cstack;
        }

        @Override
        public AllocSiteNode allocSite() {
            return asn;
        }

        @Override
        public Type preciseType() {
            return asn.instantiatedType;
        }

        @Override
        public Type type() {
            return asn.instantiatedType;
        }

        @Override
        public boolean equalsSemantic(HContext that) {
            if (!(that instanceof SCSHContext)) {
                return false;
            }
            return equalsSemantic((SCSHContext) that);
        }

        private boolean equalsSemantic(SCSHContext that) {
            if (this == that) return true;
            return (this.asn == that.asn || (this.asn != null && this.asn.equals(that.asn)))
                    && (this.cstack == that.cstack || this.cstack.equalsSemantic(that.cstack));
        }

        @Override
        public boolean equals(Object o) {
            return (this == o);
        }

        @Override
        public int hashCode() {
            return asn.hashCode();
        }

        @Override
        public String toString() {
            return "(" + asn + ", " + cstack + ")";
        }

        @Override
        public boolean hasPreciseType() {
            return asn.isPreciseType();
        }
    }

}
