package accrue.analysis.pointer.analyses;

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

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

/**
 * Analysis where the contexts are based on procedure call sites
 */
public class CallSiteContextSensitiveAnalysis 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 CallSiteContextSensitiveAnalysis() {
        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 CallSiteContextSensitiveAnalysis(int sensitivity) {
        this.sensitivity = sensitivity;
    }

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

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

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

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

    @Override
    protected 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 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);
            s.add(csn);
            s.addAll(sites);
            while (s.size() > sensitivity) {
                s.remove(s.size() - 1);
            }
            return new ContextStack(s);
        }

        public AllocationName pushAllocSite(AllocSiteNode asn, int sensitivity) {
            return new AllocationName(this, asn, asn.instantiatedType);
        }

        @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 AllocationName implements HContext {
        private final AllocSiteNode asn;
        private final ContextStack context;
        private final Type topAllocationSiteType;
        private final boolean isPreciseType;

        public AllocationName(ContextStack contextStack, AllocSiteNode asn,
                Type instantiatedType) {
            this(contextStack, asn, instantiatedType, true);
        }

        public AllocationName(ContextStack contextStack, AllocSiteNode asn,
                Type instantiatedType, boolean isPreciseType) {
            this.context = contextStack;
            this.asn = asn;
            this.topAllocationSiteType = instantiatedType;
            this.hashCode = this.computeHashCode();
            this.isPreciseType = isPreciseType;
        }

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

        @Override
        public Type preciseType() {
            return isPreciseType ? this.topAllocationSiteType : null;
        }

        @Override
        public Type type() {
            return topAllocationSiteType;
        }

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

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

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

        private final int hashCode;

        private int computeHashCode() {
            return context.hashCode()
                    ^ (topAllocationSiteType == null ? 0
                            : topAllocationSiteType.hashCode())
                    ^ asn.hashCode();
        }

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

        @Override
        public String toString() {
            // TODO Auto-generated method stub
            return "stack: " + context + ", node: " + asn;
        }

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

}
