package accrue.analysis.pointer.analyses;

import java.util.concurrent.ConcurrentHashMap;

import accrue.analysis.pointer.AllocSiteNode;
import accrue.analysis.pointer.CContext;
import accrue.analysis.pointer.CallSiteNode;
import accrue.analysis.pointer.HContext;
import accrue.analysis.pointer.ReferenceVariable;

/**
 * A HeapAbstractionFactory is responsible for providing an appropriate
 * abstraction of the heap for pointer analysis. It is based on the
 * factoring of pointer analysis in the paper "Pick Your Contexts Well: 
 * Understanding Object-Sensitivity" by Smaragdakis, Bravenboer, and 
 * Lhotak, POPL 2011.
 *
 */
public abstract class HeapAbstractionFactory {

    /**
     * Produce an abstract object (i.e., HContext) to represent an
     * object created at allocation site <code>alloc</code> in code
     * context <code>context</code>. This method wraps recordImpl to
     * enforce memoization.
     */
    public final HContext record(CContext context, AllocSiteNode alloc) {
        return memoize(recordImpl(context, alloc));
    }

    /**
     * Produce an abstract object (i.e., HContext) to represent an
     * object created at allocation site <code>alloc</code> in code
     * context <code>context</code>.
     */
    protected abstract HContext recordImpl(CContext context, AllocSiteNode alloc);

    /**
     * Produce a code context (i.e., CContext) to analysis code
     * called from call site <code>callSite</code> in code context
     * <code>callerContext</code>, where <code>receiver</code> is
     * the abstract object for the receiver of the method invocation.
     * If the method being called does not have a receiver (i.e., it
     * is a static method), then <code>receiver</code> will be null.
     * This method wraps mergeImpl to
     * enforce memoization.
     */
    public final CContext merge(CallSiteNode callSite, HContext receiver,
            CContext callerContext) {
        return memoize(mergeImpl(callSite, receiver, callerContext));
    }

    /**
     * Produce a code context (i.e., CContext) to analysis code
     * called from call site <code>callSite</code> in code context
     * <code>callerContext</code>, where <code>receiver</code> is
     * the abstract object for the recevier of the method invocation.
     * If the method being called does not have a receiver (i.e., it
     * is a static method), then <code>receiver</code> will be null.
     */
    protected abstract CContext mergeImpl(CallSiteNode callSite,
            HContext receiver, CContext callerContext);

    /**
     * Fix the code context for a reference variable, if needed, when creating
     * a reference variable replica for reference variable <code>r</code>. 
     * The need for this method is motivated by an object sensitive analysis
     * where static fields should only have a single replica, i.e., if f is a static
     * field of C, and o is an object of class C, then an expression o.f should
     * be represented by a reference variable replica that has the
     * ObjectField for C.f in the unique static context, not the context for o. 
     */
    public CContext fixContextForReferenceVariable(CContext context,
            ReferenceVariable r) {
        if (r.isSingletonReferenceVariable()) {
            // singleton variables should only appear in the initial context.
            // This occurs for local nodes used to represent static field
            // targets.
            return initialContext();
        }
        return context;
    }

    /**
     * Return the initial CContext, i.e., to analyze the main method. This method
     * wraps initialContextImpl to enforce memoization.
     */
    public final CContext initialContext() {
        return memoize(initialContextImpl());
    }

    /**
     * Return the initial CContext, i.e., to analyze the main method. 
     */
    protected abstract CContext initialContextImpl();

    /**
     * Initialize the factory.
     */
    public void init() {
        clearMemoization();
    }

    /* *********
     * Memoization code
     */
    private static ConcurrentHashMap<HWrapper, HContext> hmemo =
            createConcurrentHashMap();
    private static ConcurrentHashMap<CWrapper, CContext> cmemo =
            createConcurrentHashMap();

    protected static HContext memoize(HContext o) {
        HWrapper w = new HWrapper(o);
        HContext m = hmemo.get(w);
        if (m == null) {
            m = hmemo.putIfAbsent(w, o);
            if (m == null) {
                m = o;
            }
        }
        return m;
    }

    private static <W, T> ConcurrentHashMap<W, T> createConcurrentHashMap() {
        return new ConcurrentHashMap<W, T>(16,
                                           0.75f,
                                           Runtime.getRuntime()
                                                  .availableProcessors());
    }

    protected static CContext memoize(CContext o) {
        CWrapper w = new CWrapper(o);
        CContext m = cmemo.get(w);
        if (m == null) {
            m = cmemo.putIfAbsent(w, o);
            if (m == null) {
                m = o;
            }
        }

        return m;
    }

    public void clearMemoization() {
        hmemo = createConcurrentHashMap();
        cmemo = createConcurrentHashMap();
    }

    private static interface Wrapper {

    }

    private static class HWrapper implements Wrapper {
        HWrapper(HContext o) {
            this.o = o;
        }

        final HContext o;

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

        @Override
        public boolean equals(Object that) {
            return o.equalsSemantic(((HWrapper) that).o);
        }
    }

    private static class CWrapper implements Wrapper {
        CWrapper(CContext o) {
            this.o = o;
        }

        final CContext o;

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

        @Override
        public boolean equals(Object that) {
            return o.equalsSemantic(((CWrapper) that).o);
        }
    }

}
