package accrue.analysis.interprocanalysis;

import accrue.analysis.pointer.HContext;

import java.util.HashMap;
import java.util.Map;

import polyglot.types.FieldInstance;
import polyglot.util.InternalCompilerError;

/**
 * Represents an abstract location, i.e., zero or more concrete locations.
 */
public class AbstractLocation {
    public static final String ARRAY_CONTENTS = "[contents]";
    public final HContext context;
    public final FieldInstance fi;
    public final String fieldName;

    public static void clearMemoization() {
        memo = new HashMap<AbsLocWrapper, AbstractLocation>();
    }

    private static Map<AbsLocWrapper, AbstractLocation> memo =
            new HashMap<AbsLocWrapper, AbstractLocation>();

    private static AbstractLocation memoize(AbstractLocation o) {
        AbsLocWrapper w = new AbsLocWrapper(o);
        AbstractLocation m = memo.get(w);
        if (m == null) {
            memo.put(w, o);
            m = o;
        }
        return m;
    }

    public static AbstractLocation create(HContext on, FieldInstance fi) {
        return memoize(new AbstractLocation(on, fi));
    }

    public static AbstractLocation create(FieldInstance fi) {
        return memoize(new AbstractLocation(fi));
    }

    public static AbstractLocation create(HContext on, String fieldName) {
        return memoize(new AbstractLocation(on, fieldName));
    }

    private AbstractLocation(HContext context, FieldInstance fi) {
        this.fi = fi;
        this.fieldName = fi.name();
        this.context = context;
        if (context != null && fi.flags().isStatic()) {
            throw new InternalCompilerError("Static field with context");
        }
        else if (context == null && fi.flags().isStatic()) {
            throw new InternalCompilerError("Non-static field with no context");
        }
        this.memoizedHashCode = computeHashCode();
    }

    private AbstractLocation(HContext context, String fieldName) {
        this.fi = null;
        this.fieldName = fieldName;
        this.context = context;
        this.memoizedHashCode = computeHashCode();
    }

    private AbstractLocation(FieldInstance fi) {
        this.fi = fi;
        this.fieldName = fi.name();
        this.context = null;
        if (!fi.flags().isStatic()) {
            throw new InternalCompilerError("Non-static field with no context");
        }
        this.memoizedHashCode = computeHashCode();
    }

    private static class AbsLocWrapper {
        AbsLocWrapper(AbstractLocation loc) {
            this.loc = loc;
        }

        final AbstractLocation loc;

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

        @Override
        public boolean equals(Object o) {
            return loc.equalsSemantic(((AbsLocWrapper) o).loc);
        }
    }

    private boolean equalsSemantic(AbstractLocation that) {
        if (that == this) return true;
        return this.hashCode() == that.hashCode()
                && (this.fi == that.fi || (this.fi != null && this.fi.equals(that.fi)))
                && (this.fieldName.equals(that.fieldName))
                && (this.context == that.context || (this.context != null && this.context.equals(that.context)));
    }

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

    private final int memoizedHashCode;

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

    private int computeHashCode() {
        return (this.fi == null ? this.fieldName.hashCode()
                : this.fi.hashCode())
                ^ (this.context == null ? 0 : this.context.hashCode());
    }

    @Override
    public String toString() {
        if (fi != null) {
            return this.context + "." + this.fi.container() + "."
                    + this.fieldName;
        }
        return this.context + "." + this.fieldName;
    }
}
