package accrue.analysis.pointer.analyses;

import java.util.Arrays;

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

/**
 * A 2type+1H Type Sensitive pointer analysis, as described in
 * "Pick Your Contexts Well: Understanding Object-Sensitivity"
 * by Smaragdakis, Bravenboer, and Lhotak, POPL 2011.
 *
 */
public class TypeSensitive_nTmH_Analysis extends HeapAbstractionFactory {
    /**
     * Number of elements to record for CContexts
     */
    final int n;
    /**
     * Number of elements to record for HContexts
     */
    final int m;

    public TypeSensitive_nTmH_Analysis(int n, int m) {
        this.n = n;
        this.m = m;
    }

    public TypeSensitive_nTmH_Analysis() {
        // default is T2-1H.
        this(2, 1);
    }

    @Override
    public String toString() {
        return "type(" + this.n + "," + this.m + ")";
    }

    @Override
    public HContext recordImpl(CContext context, AllocSiteNode alloc) {
        CodeContext cc = (CodeContext) context;
        ClassType d[];
        if (n == m) {
            d = cc.d;
        }
        else {
            d = new ClassType[m];
            System.arraycopy(cc.d, 0, d, 0, Math.min(n, m));
        }
        return new HeapContext(alloc, d);
    }

    @Override
    public CContext mergeImpl(CallSiteNode callSite, HContext receiver,
            CContext callerContext) {
        if (receiver == null) {
            // static method!
            return this.initialContext();
        }
        HeapContext hc = (HeapContext) receiver;
        ClassType d[] = new ClassType[n];
        if (hc.top != null) {
            d[0] = hc.top.containingClass;
        }
        System.arraycopy(hc.d, 0, d, 1, Math.min(n - 1, m));
        return new CodeContext(d);
    }

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

    private CContext EMPTY = null;

    @Override
    public CContext initialContextImpl() {
        if (EMPTY == null) {
            EMPTY = memoize(new CodeContext(new ClassType[n]));
        }
        return EMPTY;
    }

    private static class HeapContext implements HContext {
        final AllocSiteNode top;
        final ClassType d[];

        public HeapContext(AllocSiteNode top, ClassType d[]) {
            this.top = top;
            this.d = d;
        }

        @Override
        public boolean hasPreciseType() {
            return (top == null) ? false : top.isPreciseType();
        }

        @Override
        public Type preciseType() {
            if (top == null) {
                return null;
            }
            return top.isPreciseType() ? this.top.instantiatedType : null;
        }

        @Override
        public Type type() {
            if (top == null) {
                return null;
            }
            return this.top.instantiatedType;
        }

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

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

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + Arrays.hashCode(d);
            result = prime * result + ((top == null) ? 0 : top.hashCode());
            return result;
        }

        @Override
        public boolean equalsSemantic(HContext obj) {
            if (this == obj) return true;
            if (obj == null) return false;
            if (getClass() != obj.getClass()) return false;
            HeapContext other = (HeapContext) obj;
            if (!Arrays.equals(d, other.d)) return false;
            if (top == null) {
                if (other.top != null) return false;
            }
            else if (!top.equals(other.top)) return false;
            return true;
        }

        @Override
        public String toString() {
            return "HeapContext [top=" + top + ", d=" + Arrays.toString(d)
                    + "]";
        }
    }

    private static class CodeContext implements CContext {
        final ClassType d[];

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + Arrays.hashCode(d);
            return result;
        }

        @Override
        public boolean equalsSemantic(CContext obj) {
            if (this == obj) return true;
            if (obj == null) return false;
            if (getClass() != obj.getClass()) return false;
            CodeContext other = (CodeContext) obj;
            if (!Arrays.equals(d, other.d)) return false;
            return true;
        }

        public CodeContext(ClassType d[]) {
            this.d = d;
        }

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

        @Override
        public String toString() {
            return "CodeContext [d=" + Arrays.toString(d) + "]";
        }

    }

}
