package accrue.analysis.pointer.analyses;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
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;

public class FullObjSensitiveAnalysis extends HeapAbstractionFactory {
    private static final int DEFAULT_OBJECT_SENSITIVITY = 2;
    private final int objectSensitivity;

    public FullObjSensitiveAnalysis() {
        this(DEFAULT_OBJECT_SENSITIVITY);
    }

    public FullObjSensitiveAnalysis(int objectSensitivity) {
        this.objectSensitivity = objectSensitivity;
    }

    @Override
    public String toString() {
        return "Full(" + this.objectSensitivity + ")";
    }

    @Override
    public HContext recordImpl(CContext context, AllocSiteNode alloc) {
        return ((ObjectName) context).pushAllocSite(alloc, objectSensitivity);
    }

    @Override
    public CContext mergeImpl(CallSiteNode callSite, HContext receiver,
            CContext callerContext) {
        if (receiver == null) {
            return EMPTY_CONTEXT;
        }
        return (ObjectName) receiver;
    }

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

    @Override
    public CContext initialContextImpl() {
        return EMPTY_CONTEXT;
    }

    /**
     * Empty context represents methods with no invoking objects (including
     * static methods).
     */
    public CContext EMPTY_CONTEXT =
            memoize((CContext) new ObjectName(Collections.<AllocSiteNode> emptyList(),
                                              null,
                                              false));

    /**
     * A sequence of allocation sites
     */
    private static class ObjectName implements HContext, CContext {

        private final List<AllocSiteNode> sites;
        private final Type topAllocationSiteType;
        private final boolean isPreciseType;

        private ObjectName(List<AllocSiteNode> sites,
                Type topAllocationSiteType, boolean isPreciseType) {
            this.sites = sites;
            this.hashCode = computeHashCode();
            this.topAllocationSiteType = topAllocationSiteType;
            this.isPreciseType = isPreciseType;
        }

        public ObjectName pushAllocSite(AllocSiteNode asn, int objectSensitivity) {
            ArrayList<AllocSiteNode> s =
                    new ArrayList<AllocSiteNode>(sites.size() + 1);
            s.add(asn);
            s.addAll(sites);
            while (s.size() > objectSensitivity) {
                s.remove(s.size() - 1);
            }
            return new ObjectName(s, asn.expectedType(), asn.isPreciseType());

        }

        @Override
        public AllocSiteNode allocSite() {
            if (sites.isEmpty()) return null;
            return sites.get(0);
        }

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

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

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

        @Override
        public boolean equalsSemantic(HContext that) {
            return equalsSemantic((ObjectName) that);
        }

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

        private boolean equalsSemantic(ObjectName that) {
            if (this == that) return true;
            return this.sites.equals(that.sites)
                    && (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 sites.hashCode()
                    ^ (topAllocationSiteType == null ? 0
                            : topAllocationSiteType.hashCode());
        }

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

        @Override
        public String toString() {
            if (sites.isEmpty()) {
                if (topAllocationSiteType == null) {
                    return "[]";
                }
                else {
                    return "[?:" + topAllocationSiteType + "]";
                }
            }
            StringBuffer sb = new StringBuffer();
            sb.append("[");
            for (Iterator<AllocSiteNode> iter = sites.iterator(); iter.hasNext();) {
                AllocSiteNode asn = iter.next();
                sb.append(asn);
                if (iter.hasNext()) {
                    sb.append(",");
                }
            }
            sb.append("]");
            return sb.toString();
        }

    }

}
