package accrue.analysis.pointer.analyses;

import polyglot.types.Type;
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;

/**
 * Create an analysis context that is the cross product of two context
 * representations. The procedure analysis context and the heap context are both
 * represented as pairs of contexts from the initial analyses.
 */
public class CrossProductAnalysis extends HeapAbstractionFactory {

    /**
     * First analysis
     */
    private final HeapAbstractionFactory f1;
    /**
     * Second analysis
     */
    private final HeapAbstractionFactory f2;

    /**
     * Create an analysis as the cross product of two other analyses
     * 
     * @param f1
     *            first analysis
     * @param f2
     *            second analysis
     */
    public CrossProductAnalysis(HeapAbstractionFactory f1,
            HeapAbstractionFactory f2) {
        this.f1 = f1;
        this.f2 = f2;
    }

    @Override
    public HContext recordImpl(CContext context, AllocSiteNode alloc) {
        CrossProductCContext cpcc = (CrossProductCContext) context;
        HContext h1 = f1.record(cpcc.c1, alloc);
        HContext h2 = f2.record(cpcc.c2, alloc);
        return new CrossProductHContext(h1, h2);
    }

    @Override
    public CContext fixContextForReferenceVariable(CContext context,
            ReferenceVariable r) {
        CrossProductCContext cpcc = (CrossProductCContext) context;
        CContext c1 = f1.fixContextForReferenceVariable(cpcc.c1, r);
        CContext c2 = f2.fixContextForReferenceVariable(cpcc.c2, r);
        CContext cret = memoize(new CrossProductCContext(c1, c2));
//        System.out.println("correct " + context + " for " + r + " to " + cret);
        return cret;
    }

    @Override
    public CContext mergeImpl(CallSiteNode callSite, HContext receiver,
            CContext callerContext) {
        CrossProductCContext cpcc = (CrossProductCContext) callerContext;
        CrossProductHContext cphc = (CrossProductHContext) receiver;
        CContext c1;
        CContext c2;
        if (cphc != null) {
            c1 = f1.merge(callSite, cphc.h1, cpcc.c1);
            c2 = f2.merge(callSite, cphc.h2, cpcc.c2);
        }
        else {
            c1 = f1.merge(callSite, null, cpcc.c1);
            c2 = f2.merge(callSite, null, cpcc.c2);
        }
        return new CrossProductCContext(c1, c2);
    }

    @Override
    public CContext initialContextImpl() {
        CContext c1 = f1.initialContext();
        CContext c2 = f2.initialContext();
        return new CrossProductCContext(c1, c2);
    }

    @Override
    public String toString() {
        return f1 + " x " + f2;
    }

    /**
     * Heap context that is the cross product of two other heap contexts
     */
    private static class CrossProductHContext implements HContext {

        /**
         * First heap context
         */
        private HContext h1;
        /**
         * Second heap context
         */
        private HContext h2;
        /**
         * Hashcode only computed once
         */
        private int memoizedHashCode;

        /**
         * Create a heap context that is the cross product of the given heap
         * contexts
         * 
         * @param h1
         *            first context
         * @param h2
         *            second context
         */
        public CrossProductHContext(HContext h1, HContext h2) {
            this.h1 = h1;
            this.h2 = h2;
            memoizedHashCode = computeHashCode();
        }

        /**
         * compute the hash code and save it
         * 
         * @return computed hash code
         */
        private int computeHashCode() {
            int firstHash = h1.hashCode();
            // flip the bits so (b x a) has a different hash than (a x b)
            return (firstHash >>> 16 | firstHash << 16) ^ h2.hashCode();
        }

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

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

        @Override
        public boolean equalsSemantic(HContext o) {
            if (!(o instanceof CrossProductHContext)) {
                return false;
            }
            CrossProductHContext other = (CrossProductHContext) o;
            return h1.equalsSemantic(other.h1) && h2.equalsSemantic(other.h2);
        }

        @Override
        public AllocSiteNode allocSite() {
            return h1.allocSite();
        }

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

        @Override
        public String toString() {
            return h1 + " x " + h2;
        }

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

    /**
     * Procedure call context that is the cross product of two other contexts
     */
    private static class CrossProductCContext implements CContext {

        /**
         * first context
         */
        private CContext c1;
        /**
         * Second context
         */
        private CContext c2;
        /**
         * Hashcode only computed once
         */
        private int memoizedHC;

        /**
         * Create a procedure call context that is the cross product of the
         * given contexts
         * 
         * @param c1
         *            first context
         * @param c2
         *            second context
         */
        public CrossProductCContext(CContext c1, CContext c2) {
            this.c1 = c1;
            this.c2 = c2;
            memoizedHC = computeHashCode();
        }

        /**
         * compute the hash code and save it
         * 
         * @return computed hash code
         */
        private int computeHashCode() {
            int firstHash = c1.hashCode();
            // flip the bits so (b x a) has a different hash than (a x b)
            return (firstHash >>> 16 | firstHash << 16) ^ c2.hashCode();
        }

        @Override
        public boolean equalsSemantic(CContext o) {
            if (!(o instanceof CrossProductCContext)) {
                return false;
            }
            CrossProductCContext other = (CrossProductCContext) o;
            return c1.equalsSemantic(other.c1) && c2.equalsSemantic(other.c2);
        }

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

        @Override
        public String toString() {
            return c1 + " x " + c2;
        }
    }
}
