package accrue.analysis.pointer.analyses;

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

/**
 * A heap abstraction that improves the precision of object sensitive
 * analyses: once a call stack is tainted by a subclass of the
 * specified class, all heap and code contexts are distinguished by
 * the allocation site of that specified class.
 *
 */
public class DistinguishClass extends HeapAbstractionFactory {

    private final String className;
    private final boolean distinguishStaticCallsFromDistguishedObjects = true;

    public DistinguishClass(String className) {
        this.className = className;
    }

    @Override
    public String toString() {
        return "Distinguish(" + this.className + ")";
    }

    @Override
    public HContext recordImpl(CContext context, AllocSiteNode alloc) {
        CodeContext cc = (CodeContext) context;
        AllocSiteNode ccContext = cc.context;
        Type classType;
        try {
            classType =
                    alloc.instantiatedType.typeSystem()
                                          .typeForName(this.className);
        }
        catch (SemanticException e) {
            throw new InternalCompilerError(e);
        }
        if (alloc.instantiatedType.isSubtype(classType)) {
            ccContext = alloc;
        }
        return new HeapContext(alloc, ccContext);
    }

    @Override
    public CContext mergeImpl(CallSiteNode callSite, HContext receiver,
            CContext callerContext) {
        if (receiver == null) {
            if (distinguishStaticCallsFromDistguishedObjects) {
                return callerContext;
            }
            else {
                return initialContext();
            }
        }
        HeapContext hc = (HeapContext) receiver;
        return new CodeContext(hc.context);
    }

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

    private CContext EMPTY = null;

    @Override
    public CContext initialContextImpl() {
        if (EMPTY == null) {
            EMPTY = new CodeContext(null);
        }
        return EMPTY;
    }

    private static class HeapContext implements HContext {
        final AllocSiteNode top;
        final AllocSiteNode context;

        public HeapContext(AllocSiteNode top, AllocSiteNode context) {
            this.top = top;
            this.context = context;
        }

        @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 equalsSemantic((HContext) o);
            //			return this == o;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((top == null) ? 0 : top.hashCode());
            result =
                    prime * result
                            + ((context == null) ? 0 : context.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 (top == null) {
                if (other.top != null) return false;
            }
            else if (!top.equals(other.top)) return false;
            if (context == null) {
                if (other.context != null) return false;
            }
            else if (!context.equals(other.context)) return false;
            return true;
        }

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

    private static class CodeContext implements CContext {
        final AllocSiteNode context;

        public CodeContext(AllocSiteNode context) {
            this.context = context;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result =
                    prime * result
                            + ((context == null) ? 0 : context.hashCode());
            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 (context == null) {
                if (other.context != null) return false;
            }
            else if (!context.equals(other.context)) return false;
            return true;
        }

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

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

    }

}
